Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions examples/slikeplayer.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@
custom-element="amp-slikeplayer"
src="https://cdn.ampproject.org/v0/amp-slikeplayer-0.1.js"
></script>
<script
async
custom-element="amp-consent"
src="https://cdn.ampproject.org/v0/amp-consent-0.1.js"
></script>
<script
async
custom-element="amp-geo"
src="https://cdn.ampproject.org/v0/amp-geo-0.1.js"
></script>

<style amp-custom>
body {
Expand Down Expand Up @@ -249,6 +259,67 @@
font-weight: 600;
}

.consent-ui {
background: #333;
color: white;
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
font-size: 0.95rem;
}

.consent-ui p {
margin: 0;
flex: 1;
min-width: 200px;
}

.consent-ui .btn-group {
display: flex;
gap: 8px;
}

.consent-ui button {
border: none;
border-radius: 4px;
padding: 8px 20px;
font-size: 0.9rem;
cursor: pointer;
font-weight: 600;
}

.consent-ui .btn-accept {
background: #4facfe;
color: white;
}

.consent-ui .btn-reject {
background: #666;
color: white;
}

.post-consent-ui {
background: #e8f5e9;
padding: 8px 16px;
text-align: center;
font-size: 0.85rem;
color: #333;
}

.post-consent-ui button {
border: none;
background: #667eea;
color: white;
border-radius: 4px;
padding: 4px 12px;
margin-left: 8px;
cursor: pointer;
font-size: 0.85rem;
}

