Skip to content

Commit

Permalink
✨ [Chrome Attribution Report API] Add attribution reporting to amp-pi…
Browse files Browse the repository at this point in the history
…xel (ampproject#38619)

* Add attribution reporting to amp-pixel

* Add attribution reporting to amp-pixel [addressing comments]

* Add attribution reporting to amp-pixel [add validator tests]

* Add attribution reporting to amp-pixel [addressing comments]

* Add attribution reporting to amp-pixel [Using isAttributionReportingAllowed from utils]

* Add attribution reporting to amp-pixel [Fix lint failures]

* Add attribution reporting to amp-pixel [Move privacy-sandbox-utils to core for src dependencies]

* Add attribution reporting to amp-pixel [Move privacy-sandbox-utils to core for src dependencies]
  • Loading branch information
emensc52 authored Jan 25, 2023
1 parent ed06fde commit c95dcde
Show file tree
Hide file tree
Showing 14 changed files with 119 additions and 22 deletions.
4 changes: 0 additions & 4 deletions build-system/test-configs/dep-check-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,6 @@ exports.rules = [
'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js->extensions/amp-a4a/0.1/signature-verifier.js',
'extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js->extensions/amp-a4a/0.1/signature-verifier.js',

// A4A impls using attribution-reporting API
'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js->extensions/amp-a4a/0.1/privacy-sandbox-utils.js',
'extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js->extensions/amp-a4a/0.1/privacy-sandbox-utils.js',

// And a few more things depend on a4a.
'extensions/amp-ad-custom/0.1/amp-ad-custom.js->extensions/amp-a4a/0.1/amp-ad-network-base.js',
'extensions/amp-ad-custom/0.1/amp-ad-custom.js->extensions/amp-a4a/0.1/amp-ad-type-defs.js',
Expand Down
2 changes: 1 addition & 1 deletion extensions/amp-a4a/0.1/amp-a4a.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ import {triggerAnalyticsEvent} from '#utils/analytics';
import {DomTransformStream} from '#utils/dom-tranform-stream';
import {listenOnce} from '#utils/event-helper';
import {dev, devAssert, logHashParam, user, userAssert} from '#utils/log';
import {isAttributionReportingAllowed} from '#utils/privacy-sandbox-utils';

import {A4AVariableSource} from './a4a-variable-source';
import {getExtensionsFromMetadata} from './amp-ad-utils';
import {processHead} from './head-validation';
import {isAttributionReportingAllowed} from './privacy-sandbox-utils';
import {createSecureDocSkeleton, createSecureFrame} from './secure-frame';
import {SignatureVerifier, VerificationStatus} from './signature-verifier';
import {whenWithinViewport} from './within-viewport';
Expand Down
2 changes: 1 addition & 1 deletion extensions/amp-a4a/0.1/secure-frame.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {createElementWithAttributes, escapeHtml} from '#core/dom';

import {isAttributionReportingAllowed} from './privacy-sandbox-utils';
import {isAttributionReportingAllowed} from '#utils/privacy-sandbox-utils';

import {getFieSafeScriptSrcs} from '../../../src/friendly-iframe-embed';

Expand Down
2 changes: 1 addition & 1 deletion extensions/amp-a4a/0.1/test/test-amp-a4a.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {installRealTimeConfigServiceForDoc} from '#service/real-time-config/real

import * as analytics from '#utils/analytics';
import {dev, user} from '#utils/log';
import * as privacySandboxUtils from '#utils/privacy-sandbox-utils';

import {macroTask} from '#testing/helpers';
import {createIframePromise} from '#testing/iframe';
Expand Down Expand Up @@ -50,7 +51,6 @@ import {
assignAdUrlToError,
protectFunctionWrapper,
} from '../amp-a4a';
import * as privacySandboxUtils from '../privacy-sandbox-utils';
import {AMP_SIGNATURE_HEADER, VerificationStatus} from '../signature-verifier';

describes.realWin('amp-a4a: no signing', {amp: true}, (env) => {
Expand Down
5 changes: 2 additions & 3 deletions extensions/amp-ad-exit/0.1/amp-ad-exit.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {Services} from '#service';

import {getData} from '#utils/event-helper';
import {dev, devAssert, user, userAssert} from '#utils/log';
import {isAttributionReportingAllowed} from '#utils/privacy-sandbox-utils';

import {TransportMode, assertConfig, assertVendor} from './config';
import {makeClickDelaySpec} from './filters/click-delay';
Expand Down Expand Up @@ -440,9 +441,7 @@ export class AmpAdExit extends AMP.BaseElement {
* @return {boolean}
*/
detectAttributionReportingSupport() {
return this.win.document.featurePolicy?.allowsFeature(
'attribution-reporting'
);
return isAttributionReportingAllowed(this.win);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ import {Navigation} from '#service/navigation';

import {getData} from '#utils/event-helper';
import {dev, devAssert, user} from '#utils/log';

import {isAttributionReportingAllowed} from 'extensions/amp-a4a/0.1/privacy-sandbox-utils';
import {isAttributionReportingAllowed} from '#utils/privacy-sandbox-utils';

import {AdsenseSharedState} from './adsense-shared-state';
import {ResponsiveState} from './responsive-state';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ import {Navigation} from '#service/navigation';
import {RTC_VENDORS} from '#service/real-time-config/callout-vendors';

import {dev, devAssert, user} from '#utils/log';

import {isAttributionReportingAllowed} from 'extensions/amp-a4a/0.1/privacy-sandbox-utils';
import {isAttributionReportingAllowed} from '#utils/privacy-sandbox-utils';

import {
FlexibleAdSlotDataTypeDef,
Expand Down
8 changes: 7 additions & 1 deletion src/builtins/amp-pixel/amp-pixel.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ export class AmpPixel extends BaseElement {
if (!this.win) {
return;
}
const pixel = createPixel(this.win, src, this.referrerPolicy_);

const pixel = createPixel(
this.win,
src,
this.referrerPolicy_,
this.element.getAttribute('attributionsrc')
);
dev().info(TAG, 'pixel triggered: ', src);
return pixel;
});
Expand Down
20 changes: 14 additions & 6 deletions src/pixel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,38 @@ import {WindowInterface} from '#core/window/interface';

import {user} from '#utils/log';

import {isAttributionReportingAllowed} from './utils/privacy-sandbox-utils';

/** @const {string} */
const TAG = 'pixel';

/**
* @param {!Window} win
* @param {string} src
* @param {?string=} referrerPolicy
* @param {string=} attributionSrc
* @return {!Element}
*/
export function createPixel(win, src, referrerPolicy) {
export function createPixel(win, src, referrerPolicy, attributionSrc) {
// Caller need to verify window is not destroyed when creating pixel
if (referrerPolicy && referrerPolicy !== 'no-referrer') {
user().error(TAG, 'Unsupported referrerPolicy: %s', referrerPolicy);
}

return referrerPolicy === 'no-referrer'
? createNoReferrerPixel(win, src)
: createImagePixel(win, src);
? createNoReferrerPixel(win, src, attributionSrc)
: createImagePixel(win, src, false, attributionSrc);
}

/**
* @param {!Window} win
* @param {string} src
* @param {string=} attributionSrc
* @return {!Element}
*/
function createNoReferrerPixel(win, src) {
function createNoReferrerPixel(win, src, attributionSrc) {
if (isReferrerPolicySupported()) {
return createImagePixel(win, src, true);
return createImagePixel(win, src, true, attributionSrc);
} else {
// if "referrerPolicy" is not supported, use iframe wrapper
// to scrub the referrer.
Expand All @@ -54,15 +58,19 @@ function createNoReferrerPixel(win, src) {
* @param {!Window} win
* @param {string} src
* @param {boolean=} noReferrer
* @param {string=} attributionSrc
* @return {!Image}
*/
function createImagePixel(win, src, noReferrer = false) {
function createImagePixel(win, src, noReferrer = false, attributionSrc) {
const Image = WindowInterface.getImage(win);
const image = new Image();
if (noReferrer) {
image.referrerPolicy = 'no-referrer';
}
image.src = src;
if (isAttributionReportingAllowed(win)) {
image.attributionsrc = attributionSrc;
}
return image;
}

Expand Down
File renamed without changes.
48 changes: 47 additions & 1 deletion test/unit/builtins/test-amp-pixel.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {installUrlReplacementsForEmbed} from '#service/url-replacements-impl';
import {VariableSource} from '#service/variable-source';

import * as privacySandboxUtils from 'src/utils/privacy-sandbox-utils';

describes.realWin('amp-pixel', {amp: true}, (env) => {
const urlErrorRegex = /src attribute must start with/;

Expand Down Expand Up @@ -33,12 +35,16 @@ describes.realWin('amp-pixel', {amp: true}, (env) => {

/**
* @param {string=} opt_src
* @param {string=} opt_attributionsrc
* @return {!Promise<?Image>}
*/
function trigger(opt_src) {
function trigger(opt_src, opt_attributionsrc) {
if (opt_src != null) {
pixel.setAttribute('src', opt_src);
}
if (opt_attributionsrc != null) {
pixel.setAttribute('attributionsrc', opt_attributionsrc);
}
whenFirstVisibleResolver();
return whenFirstVisiblePromise
.then(() => {
Expand Down Expand Up @@ -132,6 +138,46 @@ describes.realWin('amp-pixel', {amp: true}, (env) => {
);
});
});

it('should not allow attribution reporting', () => {
const attributionSrc =
'//pubads.g.doubleclick.net/activity;dc_iu=1/abc;ord=2';
return trigger(null, attributionSrc).then((img) => {
// Protocol is resolved to `http:` relative to test server.
expect(img.src).to.equal(
'https://pubads.g.doubleclick.net/activity;dc_iu=1/abc;ord=1?'
);
expect(img.attributionsrc).to.be.undefined;
});
});

it('should allow attribution reporting with empty attributionsrc', () => {
env.sandbox
.stub(privacySandboxUtils, 'isAttributionReportingAllowed')
.returns(true);
const attributionSrc = '';
return trigger(null, attributionSrc).then((img) => {
// Protocol is resolved to `http:` relative to test server.
expect(img.src).to.equal(
'https://pubads.g.doubleclick.net/activity;dc_iu=1/abc;ord=1?'
);
expect(img.attributionsrc).to.equal('');
});
});

it('should allow attribution reporting with attributionsrc defined', () => {
env.sandbox
.stub(privacySandboxUtils, 'isAttributionReportingAllowed')
.returns(true);
const attributionSrc = 'https://adtech.example';
return trigger(null, attributionSrc).then((img) => {
// Protocol is resolved to `http:` relative to test server.
expect(img.src).to.equal(
'https://pubads.g.doubleclick.net/activity;dc_iu=1/abc;ord=1?'
);
expect(img.attributionsrc).to.equal('https://adtech.example');
});
});
});

describes.realWin(
Expand Down
18 changes: 18 additions & 0 deletions validator/testdata/amp4ads_feature_tests/amp_pixel_ar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!--
Test Description:
Verify amp-pixel with Attribution Reporting API is valid.
-->
<!doctype html>
<html ⚡4ads>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1">
<style amp4ads-boilerplate>body{visibility:hidden}</style>
<script async src="https://cdn.ampproject.org/amp4ads-v0.js"></script>
</head>
<body>
Hello, world.
<amp-pixel src="https://pixel.me?yes=please" attributionsrc=""></amp-pixel>
<amp-pixel src="https://pixel.me?yes=please" attributionsrc="https://adtech.example"></amp-pixel>
</body>
</html>
19 changes: 19 additions & 0 deletions validator/testdata/amp4ads_feature_tests/amp_pixel_ar.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
PASS
| <!--
| Test Description:
| Verify amp-pixel with Attribution Reporting API is valid.
| -->
| <!doctype html>
| <html ⚡4ads>
| <head>
| <meta charset="utf-8">
| <meta name="viewport" content="width=device-width,minimum-scale=1">
| <style amp4ads-boilerplate>body{visibility:hidden}</style>
| <script async src="https://cdn.ampproject.org/amp4ads-v0.js"></script>
| </head>
| <body>
| Hello, world.
| <amp-pixel src="https://pixel.me?yes=please" attributionsrc=""></amp-pixel>
| <amp-pixel src="https://pixel.me?yes=please" attributionsrc="https://adtech.example"></amp-pixel>
| </body>
| </html>
7 changes: 7 additions & 0 deletions validator/validator-main.protoascii
Original file line number Diff line number Diff line change
Expand Up @@ -4064,6 +4064,13 @@ tags: { # <amp-pixel>
}
disallowed_value_regex: "__amp_source_origin"
}
attrs: {
name: "attributionsrc"
value_url: {
protocol: "https"
allow_empty: true
}
}
attr_lists: "extended-amp-global"
spec_url: "https://amp.dev/documentation/components/amp-pixel/"
amp_layout {
Expand Down

0 comments on commit c95dcde

Please sign in to comment.