Skip to content

Commit

Permalink
Merge pull request #1253 from jridgewell/dynamic-css-classes
Browse files Browse the repository at this point in the history
Add Dynamic CSS Classes extension
  • Loading branch information
jridgewell committed Dec 29, 2015
2 parents f3edeb2 + 8b46ced commit 3a28d82
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 0 deletions.
101 changes: 101 additions & 0 deletions extensions/amp-dynamic-css-classes/0.1/amp-dynamic-css-classes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Copyright 2015 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 {parseUrl} from '../../../src/url';
import {viewerFor} from '../../../src/viewer';
import {log} from '../../../src/log';
import {isExperimentOn} from '../../../src/experiments';

/** @const */
const TAG = 'AmpDynamicCssClasses';

/** @const */
const EXPERIMENT = 'dynamic-css-classes';

/**
* Returns an array of referrers which vary in level of subdomain specificity.
*
* @param {string} referrer
* @return {!Array<string>}
* @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;
}
domainBase = domain;
referrers.push(domain);
return referrers;
}, []);
}

/**
* Adds CSS classes onto the HTML element.
* @param {!Window} win
* @param {!Array<string>} classes
*/
function addDynamicCssClasses(win, classes) {
const documentElement = win.document.documentElement;
const classList = documentElement.classList;

for (let i = 0; i < classes.length; i++) {
classList.add(classes[i]);
}
}


/**
* Adds dynamic css classes based on the referrer, with a separate class for
* each level of subdomain specificity.
* @param {!Window} win
*/
function addReferrerClasses(win) {
const classes = referrers_(win.document.referrer).map(referrer => {
return `amp-referrer-${referrer}`;
});
addDynamicCssClasses(win, classes);
}


/**
* Adds a dynamic css class `amp-viewer` if this document is inside a viewer.
* @param {!Window} win
*/
function addViewerClass(win) {
const viewer = viewerFor(win);
if (viewer.isEmbedded()) {
addDynamicCssClasses(win, ['amp-viewer']);
}
}

/**
* @param {!Window} win
*/
function addRuntimeClasses(win) {
if (isExperimentOn(win, EXPERIMENT)) {
addReferrerClasses(win);
addViewerClass(win);
} else {
log.warn(TAG, `Experiment ${EXPERIMENT} disabled`);
}
}

addRuntimeClasses(AMP.win);
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright 2015 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 {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';

it('contains the domain', () => {
expect(referrers_(referrer)).to.deep.equal(['localhost']);
});
});

describe('when referrer has no subdomains', () => {
const referrer = 'http://google.com/test/ing?this#referrer';
const referrers = referrers_(referrer);

it('contains the TLD', () => {
expect(referrers).to.contain('com');
});

it('contains the domain', () => {
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 referrers = referrers_(referrer);

it('contains the TLD', () => {
expect(referrers).to.contain('com');
});

it('contains the domain', () => {
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'
]);
expect(referrers.length).to.equal(5);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright 2015 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 {createServedIframe} from '../../../../testing/iframe';
import {toggleExperiment} from '../../../../src/experiments';

const iframeSrc = '/base/test/fixtures/served/amp-dynamic-css-classes.html';

describe('dynamic classes are inserted at runtime', () => {
let documentElement, win;
beforeEach(() => {
return createServedIframe(iframeSrc).then(fixture => {
win = fixture.win;
documentElement = fixture.doc.documentElement;
});
});

describe('when experiment is disabled', () => {
beforeEach(() => {
toggleExperiment(win, 'dynamic-css-classes', false);
return win.insertDynamicCssScript();
});

it('should not include referrer classes', () => {
expect(documentElement).not.to.have.class('amp-referrer-localhost');
});

it('should not include viewer class', () => {
expect(documentElement).not.to.have.class('amp-viewer');
});
});

describe('when experiment is enabled', () => {
beforeEach(() => {
toggleExperiment(win, 'dynamic-css-classes', true);
return win.insertDynamicCssScript();
});

it('should include referrer classes', () => {
expect(documentElement).to.have.class('amp-referrer-localhost');
});

it('should include viewer class', () => {
expect(documentElement).to.have.class('amp-viewer');
});
});
});
37 changes: 37 additions & 0 deletions extensions/amp-dynamic-css-classes/amp-dynamic-css-classes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!---
Copyright 2015 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.
-->

### <a name="amp-dynamic-css-classes"></a> AMP Dynamic CSS Classes

The AMP Dynamic CSS Classes extension adds several dynamic CSS class
names onto the HTML element.

#### Behavior

The AMP Dynamic CSS Classes extension adds the following CSS classes
onto the HTML element:

**amp-referrer-***

One or more referrer classes will be set, one for each level of
subdomain specificity. For example, `www.google.com` will add three
classes: `amp-referrer-www-google-com`, `amp-referrer-google-com`, and
`amp-referrer-com`.

**amp-viewer**

The `amp-viewer` class will be set if the current document is being
displayed inside a Viewer.
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function buildExtensions(options) {
buildExtension('amp-audio', '0.1', false, options);
buildExtension('amp-brightcove', '0.1', false, options);
buildExtension('amp-carousel', '0.1', true, options);
buildExtension('amp-dynamic-css-classes', '0.1', false, options);
buildExtension('amp-fit-text', '0.1', true, options);
buildExtension('amp-font', '0.1', false, options);
buildExtension('amp-iframe', '0.1', false, options);
Expand Down
25 changes: 25 additions & 0 deletions test/fixtures/served/amp-dynamic-css-classes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!doctype html>
<html >
<head>
<meta charset="utf-8">
<title>AMP #0</title>
<link rel="canonical" href="amps.html" >
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<style>body {opacity: 0}</style><noscript><style>body {opacity: 1}</style></noscript>
<script async src="/base/dist/amp.js"></script>
<script>
function insertDynamicCssScript() {
return new Promise((resolve, reject) => {
var script = document.createElement('script');
script.src = '/base/dist/v0/amp-dynamic-css-classes-0.1.max.js';
script.onload = resolve;
script.onerror = reject;
document.getElementsByTagName('head')[0].appendChild(script);
});
}
</script>
</head>
<body>

</body>
</html>
21 changes: 21 additions & 0 deletions testing/iframe.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,27 @@ export function createIframePromise(opt_runtimeOff, opt_beforeLayoutCallback) {
});
}

export function createServedIframe(src) {
return new Promise(function(resolve, reject) {
const iframe = document.createElement('iframe');
iframe.name = 'test_' + iframeCount++;
iframe.src = src;
iframe.onload = function() {
const win = iframe.contentWindow;
win.AMP_TEST = true;
installCoreServices(win);
registerForUnitTest(win);
resolve({
win: win,
doc: win.document,
iframe: iframe
});
};
iframe.onerror = reject;
document.body.appendChild(iframe);
});
}

/**
* Returns a promise for when the condition becomes true.
* @param {string} description
Expand Down
8 changes: 8 additions & 0 deletions tools/experiments/experiments.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ const EXPERIMENTS = [
spec: 'https://github.com/ampproject/amphtml/blob/master/extensions/' +
'amp-user-notification/amp-user-notification.md',
},

// Dynamic CSS Classes
{
id: 'dynamic-css-classes',
name: 'Dynamic CSS Classes',
spec: 'https://github.com/ampproject/amphtml/blob/master/extensions/' +
'amp-dynamic-css-classes/amp-dynamic-css-classes.md',
},
];


Expand Down

0 comments on commit 3a28d82

Please sign in to comment.