From 8e71f6f33f9a0bc6dd158029c90261f957e2cec1 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Tue, 29 Dec 2015 18:34:57 -0500 Subject: [PATCH] Normalize certain referrers across devices - We normalize `t.co` referrers to `twitter.com` - We add a `www.pinterest.com` referrer when inside the Android Pinterest app. --- .../0.1/amp-dynamic-css-classes.js | 54 ++++++++++++++++-- .../0.1/test/test-dynamic-classes.js | 16 +++--- .../0.1/test/test-runtime-classes.js | 57 ++++++++++++++++--- .../amp-dynamic-css-classes.md | 7 +++ src/platform.js | 12 ++-- 5 files changed, 120 insertions(+), 26 deletions(-) diff --git a/extensions/amp-dynamic-css-classes/0.1/amp-dynamic-css-classes.js b/extensions/amp-dynamic-css-classes/0.1/amp-dynamic-css-classes.js index 6c61ca01414b..1f407495ace8 100644 --- a/extensions/amp-dynamic-css-classes/0.1/amp-dynamic-css-classes.js +++ b/extensions/amp-dynamic-css-classes/0.1/amp-dynamic-css-classes.js @@ -25,6 +25,28 @@ const TAG = 'AmpDynamicCssClasses'; /** @const */ const EXPERIMENT = 'dynamic-css-classes'; +/** + * Strips everything but the domain from referrer string. + * @param {!Window} win + * @returns {string} + */ +function referrerDomain(win) { + const referrer = win.document.referrer; + if (referrer) { + return parseUrl(referrer).hostname; + } + return ''; +} + +/** + * Grabs the User Agent string. + * @param {!Window} win + * @returns {string} + */ +function userAgent(win) { + return win.navigator.userAgent; +} + /** * Returns an array of referrers which vary in level of subdomain specificity. * @@ -33,13 +55,12 @@ const EXPERIMENT = 'dynamic-css-classes'; * @private Visible for testing only! */ export function referrers_(referrer) { - referrer = parseUrl(referrer).hostname; const domains = referrer.split('.'); let domainBase = ''; return domains.reduceRight((referrers, domain) => { if (domainBase) { - domain += '-' + domainBase; + domain += '.' + domainBase; } domainBase = domain; referrers.push(domain); @@ -47,6 +68,29 @@ export function referrers_(referrer) { }, []); } +/** + * Normalizes certain referrers across devices. + * @param {!Window} win + * @returns {!Array} + */ +function normalizedReferrers(win) { + const referrer = referrerDomain(win); + + // Normalize t.co names to twitter.com + if (referrer === 't.co') { + return referrers_('twitter.com'); + } + + // Pinterest does not reliably set the referrer on Android + // Instead, we inspect the User Agent string. + if (!referrer && /Pinterest/.test(userAgent(win))) { + return referrers_('www.pinterest.com'); + } + + return referrers_(referrer); +} + + /** * Adds CSS classes onto the HTML element. * @param {!Window} win @@ -68,8 +112,9 @@ function addDynamicCssClasses(win, classes) { * @param {!Window} win */ function addReferrerClasses(win) { - const classes = referrers_(win.document.referrer).map(referrer => { - return `amp-referrer-${referrer}`; + const referrers = normalizedReferrers(win); + const classes = referrers.map(referrer => { + return `amp-referrer-${referrer.replace(/\./g, '-')}`; }); addDynamicCssClasses(win, classes); } @@ -86,6 +131,7 @@ function addViewerClass(win) { } } + /** * @param {!Window} win */ diff --git a/extensions/amp-dynamic-css-classes/0.1/test/test-dynamic-classes.js b/extensions/amp-dynamic-css-classes/0.1/test/test-dynamic-classes.js index a63a5886030b..b1ee9f015408 100644 --- a/extensions/amp-dynamic-css-classes/0.1/test/test-dynamic-classes.js +++ b/extensions/amp-dynamic-css-classes/0.1/test/test-dynamic-classes.js @@ -19,7 +19,7 @@ import {referrers_} from '../amp-dynamic-css-classes'; describe('amp-dynamic-css-classes', () => { describe('referrers_', () => { describe('when referrer is TLD-less', () => { - const referrer = 'http://localhost/test/ing?this#referrer'; + const referrer = 'localhost'; it('contains the domain', () => { expect(referrers_(referrer)).to.deep.equal(['localhost']); @@ -27,7 +27,7 @@ describe('amp-dynamic-css-classes', () => { }); describe('when referrer has no subdomains', () => { - const referrer = 'http://google.com/test/ing?this#referrer'; + const referrer = 'google.com'; const referrers = referrers_(referrer); it('contains the TLD', () => { @@ -35,13 +35,13 @@ describe('amp-dynamic-css-classes', () => { }); it('contains the domain', () => { - expect(referrers).to.contain('google-com'); + expect(referrers).to.contain('google.com'); expect(referrers.length).to.equal(2); }); }); describe('when referrer has subdomains', () => { - const referrer = 'http://a.b.c.google.com/test/ing?this#referrer'; + const referrer = 'a.b.c.google.com'; const referrers = referrers_(referrer); it('contains the TLD', () => { @@ -49,14 +49,14 @@ describe('amp-dynamic-css-classes', () => { }); it('contains the domain', () => { - expect(referrers).to.contain('google-com'); + expect(referrers).to.contain('google.com'); }); it('contains each subdomain', () => { expect(referrers).to.include.members([ - 'c-google-com', - 'b-c-google-com', - 'a-b-c-google-com' + 'c.google.com', + 'b.c.google.com', + 'a.b.c.google.com' ]); expect(referrers.length).to.equal(5); }); diff --git a/extensions/amp-dynamic-css-classes/0.1/test/test-runtime-classes.js b/extensions/amp-dynamic-css-classes/0.1/test/test-runtime-classes.js index 3ba334fbd5f1..915049aead3a 100644 --- a/extensions/amp-dynamic-css-classes/0.1/test/test-runtime-classes.js +++ b/extensions/amp-dynamic-css-classes/0.1/test/test-runtime-classes.js @@ -17,21 +17,46 @@ import {createServedIframe} from '../../../../testing/iframe'; import {toggleExperiment} from '../../../../src/experiments'; +function overwrite(object, property, value) { + Object.defineProperty(object, property, { + enumerable: true, + writeable: false, + configurable: true, + value: value + }); +} + const iframeSrc = '/base/test/fixtures/served/amp-dynamic-css-classes.html'; +const tcoReferrer = 'http://t.co/xyzabc123'; +const PinterestUA = 'Mozilla/5.0 (Linux; Android 5.1.1; SM-G920F' + + ' Build/LMY47X; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0' + + ' Chrome/47.0.2526.100 Mobile Safari/537.36 [Pinterest/Android]'; + describe('dynamic classes are inserted at runtime', () => { - let documentElement, win; - beforeEach(() => { + let documentElement; + + function setup(enabled, userAgent, referrer) { return createServedIframe(iframeSrc).then(fixture => { - win = fixture.win; + const win = fixture.win; documentElement = fixture.doc.documentElement; + + toggleExperiment(win, 'dynamic-css-classes', enabled); + + if (userAgent !== undefined) { + overwrite(win.navigator, 'userAgent', userAgent); + } + if (referrer !== undefined) { + overwrite(fixture.doc, 'referrer', referrer); + } + + return win.insertDynamicCssScript(); }); - }); + } describe('when experiment is disabled', () => { beforeEach(() => { - toggleExperiment(win, 'dynamic-css-classes', false); - return win.insertDynamicCssScript(); + return setup(false); }); it('should not include referrer classes', () => { @@ -45,8 +70,7 @@ describe('dynamic classes are inserted at runtime', () => { describe('when experiment is enabled', () => { beforeEach(() => { - toggleExperiment(win, 'dynamic-css-classes', true); - return win.insertDynamicCssScript(); + return setup(true); }); it('should include referrer classes', () => { @@ -57,4 +81,21 @@ describe('dynamic classes are inserted at runtime', () => { expect(documentElement).to.have.class('amp-viewer'); }); }); + + describe('Normalizing Referrers', () => { + it('should normalize twitter shortlinks to twitter', () => { + return setup(true, '', tcoReferrer).then(() => { + expect(documentElement).to.have.class('amp-referrer-com'); + expect(documentElement).to.have.class('amp-referrer-twitter-com'); + }); + }); + + it('should normalize pinterest on android', () => { + return setup(true, PinterestUA, '').then(() => { + expect(documentElement).to.have.class('amp-referrer-com'); + expect(documentElement).to.have.class('amp-referrer-pinterest-com'); + expect(documentElement).to.have.class('amp-referrer-www-pinterest-com'); + }); + }); + }); }); diff --git a/extensions/amp-dynamic-css-classes/amp-dynamic-css-classes.md b/extensions/amp-dynamic-css-classes/amp-dynamic-css-classes.md index 86e913e71f7e..49ee6c96c632 100644 --- a/extensions/amp-dynamic-css-classes/amp-dynamic-css-classes.md +++ b/extensions/amp-dynamic-css-classes/amp-dynamic-css-classes.md @@ -31,6 +31,13 @@ subdomain specificity. For example, `www.google.com` will add three classes: `amp-referrer-www-google-com`, `amp-referrer-google-com`, and `amp-referrer-com`. +We currently have a few special cases: + +- When the user came through a Twitter `t.co` short link, we instead use + `twitter.com` as the referrer. +- When the string "Pinterest" is present in the User Agent string and + there is no referrer, we use `www.pinterest.com` as the referrer. + **amp-viewer** The `amp-viewer` class will be set if the current document is being diff --git a/src/platform.js b/src/platform.js index 7258de5b0796..362487fca4c3 100644 --- a/src/platform.js +++ b/src/platform.js @@ -27,8 +27,8 @@ export class Platform { * @param {!Window} win */ constructor(win) { - /** @const {!Window} */ - this.win = win; + /** @const {!Navigator} */ + this.navigator = win.navigator; } /** @@ -36,7 +36,7 @@ export class Platform { * @return {boolean} */ isIos() { - return /iPhone|iPad|iPod/i.test(this.win.navigator.userAgent); + return /iPhone|iPad|iPod/i.test(this.navigator.userAgent); } /** @@ -44,16 +44,16 @@ export class Platform { * @return {boolean} */ isSafari() { - return /Safari/i.test(this.win.navigator.userAgent) && !this.isChrome(); + return /Safari/i.test(this.navigator.userAgent) && !this.isChrome(); } /** - * Whether the current browser a Chrome browser. + * Whether the current browser is a Chrome browser. * @return {boolean} */ isChrome() { // Also true for MS Edge :) - return /Chrome|CriOS/i.test(this.win.navigator.userAgent); + return /Chrome|CriOS/i.test(this.navigator.userAgent); } };