Skip to content

Commit

Permalink
Support referrerPolicy:no-referrer in amp-analytics (#16734)
Browse files Browse the repository at this point in the history
* Support referrerPolicy:no-referrer in amp-analytics

* Add test

* Fix type checks.

* Add documentation
  • Loading branch information
lannka authored Jul 16, 2018
1 parent 7880fe1 commit 8ab0234
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 60 deletions.
55 changes: 2 additions & 53 deletions builtins/amp-pixel.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@

import {BaseElement} from '../src/base-element';
import {Services} from '../src/services';
import {createElementWithAttributes} from '../src/dom';
import {createPixel} from '../src/pixel';
import {dev, user} from '../src/log';
import {dict} from '../src/utils/object';
import {registerElement} from '../src/service/custom-element-registry';
import {toWin} from '../src/types';

const TAG = 'amp-pixel';

Expand Down Expand Up @@ -89,9 +87,7 @@ export class AmpPixel extends BaseElement {
return Services.urlReplacementsForDoc(this.element)
.expandUrlAsync(this.assertSource_(src))
.then(src => {
const pixel = this.referrerPolicy_
? createNoReferrerPixel(this.element, src)
: createImagePixel(this.win, src);
const pixel = createPixel(this.win, src, this.referrerPolicy_);
dev().info(TAG, 'pixel triggered: ', src);
return pixel;
});
Expand All @@ -112,53 +108,6 @@ export class AmpPixel extends BaseElement {
}
}

/**
* @param {!Element} parentElement
* @param {string} src
* @return {!Element}
*/
function createNoReferrerPixel(parentElement, src) {
if (isReferrerPolicySupported()) {
return createImagePixel(toWin(parentElement.ownerDocument.defaultView), src,
true);
} else {
// if "referrerPolicy" is not supported, use iframe wrapper
// to scrub the referrer.
const iframe = createElementWithAttributes(
/** @type {!Document} */ (parentElement.ownerDocument), 'iframe', dict({
'src': 'about:blank',
}));
parentElement.appendChild(iframe);
createImagePixel(iframe.contentWindow, src);
return iframe;
}
}

/**
* @param {!Window} win
* @param {string} src
* @param {boolean=} noReferrer
* @return {!Image}
*/
function createImagePixel(win, src, noReferrer) {
const image = new win.Image();
if (noReferrer) {
image.referrerPolicy = 'no-referrer';
}
image.src = src;
return image;
}

/**
* Check if element attribute "referrerPolicy" is supported by the browser.
* At this moment (4/14/2017), Safari does not support it yet.
*
* @return {boolean}
*/
export function isReferrerPolicySupported() {
return 'referrerPolicy' in Image.prototype;
}

/**
* @param {!Window} win Destination window for the new element.
*/
Expand Down
18 changes: 18 additions & 0 deletions examples/analytics.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@
<div id="container">
Container for analytics tags. Positioned far away from top to make sure that doesn't matter.

<amp-analytics>
<script type="application/json">
{
"requests": {
"endpoint": "/analytics/no-referrer"
},
"triggers": {
"pageview": {
"on": "visible",
"request": "endpoint"
}
},
"transport": {
"referrerPolicy": "no-referrer"
}
}
</script>
</amp-analytics>
<amp-analytics id="analytics1">
<script type="application/json">
{
Expand Down
23 changes: 16 additions & 7 deletions extensions/amp-analytics/0.1/transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
checkCorsUrl,
parseUrlDeprecated,
} from '../../../src/url';
import {createPixel} from '../../../src/pixel';
import {dev, user} from '../../../src/log';
import {loadPromise} from '../../../src/event-helper';
import {removeElement} from '../../../src/dom';
Expand All @@ -31,11 +32,19 @@ const TAG_ = 'amp-analytics.Transport';
/**
* @param {!Window} win
* @param {string} request
* @param {!Object<string, string>} transportOptions
* @param {!Object<string, string|boolean>} transportOptions
*/
export function sendRequest(win, request, transportOptions) {
assertHttpsUrl(request, 'amp-analytics request');
checkCorsUrl(request);

const referrerPolicy = transportOptions['referrerPolicy'];

if (referrerPolicy === 'no-referrer') {
transportOptions['beacon'] = false;
transportOptions['xhrpost'] = false;
}

if (transportOptions['beacon'] &&
Transport.sendRequestUsingBeacon(win, request)) {
return;
Expand All @@ -48,7 +57,8 @@ export function sendRequest(win, request, transportOptions) {
if (image) {
const suppressWarnings = (typeof image == 'object' &&
image['suppressWarnings']);
Transport.sendRequestUsingImage(request, suppressWarnings);
Transport.sendRequestUsingImage(
win, request, suppressWarnings, /** @type {string|undefined} */ (referrerPolicy));
return;
}
user().warn(TAG_, 'Failed to send request', request, transportOptions);
Expand All @@ -60,14 +70,13 @@ export function sendRequest(win, request, transportOptions) {
export class Transport {

/**
* @param {!Window} win
* @param {string} request
* @param {boolean} suppressWarnings
* @param {string|undefined} referrerPolicy
*/
static sendRequestUsingImage(request, suppressWarnings) {
const image = new Image();
image.src = request;
image.width = 1;
image.height = 1;
static sendRequestUsingImage(win, request, suppressWarnings, referrerPolicy) {
const image = createPixel(win, request, referrerPolicy);
loadPromise(image).then(() => {
dev().fine(TAG_, 'Sent image request', request);
}).catch(() => {
Expand Down
14 changes: 14 additions & 0 deletions extensions/amp-analytics/amp-analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,20 @@ In the example below, an `iframe` URL is not specified, and `beacon` and `xhrpos

To learn more, see [this example that implements iframe transport client API] (https://github.com/ampproject/amphtml/blob/master/examples/analytics-iframe-transport-remote-frame.html) and [this example page that incorporates that iframe](https://github.com/ampproject/amphtml/blob/master/examples/analytics-iframe-transport.amp.html). The example loads a [fake ad](https://github.com/ampproject/amphtml/blob/master/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html), which contains the `amp-analytics` tag. Note that the fake ad content includes some extra configuration instructions that must be followed.

##### Referrer Policy

Referrer policy can be specified as `referrerPolicy` field in the `transport` config. Currently only `no-referrer` is supported.
Referrer policy is only available for `image` transport. If `referrerPolicy: no-referrer` is specified, the `beacon` & `xhrpost` transports are overridden to `false`.

```javascript
"transport": {
"beacon": false,
"xhrpost": false,
"image": true,
"referrerPolicy": "no-referrer"
}
```

## Validation

See [amp-analytics rules](https://github.com/ampproject/amphtml/blob/master/extensions/amp-analytics/validator-amp-analytics.protoascii) in the AMP validator specification.
Expand Down
85 changes: 85 additions & 0 deletions src/pixel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {createElementWithAttributes} from '../src/dom';
import {dict} from '../src/utils/object';
import {user} from '../src/log';

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

/**
* @param {!Window} win
* @param {string} src
* @param {?string=} referrerPolicy
* @return {!Element}
*/
export function createPixel(win, src, referrerPolicy) {
if (referrerPolicy && referrerPolicy !== 'no-referrer') {
user().error(TAG, 'Unsupported referrerPolicy: ' + referrerPolicy);
}

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

/**
* @param {!Window} win
* @param {string} src
* @return {!Element}
*/
function createNoReferrerPixel(win, src) {
if (isReferrerPolicySupported()) {
return createImagePixel(win, src, true);
} else {
// if "referrerPolicy" is not supported, use iframe wrapper
// to scrub the referrer.
const iframe = createElementWithAttributes(
/** @type {!Document} */ (win.document), 'iframe', dict({
'src': 'about:blank',
'style': 'display:none',
}));
win.document.body.appendChild(iframe);
createImagePixel(iframe.contentWindow, src);
return iframe;
}
}

/**
* @param {!Window} win
* @param {string} src
* @param {boolean=} noReferrer
* @return {!Image}
*/
function createImagePixel(win, src, noReferrer = false) {
const image = new win.Image();
if (noReferrer) {
image.referrerPolicy = 'no-referrer';
}
image.src = src;
return image;
}

/**
* Check if element attribute "referrerPolicy" is supported by the browser.
* Safari 11.1 does not support it yet.
*
* @return {boolean}
*/
function isReferrerPolicySupported() {
return 'referrerPolicy' in Image.prototype;
}
82 changes: 82 additions & 0 deletions test/integration/test-amp-analytics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
depositRequestUrl,
withdrawRequest,
} from '../../testing/test-helper';

describe.configure().run('amp-analytics', function() {
this.timeout(15000);

describes.integration('amp-analytics integration test', {
body:
`<amp-analytics>
<script type="application/json">
{
"requests": {
"endpoint": "${depositRequestUrl('analytics-has-referrer')}"
},
"triggers": {
"pageview": {
"on": "visible",
"request": "endpoint"
}
}
}
</script>
</amp-analytics>
`,
extensions: ['amp-analytics'],
}, env => {
it('should keep referrer if no referrerpolicy specified', () => {
return withdrawRequest(env.win,
'analytics-has-referrer').then(request => {
expect(request.headers.referer).to.be.ok;
});
});
});

describes.integration('amp-analytics integration test', {
body:
`<amp-analytics>
<script type="application/json">
{
"requests": {
"endpoint": "${depositRequestUrl('analytics-no-referrer')}"
},
"triggers": {
"pageview": {
"on": "visible",
"request": "endpoint"
}
},
"transport": {
"referrerPolicy": "no-referrer"
}
}
</script>
</amp-analytics>
`,
extensions: ['amp-analytics'],
}, env => {
it('should remove referrer if referrerpolicy=no-referrer', () => {
return withdrawRequest(env.win, 'analytics-no-referrer').then(request => {
expect(request.headers.referer).to.not.be.ok;
});
});
});
});

0 comments on commit 8ab0234

Please sign in to comment.