diff --git a/extensions/amp-a4a/0.1/amp-a4a.js b/extensions/amp-a4a/0.1/amp-a4a.js index d42711a1d229..25ea7b3e5591 100644 --- a/extensions/amp-a4a/0.1/amp-a4a.js +++ b/extensions/amp-a4a/0.1/amp-a4a.js @@ -788,6 +788,9 @@ export class AmpA4A extends AMP.BaseElement { const consentStringType = consentMetadata ? consentMetadata['consentStringType'] : consentMetadata; + const purposeOne = consentMetadata + ? consentMetadata['purposeOne'] + : consentMetadata; const gppSectionId = consentMetadata ? consentMetadata['gppSectionId'] : consentMetadata; @@ -802,6 +805,7 @@ export class AmpA4A extends AMP.BaseElement { gdprApplies, additionalConsent, consentSharedData, + purposeOne, gppSectionId, }, this.tryExecuteRealTimeConfig_( @@ -2404,6 +2408,13 @@ export class AmpA4A extends AMP.BaseElement { * @return {Promise>|undefined} */ tryExecuteRealTimeConfig_(consentState, consentString, consentMetadata) { + const hasStorageConsent = + consentState != CONSENT_POLICY_STATE.UNKNOWN && + consentState != CONSENT_POLICY_STATE.INSUFFICIENT && + ((consentMetadata?.gdprApplies && + consentString && + consentMetadata?.purposeOne) || + !consentMetadata?.gdprApplies); if (this.element.getAttribute('rtc-config')) { installRealTimeConfigServiceForDoc(this.getAmpDoc()); return this.getBlockRtc_().then((shouldBlock) => @@ -2413,7 +2424,7 @@ export class AmpA4A extends AMP.BaseElement { (realTimeConfig) => realTimeConfig.maybeExecuteRealTimeConfig( this.element, - this.getCustomRealTimeConfigMacros_(), + this.getCustomRealTimeConfigMacros_(hasStorageConsent), consentState, consentString, consentMetadata, @@ -2427,10 +2438,11 @@ export class AmpA4A extends AMP.BaseElement { /** * To be overriden by network impl. Should return a mapping of macro keys * to values for substitution in publisher-specified URLs for RTC. + * @param {?boolean} unusedHasStorageConsent * @return {!Object} */ - getCustomRealTimeConfigMacros_() { + getCustomRealTimeConfigMacros_(unusedHasStorageConsent) { return {}; } diff --git a/extensions/amp-a4a/0.1/test/test-amp-a4a.js b/extensions/amp-a4a/0.1/test/test-amp-a4a.js index 210329b1eb51..30791a4e45c5 100644 --- a/extensions/amp-a4a/0.1/test/test-amp-a4a.js +++ b/extensions/amp-a4a/0.1/test/test-amp-a4a.js @@ -2811,6 +2811,7 @@ describes.realWin('amp-a4a', {amp: true}, (env) => { gdprApplies, 'consentStringType': 1, 'additionalConsent': 'abc123', + purposeOne: true, 'gppSectionId': '1,2', }; consentSharedData = { @@ -2858,6 +2859,7 @@ describes.realWin('amp-a4a', {amp: true}, (env) => { consentStringType: consentMetadata['consentStringType'], additionalConsent: consentMetadata['additionalConsent'], consentSharedData, + purposeOne: consentMetadata['purposeOne'], gppSectionId: consentMetadata['gppSectionId'], }) ).calledOnce; @@ -2915,6 +2917,7 @@ describes.realWin('amp-a4a', {amp: true}, (env) => { consentStringType: consentMetadata['consentStringType'], additionalConsent: consentMetadata['additionalConsent'], consentSharedData, + purposeOne: consentMetadata['purposeOne'], gppSectionId: consentMetadata['gppSectionId'], }) ).calledOnce; @@ -2966,6 +2969,7 @@ describes.realWin('amp-a4a', {amp: true}, (env) => { gdprApplies: null, additionalConsent: null, consentSharedData: null, + purposeOne: null, gppSectionId: null, }) ).calledOnce; @@ -3537,9 +3541,96 @@ describes.realWin('AmpA4a-RTC', {amp: true}, (env) => { }); }); + describe('#tryExecuteRealTimeConfig storage consent test', () => { + let getCustomRealTimeConfigMacrosSpy; + + beforeEach(() => { + element.setAttribute('rtc-config', true); + getCustomRealTimeConfigMacrosSpy = env.sandbox.spy( + a4a, + 'getCustomRealTimeConfigMacros_' + ); + }); + + for (const { + consentState, + consentString, + gdprApplies, + hasStorageConsent, + purposeOne, + } of [ + // Unknown consent + { + consentState: CONSENT_POLICY_STATE.UNKNOWN, + gdprApplies: true, + consentString: 'string', + purposeOne: true, + hasStorageConsent: false, + }, + // Insufficient consent + { + consentState: CONSENT_POLICY_STATE.INSUFFICIENT, + gdprApplies: true, + consentString: 'string', + purposeOne: true, + hasStorageConsent: false, + }, + // GDPR doesn't apply + { + consentState: CONSENT_POLICY_STATE.SUFFICIENT, + gdprApplies: false, + consentString: '', + purposeOne: false, + hasStorageConsent: true, + }, + // No consent string + { + consentState: CONSENT_POLICY_STATE.SUFFICIENT, + gdprApplies: true, + consentString: '', + purposeOne: true, + hasStorageConsent: false, + }, + // no purpose one consent + { + consentState: CONSENT_POLICY_STATE.SUFFICIENT, + gdprApplies: true, + consentString: 'string', + purposeOne: false, + hasStorageConsent: false, + }, + // GDPR applies and all prerequisite satisfied + { + consentState: CONSENT_POLICY_STATE.SUFFICIENT, + gdprApplies: true, + consentString: 'string', + purposeOne: true, + hasStorageConsent: true, + }, + ]) { + it( + `storageConsent test - consentState=${consentState}, ` + + `consentString=${consentString}, gdprApplies=${gdprApplies},` + + `purposeOne=${purposeOne} -> hasStorageConsent=${hasStorageConsent}`, + () => { + return a4a + .tryExecuteRealTimeConfig_(consentState, consentString, { + gdprApplies, + purposeOne, + }) + .then(() => { + expect(getCustomRealTimeConfigMacrosSpy).to.be.calledWith( + hasStorageConsent + ); + }); + } + ); + } + }); + describe('#getCustomRealTimeConfigMacros_', () => { it('should return empty object', () => { - expect(a4a.getCustomRealTimeConfigMacros_()).to.deep.equal({}); + expect(a4a.getCustomRealTimeConfigMacros_(true)).to.deep.equal({}); }); }); diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js b/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js index fe0e2b7d50f2..43cf0c6a90cf 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js @@ -955,7 +955,7 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } /** @override */ - getCustomRealTimeConfigMacros_() { + getCustomRealTimeConfigMacros_(hasStorageConsent) { /** * This lists allowed attributes on the amp-ad element to be used as * macros for constructing the RTC URL. Add attributes here, in lowercase, @@ -983,12 +983,14 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { (tryParseJson(this.element.getAttribute('json')) || {})['targeting'] ), ADCID: (opt_timeout) => - getOrCreateAdCid( - this.getAmpDoc(), - 'AMP_ECID_GOOGLE', - '_ga', - parseInt(opt_timeout, 10) - ), + hasStorageConsent + ? getOrCreateAdCid( + this.getAmpDoc(), + 'AMP_ECID_GOOGLE', + '_ga', + parseInt(opt_timeout, 10) + ) + : Promise.resolve(undefined), ATTR: (name) => { if (!allowlist[name.toLowerCase()]) { dev().warn(TAG, `Invalid attribute ${name}`); diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-rtc.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-rtc.js index 0ecbd59fae08..3f1f370d2a10 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-rtc.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-rtc.js @@ -392,8 +392,7 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, (env) => { }); describe('getCustomRealTimeConfigMacros', () => { - // TODO(bradfrizzell, #18574): Fix failing referrer check and re-enable. - it.skip('should return correct macros', () => { + it('should return correct macros', () => { const macros = { 'data-slot': '5678', 'height': '50', @@ -419,18 +418,21 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, (env) => { 'json': JSON.stringify(json), }); env.win.document.body.appendChild(element); - env.sandbox.defineProperty(env.win.document, 'referrer', { - value: 'https://www.google.com/', - }); + // TODO(bradfrizzell, #18574): Fix failing referrer check and re-enable. + //env.sandbox.defineProperty(env.win.document, 'referrer', { + // value: 'https://www.google.com/', + //}); const docInfo = Services.documentInfoForDoc(element); impl = new AmpAdNetworkDoubleclickImpl( element, env.win.document, env.win ); - const docViewport = Services.viewportForDoc(this.getAmpDoc()); + const docViewport = Services.viewportForDoc(impl.getAmpDoc()); impl.populateAdUrlState(); - const customMacros = impl.getCustomRealTimeConfigMacros_(); + const customMacros = impl.getCustomRealTimeConfigMacros_( + /*hasStorageConsent=*/ true + ); expect(customMacros.PAGEVIEWID()).to.equal(docInfo.pageViewId); expect(customMacros.PAGEVIEWID_64()).to.equal(docInfo.pageViewId64); expect(customMacros.HREF()).to.equal(env.win.location.href); @@ -443,7 +445,7 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, (env) => { docViewport.getScrollHeight() ); expect(customMacros.BKG_STATE()).to.equal( - this.getAmpDoc().isVisible() ? 'visible' : 'hidden' + impl.getAmpDoc().isVisible() ? 'visible' : 'hidden' ); Object.keys(macros).forEach((macro) => { expect(customMacros.ATTR(macro)).to.equal(macros[macro]); @@ -469,7 +471,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, (env) => { env.win ); impl.populateAdUrlState(); - const customMacros = impl.getCustomRealTimeConfigMacros_(); + const customMacros = impl.getCustomRealTimeConfigMacros_( + /*hasStorageConsent=*/ true + ); let adcid; return customMacros.ADCID().then((adcid1) => { adcid = adcid1; @@ -480,6 +484,25 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, (env) => { }); }); + it('should not set adcid if no storage consent', () => { + element = createElementWithAttributes(env.win.document, 'amp-ad', { + type: 'doubleclick', + }); + env.win.document.body.appendChild(element); + impl = new AmpAdNetworkDoubleclickImpl( + element, + env.win.document, + env.win + ); + impl.populateAdUrlState(); + const customMacros = impl.getCustomRealTimeConfigMacros_( + /*hasStorageConsent=*/ false + ); + return customMacros.ADCID().then((adcid) => { + expect(adcid).to.be.undefined; + }); + }); + it('should respect timeout for adcid', () => { element = createElementWithAttributes(env.win.document, 'amp-ad', { type: 'doubleclick', @@ -491,7 +514,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, (env) => { env.win ); impl.populateAdUrlState(); - const customMacros = impl.getCustomRealTimeConfigMacros_(); + const customMacros = impl.getCustomRealTimeConfigMacros_( + /*hasStorageConsent=*/ true + ); return customMacros.ADCID(0).then((adcid) => { expect(adcid).to.be.undefined; }); @@ -510,7 +535,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, (env) => { impl.populateAdUrlState(); const viewer = Services.viewerForDoc(impl.getAmpDoc()); env.sandbox.stub(viewer, 'getReferrerUrl').returns(new Promise(() => {})); - const customMacros = impl.getCustomRealTimeConfigMacros_(); + const customMacros = impl.getCustomRealTimeConfigMacros_( + /*hasStorageConsent=*/ true + ); return expect(customMacros.REFERRER(0)).to.eventually.be.undefined; }); @@ -528,7 +555,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, (env) => { env.win ); impl.populateAdUrlState(); - const customMacros = impl.getCustomRealTimeConfigMacros_(); + const customMacros = impl.getCustomRealTimeConfigMacros_( + /*hasStorageConsent=*/ true + ); expect(customMacros.TGT()).to.equal(JSON.stringify(json['targeting'])); }); }); diff --git a/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js index e8740669ad3a..e078b82ed7a5 100644 --- a/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js +++ b/extensions/amp-ad-network-smartadserver-impl/0.1/amp-ad-network-smartadserver-impl.js @@ -133,7 +133,7 @@ export class AmpAdNetworkSmartadserverImpl extends AmpA4A { } /** @override */ - getCustomRealTimeConfigMacros_() { + getCustomRealTimeConfigMacros_(hasStorageConsent) { const allowed = { 'width': true, 'height': true, @@ -156,12 +156,14 @@ export class AmpAdNetworkSmartadserverImpl extends AmpA4A { (tryParseJson(this.element.getAttribute('json')) || {})['targeting'] ), ADCID: (opt_timeout) => - getOrCreateAdCid( - this.getAmpDoc(), - 'AMP_ECID_GOOGLE', - '_ga', - parseInt(opt_timeout, 10) - ), + hasStorageConsent + ? getOrCreateAdCid( + this.getAmpDoc(), + 'AMP_ECID_GOOGLE', + '_ga', + parseInt(opt_timeout, 10) + ) + : Promise.resolve(undefined), ATTR: (name) => { if (!allowed[name]) { dev().warn(TAG, `Invalid attribute ${name}`); diff --git a/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js b/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js index df574c62a670..046af00a339b 100644 --- a/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js +++ b/extensions/amp-ad-network-smartadserver-impl/0.1/test/test-amp-ad-network-smartadserver-impl.js @@ -113,7 +113,9 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { impl = new AmpAdNetworkSmartadserverImpl(element, env.win.doc, win); const docInfo = Services.documentInfoForDoc(element); - const customMacros = impl.getCustomRealTimeConfigMacros_(); + const customMacros = impl.getCustomRealTimeConfigMacros_( + /*hasStorageConsent=*/ false + ); expect(customMacros.PAGEVIEWID()).to.equal(docInfo.pageViewId); expect(customMacros.PAGEVIEWID_64()).to.equal(docInfo.pageViewId64); @@ -157,6 +159,18 @@ describes.realWin('amp-ad-network-smartadserver-impl', realWinConfig, (env) => { ); expect(customMacros.ATTR('not-allowed')).to.equal(''); }); + + it('should not set adcid if no storage consent', () => { + element = createElementWithAttributes(doc, 'amp-ad'); + impl = new AmpAdNetworkSmartadserverImpl(element); + const customMacros = impl.getCustomRealTimeConfigMacros_( + /*hasStorageConsent=*/ false + ); + + return customMacros.ADCID().then((adcid) => { + expect(adcid).to.be.undefined; + }); + }); }); describe('getAdUrl', () => { diff --git a/extensions/amp-ad-network-valueimpression-impl/0.1/amp-ad-network-valueimpression-impl.js b/extensions/amp-ad-network-valueimpression-impl/0.1/amp-ad-network-valueimpression-impl.js index d2a379ff9c64..2c1dd12b3adc 100644 --- a/extensions/amp-ad-network-valueimpression-impl/0.1/amp-ad-network-valueimpression-impl.js +++ b/extensions/amp-ad-network-valueimpression-impl/0.1/amp-ad-network-valueimpression-impl.js @@ -1,4 +1,5 @@ import '#service/real-time-config/real-time-config-impl'; +import {CONSENT_POLICY_STATE} from '#core/constants/consent-state'; import {Deferred} from '#core/data-structures/promise'; import {domFingerprintPlain} from '#core/dom/fingerprint'; import {getPageLayoutBoxBlocking} from '#core/dom/layout/page-layout-box'; @@ -191,7 +192,7 @@ export class AmpAdNetworkValueimpressionImpl extends AmpA4A { /** @override */ getAdUrl(opt_consentTuple, opt_rtcResponsesPromise, opt_serveNpaSignal) { const consentTuple = opt_consentTuple || {}; - const {consentString, gdprApplies} = consentTuple; + const {consentState, consentString, gdprApplies, purposeOne} = consentTuple; const {win} = this; const ampDoc = this.getAmpDoc(); @@ -229,10 +230,17 @@ export class AmpAdNetworkValueimpressionImpl extends AmpA4A { }); const startTime = Date.now(); + const hasStorageConsent = + consentState != CONSENT_POLICY_STATE.UNKNOWN && + consentState != CONSENT_POLICY_STATE.INSUFFICIENT && + ((gdprApplies && consentString && purposeOne) || !gdprApplies); + Promise.all([ rtcParamsPromise, referrerPromise, - getOrCreateAdCid(ampDoc, 'AMP_ECID_GOOGLE', '_ga'), + hasStorageConsent + ? getOrCreateAdCid(ampDoc, 'AMP_ECID_GOOGLE', '_ga') + : Promise.resolve(undefined), ]).then((results) => { const clientId = results[2]; const referrer = results[1];