@media (max-width: 768px) {
.header h1 {
font-size: 2rem;
Expand All @@ -267,6 +338,50 @@
</head>

<body>
<!-- Geo detection for consent -->
<amp-geo layout="nodisplay">
<script type="application/json">
{
"ISOCountryGroups": {
"eea": ["at", "be", "bg", "hr", "cy", "cz", "dk", "ee", "fi", "fr",
"de", "gr", "hu", "ie", "it", "lv", "lt", "lu", "mt", "nl",
"pl", "pt", "ro", "sk", "si", "es", "se", "gb", "is", "li",
"no", "ch"]
}
}
</script>
</amp-geo>

<!-- Consent management -->
<amp-consent id="slike-consent" layout="nodisplay">
<script type="application/json">
{
"consentInstanceId": "slike-player-consent",
"consentRequired": true,
"promptUI": "consent-prompt",
"postPromptUI": "post-consent-prompt"
}
Comment on lines +341 to +363
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amp-geo is configured with an eea group, but the amp-consent config doesn't reference any geo group (e.g., via promptIfUnknownForGeoGroup / consentRequired gating), so geo detection has no effect and the consent UI will always be required. Either wire amp-consent to the eea group as described in the PR summary, or remove the unused amp-geo configuration from the example.

Copilot uses AI. Check for mistakes.
</script>
<div id="consent-prompt" class="consent-ui">
<p>
We use cookies and data to deliver and improve our services. By
accepting, you agree to personalized content and ads.
</p>
<div class="btn-group">
<button class="btn-accept" on="tap:slike-consent.accept">
Accept
</button>
<button class="btn-reject" on="tap:slike-consent.reject">
Reject
</button>
</div>
</div>
<div id="post-consent-prompt" class="post-consent-ui">
Consent preference saved.
<button on="tap:slike-consent.prompt">Manage</button>
</div>
</amp-consent>

<!-- Header Section -->
<div class="header">
<div class="container">
Expand Down
32 changes: 32 additions & 0 deletions extensions/amp-slikeplayer/0.1/amp-slikeplayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {installVideoManagerForDoc} from '#service/video-manager-impl';
import {getData, listen} from '#utils/event-helper';
import {userAssert} from '#utils/log';

import {getConsentDataToForward} from '../../../src/consent';
import {disableScrollingOnIframe} from '../../../src/iframe-helper';
import {
addUnsafeAllowAutoplay,
Expand Down Expand Up @@ -266,12 +267,20 @@ export class AmpSlikeplayer extends AMP.BaseElement {
}

const data = objOrParseJson(messageData);

// Handle consent request from iframe (sent as raw object with type field)
if (data['type'] === 'send-consent-data') {
this.sendConsentData_();
return;
}

const event = data['event'];
const detail = data['detail'];
if (event === 'ready') {
detail && this.onReadyOnce_(detail);
return;
}

const {element} = this;
if (redispatch(element, event, CleoEvent)) {
return;
Expand Down Expand Up @@ -338,6 +347,29 @@ export class AmpSlikeplayer extends AMP.BaseElement {
this.postMessage_('handleViewport', inViewport);
}

/**
* Fetches consent data from AMP consent service
* and forwards it to the iframe via postMessage.
* @private
*/
sendConsentData_() {
getConsentDataToForward(this.element, this.getConsentPolicy()).then(
(consents) => {
if (!this.iframe_ || !this.iframe_.contentWindow) {
return;
}
this.iframe_.contentWindow./*OK*/ postMessage(
{
'sentinel': 'amp',
'type': 'consent-data',
...consents,
},
this.targetOrigin_
);
Comment on lines +355 to +368
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sendConsentData_() uses this.targetOrigin_ as the postMessage targetOrigin, but targetOrigin_ is never defined anywhere in this class. This will result in the message being sent with an invalid/incorrect targetOrigin (often preventing delivery). Capture the request's origin from the triggering message event and use that as targetOrigin (or otherwise compute/store a validated origin when creating the iframe) instead of referencing an undefined field.

Copilot uses AI. Check for mistakes.
}
);
}

/**
* @override
*/
Expand Down
49 changes: 49 additions & 0 deletions extensions/amp-slikeplayer/0.1/test/test-amp-slikeplayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '../amp-slikeplayer';

import {listenOncePromise} from '#utils/event-helper';

import * as consent from '../../../../src/consent';
import {VideoEvents_Enum} from '../../../../src/video-interface';

describes.realWin(
Expand Down Expand Up @@ -212,5 +213,53 @@ describes.realWin(
// Subsequent layout should be possible
await el.layoutCallback();
});

it('calls sendConsentData_ on send-consent-data message', async () => {
const consentData = {
consentPolicyState: 1,
consentString: 'abc123',
consentMetadata: {gdprApplies: true, purposeOne: true},
consentPolicySharedData: null,
};
env.sandbox
.stub(consent, 'getConsentDataToForward')
.resolves(consentData);

const {iframe, impl} = await buildPlayer();
const sendSpy = env.sandbox.spy(impl, 'sendConsentData_');

// Simulate consent request from iframe (raw object, not JSON)
impl.onMessage_({
source: iframe.contentWindow,
data: {type: 'send-consent-data', sentinel: 'amp'},
});

expect(sendSpy).to.have.been.calledOnce;

// Wait for the consent promise to resolve
await new Promise((r) => setTimeout(r, 0));

expect(consent.getConsentDataToForward).to.have.been.calledOnce;
});
Comment on lines +217 to +243
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only verifies that sendConsentData_ was invoked and that getConsentDataToForward was called, but it never asserts that consent data is actually forwarded to the iframe (e.g., that postMessage is called with {sentinel:'amp', type:'consent-data', ...} and the expected consent fields). Add an assertion around the postMessage call/payload so regressions in the response format or posting behavior are caught.

Copilot uses AI. Check for mistakes.

it('does not send consent data if iframe is gone', async () => {
const consentData = {consentPolicyState: 2};
env.sandbox
.stub(consent, 'getConsentDataToForward')
.resolves(consentData);

const {iframe, impl} = await buildPlayer();

// Destroy iframe before consent resolves
impl.iframe_ = null;

impl.onMessage_({
source: iframe.contentWindow,
data: {type: 'send-consent-data', sentinel: 'amp'},
});

await new Promise((r) => setTimeout(r, 0));
Comment on lines +247 to +261
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test name/comment says the iframe is destroyed before the consent promise resolves, but impl.iframe_ is set to null before onMessage_ is called. That means onMessage_ returns early and the async path in sendConsentData_ (the guard that checks this.iframe_ after consent data resolves) is never exercised. To cover the intended edge case, trigger the request first and then null out impl.iframe_ while the consent promise is still pending (e.g., with a manually controlled promise).

Suggested change
env.sandbox
.stub(consent, 'getConsentDataToForward')
.resolves(consentData);
const {iframe, impl} = await buildPlayer();
// Destroy iframe before consent resolves
impl.iframe_ = null;
impl.onMessage_({
source: iframe.contentWindow,
data: {type: 'send-consent-data', sentinel: 'amp'},
});
await new Promise((r) => setTimeout(r, 0));
let resolveConsentData;
const consentDataPromise = new Promise((resolve) => {
resolveConsentData = resolve;
});
env.sandbox
.stub(consent, 'getConsentDataToForward')
.returns(consentDataPromise);
const {iframe, impl} = await buildPlayer();
impl.onMessage_({
source: iframe.contentWindow,
data: {type: 'send-consent-data', sentinel: 'amp'},
});
// Destroy iframe while consent is still pending so the async guard
// inside sendConsentData_() is exercised after the promise resolves.
impl.iframe_ = null;
resolveConsentData(consentData);
await Promise.resolve();

Copilot uses AI. Check for mistakes.
// No error thrown — silently skipped
});
}
);
Loading