diff --git a/.eslintignore b/.eslintignore index bc176cb8dbf6..5c9609917d0c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,11 +12,7 @@ firebase third_party test/coverage **/*.extern.js -validator/dist -validator/webui/dist -validator/node_modules -validator/nodejs/node_modules -validator/webui/node_modules +validator/ eslint-rules babel.config.js karma.conf.js diff --git a/.eslintrc b/.eslintrc index 2fa82ef42bdd..1eeadacff7d2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,6 +7,7 @@ "google-camelcase", "jsdoc", "notice", + "prettier", "sort-imports-es6-autofix", "sort-requires" ], @@ -76,19 +77,11 @@ "amphtml-internal/todo-format": 0, "amphtml-internal/unused-private-field": 0, "amphtml-internal/vsync": 0, - "array-bracket-spacing": [2, "never"], - "arrow-parens": [2, "as-needed"], - "arrow-spacing": 2, "chai-expect/missing-assertion": 2, "chai-expect/no-inner-compare": 2, "chai-expect/terminating-properties": 2, - "comma-dangle": [2, "always-multiline"], - "computed-property-spacing": [2, "never"], "curly": 2, - "dot-location": [2, "property"], - "eol-last": 2, "google-camelcase/google-camelcase": 2, - "indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2, "MemberExpression": 2, "ObjectExpression": 1, "CallExpression": { "arguments": 2 } }], "jsdoc/check-param-names": 2, "jsdoc/check-tag-names": 2, "jsdoc/check-types": 2, @@ -96,13 +89,6 @@ "jsdoc/require-param-name": 2, "jsdoc/require-param-type": 2, "jsdoc/require-returns-type": 2, - "key-spacing": 2, - "max-len": [2, 80, 4, { - "ignoreTrailingComments": true, - "ignoreRegExpLiterals": true, - "ignorePattern": "^} from.*;|= require\\(.*;$|@typedef|@param|@return|@private|@const|@type|@implements", - "ignoreUrls": true - }], "no-alert": 2, "no-cond-assign": 2, "no-debugger": 2, @@ -111,21 +97,17 @@ "no-eval": 2, "no-extend-native": 2, "no-extra-bind": 2, - "no-extra-semi": 2, "no-implicit-coercion": [2, { "boolean": false }], "no-implied-eval": 2, "no-iterator": 2, "no-lone-blocks": 2, - "no-multi-spaces": 2, "no-native-reassign": 2, "no-redeclare": 2, "no-restricted-globals": [2, "error", "event"], "no-script-url": 2, "no-self-compare": 2, "no-sequences": 2, - "no-spaced-func": 2, "no-throw-literal": 2, - "no-trailing-spaces": 2, "no-unused-expressions": 0, "no-unused-vars": [2, { "argsIgnorePattern": "^(var_args$|opt_|unused)", @@ -145,13 +127,14 @@ } } ], - "object-curly-spacing": [2, "never", { - "objectsInObjects": false, - "arraysInObjects": false - }], "object-shorthand": [2, "properties", { "avoidQuotes": true }], "prefer-const": 2, - "quotes": [2, "single", "avoid-escape"], + "prettier/prettier": [2, { + "bracketSpacing": false, + "singleQuote": true, + "trailingComma": "es5", + "quoteProps": "preserve" + }], "radix": 2, "require-jsdoc": [2, { "require": { @@ -162,20 +145,12 @@ "FunctionExpression": false } }], - "semi": 2, - "keyword-spacing": [2, { "before": true, "after": true }], "sort-imports-es6-autofix/sort-imports-es6": [2, { "ignoreCase": false, "ignoreMemberSort": false, "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] }], - "sort-requires/sort-requires": 2, - "space-before-blocks": 2, - "space-before-function-paren": [2, "never"], - "space-in-parens": 2, - "space-infix-ops": 2, - "space-unary-ops": [1, { "words": true, "nonwords": false }], - "wrap-iife": [2, "any"] + "sort-requires/sort-requires": 2 }, "overrides": [ { diff --git a/3p/3d-gltf/index.js b/3p/3d-gltf/index.js index f893471cfff5..9ddf8f3db762 100644 --- a/3p/3d-gltf/index.js +++ b/3p/3d-gltf/index.js @@ -39,14 +39,15 @@ const loadThree = (global, cb) => { const loadScriptCb = url => cb => loadScript(global, url, cb); const loadThreeExample = examplePath => loadScriptCb( - 'https://cdn.jsdelivr.net/npm/three@0.91/examples/js/' + examplePath); + 'https://cdn.jsdelivr.net/npm/three@0.91/examples/js/' + examplePath + ); seq( - loadScriptCb( - 'https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.js'), - parallel( - loadThreeExample('loaders/GLTFLoader.js'), - loadThreeExample('controls/OrbitControls.js')) + loadScriptCb('https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.js'), + parallel( + loadThreeExample('loaders/GLTFLoader.js'), + loadThreeExample('controls/OrbitControls.js') + ) )(cb); }; @@ -65,16 +66,22 @@ export function gltfViewer(global) { if (!e.lengthComputable) { return; } - nonSensitiveDataPostMessage('progress', dict({ - 'total': e.total, - 'loaded': e.loaded, - })); + nonSensitiveDataPostMessage( + 'progress', + dict({ + 'total': e.total, + 'loaded': e.loaded, + }) + ); }, onerror: err => { user().error('3DGLTF', err); - nonSensitiveDataPostMessage('error', dict({ - 'error': (err || '').toString(), - })); + nonSensitiveDataPostMessage( + 'error', + dict({ + 'error': (err || '').toString(), + }) + ); }, }); listenParent(global, 'action', msg => { diff --git a/3p/3d-gltf/viewer.js b/3p/3d-gltf/viewer.js index 438cfbad7e72..ea706f389725 100644 --- a/3p/3d-gltf/viewer.js +++ b/3p/3d-gltf/viewer.js @@ -21,8 +21,7 @@ import AnimationLoop from './animation-loop'; const CAMERA_DISTANCE_FACTOR = 1; const CAMERA_FAR_FACTOR = 50; -const CAMERA_NEAR_FACTOR = .1; - +const CAMERA_NEAR_FACTOR = 0.1; export default class GltfViewer { /** @@ -45,8 +44,9 @@ export default class GltfViewer { /** @private */ this.controls_ = new THREE.OrbitControls( - this.camera_, - this.renderer_.domElement); + this.camera_, + this.renderer_.domElement + ); /** @private */ this.scene_ = new THREE.Scene(); @@ -59,7 +59,7 @@ export default class GltfViewer { /** @private */ this.ampInViewport_ = - options['initialIntersection']['intersectionRatio'] > 0; + options['initialIntersection']['intersectionRatio'] > 0; /** @private */ this.setSize_ = this.setupSize_(); @@ -86,17 +86,20 @@ export default class GltfViewer { * @private */ setModelRotation_(args) { - const xAngle = 'x' in args - ? this.getModelRotationOnAxis_(args, 'x') - : this.model_.rotation.x; + const xAngle = + 'x' in args + ? this.getModelRotationOnAxis_(args, 'x') + : this.model_.rotation.x; - const yAngle = 'y' in args - ? this.getModelRotationOnAxis_(args, 'y') - : this.model_.rotation.y; + const yAngle = + 'y' in args + ? this.getModelRotationOnAxis_(args, 'y') + : this.model_.rotation.y; - const zAngle = 'z' in args - ? this.getModelRotationOnAxis_(args, 'z') - : this.model_.rotation.z; + const zAngle = + 'z' in args + ? this.getModelRotationOnAxis_(args, 'z') + : this.model_.rotation.z; this.model_.rotation.set(xAngle, yAngle, zAngle); this.animationLoop_.needsUpdate = true; @@ -161,12 +164,12 @@ export default class GltfViewer { * * @private */ setupLight_() { - const amb = new THREE.AmbientLight(0xEDECD5, .5); + const amb = new THREE.AmbientLight(0xedecd5, 0.5); - const dir1 = new THREE.DirectionalLight(0xFFFFFF, .5); + const dir1 = new THREE.DirectionalLight(0xffffff, 0.5); dir1.position.set(0, 5, 3); - const dir2 = new THREE.DirectionalLight(0xAECDD6, .4); + const dir2 = new THREE.DirectionalLight(0xaecdd6, 0.4); dir2.position.set(-1, -2, 4); const light = new THREE.Group(); @@ -188,12 +191,14 @@ export default class GltfViewer { this.renderer_.gammaOutput = true; this.renderer_.gammaFactor = 2.2; this.renderer_.setPixelRatio( - Math.min( - this.options_['rendererSettings']['maxPixelRatio'], - devicePixelRatio)); + Math.min( + this.options_['rendererSettings']['maxPixelRatio'], + devicePixelRatio + ) + ); this.renderer_.setClearColor( - this.options_['rendererSettings']['clearColor'], - this.options_['rendererSettings']['clearAlpha'] + this.options_['rendererSettings']['clearColor'], + this.options_['rendererSettings']['clearAlpha'] ); } @@ -222,9 +227,9 @@ export default class GltfViewer { this.camera_.far = sizeLength * CAMERA_FAR_FACTOR; this.camera_.near = sizeLength * CAMERA_NEAR_FACTOR; this.camera_.position.lerpVectors( - center, - bbox.max, - 1 + CAMERA_DISTANCE_FACTOR + center, + bbox.max, + 1 + CAMERA_DISTANCE_FACTOR ); this.camera_.lookAt(center); @@ -240,23 +245,22 @@ export default class GltfViewer { loader.crossOrigin = true; loader.load( - this.options_['src'], - /** @param {{scene: !THREE.Scene}} gltfData */ - gltfData => { - this.setupCameraForObject_(gltfData.scene); - gltfData.scene.children - .slice() - .forEach(child => { - this.model_.add(child); - }); - - this.scene_.add(this.model_); - - this.animationLoop_.needsUpdate = true; - this.handlers_.onload(); - }, - this.handlers_.onprogress, - this.handlers_.onerror); + this.options_['src'], + /** @param {{scene: !THREE.Scene}} gltfData */ + gltfData => { + this.setupCameraForObject_(gltfData.scene); + gltfData.scene.children.slice().forEach(child => { + this.model_.add(child); + }); + + this.scene_.add(this.model_); + + this.animationLoop_.needsUpdate = true; + this.handlers_.onload(); + }, + this.handlers_.onprogress, + this.handlers_.onerror + ); } /** @private */ diff --git a/3p/3p.js b/3p/3p.js index 755c79e87538..2a825b5fa5e1 100644 --- a/3p/3p.js +++ b/3p/3p.js @@ -21,16 +21,13 @@ // Note: loaded by 3p system. Cannot rely on babel polyfills. - import {devAssert, rethrowAsync, userAssert} from '../src/log'; import {hasOwn, map} from '../src/utils/object'; import {isArray} from '../src/types'; - /** @typedef {function(!Window, !Object)} */ let ThirdPartyFunctionDef; - /** * @const {!Object} * @visibleForTesting @@ -82,9 +79,10 @@ export function run(id, win, data) { * @param {function()=} opt_cb */ export function writeScript(win, url, opt_cb) { - /*eslint no-useless-concat: 0*/ - win.document - .write('<' + 'script src="' + encodeURI(url) + '"><' + '/script>'); + win.document.write( + // eslint-disable-next-line no-useless-concat + '<' + 'script src="' + encodeURI(url) + '"><' + '/script>' + ); if (opt_cb) { executeAfterWriteScript(win, opt_cb); } @@ -120,7 +118,7 @@ export function loadScript(win, url, opt_cb, opt_errorCb) { export function nextTick(win, fn) { const P = win.Promise; if (P) { - P.resolve().then/*OK*/(fn); + P.resolve()./*OK*/ then(fn); } else { win.setTimeout(fn, 0); } @@ -135,6 +133,7 @@ export function nextTick(win, fn) { function executeAfterWriteScript(win, fn) { const index = syncScriptLoads++; win['__runScript' + index] = fn; + // eslint-disable-next-line no-useless-concat win.document.write('<' + 'script>__runScript' + index + '()<' + '/script>'); } @@ -231,8 +230,12 @@ export function validateData(data, mandatoryFields, opt_optionalFields) { validateExactlyOne(data, field); allowedFields = allowedFields.concat(field); } else { - userAssert(data[field], - 'Missing attribute for %s: %s.', data.type, field); + userAssert( + data[field], + 'Missing attribute for %s: %s.', + data.type, + field + ); allowedFields.push(field); } } @@ -248,10 +251,12 @@ export function validateData(data, mandatoryFields, opt_optionalFields) { * @param {!Array} alternativeFields */ function validateExactlyOne(data, alternativeFields) { - userAssert(alternativeFields.filter(field => data[field]).length === 1, - '%s must contain exactly one of attributes: %s.', - data.type, - alternativeFields.join(', ')); + userAssert( + alternativeFields.filter(field => data[field]).length === 1, + '%s must contain exactly one of attributes: %s.', + data.type, + alternativeFields.join(', ') + ); } /** diff --git a/3p/ampcontext-integration.js b/3p/ampcontext-integration.js index fd75cbc36d8c..e71a05e86c5f 100644 --- a/3p/ampcontext-integration.js +++ b/3p/ampcontext-integration.js @@ -19,7 +19,6 @@ import {computeInMasterFrame} from './3p'; import {dev, user, userAssert} from '../src/log'; import {dict} from '../src/utils/object'; - /** * Returns the "master frame" for all widgets of a given type. * This frame should be used to e.g. fetch scripts that can @@ -31,8 +30,8 @@ import {dict} from '../src/utils/object'; */ export function masterSelection(win, type) { type = type.toLowerCase(); - const configType = adConfig[type] && - adConfig[type]['masterFrameAccessibleType']; + const configType = + adConfig[type] && adConfig[type]['masterFrameAccessibleType']; // The master has a special name. const masterName = 'frame_' + (configType || type) + '_master'; let master; @@ -52,9 +51,7 @@ export function masterSelection(win, type) { return master; } - export class IntegrationAmpContext extends AbstractAmpContext { - /** @override */ isAbstractImplementation_() { return false; @@ -67,13 +64,14 @@ export class IntegrationAmpContext extends AbstractAmpContext { updateDimensionsEnabled_() { // Only make this available to selected embeds until the generic solution is // available. - return (this.embedType_ === 'facebook' - || this.embedType_ === 'twitter' - || this.embedType_ === 'github' - || this.embedType_ === 'mathml' - || this.embedType_ === 'reddit' - || this.embedType_ === 'yotpo' - || this.embedType_ === 'embedly' + return ( + this.embedType_ === 'facebook' || + this.embedType_ === 'twitter' || + this.embedType_ === 'github' || + this.embedType_ === 'mathml' || + this.embedType_ === 'reddit' || + this.embedType_ === 'yotpo' || + this.embedType_ === 'embedly' ); } @@ -132,9 +130,12 @@ export class IntegrationAmpContext extends AbstractAmpContext { * @param {string} entityId See comment above for content. */ reportRenderedEntityIdentifier(entityId) { - this.client_.sendMessage('entity-id', dict({ - 'id': user().assertString(entityId), - })); + this.client_.sendMessage( + 'entity-id', + dict({ + 'id': user().assertString(entityId), + }) + ); } /** diff --git a/3p/ampcontext-lib.js b/3p/ampcontext-lib.js index ff691c82028f..84a8437f86a1 100644 --- a/3p/ampcontext-lib.js +++ b/3p/ampcontext-lib.js @@ -20,15 +20,12 @@ import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-impor import {AmpContext} from './ampcontext.js'; import {initLogConstructor, setReportError} from '../src/log'; - initLogConstructor(); - // TODO(alanorozco): Refactor src/error.reportError so it does not contain big // transitive dependencies and can be included here. setReportError(() => {}); - /** * If window.context does not exist, we must instantiate a replacement and * assign it to window.context, to provide the creative with all the required diff --git a/3p/ampcontext.js b/3p/ampcontext.js index 3865d900d935..c8e9c5b2a0a1 100644 --- a/3p/ampcontext.js +++ b/3p/ampcontext.js @@ -24,13 +24,14 @@ import {parseUrlDeprecated} from '../src/url'; import {tryParseJson} from '../src/json'; export class AbstractAmpContext { - /** * @param {!Window} win The window that the instance is built inside. */ constructor(win) { - devAssert(!this.isAbstractImplementation_(), - 'Should not construct AbstractAmpContext instances directly'); + devAssert( + !this.isAbstractImplementation_(), + 'Should not construct AbstractAmpContext instances directly' + ); /** @protected {!Window} */ this.win_ = win; @@ -128,12 +129,13 @@ export class AbstractAmpContext { /** Registers an general handler for page visibility. */ listenForPageVisibility_() { this.client_.makeRequest( - MessageType.SEND_EMBED_STATE, - MessageType.EMBED_STATE, - data => { - this.hidden = data['pageHidden']; - this.dispatchVisibilityChangeEvent_(); - }); + MessageType.SEND_EMBED_STATE, + MessageType.EMBED_STATE, + data => { + this.hidden = data['pageHidden']; + this.dispatchVisibilityChangeEvent_(); + } + ); } /** @@ -169,11 +171,12 @@ export class AbstractAmpContext { */ observeIntersection(callback) { const unlisten = this.client_.makeRequest( - MessageType.SEND_INTERSECTIONS, - MessageType.INTERSECTION, - intersection => { - callback(intersection['changes']); - }); + MessageType.SEND_INTERSECTIONS, + MessageType.INTERSECTION, + intersection => { + callback(intersection['changes']); + } + ); if (!isExperimentOn('no-initial-intersection')) { // eslint-disable-line // Call the callback with the value that was transmitted when the @@ -196,10 +199,14 @@ export class AbstractAmpContext { * @param {function(*)} callback to be invoked with the HTML string */ getHtml(selector, attributes, callback) { - this.client_.getData(MessageType.GET_HTML, dict({ - 'selector': selector, - 'attributes': attributes, - }), callback); + this.client_.getData( + MessageType.GET_HTML, + dict({ + 'selector': selector, + 'attributes': attributes, + }), + callback + ); } /** @@ -208,8 +215,7 @@ export class AbstractAmpContext { * @param {function(*)} callback */ getConsentState(callback) { - this.client_.getData( - MessageType.GET_CONSENT_STATE, null, callback); + this.client_.getData(MessageType.GET_CONSENT_STATE, null, callback); } /** @@ -220,11 +226,14 @@ export class AbstractAmpContext { * @param {boolean=} hasOverflow Whether the ad handles its own overflow ele */ requestResize(width, height, hasOverflow) { - this.client_.sendMessage(MessageType.EMBED_SIZE, dict({ - 'width': width, - 'height': height, - 'hasOverflow': hasOverflow, - })); + this.client_.sendMessage( + MessageType.EMBED_SIZE, + dict({ + 'width': width, + 'height': height, + 'hasOverflow': hasOverflow, + }) + ); } /** @@ -236,7 +245,8 @@ export class AbstractAmpContext { */ onResizeSuccess(callback) { this.client_.registerCallback(MessageType.EMBED_SIZE_CHANGED, obj => { - callback(obj['requestedHeight'], obj['requestedWidth']); }); + callback(obj['requestedHeight'], obj['requestedWidth']); + }); } /** @@ -278,8 +288,9 @@ export class AbstractAmpContext { setupMetadata_(data) { // TODO(alanorozco): Use metadata utils in 3p/frame-metadata const dataObject = devAssert( - typeof data === 'string' ? tryParseJson(data) : data, - 'Could not setup metadata.'); + typeof data === 'string' ? tryParseJson(data) : data, + 'Could not setup metadata.' + ); const context = dataObject._context || dataObject.attributes._context; @@ -327,7 +338,7 @@ export class AbstractAmpContext { // Add window keeping the top-most one at the front. ancestors.push(win.parent); } - return ancestors[(ancestors.length - 1) - depth]; + return ancestors[ancestors.length - 1 - depth]; } /** @@ -341,7 +352,7 @@ export class AbstractAmpContext { // TODO(alanorozco): why the heck could AMP_CONTEXT_DATA be two different // types? FIX THIS. if (isObject(this.win_.sf_) && this.win_.sf_.cfg) { - this.setupMetadata_(/** @type {string}*/(this.win_.sf_.cfg)); + this.setupMetadata_(/** @type {string}*/ (this.win_.sf_.cfg)); } else if (this.win_.AMP_CONTEXT_DATA) { if (typeof this.win_.AMP_CONTEXT_DATA == 'string') { this.sentinel = this.win_.AMP_CONTEXT_DATA; @@ -361,9 +372,12 @@ export class AbstractAmpContext { if (!e.message) { return; } - this.client_.sendMessage(MessageType.USER_ERROR_IN_IFRAME, dict({ - 'message': e.message, - })); + this.client_.sendMessage( + MessageType.USER_ERROR_IN_IFRAME, + dict({ + 'message': e.message, + }) + ); } } diff --git a/3p/beopinion.js b/3p/beopinion.js index c26bf1bd5509..394956c6a765 100644 --- a/3p/beopinion.js +++ b/3p/beopinion.js @@ -100,7 +100,7 @@ function getBeOpinionAsyncInit(global, accountId) { }, onHeightChange: function(newHeight) { const c = global.document.getElementById('c'); - const boundingClientRect = c./*REVIEW*/getBoundingClientRect(); + const boundingClientRect = c./*REVIEW*/ getBoundingClientRect(); context.onResizeDenied(context.requestResize.bind(context)); context.requestResize(boundingClientRect.width, newHeight); }, diff --git a/3p/bodymovinanimation.js b/3p/bodymovinanimation.js index da0029214407..55a58ce55321 100644 --- a/3p/bodymovinanimation.js +++ b/3p/bodymovinanimation.js @@ -34,9 +34,10 @@ let animationHandler; * @param {!Function} cb */ function getBodymovinAnimationSdk(global, renderer, cb) { - const scriptToLoad = renderer === 'svg' ? - 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin_light.min.js' : - 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin.min.js'; + const scriptToLoad = + renderer === 'svg' + ? 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin_light.min.js' + : 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin.min.js'; loadScript(global, scriptToLoad, function() { cb(global.bodymovin); }); @@ -58,8 +59,9 @@ function parseMessage(event) { if (eventMessage['valueType'] === 'time') { animationHandler.goToAndStop(eventMessage['value']); } else { - const frameNumber = - Math.round(eventMessage['value'] * animationHandler.totalFrames); + const frameNumber = Math.round( + eventMessage['value'] * animationHandler.totalFrames + ); animationHandler.goToAndStop(frameNumber, true); } } @@ -89,11 +91,12 @@ export function bodymovinanimation(global) { autoplay: dataReceived['autoplay'], animationData: dataReceived['animationData'], }); - const message = JSON.stringify(dict({ - 'action': 'ready', - })); + const message = JSON.stringify( + dict({ + 'action': 'ready', + }) + ); global.addEventListener('message', parseMessage, false); - global.parent. /*OK*/postMessage(message, '*'); + global.parent./*OK*/ postMessage(message, '*'); }); } - diff --git a/3p/embedly.js b/3p/embedly.js index 863933768c8e..aeaf9420bf72 100644 --- a/3p/embedly.js +++ b/3p/embedly.js @@ -84,10 +84,7 @@ export function embedly(global, data) { // Add whitelisted data attributes and values to card // when these are provided by component. for (const key in CardOptions) { - if ( - hasOwn(CardOptions, key) && - typeof data[key] !== 'undefined' - ) { + if (hasOwn(CardOptions, key) && typeof data[key] !== 'undefined') { card.setAttribute(`data-${CardOptions[key]}`, data[key]); } } @@ -111,8 +108,8 @@ export function embedly(global, data) { // Use embedly SDK to listen to resize event from loaded card global.window['embedly']('on', RESIZE_EVENT_NAME, function(iframe) { context.requestResize( - iframe./*OK*/width, - parseInt(iframe./*OK*/height, 10) + /* margin */ 5 + iframe./*OK*/ width, + parseInt(iframe./*OK*/ height, 10) + /* margin */ 5 ); }); }); diff --git a/3p/environment.js b/3p/environment.js index a1213667a97d..b531265ca4b9 100644 --- a/3p/environment.js +++ b/3p/environment.js @@ -43,7 +43,7 @@ export function manageWin(win) { manageWin_(win); } catch (e) { // We use a try block, because the ad integrations often swallow errors. - console./*OK*/error(e.message, e.stack); + console./*OK*/ error(e.message, e.stack); } } @@ -65,7 +65,6 @@ function manageWin_(win) { blockSyncPopups(win); } - /** * Add instrumentation code to doc.write. * @param {!Window} parent @@ -115,15 +114,14 @@ function maybeInstrumentsNodes(win, addedNodes) { } const src = node.getAttribute('src'); const srcdoc = node.getAttribute('srcdoc'); - if (src == null || /^(about:|javascript:)/i.test(src.trim()) || - srcdoc) { + if (src == null || /^(about:|javascript:)/i.test(src.trim()) || srcdoc) { if (node.contentWindow) { instrumentIframeWindow(node, win, node.contentWindow); node.addEventListener('load', () => { try { instrumentIframeWindow(node, win, node.contentWindow); } catch (e) { - console./*OK*/error(e.message, e.stack); + console./*OK*/ error(e.message, e.stack); } }); } else if (srcdoc) { @@ -131,7 +129,7 @@ function maybeInstrumentsNodes(win, addedNodes) { } } } catch (e) { - console./*OK*/error(e.message, e.stack); + console./*OK*/ error(e.message, e.stack); } } } @@ -196,7 +194,7 @@ function instrumentEntryPoints(win) { next(); if (typeof fn == 'string') { // Handle rare and dangerous string arg case. - return (0, win.eval/*NOT OK but whatcha gonna do.*/).call(win, fn); // lgtm [js/useless-expression] + return (0, win.eval /*NOT OK but whatcha gonna do.*/).call(win, fn); // lgtm [js/useless-expression] } else { return fn.apply(this, arguments); } @@ -245,7 +243,7 @@ function blockSyncPopups(win) { return false; }; } catch (e) { - console./*OK*/error(e.message, e.stack); + console./*OK*/ error(e.message, e.stack); } } diff --git a/3p/facebook.js b/3p/facebook.js index 7e08373c7357..067d5b0d0c37 100644 --- a/3p/facebook.js +++ b/3p/facebook.js @@ -32,9 +32,13 @@ import {userAssert} from '../src/log'; * @param {string} locale */ function getFacebookSdk(global, cb, locale) { - loadScript(global, 'https://connect.facebook.net/' + locale + '/sdk.js', () => { - cb(global.FB); - }); + loadScript( + global, + 'https://connect.facebook.net/' + locale + '/sdk.js', + () => { + cb(global.FB); + } + ); } /** @@ -100,8 +104,10 @@ function getCommentContainer(global, data) { const c = global.document.getElementById('c'); const container = createContainer(global, 'comment-embed', data.href); container.setAttribute( - 'data-include-parent', data.includeCommentParent || 'false'); - container.setAttribute('data-width', c./*OK*/offsetWidth); + 'data-include-parent', + data.includeCommentParent || 'false' + ); + container.setAttribute('data-width', c./*OK*/ offsetWidth); return container; } @@ -123,9 +129,12 @@ function getDefaultEmbedAs(href) { function getEmbedContainer(global, data) { const embedAs = data.embedAs || getDefaultEmbedAs(data.href); - userAssert(['post', 'video', 'comment'].indexOf(embedAs) !== -1, - 'Attribute data-embed-as for value is wrong, should be' + - ' "post", "video" or "comment" but was: %s', embedAs); + userAssert( + ['post', 'video', 'comment'].indexOf(embedAs) !== -1, + 'Attribute data-embed-as for value is wrong, should be' + + ' "post", "video" or "comment" but was: %s', + embedAs + ); switch (embedAs) { case 'comment': @@ -157,7 +166,7 @@ function getPageContainer(global, data) { // Note: The facebook embed allows a maximum width of 500px. // If the container's width exceeds that, the embed's width will // be clipped to 500px. - container.setAttribute('data-width', c./*OK*/offsetWidth); + container.setAttribute('data-width', c./*OK*/ offsetWidth); return container; } @@ -211,30 +220,36 @@ export function facebook(global, data) { container = getLikeContainer(global, data); } else if (extension === 'AMP-FACEBOOK-COMMENTS') { container = getCommentsContainer(global, data); - } else /*AMP-FACEBOOK */ { + } /*AMP-FACEBOOK */ else { container = getEmbedContainer(global, data); } global.document.getElementById('c').appendChild(container); - getFacebookSdk(global, FB => { - // Dimensions are given by the parent frame. - delete data.width; - delete data.height; + getFacebookSdk( + global, + FB => { + // Dimensions are given by the parent frame. + delete data.width; + delete data.height; - FB.Event.subscribe('xfbml.resize', event => { - context.updateDimensions( + FB.Event.subscribe('xfbml.resize', event => { + context.updateDimensions( parseInt(event.width, 10), - parseInt(event.height, 10) + /* margins */ 20); - }); - - FB.init({xfbml: true, version: 'v2.5'}); - - // Report to parent that the SDK has loaded and is ready to paint - const message = JSON.stringify(dict({ - 'action': 'ready', - })); - global.parent. /*OK*/postMessage(message, '*'); - - }, data.locale ? data.locale : dashToUnderline(window.navigator.language)); + parseInt(event.height, 10) + /* margins */ 20 + ); + }); + + FB.init({xfbml: true, version: 'v2.5'}); + + // Report to parent that the SDK has loaded and is ready to paint + const message = JSON.stringify( + dict({ + 'action': 'ready', + }) + ); + global.parent./*OK*/ postMessage(message, '*'); + }, + data.locale ? data.locale : dashToUnderline(window.navigator.language) + ); } diff --git a/3p/frame-metadata.js b/3p/frame-metadata.js index 3d101052e9e9..c1ba8732b3c7 100644 --- a/3p/frame-metadata.js +++ b/3p/frame-metadata.js @@ -21,7 +21,6 @@ import {once} from '../src/utils/function.js'; import {parseJson} from '../src/json'; import {parseUrlDeprecated} from '../src/url'; - /** * @typedef {{ * ampcontextFilepath: ?string, @@ -46,7 +45,6 @@ import {parseUrlDeprecated} from '../src/url'; */ export let ContextStateDef; - /** @const {!JsonObject} */ const FALLBACK = dict({ 'attributes': dict({ @@ -54,7 +52,6 @@ const FALLBACK = dict({ }), }); - /** * Gets metadata encoded in iframe name attribute. * @return {!JsonObject} @@ -68,14 +65,12 @@ const allMetadata = once(() => { return parseJson(iframeName); } catch (err) { if (!getMode().test) { - dev().info( - 'INTEGRATION', 'Could not parse context from:', iframeName); + dev().info('INTEGRATION', 'Could not parse context from:', iframeName); } return FALLBACK; } }); - /** * @return {{mode: !Object, experimentToggles: !Object}} */ @@ -88,7 +83,6 @@ export function getAmpConfig() { }; } - /** * @return {!JsonObject} */ @@ -103,7 +97,6 @@ const getAttributeDataImpl_ = once(() => { return data; }); - /** * @return {!JsonObject} */ @@ -112,7 +105,6 @@ export function getAttributeData() { return getAttributeDataImpl_(); } - /** * @return {!Location} */ @@ -121,7 +113,6 @@ const getLocationImpl_ = once(() => { return parseUrlDeprecated(href); }); - /** * @return {!Location} */ @@ -130,7 +121,6 @@ export function getLocation() { return getLocationImpl_(); } - /** * @return {!ContextStateDef} */ @@ -158,7 +148,6 @@ export function getContextState() { }; } - /** * @return {string} */ diff --git a/3p/github.js b/3p/github.js index b7859ae6d721..b97a14ec3a5e 100644 --- a/3p/github.js +++ b/3p/github.js @@ -40,12 +40,13 @@ function getGistJs(global, scriptSource, cb) { */ export function github(global, data) { userAssert( - data.gistid, - 'The data-gistid attribute is required for %s', - data.element); + data.gistid, + 'The data-gistid attribute is required for %s', + data.element + ); let gistUrl = - 'https://gist.github.com/' + encodeURIComponent(data.gistid) + '.js'; + 'https://gist.github.com/' + encodeURIComponent(data.gistid) + '.js'; if (data.file) { gistUrl += '?file=' + encodeURIComponent(data.file); @@ -65,8 +66,8 @@ export function github(global, data) { } context.updateDimensions( - gistContainer./*OK*/offsetWidth, - gistContainer./*OK*/offsetHeight + gistContainer./*OK*/ offsetWidth, + gistContainer./*OK*/ offsetHeight ); }); } diff --git a/3p/iframe-messaging-client.js b/3p/iframe-messaging-client.js index 9fc838a24a5f..cf2d13aa5b5d 100644 --- a/3p/iframe-messaging-client.js +++ b/3p/iframe-messaging-client.js @@ -26,7 +26,6 @@ import {getData} from '../src/event-helper'; import {getMode} from '../src/mode'; export class IframeMessagingClient { - /** * @param {!Window} win A window object. */ @@ -126,11 +125,15 @@ export class IframeMessagingClient { * @param {JsonObject=} opt_payload The payload of message to send. */ sendMessage(type, opt_payload) { - this.hostWindow_.postMessage/*OK*/( - serializeMessage( - type, dev().assertString(this.sentinel_), - opt_payload, this.rtvVersion_), - '*'); + this.hostWindow_./*OK*/ postMessage( + serializeMessage( + type, + dev().assertString(this.sentinel_), + opt_payload, + this.rtvVersion_ + ), + '*' + ); } /** diff --git a/3p/iframe-transport-client.js b/3p/iframe-transport-client.js index a0c9fd9951ca..003cc842e3e5 100644 --- a/3p/iframe-transport-client.js +++ b/3p/iframe-transport-client.js @@ -27,7 +27,6 @@ const TAG_ = 'iframe-transport-client'; * creatives. */ export class IframeTransportClient { - /** @param {!Window} win */ constructor(win) { /** @private {!Window} */ @@ -39,47 +38,63 @@ export class IframeTransportClient { const parsedFrameName = tryParseJson(this.win_.name); /** @private {string} */ - this.vendor_ = dev().assertString(parsedFrameName['type'], - 'Parent frame must supply vendor name as type in ' + - this.win_.location.href); + this.vendor_ = dev().assertString( + parsedFrameName['type'], + 'Parent frame must supply vendor name as type in ' + + this.win_.location.href + ); // Note: amp-ad-exit will validate the vendor name before performing // variable substitution, so if the vendor name is not a valid one from // vendors.js, then its response messages will have no effect. - devAssert(this.vendor_.length, 'Vendor name cannot be empty in ' + - this.win_.location.href); + devAssert( + this.vendor_.length, + 'Vendor name cannot be empty in ' + this.win_.location.href + ); /** @protected {!IframeMessagingClient} */ this.iframeMessagingClient_ = new IframeMessagingClient(win); this.iframeMessagingClient_.setHostWindow(this.win_.parent); - this.iframeMessagingClient_.setSentinel(dev().assertString( + this.iframeMessagingClient_.setSentinel( + dev().assertString( parsedFrameName['sentinel'], - 'Invalid/missing sentinel on iframe name attribute' + this.win_.name)); + 'Invalid/missing sentinel on iframe name attribute' + this.win_.name + ) + ); this.iframeMessagingClient_.makeRequest( - MessageType.SEND_IFRAME_TRANSPORT_EVENTS, - MessageType.IFRAME_TRANSPORT_EVENTS, - eventData => { - const events = + MessageType.SEND_IFRAME_TRANSPORT_EVENTS, + MessageType.IFRAME_TRANSPORT_EVENTS, + eventData => { + const events = /** * @type * {!Array<../src/3p-frame-messaging.IframeTransportEvent>} */ (eventData['events']); - devAssert(events, - 'Received malformed events list in ' + this.win_.location.href); - devAssert(events.length, - 'Received empty events list in ' + this.win_.location.href); - events.forEach(event => { - try { - devAssert(event.creativeId, - 'Received malformed event in ' + this.win_.location.href); - this.contextFor_(event.creativeId).dispatch(event.message); - } catch (e) { - user().error(TAG_, - 'Exception in callback passed to onAnalyticsEvent', - e); - } - }); + devAssert( + events, + 'Received malformed events list in ' + this.win_.location.href + ); + devAssert( + events.length, + 'Received empty events list in ' + this.win_.location.href + ); + events.forEach(event => { + try { + devAssert( + event.creativeId, + 'Received malformed event in ' + this.win_.location.href + ); + this.contextFor_(event.creativeId).dispatch(event.message); + } catch (e) { + user().error( + TAG_, + 'Exception in callback passed to onAnalyticsEvent', + e + ); + } }); + } + ); } /** @@ -90,10 +105,15 @@ export class IframeTransportClient { * @private */ contextFor_(creativeId) { - return this.creativeIdToContext_[creativeId] || - (this.creativeIdToContext_[creativeId] = - new IframeTransportContext(this.win_, this.iframeMessagingClient_, - creativeId, this.vendor_)); + return ( + this.creativeIdToContext_[creativeId] || + (this.creativeIdToContext_[creativeId] = new IframeTransportContext( + this.win_, + this.iframeMessagingClient_, + creativeId, + this.vendor_ + )) + ); } /** @@ -127,9 +147,11 @@ export class IframeTransportContext { /** @private {?function(string)} */ this.listener_ = null; - userAssert(win['onNewContextInstance'] && + userAssert( + win['onNewContextInstance'] && typeof win['onNewContextInstance'] == 'function', - 'Must implement onNewContextInstance in ' + win.location.href); + 'Must implement onNewContextInstance in ' + win.location.href + ); win['onNewContextInstance'](this); } @@ -158,9 +180,10 @@ export class IframeTransportContext { * @param {!Object} data */ sendResponseToCreative(data) { - this.iframeMessagingClient_./*OK*/sendMessage( - MessageType.IFRAME_TRANSPORT_RESPONSE, - /** @type {!JsonObject} */ - (Object.assign({message: data}, this.baseMessage_))); + this.iframeMessagingClient_./*OK*/ sendMessage( + MessageType.IFRAME_TRANSPORT_RESPONSE, + /** @type {!JsonObject} */ + (Object.assign({message: data}, this.baseMessage_)) + ); } } diff --git a/3p/integration.js b/3p/integration.js index 649fc752485d..b3cf025ab7dd 100644 --- a/3p/integration.js +++ b/3p/integration.js @@ -25,16 +25,10 @@ // src/polyfills.js must be the first import. import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-imports-es6 -import { - IntegrationAmpContext, -} from './ampcontext-integration'; +import {IntegrationAmpContext} from './ampcontext-integration'; import {dict} from '../src/utils/object.js'; import {endsWith} from '../src/string'; -import { - getAmpConfig, - getEmbedType, - getLocation, -} from './frame-metadata'; +import {getAmpConfig, getEmbedType, getLocation} from './frame-metadata'; import {getMode} from '../src/mode'; import {getSourceUrl, isProxyOrigin, parseUrlDeprecated} from '../src/url'; import { @@ -46,15 +40,10 @@ import { import {installEmbedStateListener, manageWin} from './environment'; import {internalRuntimeVersion} from '../src/internal-version'; import {parseJson} from '../src/json'; -import { - register, - run, - setExperimentToggles, -} from './3p'; +import {register, run, setExperimentToggles} from './3p'; import {startsWith} from '../src/string.js'; import {urls} from '../src/config'; - // 3P - please keep in alphabetic order import {beopinion} from './beopinion'; import {bodymovinanimation} from './bodymovinanimation'; @@ -304,7 +293,6 @@ const AMP_EMBED_ALLOWED = { init(window); - if (getMode().test || getMode().localDev) { register('_ping_', _ping_); } @@ -367,7 +355,7 @@ register('caprofitx', caprofitx); register('cedato', cedato); register('chargeads', chargeads); register('colombia', colombia); -register('connatix',connatix); +register('connatix', connatix); register('contentad', contentad); register('criteo', criteo); register('csa', csa); @@ -507,7 +495,7 @@ register('weborama-display', weboramaDisplay); register('widespace', widespace); register('wisteria', wisteria); register('wpmedia', wpmedia); -register('xlift' , xlift); +register('xlift', xlift); register('yahoo', yahoo); register('yahoojp', yahoojp); register('yandex', yandex); @@ -537,7 +525,6 @@ const defaultAllowedTypesInCustomFrame = [ '_ping_', ]; - /** * Initialize 3p frame. * @param {!Window} win @@ -554,7 +541,6 @@ function init(win) { setExperimentToggles(config.experimentToggles); } - /** * Visible for testing. * Draws a 3p embed to the window. Expects the data to include the 3p type. @@ -569,12 +555,15 @@ function init(win) { export function draw3p(win, data, configCallback) { const type = data['type']; - userAssert(isTagNameAllowed(type, win.context.tagName), - 'Embed type %s not allowed with tag %s', type, win.context.tagName); + userAssert( + isTagNameAllowed(type, win.context.tagName), + 'Embed type %s not allowed with tag %s', + type, + win.context.tagName + ); if (configCallback) { configCallback(data, data => { - userAssert(data, - 'Expected configuration to be passed as first argument'); + userAssert(data, 'Expected configuration to be passed as first argument'); run(type, win, data); }); } else { @@ -594,8 +583,11 @@ export function draw3p(win, data, configCallback) { * @param {!Array=} opt_allowedEmbeddingOrigins List of domain suffixes * that are allowed to embed this frame. */ -window.draw3p = function(opt_configCallback, opt_allowed3pTypes, - opt_allowedEmbeddingOrigins) { +window.draw3p = function( + opt_configCallback, + opt_allowed3pTypes, + opt_allowedEmbeddingOrigins +) { try { const location = getLocation(); @@ -613,9 +605,10 @@ window.draw3p = function(opt_configCallback, opt_allowed3pTypes, // and the compiler not being able to discern otherwise // TODO(alanorozco): Do this more elegantly once old impl is cleaned up. draw3p( - window, - (/** @type {!IntegrationAmpContext} */ (window.context)).data || {}, - opt_configCallback); + window, + /** @type {!IntegrationAmpContext} */ (window.context).data || {}, + opt_configCallback + ); window.context.bootstrapLoaded(); } catch (e) { @@ -651,9 +644,12 @@ export function validateParentOrigin(window, parentLocation) { if (!ancestors || !ancestors.length) { return; } - userAssert(ancestors[0] == parentLocation.origin, - 'Parent origin mismatch: %s, %s', - ancestors[0], parentLocation.origin); + userAssert( + ancestors[0] == parentLocation.origin, + 'Parent origin mismatch: %s, %s', + ancestors[0], + parentLocation.origin + ); } /** @@ -679,8 +675,11 @@ export function validateAllowedTypes(window, type, allowedTypes) { if (defaultAllowedTypesInCustomFrame.indexOf(type) != -1) { return; } - userAssert(allowedTypes && allowedTypes.indexOf(type) != -1, - 'Non-whitelisted 3p type for custom iframe: %s', type); + userAssert( + allowedTypes && allowedTypes.indexOf(type) != -1, + 'Non-whitelisted 3p type for custom iframe: %s', + type + ); } /** @@ -703,7 +702,7 @@ export function validateAllowedEmbeddingOrigins(window, allowedHostnames) { // the referrer. The referrer is used because it should be // trustable. hostname = parseUrlDeprecated(getSourceUrl(window.document.referrer)) - .hostname; + .hostname; } for (let i = 0; i < allowedHostnames.length; i++) { // Either the hostname is exactly as whitelisted… @@ -715,8 +714,9 @@ export function validateAllowedEmbeddingOrigins(window, allowedHostnames) { return; } } - throw new Error('Invalid embedding hostname: ' + hostname + ' not in ' - + allowedHostnames); + throw new Error( + 'Invalid embedding hostname: ' + hostname + ' not in ' + allowedHostnames + ); } /** @@ -777,10 +777,16 @@ export function isTagNameAllowed(type, tagName) { * @param {boolean} isCanary */ function lightweightErrorReport(e, isCanary) { - new Image().src = urls.errorReporting + - '?3p=1&v=' + encodeURIComponent(internalRuntimeVersion()) + - '&m=' + encodeURIComponent(e.message) + - '&ca=' + (isCanary ? 1 : 0) + - '&r=' + encodeURIComponent(document.referrer) + - '&s=' + encodeURIComponent(e.stack || ''); + new Image().src = + urls.errorReporting + + '?3p=1&v=' + + encodeURIComponent(internalRuntimeVersion()) + + '&m=' + + encodeURIComponent(e.message) + + '&ca=' + + (isCanary ? 1 : 0) + + '&r=' + + encodeURIComponent(document.referrer) + + '&s=' + + encodeURIComponent(e.stack || ''); } diff --git a/3p/mathml.js b/3p/mathml.js index 23c88934b2a7..ffe0d42133a8 100644 --- a/3p/mathml.js +++ b/3p/mathml.js @@ -40,43 +40,44 @@ function getMathmlJs(global, scriptSource, cb) { */ export function mathml(global, data) { userAssert( - data.formula, - 'The formula attribute is required for %s', - data.element); + data.formula, + 'The formula attribute is required for %s', + data.element + ); getMathmlJs( - global, - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML', - mathjax => { - // Dimensions are given by the parent frame. - delete data.width; - delete data.height; - const div = document.createElement('div'); - div.setAttribute('id', 'mathmlformula'); - div.textContent = data.formula; - setStyle(div, 'visibility', 'hidden'); - global.document.body.appendChild(div); - mathjax.Hub.Config({ - showMathMenu: false, - }); - mathjax.Hub.Queue(function() { - const rendered = document.getElementById('MathJax-Element-1-Frame'); - // Remove built in mathjax margins. - let display = document.getElementsByClassName('MJXc-display'); - if (!display[0]) { - const span = document.createElement('span'); - span.setAttribute('class', 'mjx-chtml MJXc-display'); - span.appendChild(rendered); - div.appendChild(span); - display = document.getElementsByClassName('MJXc-display'); - } - display[0].setAttribute('style','margin-top:0;margin-bottom:0'); - context.requestResize( - rendered./*OK*/offsetWidth, - rendered./*OK*/offsetHeight - ); - setStyle(div, 'visibility', 'visible'); - }); - } + global, + 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML', + mathjax => { + // Dimensions are given by the parent frame. + delete data.width; + delete data.height; + const div = document.createElement('div'); + div.setAttribute('id', 'mathmlformula'); + div.textContent = data.formula; + setStyle(div, 'visibility', 'hidden'); + global.document.body.appendChild(div); + mathjax.Hub.Config({ + showMathMenu: false, + }); + mathjax.Hub.Queue(function() { + const rendered = document.getElementById('MathJax-Element-1-Frame'); + // Remove built in mathjax margins. + let display = document.getElementsByClassName('MJXc-display'); + if (!display[0]) { + const span = document.createElement('span'); + span.setAttribute('class', 'mjx-chtml MJXc-display'); + span.appendChild(rendered); + div.appendChild(span); + display = document.getElementsByClassName('MJXc-display'); + } + display[0].setAttribute('style', 'margin-top:0;margin-bottom:0'); + context.requestResize( + rendered./*OK*/ offsetWidth, + rendered./*OK*/ offsetHeight + ); + setStyle(div, 'visibility', 'visible'); + }); + } ); } diff --git a/3p/messaging.js b/3p/messaging.js index fc72a450ec94..c7f3434da479 100644 --- a/3p/messaging.js +++ b/3p/messaging.js @@ -30,8 +30,7 @@ export function nonSensitiveDataPostMessage(type, opt_object) { const object = opt_object || /** @type {JsonObject} */ ({}); object['type'] = type; object['sentinel'] = window.context.sentinel; - window.parent./*OK*/postMessage(object, - window.context.location.origin); + window.parent./*OK*/ postMessage(object, window.context.location.origin); } /** @@ -76,15 +75,18 @@ function startListening(win) { win.addEventListener('message', function(event) { // Cheap operations first, so we don't parse JSON unless we have to. const eventData = getData(event); - if (event.source != win.parent || - event.origin != win.context.location.origin || - typeof eventData != 'string' || - eventData.indexOf('amp-') != 0) { + if ( + event.source != win.parent || + event.origin != win.context.location.origin || + typeof eventData != 'string' || + eventData.indexOf('amp-') != 0 + ) { return; } // Parse JSON only once per message. - const data = /** @type {!JsonObject} */ ( - parseJson(/**@type {string} */ (getData(event)).substr(4))); + const data = /** @type {!JsonObject} */ (parseJson( + /**@type {string} */ (getData(event)).substr(4) + )); if (win.context.sentinel && data['sentinel'] != win.context.sentinel) { return; } diff --git a/3p/polyfills.js b/3p/polyfills.js index f302d5889893..9d2545379b7a 100644 --- a/3p/polyfills.js +++ b/3p/polyfills.js @@ -18,7 +18,6 @@ * @fileoverview Loads all polyfills needed by the AMP 3p integration frame. */ - // This list should not get longer without a very good reason. import {install as installMathSign} from '../src/polyfills/math-sign'; import {install as installObjectAssign} from '../src/polyfills/object-assign'; diff --git a/3p/recaptcha.js b/3p/recaptcha.js index df94d94bb0da..8a88989dea92 100644 --- a/3p/recaptcha.js +++ b/3p/recaptcha.js @@ -17,8 +17,7 @@ // src/polyfills.js must be the first import. import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-imports-es6 -import ampToolboxCacheUrl from - '../third_party/amp-toolbox-cache-url/dist/amp-toolbox-cache-url.esm'; +import ampToolboxCacheUrl from '../third_party/amp-toolbox-cache-url/dist/amp-toolbox-cache-url.esm'; import {IframeMessagingClient} from './iframe-messaging-client'; import { @@ -57,10 +56,10 @@ const TAG = 'RECAPTCHA'; /** @const {string} */ const RECAPTCHA_API_URL = 'https://www.google.com/recaptcha/api.js?render='; -/** {?IframeMessaginClient} **/ +/** {?IframeMessaginClient} */ let iframeMessagingClient = null; -/** {?string} **/ +/** {?string} */ let sitekey = null; /** @@ -79,7 +78,6 @@ init(); * @param {string} recaptchaApiBaseUrl */ export function initRecaptcha(recaptchaApiBaseUrl = RECAPTCHA_API_URL) { - const win = window; /** @@ -97,22 +95,27 @@ export function initRecaptcha(recaptchaApiBaseUrl = RECAPTCHA_API_URL) { // Get our sitekey from the iframe name attribute devAssert( - dataObject.sitekey, - 'The sitekey is required for the iframe' + dataObject.sitekey, + 'The sitekey is required for the iframe' ); sitekey = dataObject.sitekey; const recaptchaApiUrl = recaptchaApiBaseUrl + sitekey; - loadScript(win, recaptchaApiUrl, function() { - const {grecaptcha} = win; + loadScript( + win, + recaptchaApiUrl, + function() { + const {grecaptcha} = win; - grecaptcha.ready(function() { - initializeIframeMessagingClient(win, grecaptcha, dataObject); - iframeMessagingClient./*OK*/sendMessage('amp-recaptcha-ready'); - }); - }, function() { - dev().error(TAG + ' Failed to load recaptcha api script'); - }); + grecaptcha.ready(function() { + initializeIframeMessagingClient(win, grecaptcha, dataObject); + iframeMessagingClient./*OK*/ sendMessage('amp-recaptcha-ready'); + }); + }, + function() { + dev().error(TAG + ' Failed to load recaptcha api script'); + } + ); } window.initRecaptcha = initRecaptcha; @@ -126,8 +129,8 @@ function initializeIframeMessagingClient(win, grecaptcha, dataObject) { iframeMessagingClient = new IframeMessagingClient(win); iframeMessagingClient.setSentinel(dataObject.sentinel); iframeMessagingClient.registerCallback( - 'amp-recaptcha-action', - actionTypeHandler.bind(this, win, grecaptcha) + 'amp-recaptcha-action', + actionTypeHandler.bind(this, win, grecaptcha) ); } @@ -144,27 +147,38 @@ function initializeIframeMessagingClient(win, grecaptcha, dataObject) { * @param {Object} data */ function actionTypeHandler(win, grecaptcha, data) { - doesOriginDomainMatchIframeSrc(win, data).then(() => { - const executePromise = grecaptcha.execute(sitekey, { - action: data.action, - }); + doesOriginDomainMatchIframeSrc(win, data) + .then(() => { + const executePromise = grecaptcha.execute(sitekey, { + action: data.action, + }); - // .then() promise pollyfilled by recaptcha api script - executePromise./*OK*/then(function(token) { - iframeMessagingClient./*OK*/sendMessage('amp-recaptcha-token', dict({ - 'id': data.id, - 'token': token, - })); - }, function(err) { - user().error(TAG, '%s', err.message); - iframeMessagingClient./*OK*/sendMessage('amp-recaptcha-error', dict({ - 'id': data.id, - 'error': err.message, - })); + // .then() promise pollyfilled by recaptcha api script + executePromise./*OK*/ then( + function(token) { + iframeMessagingClient./*OK*/ sendMessage( + 'amp-recaptcha-token', + dict({ + 'id': data.id, + 'token': token, + }) + ); + }, + function(err) { + user().error(TAG, '%s', err.message); + iframeMessagingClient./*OK*/ sendMessage( + 'amp-recaptcha-error', + dict({ + 'id': data.id, + 'error': err.message, + }) + ); + } + ); + }) + .catch(error => { + dev().error(TAG, '%s', error.message); }); - }).catch(error => { - dev().error(TAG, '%s', error.message); - }); } /** @@ -175,11 +189,8 @@ function actionTypeHandler(win, grecaptcha, data) { * @return {!Promise} */ export function doesOriginDomainMatchIframeSrc(win, data) { - if (!data.origin) { - return Promise.reject( - new Error('Could not retreive the origin domain') - ); + return Promise.reject(new Error('Could not retreive the origin domain')); } // Using the deprecated parseUrl here, as we don't have access @@ -191,10 +202,11 @@ export function doesOriginDomainMatchIframeSrc(win, data) { return compareCurlsDomain(win, curlsSubdomain, data.origin); } - return ampToolboxCacheUrl.createCurlsSubdomain(data.origin) - .then(curlsSubdomain => { - return compareCurlsDomain(win, curlsSubdomain, data.origin); - }); + return ampToolboxCacheUrl + .createCurlsSubdomain(data.origin) + .then(curlsSubdomain => { + return compareCurlsDomain(win, curlsSubdomain, data.origin); + }); } /** @@ -206,19 +218,18 @@ export function doesOriginDomainMatchIframeSrc(win, data) { * @return {!Promise} */ function compareCurlsDomain(win, curlsSubdomain, origin) { - // Get the hostname after the culrs subdomain of the current iframe window - const locationWithoutCurlsSubdomain = - win.location.hostname.split('.').slice(1).join('.'); - const curlsHostname = - curlsSubdomain + '.' + locationWithoutCurlsSubdomain; + const locationWithoutCurlsSubdomain = win.location.hostname + .split('.') + .slice(1) + .join('.'); + const curlsHostname = curlsSubdomain + '.' + locationWithoutCurlsSubdomain; if (curlsHostname === win.location.hostname) { return Promise.resolve(); } return Promise.reject( - new Error('Origin domain does not match Iframe src: ' + origin) + new Error('Origin domain does not match Iframe src: ' + origin) ); } - diff --git a/3p/reddit.js b/3p/reddit.js index 2743e0aa1258..c1f4d81e8b2e 100644 --- a/3p/reddit.js +++ b/3p/reddit.js @@ -85,7 +85,8 @@ export function reddit(global, data) { global.addEventListener('resize', event => { global.context.updateDimensions( - event.target.outerWidth, - event.target.outerHeight); + event.target.outerWidth, + event.target.outerHeight + ); }); } diff --git a/3p/twitter.js b/3p/twitter.js index 16f0640243e5..f7b6c32eaf87 100644 --- a/3p/twitter.js +++ b/3p/twitter.js @@ -65,21 +65,24 @@ export function twitter(global, data) { delete data.height; if (data.tweetid) { - twttr.widgets.createTweet(cleanupTweetId_(data.tweetid), tweet, data) - ./*OK*/then(el => tweetCreated(twttr, el)); + twttr.widgets + .createTweet(cleanupTweetId_(data.tweetid), tweet, data) + ./*OK*/ then(el => tweetCreated(twttr, el)); } else if (data.momentid) { - twttr.widgets.createMoment(data.momentid, tweet, data) - ./*OK*/then(el => tweetCreated(twttr, el)); + twttr.widgets + .createMoment(data.momentid, tweet, data) + ./*OK*/ then(el => tweetCreated(twttr, el)); } else if (data.timelineSourceType) { // Extract properties starting with 'timeline'. const timelineData = Object.keys(data) - .filter(prop => startsWith(prop, 'timeline')) - .reduce((newData, prop) => { - newData[stripPrefixCamelCase(prop, 'timeline')] = data[prop]; - return newData; - }, {}); - twttr.widgets.createTimeline(timelineData, tweet, data) - ./*OK*/then(el => tweetCreated(twttr, el)); + .filter(prop => startsWith(prop, 'timeline')) + .reduce((newData, prop) => { + newData[stripPrefixCamelCase(prop, 'timeline')] = data[prop]; + return newData; + }, {}); + twttr.widgets + .createTimeline(timelineData, tweet, data) + ./*OK*/ then(el => tweetCreated(twttr, el)); } }); @@ -108,15 +111,16 @@ export function twitter(global, data) { * @param {!Element} container */ function resize(container) { - const height = container./*OK*/offsetHeight; + const height = container./*OK*/ offsetHeight; // 0 height is always wrong and we should get another resize request // later. if (height == 0) { return; } context.updateDimensions( - container./*OK*/offsetWidth, - height + /* margins */ 20); + container./*OK*/ offsetWidth, + height + /* margins */ 20 + ); } /** diff --git a/3p/viqeoplayer.js b/3p/viqeoplayer.js index 0b7f2397aebf..787b930f3e17 100644 --- a/3p/viqeoplayer.js +++ b/3p/viqeoplayer.js @@ -56,9 +56,9 @@ function viqeoPlayerInitLoaded(global, VIQEO) { * @private */ function subscribe(playerEventName, targetEventName) { - VIQEO['subscribeTracking']( - () => { sendMessage(targetEventName); }, - `Player:${playerEventName}`); + VIQEO['subscribeTracking'](() => { + sendMessage(targetEventName); + }, `Player:${playerEventName}`); } /** @@ -68,8 +68,8 @@ function viqeoPlayerInitLoaded(global, VIQEO) { */ function subscribeTracking(eventsDescription) { VIQEO['subscribeTracking'](params => { - const name = params && params['trackingParams'] && - params['trackingParams'].name; + const name = + params && params['trackingParams'] && params['trackingParams'].name; const targetEventName = eventsDescription[name]; if (targetEventName) { sendMessage(targetEventName); @@ -79,12 +79,12 @@ function viqeoPlayerInitLoaded(global, VIQEO) { const sendMessage = (eventName, value = null) => { const {parent} = global; - const message = /** @type {JsonObject} */({ + const message = /** @type {JsonObject} */ ({ source: 'ViqeoPlayer', action: eventName, value, }); - parent./*OK*/postMessage(message, '*'); + parent./*OK*/ postMessage(message, '*'); }; /** @@ -121,16 +121,11 @@ export function viqeoplayer(global) { let scriptPlayerInit = data['script-url']; scriptPlayerInit = - (scriptPlayerInit - && tryDecodeUriComponent(scriptPlayerInit) - ) - || - (kindIsProd - ? 'https://cdn.viqeo.tv/js/vq_starter.js' - : 'https://static.viqeo.tv/js/vq_player_init.js?branch=dev1' - ); + (scriptPlayerInit && tryDecodeUriComponent(scriptPlayerInit)) || + (kindIsProd + ? 'https://cdn.viqeo.tv/js/vq_starter.js' + : 'https://static.viqeo.tv/js/vq_player_init.js?branch=dev1'); - global['onViqeoLoad'] = VIQEO => - viqeoPlayerInitLoaded(global, VIQEO); + global['onViqeoLoad'] = VIQEO => viqeoPlayerInitLoaded(global, VIQEO); loadScript(global, scriptPlayerInit); } diff --git a/3p/yotpo.js b/3p/yotpo.js index a6fe7b7927fc..ab52046da932 100644 --- a/3p/yotpo.js +++ b/3p/yotpo.js @@ -27,7 +27,7 @@ function getContainerScript(global, scriptSource, cb) { global.Yotpo = global.Yotpo || {}; delete global.Yotpo.widgets['testimonials']; const yotpoWidget = - (typeof global.yotpo === 'undefined') ? undefined : global.yotpo; + typeof global.yotpo === 'undefined' ? undefined : global.yotpo; yotpoWidget.on('CssReady', function() { cb(yotpoWidget, 'cssLoaded'); }); @@ -152,8 +152,8 @@ function getReviewsTabContainer(global) { */ function getProductGalleryContainer(global, data) { const container = global.document.createElement('div'); - container.className = 'yotpo yotpo-pictures-gallery yotpo-product-gallery ' + - 'yotpo-size-6'; + container.className = + 'yotpo yotpo-pictures-gallery yotpo-product-gallery yotpo-size-6'; container.setAttribute('data-product-id', data.productId); container.setAttribute('data-demo', data.demo); container.setAttribute('data-layout-rows', data.layoutRows); @@ -170,7 +170,6 @@ function getProductGalleryContainer(global, data) { return container; } - /** * Create DOM element for the Yotpo Visual UGC Gallery plugin: * @param {!Window} global @@ -211,10 +210,14 @@ function getEmbeddedWidgetContainer(global, data) { embeddedWidget.setAttribute('data-width', data.width); embeddedWidget.setAttribute('data-reviews', data.reviews); embeddedWidget.setAttribute('data-header-text', data.headerText); - embeddedWidget.setAttribute('data-header-background-color', - data.headerBackgroundColor); - embeddedWidget.setAttribute('data-body-background-color', - data.bodyBackgroundColor); + embeddedWidget.setAttribute( + 'data-header-background-color', + data.headerBackgroundColor + ); + embeddedWidget.setAttribute( + 'data-body-background-color', + data.bodyBackgroundColor + ); embeddedWidget.setAttribute('data-font-size', data.fontSize); embeddedWidget.setAttribute('data-font-color', data.fontColor); embeddedWidget.setAttribute('data-yotpo-element-id', data.yotpoElementId); @@ -250,7 +253,8 @@ function getPhotosCarouselContainer(global, data) { */ function getPromotedProductsContainer(global, data) { const container = global.document.createElement('div'); - container.className = 'yotpo yotpo-main-widget yotpo-promoted-product ' + + container.className = + 'yotpo yotpo-main-widget yotpo-promoted-product ' + 'yotpo-medium promoted-products-box'; container.setAttribute('id', 'widget-div-id'); @@ -293,10 +297,10 @@ export function yotpo(global, data) { global.document.getElementById('c').appendChild(container); - let cssLoaded = false; let batchLoaded = false; - const scriptSource = 'https://staticw2.yotpo.com/' + data.appKey + '/widget.js'; + const scriptSource = + 'https://staticw2.yotpo.com/' + data.appKey + '/widget.js'; getContainerScript(global, scriptSource, (yotpoWidget, eventType) => { if (eventType === 'cssLoaded') { cssLoaded = true; @@ -309,8 +313,9 @@ export function yotpo(global, data) { setTimeout(() => { if (yotpoWidget.widgets[0]) { context.updateDimensions( - yotpoWidget.widgets[0].element./*OK*/offsetWidth, - yotpoWidget.widgets[0].element./*OK*/offsetHeight); + yotpoWidget.widgets[0].element./*OK*/ offsetWidth, + yotpoWidget.widgets[0].element./*OK*/ offsetHeight + ); } }, 100); } diff --git a/README.md b/README.md index af708de4a8c2..a62e37978c11 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # AMP HTML ⚡ [![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovateapp.com/) +[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [AMP HTML](https://amp.dev) is a way to build web pages that render with reliable and fast performance. It is our attempt at fixing what many perceive as painfully slow page load times – especially when reading content on the mobile web. AMP HTML is built on existing web technologies; an AMP page will load (quickly) in any modern browser. diff --git a/ads/_a4a-config.js b/ads/_a4a-config.js index dc89c85da5dc..15030ca0b3a1 100644 --- a/ads/_a4a-config.js +++ b/ads/_a4a-config.js @@ -14,22 +14,11 @@ * limitations under the License. */ -import { - adsenseIsA4AEnabled, -} from '../extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config'; -import { - cloudflareIsA4AEnabled, -} from - '../extensions/amp-ad-network-cloudflare-impl/0.1/cloudflare-a4a-config'; -import { - gmosspIsA4AEnabled, -} from - '../extensions/amp-ad-network-gmossp-impl/0.1/gmossp-a4a-config'; +import {adsenseIsA4AEnabled} from '../extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config'; +import {cloudflareIsA4AEnabled} from '../extensions/amp-ad-network-cloudflare-impl/0.1/cloudflare-a4a-config'; +import {gmosspIsA4AEnabled} from '../extensions/amp-ad-network-gmossp-impl/0.1/gmossp-a4a-config'; import {map} from '../src/utils/object'; -import { - tripleliftIsA4AEnabled, -} from - '../extensions/amp-ad-network-triplelift-impl/0.1/triplelift-a4a-config'; +import {tripleliftIsA4AEnabled} from '../extensions/amp-ad-network-triplelift-impl/0.1/triplelift-a4a-config'; /** * Registry for A4A (AMP Ads for AMPHTML pages) "is supported" predicates. @@ -79,5 +68,6 @@ export const signingServerURLs = { 'google': 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', 'google-dev': 'https://cdn.ampproject.org/amp-ad-verifying-keyset-dev.json', 'cloudflare': 'https://amp.cloudflare.com/amp-ad-verifying-keyset.json', - 'cloudflare-dev': 'https://amp.cloudflare.com/amp-ad-verifying-keyset-dev.json', + 'cloudflare-dev': + 'https://amp.cloudflare.com/amp-ad-verifying-keyset-dev.json', }; diff --git a/ads/_config.js b/ads/_config.js index 344ab1c42734..d561cec0314f 100644 --- a/ads/_config.js +++ b/ads/_config.js @@ -139,10 +139,7 @@ export const adConfig = { 'admixer': { renderStartImplemented: true, - preconnect: [ - 'https://inv-nets.admixer.net', - 'https://cdn.admixer.net', - ], + preconnect: ['https://inv-nets.admixer.net', 'https://cdn.admixer.net'], }, 'adocean': { @@ -201,16 +198,11 @@ export const adConfig = { 'adtech': { prefetch: 'https://s.aolcdn.com/os/ads/adsWrapper3.js', - preconnect: [ - 'https://mads.at.atwola.com', - 'https://aka-cdn.adtechus.com', - ], + preconnect: ['https://mads.at.atwola.com', 'https://aka-cdn.adtechus.com'], }, 'adthrive': { - prefetch: [ - 'https://www.googletagservices.com/tag/js/gpt.js', - ], + prefetch: ['https://www.googletagservices.com/tag/js/gpt.js'], preconnect: [ 'https://partner.googleadservices.com', 'https://securepubads.g.doubleclick.net', @@ -220,34 +212,24 @@ export const adConfig = { }, 'adunity': { - preconnect: [ - 'https://content.adunity.com', - ], + preconnect: ['https://content.adunity.com'], renderStartImplemented: true, }, 'aduptech': { prefetch: 'https://s.d.adup-tech.com/jsapi', - preconnect: [ - 'https://d.adup-tech.com', - 'https://m.adup-tech.com', - ], + preconnect: ['https://d.adup-tech.com', 'https://m.adup-tech.com'], renderStartImplemented: true, }, 'adventive': { - preconnect: [ - 'https://ads.adventive.com', - 'https://amp.adventivedev.com', - ], + preconnect: ['https://ads.adventive.com', 'https://amp.adventivedev.com'], renderStartImplemented: true, }, 'adverline': { prefetch: 'https://ads.adverline.com/richmedias/amp.js', - preconnect: [ - 'https://adnext.fr', - ], + preconnect: ['https://adnext.fr'], renderStartImplemented: true, }, @@ -275,9 +257,7 @@ export const adConfig = { 'https://cdn.as.amanad.adtdp.com/sdk/asot-amp.js', 'https://cdn.as.amanad.adtdp.com/sdk/asot-v2.js', ], - preconnect: [ - 'https://ad.as.amanad.adtdp.com', - ], + preconnect: ['https://ad.as.amanad.adtdp.com'], }, 'appvador': { @@ -292,10 +272,7 @@ export const adConfig = { }, 'amoad': { - prefetch: [ - 'https://j.amoad.com/js/a.js', - 'https://j.amoad.com/js/n.js', - ], + prefetch: ['https://j.amoad.com/js/a.js', 'https://j.amoad.com/js/n.js'], preconnect: [ 'https://d.amoad.com', 'https://i.amoad.com', @@ -334,10 +311,7 @@ export const adConfig = { 'bringhub': { renderStartImplemented: true, - preconnect: [ - 'https://static.bh-cdn.com', - 'https://core-api.bringhub.io', - ], + preconnect: ['https://static.bh-cdn.com', 'https://core-api.bringhub.io'], }, 'broadstreetads': { @@ -345,12 +319,8 @@ export const adConfig = { }, 'caajainfeed': { - prefetch: [ - 'https://cdn.amanad.adtdp.com/sdk/ajaamp.js', - ], - preconnect: [ - 'https://ad.amanad.adtdp.com', - ], + prefetch: ['https://cdn.amanad.adtdp.com/sdk/ajaamp.js'], + preconnect: ['https://ad.amanad.adtdp.com'], }, 'capirs': { @@ -381,7 +351,6 @@ export const adConfig = { 'contentad': {}, - 'criteo': { prefetch: 'https://static.criteo.net/js/ld/publishertag.js', preconnect: 'https://cas.criteo.com', @@ -548,9 +517,7 @@ export const adConfig = { }, 'ix': { - prefetch: [ - 'https://js-sec.indexww.com/apl/amp.js', - ], + prefetch: ['https://js-sec.indexww.com/apl/amp.js'], preconnect: 'https://as-sec.casalemedia.com', renderStartImplemented: true, }, @@ -668,10 +635,7 @@ export const adConfig = { 'mixpo': { prefetch: 'https://cdn.mixpo.com/js/loader.js', - preconnect: [ - 'https://player1.mixpo.com', - 'https://player2.mixpo.com', - ], + preconnect: ['https://player1.mixpo.com', 'https://player2.mixpo.com'], }, 'monetizer101': { @@ -707,16 +671,11 @@ export const adConfig = { 'nend': { prefetch: 'https://js1.nend.net/js/amp.js', - preconnect: [ - 'https://output.nend.net', - 'https://img1.nend.net', - ], + preconnect: ['https://output.nend.net', 'https://img1.nend.net'], }, 'netletix': { - preconnect: [ - 'https://call.netzathleten-media.de', - ], + preconnect: ['https://call.netzathleten-media.de'], renderStartImplemented: true, }, @@ -758,9 +717,7 @@ export const adConfig = { 'outbrain': { renderStartImplemented: true, prefetch: 'https://widgets.outbrain.com/widgetAMP/outbrainAMP.min.js', - preconnect: [ - 'https://odb.outbrain.com', - ], + preconnect: ['https://odb.outbrain.com'], consentHandlingOverride: true, }, @@ -804,9 +761,7 @@ export const adConfig = { }, 'pubmine': { - prefetch: [ - 'https://s.pubmine.com/head.js', - ], + prefetch: ['https://s.pubmine.com/head.js'], preconnect: 'https://delivery.g.switchadhub.com', renderStartImplemented: true, }, @@ -844,7 +799,8 @@ export const adConfig = { }, 'revcontent': { - prefetch: 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js', + prefetch: + 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js', preconnect: [ 'https://trends.revcontent.com', 'https://cdn.revcontent.com', @@ -954,10 +910,7 @@ export const adConfig = { 'swoop': { prefetch: 'https://www.swoop-amp.com/amp.js', - preconnect: [ - 'https://www.swpsvc.com', - 'https://client.swpcld.com', - ], + preconnect: ['https://www.swpsvc.com', 'https://client.swpcld.com'], renderStartImplemented: true, }, @@ -998,9 +951,7 @@ export const adConfig = { }, 'uzou': { - preconnect: [ - 'https://speee-ad.akamaized.net', - ], + preconnect: ['https://speee-ad.akamaized.net'], renderStartImplemented: true, }, @@ -1033,19 +984,13 @@ export const adConfig = { 'vmfive': { prefetch: 'https://man.vm5apis.com/dist/adn-web-sdk.js', - preconnect: [ - 'https://vawpro.vm5apis.com', - 'https://vahfront.vm5apis.com', - ], + preconnect: ['https://vawpro.vm5apis.com', 'https://vahfront.vm5apis.com'], renderStartImplemented: true, }, 'webediads': { prefetch: 'https://eu1.wbdds.com/amp.min.js', - preconnect: [ - 'https://goutee.top', - 'https://mediaathay.org.uk', - ], + preconnect: ['https://goutee.top', 'https://mediaathay.org.uk'], renderStartImplemented: true, }, @@ -1064,10 +1009,7 @@ export const adConfig = { 'wpmedia': { prefetch: 'https://std.wpcdn.pl/wpjslib/wpjslib-amp.js', - preconnect: [ - 'https://www.wp.pl', - 'https://v.wpimg.pl', - ], + preconnect: ['https://www.wp.pl', 'https://v.wpimg.pl'], renderStartImplemented: true, }, @@ -1108,10 +1050,7 @@ export const adConfig = { 'yieldmo': { prefetch: 'https://static.yieldmo.com/ym.1.js', - preconnect: [ - 'https://s.yieldmo.com', - 'https://ads.yieldmo.com', - ], + preconnect: ['https://s.yieldmo.com', 'https://ads.yieldmo.com'], renderStartImplemented: true, }, @@ -1131,9 +1070,7 @@ export const adConfig = { 'zen': { prefetch: 'https://zen.yandex.ru/widget-loader', - preconnect: [ - 'https://yastatic.net/', - ], + preconnect: ['https://yastatic.net/'], renderStartImplemented: true, }, @@ -1152,5 +1089,4 @@ export const adConfig = { prefetch: 'https://dup.baidustatic.com/js/dm.js', renderStartImplemented: true, }, - }; diff --git a/ads/_ping_.js b/ads/_ping_.js index e26d5dbaa294..61f82bea4184 100644 --- a/ads/_ping_.js +++ b/ads/_ping_.js @@ -28,8 +28,7 @@ export function _ping_(global, data) { // for testing only. see #10628 global.networkIntegrationDataParamForTesting = data; - validateData(data, ['url'], - ['valid', 'adHeight', 'adWidth', 'enableIo']); + validateData(data, ['url'], ['valid', 'adHeight', 'adWidth', 'enableIo']); userAssert(!data['error'], 'Fake user error!'); global.document.getElementById('c').textContent = data.ping; global.ping = Object.create(null); @@ -43,8 +42,7 @@ export function _ping_(global, data) { }); if (data.ad_container) { - devAssert( - global.context.container == data.ad_container, 'wrong container'); + devAssert(global.context.container == data.ad_container, 'wrong container'); } if (data.valid == 'false') { // Immediately send no-content for visual diff test @@ -75,8 +73,11 @@ export function _ping_(global, data) { if (data.enableIo) { global.context.observeIntersection(function(changes) { changes.forEach(function(c) { - dev().info('AMP-AD', 'Intersection: (WxH)' + - `${c.intersectionRect.width}x${c.intersectionRect.height}`); + dev().info( + 'AMP-AD', + 'Intersection: (WxH)' + + `${c.intersectionRect.width}x${c.intersectionRect.height}` + ); }); // store changes to global.lastIO for testing purpose global.ping.lastIO = changes[changes.length - 1]; diff --git a/ads/a9.js b/ads/a9.js index fa02df6cb3bf..c9a5deb759ef 100644 --- a/ads/a9.js +++ b/ads/a9.js @@ -19,24 +19,58 @@ import {loadScript, validateData, writeScript} from '../3p/3p'; import {parseJson} from '../src/json'; const mandatoryParams = [], - optionalParams = [ - 'ad_mode', 'placement', 'tracking_id', 'ad_type', - 'marketplace', 'region', 'title', - 'default_search_phrase', 'default_category', 'linkid', - 'search_bar', 'search_bar_position', 'rows', - 'design', 'asins', 'debug', 'aax_src_id', - 'header_style', 'link_style', 'link_hover_style', - 'text_style', 'random_permute', 'render_full_page', - 'axf_exp_name', 'axf_treatment', 'disable_borders', - 'attributes', 'carousel', 'feedback_enable', - 'max_ads_in_a_row', 'list_price', 'prime', - 'prime_position', 'widget_padding', - 'strike_text_style', 'brand_text_link', 'brand_position', - 'large_rating', 'rating_position', 'max_title_height', - 'enable_swipe_on_mobile', 'overrides', - 'ead', 'force_win_bid', 'fallback_mode', 'url', - 'regionurl', 'divid', 'recomtype', 'adinstanceid', - ]; + optionalParams = [ + 'ad_mode', + 'placement', + 'tracking_id', + 'ad_type', + 'marketplace', + 'region', + 'title', + 'default_search_phrase', + 'default_category', + 'linkid', + 'search_bar', + 'search_bar_position', + 'rows', + 'design', + 'asins', + 'debug', + 'aax_src_id', + 'header_style', + 'link_style', + 'link_hover_style', + 'text_style', + 'random_permute', + 'render_full_page', + 'axf_exp_name', + 'axf_treatment', + 'disable_borders', + 'attributes', + 'carousel', + 'feedback_enable', + 'max_ads_in_a_row', + 'list_price', + 'prime', + 'prime_position', + 'widget_padding', + 'strike_text_style', + 'brand_text_link', + 'brand_position', + 'large_rating', + 'rating_position', + 'max_title_height', + 'enable_swipe_on_mobile', + 'overrides', + 'ead', + 'force_win_bid', + 'fallback_mode', + 'url', + 'regionurl', + 'divid', + 'recomtype', + 'adinstanceid', + ]; const prefix = 'amzn_assoc_'; /** @@ -51,25 +85,22 @@ export function a9(global, data) { validateData(data, mandatoryParams, optionalParams); - const publisherUrl = global.context.canonicalUrl || - global.context.sourceUrl; + const publisherUrl = global.context.canonicalUrl || global.context.sourceUrl; if (data.amzn_assoc_ad_mode) { if (data.amzn_assoc_ad_mode === 'auto') { - if (data.adinstanceid && - (data.adinstanceid !== '')) { + if (data.adinstanceid && data.adinstanceid !== '') { loadRecTag(global, data, publisherUrl); - } - else { + } else { loadSearTag(global, data, publisherUrl); } - } - else if ((data.amzn_assoc_ad_mode === 'search') - || (data.amzn_assoc_ad_mode === 'manual')) { + } else if ( + data.amzn_assoc_ad_mode === 'search' || + data.amzn_assoc_ad_mode === 'manual' + ) { loadSearTag(global, data, publisherUrl); } - } - else { + } else { loadSearTag(global, data, publisherUrl); } } @@ -79,7 +110,7 @@ export function a9(global, data) { */ function getURL(data) { let url = 'https://z-na.amazon-adsystem.com/widgets/onejs?MarketPlace=US'; - if (data.regionurl && (data.regionurl !== '')) { + if (data.regionurl && data.regionurl !== '') { url = data.regionurl; } @@ -98,8 +129,7 @@ function loadRecTag(global, data, publisherUrl) { if (data['recomtype'] === 'sync') { writeScript(global, url); - } - else if (data['recomtype'] === 'async') { + } else if (data['recomtype'] === 'async') { const d = global.document.createElement('div'); d.setAttribute('id', data['divid']); global.document.getElementById('c').appendChild(d); @@ -114,9 +144,9 @@ function loadRecTag(global, data, publisherUrl) { */ function loadSearTag(global, data, publisherUrl) { /** - * Sets macro type. - * @param {string} type - */ + * Sets macro type. + * @param {string} type + */ function setMacro(type) { if (!type) { return; @@ -128,8 +158,8 @@ function loadSearTag(global, data, publisherUrl) { } /** - * Sets the params. - */ + * Sets the params. + */ function setParams() { const url = getURL(data); let i; @@ -139,7 +169,7 @@ function loadSearTag(global, data, publisherUrl) { } if (data.amzn_assoc_fallback_mode) { - const str = (data.amzn_assoc_fallback_mode).split(','); + const str = data.amzn_assoc_fallback_mode.split(','); let types = str[0].split(':'); let typev = str[1].split(':'); types = parseJson(types[1]); @@ -151,8 +181,7 @@ function loadSearTag(global, data, publisherUrl) { } if (data.amzn_assoc_url) { global['amzn_assoc_URL'] = data['amzn_assoc_url']; - } - else { + } else { global['amzn_assoc_URL'] = publisherUrl; } diff --git a/ads/adagio.js b/ads/adagio.js index 5360b3b4b84c..eb072bef4213 100755 --- a/ads/adagio.js +++ b/ads/adagio.js @@ -21,7 +21,6 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function adagio(global, data) { - validateData(data, ['sid', 'loc']); const $neodata = global; diff --git a/ads/adblade.js b/ads/adblade.js index 859c8223f89e..495c63624d72 100644 --- a/ads/adblade.js +++ b/ads/adblade.js @@ -40,8 +40,8 @@ function addAdiantUnit(hostname, global, data) { global.document.getElementById('c').appendChild(ins); ins.parentNode.addEventListener( - 'eventAdbladeRenderStart', - global.context.renderStart() + 'eventAdbladeRenderStart', + global.context.renderStart() ); // run our JavaScript code to display the ad unit diff --git a/ads/adbutler.js b/ads/adbutler.js index 11c89fcdc3fc..51efddaff521 100644 --- a/ads/adbutler.js +++ b/ads/adbutler.js @@ -20,11 +20,11 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Window} global * @param {!Object} data */ -export function adbutler(global,data) { +export function adbutler(global, data) { validateData( - data, - ['account', 'zone', 'width', 'height'], - ['keyword', 'place'] + data, + ['account', 'zone', 'width', 'height'], + ['keyword', 'place'] ); data['place'] = data['place'] || 0; @@ -42,11 +42,11 @@ export function adbutler(global,data) { global.AdButler.ads.push({ handler(opt) { global.AdButler.register( - data['account'], - data['zone'], - [data['width'], data['height']], - placeholderID, - opt + data['account'], + data['zone'], + [data['width'], data['height']], + placeholderID, + opt ); }, opt: { diff --git a/ads/adfox.js b/ads/adfox.js index 260cccc2fff3..402748482571 100644 --- a/ads/adfox.js +++ b/ads/adfox.js @@ -23,8 +23,9 @@ import {yandex} from './yandex'; */ export function adfox(global, data) { validateData(data, ['adfoxParams', 'ownerId']); - loadScript(global, 'https://yastatic.net/pcode/adfox/loader.js', - () => initAdFox(global, data)); + loadScript(global, 'https://yastatic.net/pcode/adfox/loader.js', () => + initAdFox(global, data) + ); } /** diff --git a/ads/adgeneration.js b/ads/adgeneration.js index 40e683b56bb3..418db5efaef6 100644 --- a/ads/adgeneration.js +++ b/ads/adgeneration.js @@ -21,24 +21,27 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function adgeneration(global, data) { - validateData(data, ['id'], - ['targetid', 'displayid', 'adtype', 'option']); + validateData(data, ['id'], ['targetid', 'displayid', 'adtype', 'option']); // URL encoding const option = data.option ? encodeQueryValue(data.option) : null; - const url = 'https://i.socdm.com/sdk/js/adg-script-loader.js?' + - 'id=' + encodeURIComponent(data.id) + - '&width=' + encodeURIComponent(data.width) + - '&height=' + encodeURIComponent(data.height) + - '&async=true' + - '&adType=' + - (validateAdType(data.adType)) + - '&displayid=' + - (data.displayid ? encodeURIComponent(data.displayid) : '1') + - '&tagver=2.0.0' + - (data.targetid ? '&targetID=' + encodeURIComponent(data.targetid) : '') + - (option ? '&' + option : ''); + const url = + 'https://i.socdm.com/sdk/js/adg-script-loader.js?' + + 'id=' + + encodeURIComponent(data.id) + + '&width=' + + encodeURIComponent(data.width) + + '&height=' + + encodeURIComponent(data.height) + + '&async=true' + + '&adType=' + + validateAdType(data.adType) + + '&displayid=' + + (data.displayid ? encodeURIComponent(data.displayid) : '1') + + '&tagver=2.0.0' + + (data.targetid ? '&targetID=' + encodeURIComponent(data.targetid) : '') + + (option ? '&' + option : ''); writeScript(global, url); } @@ -48,11 +51,14 @@ export function adgeneration(global, data) { * @return {string} */ function encodeQueryValue(str) { - return str.split('&').map(v => { - const key = v.split('=')[0], + return str + .split('&') + .map(v => { + const key = v.split('=')[0], val = v.split('=')[1]; - return encodeURIComponent(key) + '=' + encodeURIComponent(val); - }).join('&'); + return encodeURIComponent(key) + '=' + encodeURIComponent(val); + }) + .join('&'); } /** * If adtype is "RECTANGLE", replace it with "RECT" diff --git a/ads/adhese.js b/ads/adhese.js index 9e2ac8d99b06..0dddd3f5d984 100644 --- a/ads/adhese.js +++ b/ads/adhese.js @@ -21,7 +21,7 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function adhese(global, data) { - validateData(data, ['location','format', 'account', 'requestType']); + validateData(data, ['location', 'format', 'account', 'requestType']); let targetParam = ''; if (data['targeting']) { const targetList = data['targeting']; @@ -29,18 +29,27 @@ export function adhese(global, data) { targetParam += encodeURIComponent(category); const targets = targetList[category]; for (let x = 0; x < targets.length; x++) { - targetParam += encodeURIComponent(targets[x]) + - (targets.length - 1 > x ? ';' : ''); + targetParam += + encodeURIComponent(targets[x]) + (targets.length - 1 > x ? ';' : ''); } targetParam += '/'; } } targetParam += '?t=' + Date.now(); - writeScript(window, 'https://ads-' + encodeURIComponent(data['account']) + - '.adhese.com/' + encodeURIComponent(data['requestType']) + '/sl' + + writeScript( + window, + 'https://ads-' + + encodeURIComponent(data['account']) + + '.adhese.com/' + + encodeURIComponent(data['requestType']) + + '/sl' + encodeURIComponent(data['location']) + - encodeURIComponent(data['position']) + '-' + - encodeURIComponent(data['format']) + '/' + targetParam); + encodeURIComponent(data['position']) + + '-' + + encodeURIComponent(data['format']) + + '/' + + targetParam + ); const co = global.document.querySelector('#c'); co.width = data['width']; co.height = data['height']; @@ -52,13 +61,20 @@ export function adhese(global, data) { * @param {!Object} e */ function getAdInfo(global, e) { - if (e.detail.isReady && e.detail.width == e.target.width && - e.detail.height == e.target.height) { + if ( + e.detail.isReady && + e.detail.width == e.target.width && + e.detail.height == e.target.height + ) { global.context.renderStart(); - } else if (e.detail.isReady && (e.detail.width != e.target.width || - e.detail.height != e.target.height)) { - global.context.renderStart({width: e.detail.width, - height: e.detail.height}); + } else if ( + e.detail.isReady && + (e.detail.width != e.target.width || e.detail.height != e.target.height) + ) { + global.context.renderStart({ + width: e.detail.width, + height: e.detail.height, + }); } else { global.context.noContentAvailable(); } diff --git a/ads/adition.js b/ads/adition.js index 475898336e26..92a258ccde88 100644 --- a/ads/adition.js +++ b/ads/adition.js @@ -23,6 +23,10 @@ import {validateData, writeScript} from '../3p/3p'; export function adition(global, data) { validateData(data, ['version']); global.data = data; - writeScript(global, 'https://imagesrv.adition.com/js/amp/v' - + encodeURIComponent(data['version']) + '.js'); + writeScript( + global, + 'https://imagesrv.adition.com/js/amp/v' + + encodeURIComponent(data['version']) + + '.js' + ); } diff --git a/ads/admanmedia.js b/ads/admanmedia.js index 2e67e0dcc35c..4a875550c0ea 100644 --- a/ads/admanmedia.js +++ b/ads/admanmedia.js @@ -24,12 +24,17 @@ export function admanmedia(global, data) { validateData(data, ['id']); const encodedId = encodeURIComponent(data.id); - loadScript(global, `https://mona.admanmedia.com/go?id=${encodedId}`, () => { - const pattern = `script[src$="id=${encodedId}"]`; - const scriptTag = global.document.querySelector(pattern); - scriptTag.setAttribute('id', `hybs-${encodedId}`); - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + loadScript( + global, + `https://mona.admanmedia.com/go?id=${encodedId}`, + () => { + const pattern = `script[src$="id=${encodedId}"]`; + const scriptTag = global.document.querySelector(pattern); + scriptTag.setAttribute('id', `hybs-${encodedId}`); + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/adocean.js b/ads/adocean.js index c4e4dbd501d4..bb0bb884089c 100644 --- a/ads/adocean.js +++ b/ads/adocean.js @@ -14,7 +14,6 @@ * limitations under the License. */ - import {CONSENT_POLICY_STATE} from '../src/consent-state'; import {computeInMasterFrame, validateData, writeScript} from '../3p/3p'; import {parseJson} from '../src/json'; @@ -43,7 +42,7 @@ function isFalseString(str) { function setupAdoConfig(mode, global, consent) { if (global['ado']) { const config = { - mode: (mode == 'sync') ? 'old' : 'new', + mode: mode == 'sync' ? 'old' : 'new', protocol: 'https:', fif: { enabled: mode != 'sync', @@ -119,9 +118,10 @@ let runSyncCount = 0; */ function runSync(global, cb) { global['__aoPrivFnct' + ++runSyncCount] = cb; - /*eslint no-useless-concat: 0*/ - global.document - .write('<' + 'script>__aoPrivFnct' + runSyncCount + '();<' + '/script>'); + global.document.write( + // eslint-disable-next-line no-useless-concat + '<' + 'script>__aoPrivFnct' + runSyncCount + '();<' + '/script>' + ); } /** @@ -201,15 +201,20 @@ function executeMaster(masterId, data, global, callback) { function requestCodes(masterId, data, global, callback) { const slaveId = data['aoId']; - computeInMasterFrame(global, 'ao-master-exec', done => { - executeMaster(masterId, data, global,codes => done(codes)); - }, codes => { - const creative = codes[slaveId]; - if (codes[slaveId + '_second_phase']) { - creative['code'] += '\n' + codes[slaveId + '_second_phase']['code']; + computeInMasterFrame( + global, + 'ao-master-exec', + done => { + executeMaster(masterId, data, global, codes => done(codes)); + }, + codes => { + const creative = codes[slaveId]; + if (codes[slaveId + '_second_phase']) { + creative['code'] += '\n' + codes[slaveId + '_second_phase']['code']; + } + callback(creative); } - callback(creative); - }); + ); } class AdoBuffer { @@ -233,8 +238,8 @@ class AdoBuffer { if (this.global.document.readyState === 'loading') { this.global.document.addEventListener( - 'DOMContentLoaded', - this._init.bind(this) + 'DOMContentLoaded', + this._init.bind(this) ); } else { this._init(); @@ -276,7 +281,6 @@ class AdoBuffer { } } - /** * * @param {string} slaveId @@ -297,7 +301,7 @@ function executeSlave(slaveId, config, global) { } else { const buffer = new AdoBuffer(placement, global); buffer.render(() => { - (new Function(config['sendHitsDef'] + config['code']))(); + new Function(config['sendHitsDef'] + config['code'])(); }); } } @@ -308,20 +312,14 @@ function executeSlave(slaveId, config, global) { * @param {!Object} data */ export function adocean(global, data) { - validateData(data, [ - 'aoEmitter', - 'aoId', - ], [ - 'aoMode', - 'aoPreview', - 'aoKeys', - 'aoVars', - 'aoClusters', - 'aoMaster', - ]); + validateData( + data, + ['aoEmitter', 'aoId'], + ['aoMode', 'aoPreview', 'aoKeys', 'aoVars', 'aoClusters', 'aoMaster'] + ); const masterId = data['aoMaster']; - const mode = (data['aoMode'] !== 'sync' || masterId ? 'buffered' : 'sync'); + const mode = data['aoMode'] !== 'sync' || masterId ? 'buffered' : 'sync'; const adoUrl = 'https://' + data['aoEmitter'] + ADO_JS_PATHS[mode]; const ctx = global.context; @@ -329,11 +327,10 @@ export function adocean(global, data) { * INSUFFICIENT and UNKNOWN should be treated as INSUFFICIENT * not defined states should be treated as INSUFFICIENT */ - const consent = ( + const consent = ctx.initialConsentState === null /* tags without data-block-on-consent */ || ctx.initialConsentState === CONSENT_POLICY_STATE.SUFFICIENT || - ctx.initialConsentState === CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED - ); + ctx.initialConsentState === CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED; writeScript(global, adoUrl, () => { setupAdoConfig(mode, global, consent); diff --git a/ads/adpicker.js b/ads/adpicker.js index c54662fc497f..fd2e703db4b5 100644 --- a/ads/adpicker.js +++ b/ads/adpicker.js @@ -22,10 +22,12 @@ import {validateData, writeScript} from '../3p/3p'; */ export function adpicker(global, data) { validateData(data, ['ph']); - const url = 'https://cdn.adpicker.net' + - '/ads/main.js?et=amp' + - '&ph=' + encodeURIComponent(data.ph) + - '&cb=' + Math.floor(89999999 * Math.random() + 10000000); + const url = + 'https://cdn.adpicker.net' + + '/ads/main.js?et=amp' + + '&ph=' + + encodeURIComponent(data.ph) + + '&cb=' + + Math.floor(89999999 * Math.random() + 10000000); writeScript(global, url); } - diff --git a/ads/adplugg.js b/ads/adplugg.js index e0dafd854009..7e179e2647fe 100644 --- a/ads/adplugg.js +++ b/ads/adplugg.js @@ -22,15 +22,15 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Window} global * @param {!Object} data */ -export function adplugg(global,data) { +export function adplugg(global, data) { // Load ad.js loadScript(global, 'https://www.adplugg.com/serve/js/ad.js'); // Validate the amp-ad attributes. validateData( - data, - ['accessCode', 'width', 'height'], //required - ['zone'] //optional + data, + ['accessCode', 'width', 'height'], //required + ['zone'] //optional ); // Get the amp wrapper element. @@ -46,39 +46,31 @@ export function adplugg(global,data) { ampwrapper.appendChild(adTag); // Get a handle on the AdPlugg SDK. - global.AdPlugg = (global.AdPlugg || []); + global.AdPlugg = global.AdPlugg || []; const {AdPlugg} = global; // Register event listeners (via async wrapper). AdPlugg.push(function() { const {AdPlugg} = global; // Register the renderStart event listener. - AdPlugg.on( - adTag, - 'adplugg:renderStart', - function(event) { - // Create the opt_data object. - const optData = {}; - if (hasOwn(event, 'width')) { - optData.width = event.width; - } - if (hasOwn(event, 'height')) { - optData.height = event.height; - } - global.context.renderStart(optData); - } - ); + AdPlugg.on(adTag, 'adplugg:renderStart', function(event) { + // Create the opt_data object. + const optData = {}; + if (hasOwn(event, 'width')) { + optData.width = event.width; + } + if (hasOwn(event, 'height')) { + optData.height = event.height; + } + global.context.renderStart(optData); + }); // Register the noContentAvailable event listener. - AdPlugg.on(adTag, 'adplugg:noContentAvailable', - function() { - global.context.noContentAvailable(); - } - ); - + AdPlugg.on(adTag, 'adplugg:noContentAvailable', function() { + global.context.noContentAvailable(); + }); }); // Fill the tag. AdPlugg.push({'command': 'run'}); - } diff --git a/ads/adreactor.js b/ads/adreactor.js index 806b9863aa6d..91c9844576ec 100644 --- a/ads/adreactor.js +++ b/ads/adreactor.js @@ -23,12 +23,18 @@ import {validateData, writeScript} from '../3p/3p'; export function adreactor(global, data) { // TODO: check mandatory fields validateData(data, [], ['zid', 'pid', 'custom3']); - const url = 'https://adserver.adreactor.com' + - '/servlet/view/banner/javascript/zone?' + - 'zid=' + encodeURIComponent(data.zid) + - '&pid=' + encodeURIComponent(data.pid) + - '&custom3=' + encodeURIComponent(data.custom3) + - '&random=' + Math.floor(89999999 * Math.random() + 10000000) + - '&millis=' + Date.now(); + const url = + 'https://adserver.adreactor.com' + + '/servlet/view/banner/javascript/zone?' + + 'zid=' + + encodeURIComponent(data.zid) + + '&pid=' + + encodeURIComponent(data.pid) + + '&custom3=' + + encodeURIComponent(data.custom3) + + '&random=' + + Math.floor(89999999 * Math.random() + 10000000) + + '&millis=' + + Date.now(); writeScript(global, url); } diff --git a/ads/adsensor.js b/ads/adsensor.js index c2687e166527..c15462c6e0b5 100644 --- a/ads/adsensor.js +++ b/ads/adsensor.js @@ -20,7 +20,8 @@ import {writeScript} from '../3p/3p'; * @param {!Window} global */ export function adsensor(global) { - - writeScript(global, 'https://wfpscripts.webspectator.com/amp/adsensor-amp.js'); - + writeScript( + global, + 'https://wfpscripts.webspectator.com/amp/adsensor-amp.js' + ); } diff --git a/ads/adspeed.js b/ads/adspeed.js index bb33d4dbb05e..91b5f7e9be05 100644 --- a/ads/adspeed.js +++ b/ads/adspeed.js @@ -23,7 +23,13 @@ import {validateData, writeScript} from '../3p/3p'; export function adspeed(global, data) { validateData(data, ['zone', 'client']); - const url = 'https://g.adspeed.net/ad.php?do=amphtml&zid=' + data.zone + '&oid=' + data.client + '&cb=' + Math.random(); + const url = + 'https://g.adspeed.net/ad.php?do=amphtml&zid=' + + data.zone + + '&oid=' + + data.client + + '&cb=' + + Math.random(); writeScript(global, url); } diff --git a/ads/adspirit.js b/ads/adspirit.js index 05497a9e6ae3..9aa21cfa12ef 100644 --- a/ads/adspirit.js +++ b/ads/adspirit.js @@ -18,9 +18,9 @@ import {setStyles} from '../src/style'; import {validateData} from '../3p/3p'; /** - * @param {!Window} global - * @param {!Object} data - */ + * @param {!Window} global + * @param {!Object} data + */ export function adspirit(global, data) { // TODO: check mandatory fields validateData(data, [], ['asmParams', 'asmHost']); diff --git a/ads/adtech.js b/ads/adtech.js index dc673bbe6d73..8df0e89190ee 100644 --- a/ads/adtech.js +++ b/ads/adtech.js @@ -32,11 +32,22 @@ export function adtech(global, data) { validateSrcContains('/addyn/', adsrc); writeScript(global, adsrc); } else { - validateData(data, ['atwmn', 'atwdiv'], [ - 'atwco', 'atwheight', 'atwhtnmat', - 'atwmoat', 'atwnetid', 'atwothat', 'atwplid', - 'atwpolar', 'atwsizes', 'atwwidth', - ]); + validateData( + data, + ['atwmn', 'atwdiv'], + [ + 'atwco', + 'atwheight', + 'atwhtnmat', + 'atwmoat', + 'atwnetid', + 'atwothat', + 'atwplid', + 'atwpolar', + 'atwsizes', + 'atwwidth', + ] + ); global.atwco = data.atwco; global.atwdiv = data.atwdiv; global.atwheight = data.atwheight; @@ -49,6 +60,6 @@ export function adtech(global, data) { global.atwpolar = data.atwpolar; global.atwsizes = data.atwsizes; global.atwwidth = data.atwwidth; - writeScript(global,'https://s.aolcdn.com/os/ads/adsWrapper3.js'); + writeScript(global, 'https://s.aolcdn.com/os/ads/adsWrapper3.js'); } } diff --git a/ads/adthrive.js b/ads/adthrive.js index 6449bbd3ef21..ad2c047a29bc 100644 --- a/ads/adthrive.js +++ b/ads/adthrive.js @@ -22,6 +22,10 @@ import {loadScript, validateData} from '../3p/3p'; */ export function adthrive(global, data) { validateData(data, ['siteId', 'adUnit'], ['sizes']); - loadScript(global, 'https://ads.adthrive.com/sites/' - + encodeURIComponent(data.siteId) + '/amp.min.js'); + loadScript( + global, + 'https://ads.adthrive.com/sites/' + + encodeURIComponent(data.siteId) + + '/amp.min.js' + ); } diff --git a/ads/adunity.js b/ads/adunity.js index f040016ce18b..67c33461b348 100644 --- a/ads/adunity.js +++ b/ads/adunity.js @@ -24,32 +24,31 @@ import {startsWith} from '../src/string'; export function adunity(global, data) { const doc = global.document; - validateData(data, [ - 'auAccount', - 'auSite', - ], - [ - 'auSection', - 'auZone', - 'auDemo', - 'auIsdemo', - 'auAd', - 'auOrder', - 'auSegment', - 'auOptions', - 'auSources', - 'auAds', - 'auTriggerFn', - 'auTriggerVal', - 'auCallbackVal', - 'auCallbackFn', - 'auPassbackFn', - 'auPassbackVal', - 'auClick', - 'auDual', - 'auImpression', - 'auVideo', - ] + validateData( + data, + ['auAccount', 'auSite'], + [ + 'auSection', + 'auZone', + 'auDemo', + 'auIsdemo', + 'auAd', + 'auOrder', + 'auSegment', + 'auOptions', + 'auSources', + 'auAds', + 'auTriggerFn', + 'auTriggerVal', + 'auCallbackVal', + 'auCallbackFn', + 'auPassbackFn', + 'auPassbackVal', + 'auClick', + 'auDual', + 'auImpression', + 'auVideo', + ] ); //prepare tag structure @@ -73,8 +72,7 @@ export function adunity(global, data) { if (startsWith(key, 'au')) { if (key == 'auVideo') { tag.setAttribute('class', 'au-video'); - } - else { + } else { const auKey = key.substring(2).toLowerCase(); tag.setAttribute('data-au-' + auKey, data[key]); } @@ -104,7 +102,9 @@ export function adunity(global, data) { * @param {!Object} data */ function renderTags(global, data) { - if (data == null) {return;} + if (data == null) { + return; + } global.context.renderStart({ width: data.width, diff --git a/ads/aduptech.js b/ads/aduptech.js index 6cde94c6a128..b69e9d606ce2 100644 --- a/ads/aduptech.js +++ b/ads/aduptech.js @@ -30,7 +30,6 @@ export function aduptech(global, data) { // load aduptech js api loadScript(global, 'https://s.d.adup-tech.com/jsapi', () => { - // force responsive ads for amp data.responsive = true; diff --git a/ads/adventive.js b/ads/adventive.js index 75fcaed58ddf..471069c87f5b 100644 --- a/ads/adventive.js +++ b/ads/adventive.js @@ -33,23 +33,23 @@ export function adventive(global, data) { } const adv = { - addInstance: () => {}, - args: {}, - isLibLoaded: false, - mode: { - dev: false, - live: false, - localDev: false, - preview: false, - prod: false, - testing: false, - }, + addInstance: () => {}, + args: {}, + isLibLoaded: false, + mode: { + dev: false, + live: false, + localDev: false, + preview: false, + prod: false, + testing: false, }, - requiredData = ['pid'], - optionalData = ['click', 'async', 'isDev'], - sld = {true: 'adventivedev', false: 'adventive'}, - thld = {true: 'amp', false: 'ads'}, - cacheTime = 5; + }, + requiredData = ['pid'], + optionalData = ['click', 'async', 'isDev'], + sld = {true: 'adventivedev', false: 'adventive'}, + thld = {true: 'amp', false: 'ads'}, + cacheTime = 5; /** * Future data: @@ -73,18 +73,23 @@ const adv = { function adventive_(global, data) { validateData(data, requiredData, optionalData); - if (!hasOwn(global, 'adventive')) { global.adventive = adv; } + if (!hasOwn(global, 'adventive')) { + global.adventive = adv; + } const ns = global.adventive; - if (!hasOwn(ns, 'context')) { ns.context = global.context; } + if (!hasOwn(ns, 'context')) { + ns.context = global.context; + } if (!Object.isFrozen(ns.mode)) { updateMode(ns.mode, global.context.location.hostname); } const cb = callback.bind(this, data.pid, ns), - url = getUrl(global.context, data, ns); - url ? - (hasOwn(data, 'async') ? loadScript : writeScript)(global, url, cb) : cb(); + url = getUrl(global.context, data, ns); + url + ? (hasOwn(data, 'async') ? loadScript : writeScript)(global, url, cb) + : cb(); } /** @@ -106,7 +111,9 @@ function updateMode(mode, hostname) { * @param {string} id * @param {!Object} ns */ -function callback(id, ns) { ns.addInstance(id); } +function callback(id, ns) { + ns.addInstance(id); +} /** * @param {!Object} context @@ -117,12 +124,13 @@ function callback(id, ns) { ns.addInstance(id); } */ function getUrl(context, data, ns) { const {mode} = ns, - isDev = hasOwn(data, 'isDev'), - sld_ = sld[!mode.dev], - thld_ = thld[isDev && !mode.live], - search = reduceSearch(ns, data.pid, data.click, context.referrer), - url = search ? - addParamsToUrl(`https://${thld_}.${sld_}.com/ad`, search) : false; + isDev = hasOwn(data, 'isDev'), + sld_ = sld[!mode.dev], + thld_ = thld[isDev && !mode.live], + search = reduceSearch(ns, data.pid, data.click, context.referrer), + url = search + ? addParamsToUrl(`https://${thld_}.${sld_}.com/ad`, search) + : false; return url; } @@ -143,15 +151,17 @@ function reduceSearch(ns, placementId, click, referrer) { let isRecipeStale = true; if (isRecipeLoaded) { const info = ns.args[placementId]; - isRecipeStale = (Date.now() - info['requestTime']) > (60 * cacheTime); + isRecipeStale = Date.now() - info['requestTime'] > 60 * cacheTime; } const needsRequest = !isRecipeLoaded || isRecipeStale; - return !needsRequest ? null : dict({ - 'click': click, - 'referrer': referrer, - 'isAmp': '1', - 'lib': !ns.isLibLoaded ? '1' : '', // may be prefetchable via _config - 'pid': needsRequest ? placementId : '', - }); + return !needsRequest + ? null + : dict({ + 'click': click, + 'referrer': referrer, + 'isAmp': '1', + 'lib': !ns.isLibLoaded ? '1' : '', // may be prefetchable via _config + 'pid': needsRequest ? placementId : '', + }); } diff --git a/ads/adverticum.js b/ads/adverticum.js index fcc8a57c28b3..04f20a28683d 100644 --- a/ads/adverticum.js +++ b/ads/adverticum.js @@ -39,5 +39,4 @@ export function adverticum(global, data) { document.getElementById(zoneid).appendChild(v); } writeScript(global, '//ad.adverticum.net/g3.js'); - } diff --git a/ads/advertserve.js b/ads/advertserve.js index 03e07faafd19..54d2f3f3ade5 100644 --- a/ads/advertserve.js +++ b/ads/advertserve.js @@ -23,12 +23,19 @@ import {validateData, writeScript} from '../3p/3p'; export function advertserve(global, data) { validateData(data, [], ['zid', 'pid', 'client']); - const url = 'https://' + data.client + '.advertserve.com' + - '/servlet/view/banner/javascript/zone?amp=true' + - '&zid=' + encodeURIComponent(data.zid) + - '&pid=' + encodeURIComponent(data.pid) + - '&random=' + Math.floor(89999999 * Math.random() + 10000000) + - '&millis=' + Date.now(); + const url = + 'https://' + + data.client + + '.advertserve.com' + + '/servlet/view/banner/javascript/zone?amp=true' + + '&zid=' + + encodeURIComponent(data.zid) + + '&pid=' + + encodeURIComponent(data.pid) + + '&random=' + + Math.floor(89999999 * Math.random() + 10000000) + + '&millis=' + + Date.now(); writeScript(global, url); } diff --git a/ads/aja.js b/ads/aja.js index 689dce615a52..d28d599c6a4f 100644 --- a/ads/aja.js +++ b/ads/aja.js @@ -21,7 +21,6 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function aja(global, data) { - validateData(data, ['asi']); const {document} = global; @@ -33,5 +32,4 @@ export function aja(global, data) { document.getElementById('c').appendChild(d); loadScript(global, 'https://cdn.as.amanad.adtdp.com/sdk/asot-amp.js'); - } diff --git a/ads/alp/handler.js b/ads/alp/handler.js index 610a944a33b1..c37966123391 100644 --- a/ads/alp/handler.js +++ b/ads/alp/handler.js @@ -73,8 +73,11 @@ export function handleClick(e, opt_viewerNavigate) { // Tag the original href with &=1 and make it a fragment param with // name click. - const fragment = 'click=' + encodeURIComponent( - addParamToUrl(link.a.href, 'amp', '1', /* opt_addToFront */ true)); + const fragment = + 'click=' + + encodeURIComponent( + addParamToUrl(link.a.href, 'amp', '1', /* opt_addToFront */ true) + ); let destination = link.eventualUrl; if (link.eventualUrl.indexOf('#') == -1) { destination += '#' + fragment; @@ -85,8 +88,9 @@ export function handleClick(e, opt_viewerNavigate) { const ancestors = win.location.ancestorOrigins; if (ancestors && ancestors[ancestors.length - 1] == 'http://localhost:8000') { destination = destination.replace( - `${parseUrlDeprecated(link.eventualUrl).host}/c/`, - 'http://localhost:8000/max/'); + `${parseUrlDeprecated(link.eventualUrl).host}/c/`, + 'http://localhost:8000/max/' + ); } e.preventDefault(); if (opt_viewerNavigate) { @@ -134,8 +138,10 @@ function getEventualUrl(a) { if (!eventualUrl) { return; } - if (!isProxyOrigin(eventualUrl) || - !startsWith(parseUrlDeprecated(eventualUrl).pathname, '/c/')) { + if ( + !isProxyOrigin(eventualUrl) || + !startsWith(parseUrlDeprecated(eventualUrl).pathname, '/c/') + ) { return; } return eventualUrl; @@ -152,9 +158,15 @@ function navigateTo(win, a, url) { const target = (a.target || '_top').toLowerCase(); const a2aAncestor = getA2AAncestor(win); if (a2aAncestor) { - a2aAncestor.win./*OK*/postMessage('a2a;' + JSON.stringify(dict({ - 'url': url, - })), a2aAncestor.origin); + a2aAncestor.win./*OK*/ postMessage( + 'a2a;' + + JSON.stringify( + dict({ + 'url': url, + }) + ), + a2aAncestor.origin + ); return; } openWindowDialog(win, url, target); @@ -171,7 +183,7 @@ export function warmupStatic(win) { // preconnects. new win.Image().src = `${urls.cdn}/preconnect.gif`; // Preload the primary AMP JS that is render blocking. - const linkRel = /*OK*/document.createElement('link'); + const linkRel = /*OK*/ document.createElement('link'); linkRel.rel = 'preload'; linkRel.setAttribute('as', 'script'); linkRel.href = `${urls.cdn}/v0.js`; @@ -192,10 +204,10 @@ export function warmupDynamic(e) { // Preloading with empty as and newly specced value `fetch` meaning the same // thing. `document` would be the right value, but this is not yet supported // in browsers. - const linkRel0 = /*OK*/document.createElement('link'); + const linkRel0 = /*OK*/ document.createElement('link'); linkRel0.rel = 'preload'; linkRel0.href = link.eventualUrl; - const linkRel1 = /*OK*/document.createElement('link'); + const linkRel1 = /*OK*/ document.createElement('link'); linkRel1.rel = 'preload'; linkRel1.as = 'fetch'; linkRel1.href = link.eventualUrl; diff --git a/ads/aniview.js b/ads/aniview.js index 85d3dc92f97b..fe19f0766f70 100644 --- a/ads/aniview.js +++ b/ads/aniview.js @@ -21,14 +21,10 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function aniview(global, data) { - const requiredParams = [ - 'publisherid', - 'channelid', - ]; + const requiredParams = ['publisherid', 'channelid']; validateData(data, requiredParams); global.avampdata = data; const scpdomain = data.scriptdomain || 'player.aniview.com'; const scpurl = 'https://' + scpdomain + '/script/6.1/ampaniview.js'; writeScript(global, scpurl); - } diff --git a/ads/appnexus.js b/ads/appnexus.js index 8fa22599904d..508c59b820cc 100644 --- a/ads/appnexus.js +++ b/ads/appnexus.js @@ -56,7 +56,6 @@ export function appnexus(global, data) { } appnexusAst(global, data); - } /** @@ -66,7 +65,8 @@ export function appnexus(global, data) { function appnexusAst(global, data) { validateData(data, ['adUnits']); let apntag; - if (context.isMaster) { // in case we are in the master iframe, we load AST + if (context.isMaster) { + // in case we are in the master iframe, we load AST context.master.apntag = context.master.apntag || {}; context.master.apntag.anq = context.master.apntag.anq || []; apntag = context.master.apntag; @@ -83,7 +83,6 @@ function appnexusAst(global, data) { data.adUnits.forEach(adUnit => { apntag.defineTag(adUnit); }); - }); loadScript(global, APPNEXUS_AST_URL, () => { apntag.anq.push(() => { diff --git a/ads/appvador.js b/ads/appvador.js index 5bd056f55a15..271f3abbddff 100644 --- a/ads/appvador.js +++ b/ads/appvador.js @@ -28,14 +28,19 @@ export function appvador(global, data) { apvDiv.setAttribute('id', 'apvad-' + data.id); container.appendChild(apvDiv); - const scriptUrl = data.customScriptSrc ? data.customScriptSrc : - 'https://cdn.apvdr.com/js/' + - (data.jsType ? encodeURIComponent(data.jsType) : 'VastAdUnit') + - '.min.js'; - const apvScript = 'new APV.' + + const scriptUrl = data.customScriptSrc + ? data.customScriptSrc + : 'https://cdn.apvdr.com/js/' + + (data.jsType ? encodeURIComponent(data.jsType) : 'VastAdUnit') + + '.min.js'; + const apvScript = + 'new APV.' + (data.jsType ? data.jsType : 'VASTAdUnit') + - '({s:"' + data.id + '",isAmpAd:true' + - (data.options ? (',' + data.options) : '') + '}).load();'; + '({s:"' + + data.id + + '",isAmpAd:true' + + (data.options ? ',' + data.options : '') + + '}).load();'; const cb = function() { const apvLoadScript = global.document.createElement('script'); diff --git a/ads/atomx.js b/ads/atomx.js index d6fb63290890..b44185aa8f41 100644 --- a/ads/atomx.js +++ b/ads/atomx.js @@ -39,4 +39,3 @@ export function atomx(global, data) { writeScript(global, 'https://s.ato.mx/p.js#' + args.join('&')); } - diff --git a/ads/baidu.js b/ads/baidu.js index 4af9a0817aa3..eecd54059af5 100644 --- a/ads/baidu.js +++ b/ads/baidu.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - loadScript, - validateData, -} from '../3p/3p'; +import {loadScript, validateData} from '../3p/3p'; /** * @param {!Window} global @@ -26,7 +23,11 @@ import { export function baidu(global, data) { validateData(data, ['cproid']); - const id = '_' + Math.random().toString(36).slice(2); + const id = + '_' + + Math.random() + .toString(36) + .slice(2); const container = global.document.createElement('div'); container.id = id; global.document.getElementById('c').appendChild(container); @@ -44,15 +45,15 @@ export function baidu(global, data) { }); loadScript( - global, - 'https://dup.baidustatic.com/js/dm.js', - () => {}, - () => { - // noContentAvailable should be called, - // if parent iframe receives no message. - // setTimeout can work, but it's not that reliable. - // So, only the faliure of JS loading is dealed with for now. - global.context.noContentAvailable(); - } + global, + 'https://dup.baidustatic.com/js/dm.js', + () => {}, + () => { + // noContentAvailable should be called, + // if parent iframe receives no message. + // setTimeout can work, but it's not that reliable. + // So, only the faliure of JS loading is dealed with for now. + global.context.noContentAvailable(); + } ); } diff --git a/ads/bidtellect.js b/ads/bidtellect.js index a05105db3c80..7950bbc485ec 100644 --- a/ads/bidtellect.js +++ b/ads/bidtellect.js @@ -33,7 +33,8 @@ export function bidtellect(global, data) { 'videotypeid', 'videocloseicon', 'targetid', - 'bustframe']; + 'bustframe', + ]; validateData(data, requiredParams, optionalParams); let params = '?t=' + encodeURIComponent(data.t); params += '&pid=' + encodeURIComponent(data.pid); diff --git a/ads/brainy.js b/ads/brainy.js index 15a746f28cc6..7647906f77b0 100644 --- a/ads/brainy.js +++ b/ads/brainy.js @@ -21,12 +21,14 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function brainy(global, data) { - validateData(data, [], ['aid', 'slotId']); - const url = 'https://proparm.jp/ssp/p/js1' - + '?_aid=' + encodeURIComponent(data['aid']) - + '&_slot=' + encodeURIComponent(data['slotId']); + const url = + 'https://proparm.jp/ssp/p/js1' + + '?_aid=' + + encodeURIComponent(data['aid']) + + '&_slot=' + + encodeURIComponent(data['slotId']); writeScript(global, url); } diff --git a/ads/bringhub.js b/ads/bringhub.js index 1cb93de7c41d..2a206aa85f66 100644 --- a/ads/bringhub.js +++ b/ads/bringhub.js @@ -21,14 +21,21 @@ import {loadScript, writeScript} from '../3p/3p'; * @param {!Object} data */ export function bringhub(global, data) { - (global._bringhub = global._bringhub || { + global._bringhub = global._bringhub || { viewId: global.context.pageViewId, htmlURL: data['htmlurl'] || global.context.canonicalUrl, ampURL: data['ampurl'] || global.context.sourceUrl, referrer: data['referrer'] || global.context.referrer, - }); + }; - writeScript(global, `https://static.bh-cdn.com/msf/amp-loader.js?v=${Date.now()}`, function() { - loadScript(global, `https://static.bh-cdn.com/msf/amp-widget.js?v=${global._bringhub.hash}`); - }); + writeScript( + global, + `https://static.bh-cdn.com/msf/amp-loader.js?v=${Date.now()}`, + function() { + loadScript( + global, + `https://static.bh-cdn.com/msf/amp-widget.js?v=${global._bringhub.hash}` + ); + } + ); } diff --git a/ads/broadstreetads.js b/ads/broadstreetads.js index cf745330ae81..43942742cd08 100644 --- a/ads/broadstreetads.js +++ b/ads/broadstreetads.js @@ -20,11 +20,11 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Window} global * @param {!Object} data */ -export function broadstreetads(global,data) { +export function broadstreetads(global, data) { validateData( - data, - ['network', 'zone', 'width', 'height'], - ['keywords', 'place'] + data, + ['network', 'zone', 'width', 'height'], + ['keywords', 'place'] ); data.place = data.place || 0; diff --git a/ads/caajainfeed.js b/ads/caajainfeed.js index 68cd1136d2bd..ae07567aca7f 100644 --- a/ads/caajainfeed.js +++ b/ads/caajainfeed.js @@ -21,35 +21,33 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function caajainfeed(global, data) { - validateData( - data, - [], - [ - 'adSpot', - 'format', - 'test', - 'optout', - 'offset', - 'ipv4', - 'ipv6', - 'networkReachability', - 'osName', - 'osVersion', - 'osLang', - 'osTimezone', - 'deviceVersion', - 'appId', - 'appVersion', - 'kv', - 'uids', - 'template', - 'protocol', - 'fields', - ] + data, + [], + [ + 'adSpot', + 'format', + 'test', + 'optout', + 'offset', + 'ipv4', + 'ipv6', + 'networkReachability', + 'osName', + 'osVersion', + 'osLang', + 'osTimezone', + 'deviceVersion', + 'appId', + 'appVersion', + 'kv', + 'uids', + 'template', + 'protocol', + 'fields', + ] ); global.caAjaInfeedConfig = data; loadScript(global, 'https://cdn.amanad.adtdp.com/sdk/ajaamp.js'); - } diff --git a/ads/capirs.js b/ads/capirs.js index ebb00129edcc..085408757682 100644 --- a/ads/capirs.js +++ b/ads/capirs.js @@ -61,7 +61,9 @@ export function capirs(global, data) { const reportId = 'capirs-' + banner['banner_id']; global.context.reportRenderedEntityIdentifier(reportId); }, - unexist: function() { global.context.noContentAvailable(); }, + unexist: function() { + global.context.noContentAvailable(); + }, }, }; @@ -78,8 +80,8 @@ function getWidth(global, banner) { if (isResponsiveAd(banner)) { width = Math.max( - global.document.documentElement./*OK*/clientWidth, - global.window./*OK*/innerWidth || 0 + global.document.documentElement./*OK*/ clientWidth, + global.window./*OK*/ innerWidth || 0 ); } else { width = banner.width; diff --git a/ads/caprofitx.js b/ads/caprofitx.js index 9548ab3b09ac..759b28836622 100644 --- a/ads/caprofitx.js +++ b/ads/caprofitx.js @@ -21,7 +21,6 @@ import {loadScript} from '../3p/3p'; * @param {!Object} data */ export function caprofitx(global, data) { - global.caprofitxConfig = data; loadScript(global, 'https://cdn.caprofitx.com/tags/amp/profitx_amp.js'); } diff --git a/ads/cedato.js b/ads/cedato.js index ba5bb2e12cdb..eff6ffc11f1d 100644 --- a/ads/cedato.js +++ b/ads/cedato.js @@ -39,8 +39,8 @@ export function cedato(global, data) { } const cb = Math.floor(Math.random() * 10000); - const domain = data.domain || - parseUrlDeprecated(global.context.sourceUrl).origin; + const domain = + data.domain || parseUrlDeprecated(global.context.sourceUrl).origin; /* Create div for ad to target */ const playerDiv = global.document.createElement('div'); @@ -50,19 +50,19 @@ export function cedato(global, data) { height: '100%', }); const playerScript = global.document.createElement('script'); - const servingDomain = data.servingDomain ? - encodeURIComponent(data.servingDomain) : - 'algovid.com'; + const servingDomain = data.servingDomain + ? encodeURIComponent(data.servingDomain) + : 'algovid.com'; const srcParams = [ 'https://p.' + servingDomain + '/player/player.js', '?p=' + encodeURIComponent(data.id), '&cb=' + cb, '&w=' + encodeURIComponent(data.width), '&h=' + encodeURIComponent(data.height), - (data.version ? '&pv=' + encodeURIComponent(data.version) : ''), - (data.subid ? '&subid=' + encodeURIComponent(data.subid) : ''), - (domain ? '&d=' + encodeURIComponent(domain) : ''), - (data.extraParams || ''), // already encoded url query string + data.version ? '&pv=' + encodeURIComponent(data.version) : '', + data.subid ? '&subid=' + encodeURIComponent(data.subid) : '', + domain ? '&d=' + encodeURIComponent(domain) : '', + data.extraParams || '', // already encoded url query string ]; playerScript.onload = () => { diff --git a/ads/colombia.js b/ads/colombia.js index 1f2ec93ffd8b..df7a3b32b487 100644 --- a/ads/colombia.js +++ b/ads/colombia.js @@ -22,8 +22,11 @@ import {loadScript, validateData} from '../3p/3p'; */ export function colombia(global, data) { validateData(data, [ - 'clmb_slot', 'clmb_position', 'clmb_section', - 'clmb_divid', 'loadingStrategy', + 'clmb_slot', + 'clmb_position', + 'clmb_section', + 'clmb_divid', + 'loadingStrategy', ]); // push the two object into the '_colombia' global (global._colombia = global._colombia || []).push({ @@ -43,5 +46,8 @@ export function colombia(global, data) { } }); }); - loadScript(global, 'https://static.clmbtech.com/ad/commons/js/colombia-amp.js'); + loadScript( + global, + 'https://static.clmbtech.com/ad/commons/js/colombia-amp.js' + ); } diff --git a/ads/connatix.js b/ads/connatix.js index 271677b3c2fa..a80fc25b6e6e 100644 --- a/ads/connatix.js +++ b/ads/connatix.js @@ -23,7 +23,6 @@ import {validateData} from '../3p/3p'; * @param {!Object} data */ export function connatix(global, data) { - validateData(data, ['connatix']); // Because 3p's loadScript does not allow for data attributes, @@ -37,9 +36,13 @@ export function connatix(global, data) { } } - window.addEventListener('connatix_no_content', function() { - window.context.noContentAvailable(); - }, false); + window.addEventListener( + 'connatix_no_content', + function() { + window.context.noContentAvailable(); + }, + false + ); script.onload = () => { global.context.renderStart(); diff --git a/ads/contentad.js b/ads/contentad.js index e6bf8110d803..356906fca5dd 100644 --- a/ads/contentad.js +++ b/ads/contentad.js @@ -41,12 +41,18 @@ export function contentad(global, data) { } /* Build API URL */ - const cadApi = 'https://api.content-ad.net/Scripts/widget2.aspx' - + '?id=' + encodeURIComponent(global.id) - + '&d=' + encodeURIComponent(global.d) - + '&wid=' + global.wid - + '&url=' + encodeURIComponent(sourceUrl) - + '&cb=' + Date.now(); + const cadApi = + 'https://api.content-ad.net/Scripts/widget2.aspx' + + '?id=' + + encodeURIComponent(global.id) + + '&d=' + + encodeURIComponent(global.d) + + '&wid=' + + global.wid + + '&url=' + + encodeURIComponent(sourceUrl) + + '&cb=' + + Date.now(); /* Call Content.ad Widget */ writeScript(global, cadApi); diff --git a/ads/criteo.js b/ads/criteo.js index e8cb79ea3f17..07442661bfa4 100644 --- a/ads/criteo.js +++ b/ads/criteo.js @@ -35,11 +35,17 @@ export function criteo(global, data) { integrationmode: 'amp', }); } else if (data.tagtype === 'rta' || data.tagtype === 'standalone') { - dev().error(TAG, 'You are using a deprecated Criteo integration', - data.tagtype); + dev().error( + TAG, + 'You are using a deprecated Criteo integration', + data.tagtype + ); } else { - dev().error(TAG, 'You are using an unknown Criteo integration', - data.tagtype); + dev().error( + TAG, + 'You are using an unknown Criteo integration', + data.tagtype + ); } }); } diff --git a/ads/dable.js b/ads/dable.js index 798b0c0d38f8..413c5b00be8a 100644 --- a/ads/dable.js +++ b/ads/dable.js @@ -24,11 +24,15 @@ export function dable(global, data) { // check required props validateData(data, ['widgetId']); - (global.dable = global.dable || function() { - (global.dable.q = global.dable.q || []).push(arguments); - }); - global.dable('setService', data['serviceName'] || - global.window.context.location.hostname); + global.dable = + global.dable || + function() { + (global.dable.q = global.dable.q || []).push(arguments); + }; + global.dable( + 'setService', + data['serviceName'] || global.window.context.location.hostname + ); global.dable('setURL', global.window.context.sourceUrl); global.dable('setRef', global.window.context.referrer); diff --git a/ads/directadvert.js b/ads/directadvert.js index 0e460ae8432b..d3e9f3083b54 100644 --- a/ads/directadvert.js +++ b/ads/directadvert.js @@ -23,13 +23,19 @@ import {loadScript, validateData} from '../3p/3p'; export function directadvert(global, data) { validateData(data, ['blockId']); - const url = 'https://code.directadvert.ru/data/' + - encodeURIComponent(data['blockId']) + '.js?async=1&div=c'; - - loadScript(global, url, () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + const url = + 'https://code.directadvert.ru/data/' + + encodeURIComponent(data['blockId']) + + '.js?async=1&div=c'; + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/distroscale.js b/ads/distroscale.js index d29a035cde0c..3ad830f2f4b0 100644 --- a/ads/distroscale.js +++ b/ads/distroscale.js @@ -40,12 +40,16 @@ export function distroscale(global, data) { src += '&f=' + encodeURIComponent(srcUrl); - global.dsAMPCallbacks = { renderStart: global.context.renderStart, noContentAvailable: global.context.noContentAvailable, }; - loadScript(global, src, () => {}, () => { - global.context.noContentAvailable(); - }); + loadScript( + global, + src, + () => {}, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/eadv.js b/ads/eadv.js index a012355bfeb7..b61884465308 100644 --- a/ads/eadv.js +++ b/ads/eadv.js @@ -22,5 +22,8 @@ import {validateData, writeScript} from '../3p/3p'; */ export function eadv(global, data) { validateData(data, ['x', 'u'], []); - writeScript(global, 'https://www.eadv.it/track/?x=' + data.x + '&u=' + data.u); + writeScript( + global, + 'https://www.eadv.it/track/?x=' + data.x + '&u=' + data.u + ); } diff --git a/ads/engageya.js b/ads/engageya.js index 2197a46e48c4..8a8864b86695 100644 --- a/ads/engageya.js +++ b/ads/engageya.js @@ -21,10 +21,9 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function engageya(global, data) { - validateData(data, ['widgetids']); - (global._engageya = global._engageya || { + global._engageya = global._engageya || { viewId: global.context.pageViewId, widgetIds: data['widgetids'], websiteId: data['websiteid'], @@ -34,7 +33,7 @@ export function engageya(global, data) { mode: data['mode'] || 1, style: data['stylecss'] || '', referrer: global.context.referrer, - }); + }; loadScript(global, 'https://widget.engageya.com/engageya_amp_loader.js'); } diff --git a/ads/epeex.js b/ads/epeex.js index 503049748ed9..160d3023d88f 100644 --- a/ads/epeex.js +++ b/ads/epeex.js @@ -21,14 +21,13 @@ import {loadScript} from '../3p/3p'; * @param {!Object} data */ export function epeex(global, data) { - - (global._epeex = global._epeex || { + global._epeex = global._epeex || { account: data['account'] || 'demoepeex', channel: data['channel'] || '1', htmlURL: data['htmlurl'] || encodeURIComponent(global.context.canonicalUrl), ampURL: data['ampurl'] || encodeURIComponent(global.context.sourceUrl), testMode: data['testmode'] || 'false', - }); + }; // load the epeex AMP remote js file loadScript(global, 'https://epeex.com/related/service/widget/amp/remote.js'); diff --git a/ads/eplanning.js b/ads/eplanning.js index 04972711af23..7c50740779e1 100644 --- a/ads/eplanning.js +++ b/ads/eplanning.js @@ -22,7 +22,12 @@ import {loadScript, validateData} from '../3p/3p'; */ export function eplanning(global, data) { validateData(data, [ - 'epl_si', 'epl_isv', 'epl_sv', 'epl_sec', 'epl_kvs', 'epl_e', + 'epl_si', + 'epl_isv', + 'epl_sv', + 'epl_sec', + 'epl_kvs', + 'epl_e', ]); // push the two object into the '_eplanning' global (global._eplanning = global._eplanning || []).push({ diff --git a/ads/ezoic.js b/ads/ezoic.js index 9f44b12aa291..f783c5777917 100644 --- a/ads/ezoic.js +++ b/ads/ezoic.js @@ -22,12 +22,16 @@ import {loadScript, validateData} from '../3p/3p'; */ export function ezoic(global, data) { // TODO: check mandatory fields - validateData(data, [], ['slot','targeting','extras']); + validateData(data, [], ['slot', 'targeting', 'extras']); loadScript(global, 'https://g.ezoic.net/ezoic/ampad.js', () => { - loadScript(global, 'https://www.googletagservices.com/tag/js/gpt.js', () => { - global.googletag.cmd.push(() => { - new window.EzoicAmpAd(global,data).createAd(); - }); - }); + loadScript( + global, + 'https://www.googletagservices.com/tag/js/gpt.js', + () => { + global.googletag.cmd.push(() => { + new window.EzoicAmpAd(global, data).createAd(); + }); + } + ); }); } diff --git a/ads/f1e.js b/ads/f1e.js index 180ab3d69f4e..74fe9db6983d 100644 --- a/ads/f1e.js +++ b/ads/f1e.js @@ -21,7 +21,7 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function f1e(global, data) { - validateData(data, ['url','target'], []); + validateData(data, ['url', 'target'], []); global.f1eData = data; writeScript(global, 'https://img.ak.impact-ad.jp/util/f1e_amp.min.js'); } diff --git a/ads/f1h.js b/ads/f1h.js index 0c38b485d6ba..942219b53faa 100644 --- a/ads/f1h.js +++ b/ads/f1h.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - loadScript, - validateData, -} from '../3p/3p'; +import {loadScript, validateData} from '../3p/3p'; /** * @param {!Window} global @@ -27,9 +24,8 @@ export function f1h(global, data) { validateData(data, ['sectionId', 'slot']); const scriptUrl = - data['debugsrc'] || 'https://img.ak.impact-ad.jp/fh/f1h_amp.js'; + data['debugsrc'] || 'https://img.ak.impact-ad.jp/fh/f1h_amp.js'; global.f1hData = data; loadScript(global, scriptUrl); } - diff --git a/ads/felmat.js b/ads/felmat.js index dc7532c0833c..2bce7bf1ee79 100644 --- a/ads/felmat.js +++ b/ads/felmat.js @@ -23,5 +23,8 @@ import {validateData, writeScript} from '../3p/3p'; export function felmat(global, data) { validateData(data, ['host', 'fmt', 'fmk', 'fmp']); global.fmParam = data; - writeScript(global, 'https://t.' + encodeURIComponent(data.host) + '/js/fmamp.js'); + writeScript( + global, + 'https://t.' + encodeURIComponent(data.host) + '/js/fmamp.js' + ); } diff --git a/ads/flite.js b/ads/flite.js index 602842d3001f..95fbfbd91e4e 100644 --- a/ads/flite.js +++ b/ads/flite.js @@ -22,19 +22,34 @@ import {loadScript, validateData} from '../3p/3p'; */ export function flite(global, data) { // TODO: check mandatory fields - validateData(data, [], ['guid','mixins']); - const {guid} = data, o = global, e = encodeURIComponent, x = 0; - let r = '', dep = ''; + validateData(data, [], ['guid', 'mixins']); + const {guid} = data, + o = global, + e = encodeURIComponent, + x = 0; + let r = '', + dep = ''; o.FLITE = o.FLITE || {}; o.FLITE.config = o.FLITE.config || {}; o.FLITE.config[guid] = o.FLITE.config[guid] || {}; o.FLITE.config[guid].cb = Math.random(); - o.FLITE.config[guid].ts = (+Number(new Date())); + o.FLITE.config[guid].ts = +Number(new Date()); r = global.context.location.href; const m = r.match(new RegExp('[A-Za-z]+:[/][/][A-Za-z0-9.-]+')); dep = data.mixins ? '&dep=' + data.mixins : ''; - const url = ['https://r.flite.com/syndication/uscript.js?i=',e(guid), - '&v=3',dep,'&x=us',x,'&cb=',o.FLITE.config[guid].cb,'&d=', - e((m && m[0]) || r), '&tz=', (new Date()).getTimezoneOffset()].join(''); + const url = [ + 'https://r.flite.com/syndication/uscript.js?i=', + e(guid), + '&v=3', + dep, + '&x=us', + x, + '&cb=', + o.FLITE.config[guid].cb, + '&d=', + e((m && m[0]) || r), + '&tz=', + new Date().getTimezoneOffset(), + ].join(''); loadScript(o, url); } diff --git a/ads/fluct.js b/ads/fluct.js index 4b0ddd7839a4..45863b78cc8a 100644 --- a/ads/fluct.js +++ b/ads/fluct.js @@ -24,9 +24,11 @@ import {validateData, writeScript} from '../3p/3p'; */ export function fluct(global, data) { validateData(data, ['g', 'u']); - writeScript(global, - `https://cdn-fluct.sh.adingo.jp/f.js?G=${encodeURIComponent(data['g'])}`, - function() { - adingoFluct.showAd(data['u']); - }); + writeScript( + global, + `https://cdn-fluct.sh.adingo.jp/f.js?G=${encodeURIComponent(data['g'])}`, + function() { + adingoFluct.showAd(data['u']); + } + ); } diff --git a/ads/freewheel.js b/ads/freewheel.js index f08d7dc92b4b..b0e0757ab56a 100644 --- a/ads/freewheel.js +++ b/ads/freewheel.js @@ -27,11 +27,25 @@ export function freewheel(global, data) { }; validateData( - data, - ['zone'], - ['zone','gdpr','gdpr_consent','useCMP','zIndex','blurDisplay', - 'timeline','soundButton','defaultMute','onOver','closeAction', - 'errorAction','pauseRatio','label','vastUrlParams'] + data, + ['zone'], + [ + 'zone', + 'gdpr', + 'gdpr_consent', + 'useCMP', + 'zIndex', + 'blurDisplay', + 'timeline', + 'soundButton', + 'defaultMute', + 'onOver', + 'closeAction', + 'errorAction', + 'pauseRatio', + 'label', + 'vastUrlParams', + ] ); loadScript(global, 'https://cdn.stickyadstv.com/prime-time/fw-amp.min.js'); diff --git a/ads/fusion.js b/ads/fusion.js index 5c3842281925..3c2bda914f72 100644 --- a/ads/fusion.js +++ b/ads/fusion.js @@ -24,10 +24,13 @@ function queryParametersToObject(input) { if (!input) { return undefined; } - return input.split('&').filter(_ => _).reduce((obj, val) => { - const kv = val.split('='); - return Object.assign(obj, {[kv[0]]: kv[1] || true}); - }, {}); + return input + .split('&') + .filter(_ => _) + .reduce((obj, val) => { + const kv = val.split('='); + return Object.assign(obj, {[kv[0]]: kv[1] || true}); + }, {}); } /** @@ -35,8 +38,11 @@ function queryParametersToObject(input) { * @param {!Object} data */ export function fusion(global, data) { - validateData(data, [], - ['mediaZone', 'layout', 'adServer', 'space', 'parameters']); + validateData( + data, + [], + ['mediaZone', 'layout', 'adServer', 'space', 'parameters'] + ); const container = global.document.getElementById('c'); const ad = global.document.createElement('div'); @@ -44,14 +50,17 @@ export function fusion(global, data) { container.appendChild(ad); const parameters = queryParametersToObject(data.parameters); - writeScript(global, - 'https://assets.adtomafusion.net/fusion/latest/fusion-amp.min.js', () => { - global.Fusion.apply(container, global.Fusion.loadAds(data, parameters)); + writeScript( + global, + 'https://assets.adtomafusion.net/fusion/latest/fusion-amp.min.js', + () => { + global.Fusion.apply(container, global.Fusion.loadAds(data, parameters)); - global.Fusion.on.warning.run(ev => { - if (ev.msg === 'Space not present in response.') { - global.context.noContentAvailable(); - } - }); + global.Fusion.on.warning.run(ev => { + if (ev.msg === 'Space not present in response.') { + global.context.noContentAvailable(); + } }); + } + ); } diff --git a/ads/giraff.js b/ads/giraff.js index 160a0a3e4250..4f317f3ffaa2 100644 --- a/ads/giraff.js +++ b/ads/giraff.js @@ -24,18 +24,26 @@ export function giraff(global, data) { validateData(data, ['blockName']); const serverName = data['serverName'] || 'code.giraff.io'; - const url = '//' + encodeURIComponent(serverName) + '/data/widget-' + - encodeURIComponent(data['blockName']) + '.js'; + const url = + '//' + + encodeURIComponent(serverName) + + '/data/widget-' + + encodeURIComponent(data['blockName']) + + '.js'; - loadScript(global, url, () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); const anchorEl = global.document.createElement('div'); const widgetId = data['widgetId'] ? '_' + data['widgetId'] : ''; anchorEl.id = 'grf_' + data['blockName'] + widgetId; global.document.getElementById('c').appendChild(anchorEl); - } diff --git a/ads/google/a4a/experiment-utils.js b/ads/google/a4a/experiment-utils.js index ba8a264bbd8a..1bf47146d353 100644 --- a/ads/google/a4a/experiment-utils.js +++ b/ads/google/a4a/experiment-utils.js @@ -20,9 +20,7 @@ import { getExperimentBranch, randomlySelectUnsetExperiments, } from '../../../src/experiments'; -import { - addExperimentIdToElement, -} from './traffic-experiments'; +import {addExperimentIdToElement} from './traffic-experiments'; /** * Attempts to select into experiment and forces branch if selected. @@ -33,12 +31,23 @@ import { * @param {boolean=} optAddExpIdToElement */ export function selectAndSetExperiments( - win, element, branches, expName, optAddExpIdToElement) { + win, + element, + branches, + expName, + optAddExpIdToElement +) { const experimentId = expUtils.maybeSelectExperiment( - win, element, branches, expName); + win, + element, + branches, + expName + ); if (!!experimentId) { - addExperimentIdToElement(optAddExpIdToElement ? - experimentId : undefined, element); + addExperimentIdToElement( + optAddExpIdToElement ? experimentId : undefined, + element + ); forceExperimentBranch(win, expName, experimentId); } return experimentId; @@ -51,10 +60,8 @@ export class ExperimentUtils { * @param {!Array} selectionBranches * @param {string} experimentName */ - maybeSelectExperiment( - win, element, selectionBranches, experimentName) { - const experimentInfoMap = - /** @type {!Object} */ ({}); + maybeSelectExperiment(win, element, selectionBranches, experimentName) { + const experimentInfoMap = /** @type {!Object} */ ({}); experimentInfoMap[experimentName] = { isTrafficEligible: () => true, branches: selectionBranches, @@ -67,5 +74,5 @@ export class ExperimentUtils { /** * ExperimentUtils singleton. * @type {!ExperimentUtils} -*/ + */ const expUtils = new ExperimentUtils(); diff --git a/ads/google/a4a/line-delimited-response-handler.js b/ads/google/a4a/line-delimited-response-handler.js index df5ccaf09a23..99f35ef9c332 100644 --- a/ads/google/a4a/line-delimited-response-handler.js +++ b/ads/google/a4a/line-delimited-response-handler.js @@ -17,13 +17,13 @@ import {tryParseJson} from '../../../src/json'; /** - * Handles an XHR response by calling lineCallback for each line delineation. - * Uses streaming where possible otherwise falls back to text. - * @param {!Window} win - * @param {!Response} response - * @param {function(string, boolean)} lineCallback - * @private - */ + * Handles an XHR response by calling lineCallback for each line delineation. + * Uses streaming where possible otherwise falls back to text. + * @param {!Window} win + * @param {!Response} response + * @param {function(string, boolean)} lineCallback + * @private + */ export function lineDelimitedStreamer(win, response, lineCallback) { let line = ''; /** @@ -50,14 +50,15 @@ export function lineDelimitedStreamer(win, response, lineCallback) { } const decoder = new TextDecoder('utf-8'); - const reader = /** @type !ReadableStreamDefaultReader */ ( - response.body.getReader()); + const reader = /** @type {!ReadableStreamDefaultReader} */ (response.body.getReader()); reader.read().then(function chunk(result) { if (result.value) { streamer( - decoder.decode( - /** @type {!ArrayBuffer} */(result.value), {'stream': true}), - result.done); + decoder.decode(/** @type {!ArrayBuffer} */ (result.value), { + 'stream': true, + }), + result.done + ); } if (!result.done) { // More chunks to read. @@ -67,22 +68,21 @@ export function lineDelimitedStreamer(win, response, lineCallback) { } /** - * Given each line, groups such that the first is JSON parsed and second - * html unescaped. - * @param {function(string, !Object, boolean)} callback - * @private - */ + * Given each line, groups such that the first is JSON parsed and second + * html unescaped. + * @param {function(string, !Object, boolean)} callback + * @private + */ export function metaJsonCreativeGrouper(callback) { let first; return function(line, done) { if (first) { const metadata = - /** @type {!Object} */(tryParseJson(first) || {}); - const lowerCasedMetadata = - Object.keys(metadata).reduce((newObj, key) => { - newObj[key.toLowerCase()] = metadata[key]; - return newObj; - }, {}); + /** @type {!Object} */ (tryParseJson(first) || {}); + const lowerCasedMetadata = Object.keys(metadata).reduce((newObj, key) => { + newObj[key.toLowerCase()] = metadata[key]; + return newObj; + }, {}); callback(unescapeLineDelimitedHtml_(line), lowerCasedMetadata, done); first = null; } else { @@ -92,13 +92,13 @@ export function metaJsonCreativeGrouper(callback) { } /** - * Unescapes characters that are escaped in line-delimited JSON-HTML. - * @param {string} html An html snippet. - * @return {string} - * @private - */ + * Unescapes characters that are escaped in line-delimited JSON-HTML. + * @param {string} html An html snippet. + * @return {string} + * @private + */ function unescapeLineDelimitedHtml_(html) { - return html.replace( - /\\(n|r|\\)/g, - (_, match) => match == 'n' ? '\n' : match == 'r' ? '\r' : '\\'); + return html.replace(/\\(n|r|\\)/g, (_, match) => + match == 'n' ? '\n' : match == 'r' ? '\r' : '\\' + ); } diff --git a/ads/google/a4a/shared/content-recommendation.js b/ads/google/a4a/shared/content-recommendation.js index d6f23e60a1c4..e5b85c7dc3b4 100644 --- a/ads/google/a4a/shared/content-recommendation.js +++ b/ads/google/a4a/shared/content-recommendation.js @@ -130,10 +130,11 @@ const LAYOUT_AD_WIDTH_MAP = { const PUB_CONTROL_LAYOUT_PREFIX = 'pub_control_'; -const PUB_CONTROL_EXAMPLE = '\n ' + - 'data-matched-content-rows-num=\"4,2\"\n' + - 'data-matched-content-columns-num=\"1,6\"\n' + - 'data-matched-content-ui-type=\"image_stacked,image_card_sidebyside\"'; +const PUB_CONTROL_EXAMPLE = + '\n ' + + 'data-matched-content-rows-num="4,2"\n' + + 'data-matched-content-columns-num="1,6"\n' + + 'data-matched-content-ui-type="image_stacked,image_card_sidebyside"'; /** * Configuration of content recommendation unit for current slot. This is the @@ -141,7 +142,8 @@ const PUB_CONTROL_EXAMPLE = '\n ' + * will be used in ad request. * @record */ -export class CoReConfig { // eslint-disable-line no-unused-vars +export class CoReConfig { + // eslint-disable-line no-unused-vars /** see comment on class */ constructor() { /** @const {number} */ @@ -195,7 +197,11 @@ export function getAutoConfig(availableWidth, isMobile) { const numColumns = 1; const numRows = 12; const slotSize = getLargerAdOneColumnSidebysideSize( - availableWidth, layoutType, numColumns, numRows); + availableWidth, + layoutType, + numColumns, + numRows + ); return { slotWidth: slotSize.width, slotHeight: slotSize.height, @@ -260,8 +266,10 @@ export function getPubControlConfig(availableWidth, rawPubControlParams) { } let index; - if (pubParams.layoutTypes.length === 2 && - availableWidth >= MIN_PUB_CONTROL_WIDTH_OF_DESKTOP) { + if ( + pubParams.layoutTypes.length === 2 && + availableWidth >= MIN_PUB_CONTROL_WIDTH_OF_DESKTOP + ) { // Publisher provided settings for both mobile and desktop and screen is // wide - use desktop. index = 1; @@ -273,11 +281,18 @@ export function getPubControlConfig(availableWidth, rawPubControlParams) { const layout = convertToPubControlLayoutType(pubParams.layoutTypes[index]); const numColumns = getOptimizedNumColumns( - availableWidth, pubParams.numberOfColumns[index], layout); + availableWidth, + pubParams.numberOfColumns[index], + layout + ); const numRows = pubParams.numberOfRows[index]; - const slotSize = - getPubControlSlotSize(availableWidth, numColumns, numRows, layout); + const slotSize = getPubControlSlotSize( + availableWidth, + numColumns, + numRows, + layout + ); if (slotSize.sizeError) { return { slotWidth: 0, @@ -323,24 +338,28 @@ function validateAndParsePubControlParams(params) { if (numberOfPubControlParams < 3) { return { validationError: `Tags ${ExternalCorePubVars.UI_TYPE}, ${ - ExternalCorePubVars.COLUMNS_NUM} and ${ - ExternalCorePubVars.ROWS_NUM} should be set together.`, + ExternalCorePubVars.COLUMNS_NUM + } and ${ExternalCorePubVars.ROWS_NUM} should be set together.`, }; } const /** !Array */ layoutTypes = params.layoutType.split(','); const /** !Array */ numberOfRows = params.numberOfRows.split(','); - const /** !Array */ numberOfColumns = - params.numberOfColumns.split(','); + const /** !Array */ numberOfColumns = params.numberOfColumns.split( + ',' + ); // Check all params have same length. - if (layoutTypes.length !== numberOfRows.length || - layoutTypes.length !== numberOfColumns.length) { + if ( + layoutTypes.length !== numberOfRows.length || + layoutTypes.length !== numberOfColumns.length + ) { return { validationError: `Lengths of parameters ${ExternalCorePubVars.UI_TYPE}, ${ - ExternalCorePubVars.COLUMNS_NUM} and ${ - ExternalCorePubVars.ROWS_NUM} must match. Example: ${ - PUB_CONTROL_EXAMPLE}`, + ExternalCorePubVars.COLUMNS_NUM + } and ${ + ExternalCorePubVars.ROWS_NUM + } must match. Example: ${PUB_CONTROL_EXAMPLE}`, }; } @@ -348,12 +367,14 @@ function validateAndParsePubControlParams(params) { return { validationError: `The parameter length of attribute ${ExternalCorePubVars.UI_TYPE}, ${ - ExternalCorePubVars.COLUMNS_NUM} and ${ - ExternalCorePubVars - .ROWS_NUM} is too long. At most 2 parameters for each ` + + ExternalCorePubVars.COLUMNS_NUM + } and ${ + ExternalCorePubVars.ROWS_NUM + } is too long. At most 2 parameters for each ` + 'attribute are needed: one for mobile and one for desktop, while ' + - `you are providing ${layoutTypes.length} parameters. Example: ${ - PUB_CONTROL_EXAMPLE}.`, + `you are providing ${ + layoutTypes.length + } parameters. Example: ${PUB_CONTROL_EXAMPLE}.`, }; } @@ -364,7 +385,8 @@ function validateAndParsePubControlParams(params) { if (isNaN(row) || row === 0) { return { validationError: `Wrong value '${numberOfRows[i]}' for ${ - ExternalCorePubVars.ROWS_NUM}.`, + ExternalCorePubVars.ROWS_NUM + }.`, }; } numberOfRowsAsNumbers.push(row); @@ -372,7 +394,8 @@ function validateAndParsePubControlParams(params) { if (isNaN(col) || col === 0) { return { validationError: `Wrong value '${numberOfColumns[i]}' for ${ - ExternalCorePubVars.COLUMNS_NUM}.`, + ExternalCorePubVars.COLUMNS_NUM + }.`, }; } numberOfColumnsAsNumbers.push(col); @@ -409,8 +432,9 @@ function getAutoSlotSize(availableWidth) { * @return {number} */ function getAdHeight(adWidth, layout) { - return adWidth * LAYOUT_ASPECT_RATIO_MAP[layout] + - LAYOUT_TEXT_HEIGHT_MAP[layout]; + return ( + adWidth * LAYOUT_ASPECT_RATIO_MAP[layout] + LAYOUT_TEXT_HEIGHT_MAP[layout] + ); } /** @@ -470,7 +494,6 @@ function getPubControlSlotSize(slotWidth, numColumns, numRows, layout) { return {width: slotWidth, height: slotHeight}; } - /** * @param {number} availableWidth * @param {!LayoutType} layoutType @@ -479,7 +502,11 @@ function getPubControlSlotSize(slotWidth, numColumns, numRows, layout) { * @return {{width: number, height: number}} */ function getLargerAdOneColumnSidebysideSize( - availableWidth, layoutType, numColumns, numRows) { + availableWidth, + layoutType, + numColumns, + numRows +) { const adWidth = getAdWidth(availableWidth, numColumns); // The title height of first ad slot 70px, should be consistent with what we // define in rendering js. @@ -498,9 +525,9 @@ function getLargerAdOneColumnSidebysideSize( * @return {!LayoutType} the new layout name with 'pub_control_' prefix. */ function convertToPubControlLayoutType(layout) { - return layout.indexOf(PUB_CONTROL_LAYOUT_PREFIX) === 0 ? - layout : - /** @type {!LayoutType} */ (PUB_CONTROL_LAYOUT_PREFIX + layout); + return layout.indexOf(PUB_CONTROL_LAYOUT_PREFIX) === 0 + ? layout + : /** @type {!LayoutType} */ (PUB_CONTROL_LAYOUT_PREFIX + layout); } /** @@ -517,8 +544,10 @@ function convertToPubControlLayoutType(layout) { function getOptimizedNumColumns(availableWidth, numColumns, layout) { const minWidth = LAYOUT_AD_WIDTH_MAP[layout]; let optimizedNumColumns = numColumns; - while (availableWidth / optimizedNumColumns < minWidth && - optimizedNumColumns > 1) { + while ( + availableWidth / optimizedNumColumns < minWidth && + optimizedNumColumns > 1 + ) { optimizedNumColumns--; } return optimizedNumColumns; diff --git a/ads/google/a4a/shared/test/test-content-recommendation.js b/ads/google/a4a/shared/test/test-content-recommendation.js index 88f4f04b8359..22594acdf895 100644 --- a/ads/google/a4a/shared/test/test-content-recommendation.js +++ b/ads/google/a4a/shared/test/test-content-recommendation.js @@ -23,106 +23,147 @@ import { describe('getAutoConfig', function() { it('should use image_stacked on wide slots', function() { const runTest = (availableWidth, expectedWidth, expectedHeight) => { - expect(getAutoConfig(availableWidth, /* isMobile= */ false)) - .to.deep.equal({ - layoutType: LayoutType.IMAGE_STACKED, - numberOfColumns: 4, - numberOfRows: 2, - slotWidth: expectedWidth, - slotHeight: expectedHeight, - }); + expect( + getAutoConfig(availableWidth, /* isMobile= */ false) + ).to.deep.equal({ + layoutType: LayoutType.IMAGE_STACKED, + numberOfColumns: 4, + numberOfRows: 2, + slotWidth: expectedWidth, + slotHeight: expectedHeight, + }); }; runTest( - /* availableWidth= */ 1440, /* expectedWidth= */ 1200, - /* expectedHeight= */ 600); + /* availableWidth= */ 1440, + /* expectedWidth= */ 1200, + /* expectedHeight= */ 600 + ); runTest( - /* availableWidth= */ 1200, /* expectedWidth= */ 1200, - /* expectedHeight= */ 600); + /* availableWidth= */ 1200, + /* expectedWidth= */ 1200, + /* expectedHeight= */ 600 + ); runTest( - /* availableWidth= */ 800, /* expectedWidth= */ 800, - /* expectedHeight= */ 480); + /* availableWidth= */ 800, + /* expectedWidth= */ 800, + /* expectedHeight= */ 480 + ); runTest( - /* availableWidth= */ 500, /* expectedWidth= */ 500, - /* expectedHeight= */ 350); + /* availableWidth= */ 500, + /* expectedWidth= */ 500, + /* expectedHeight= */ 350 + ); runTest( - /* availableWidth= */ 468, /* expectedWidth= */ 468, - /* expectedHeight= */ 327); + /* availableWidth= */ 468, + /* expectedWidth= */ 468, + /* expectedHeight= */ 327 + ); }); - it('should use mobile_banner_image_sidebyside on narrow slots on mobile', - function() { - const runTest = (availableWidth, expectedWidth, expectedHeight) => { - expect(getAutoConfig(availableWidth, /* isMobile= */ true)) - .to.deep.equal({ - layoutType: LayoutType.MOBILE_BANNER_IMAGE_SIDEBYSIDE, - numberOfColumns: 1, - numberOfRows: 12, - slotWidth: expectedWidth, - slotHeight: expectedHeight, - }); - }; - runTest( - /* availableWidth= */ 467, /* expectedWidth= */ 467, - /* expectedHeight= */ 1700); - runTest( - /* availableWidth= */ 414, /* expectedWidth= */ 414, - /* expectedHeight= */ 1520); - runTest( - /* availableWidth= */ 360, /* expectedWidth= */ 360, - /* expectedHeight= */ 1336); - runTest( - /* availableWidth= */ 320, /* expectedWidth= */ 320, - /* expectedHeight= */ 1200); - runTest( - /* availableWidth= */ 300, /* expectedWidth= */ 300, - /* expectedHeight= */ 1131); - runTest( - /* availableWidth= */ 200, /* expectedWidth= */ 200, - /* expectedHeight= */ 791); - runTest( - /* availableWidth= */ 120, /* expectedWidth= */ 120, - /* expectedHeight= */ 519); - }); + it('should use mobile_banner_image_sidebyside on narrow slots on mobile', function() { + const runTest = (availableWidth, expectedWidth, expectedHeight) => { + expect(getAutoConfig(availableWidth, /* isMobile= */ true)).to.deep.equal( + { + layoutType: LayoutType.MOBILE_BANNER_IMAGE_SIDEBYSIDE, + numberOfColumns: 1, + numberOfRows: 12, + slotWidth: expectedWidth, + slotHeight: expectedHeight, + } + ); + }; + runTest( + /* availableWidth= */ 467, + /* expectedWidth= */ 467, + /* expectedHeight= */ 1700 + ); + runTest( + /* availableWidth= */ 414, + /* expectedWidth= */ 414, + /* expectedHeight= */ 1520 + ); + runTest( + /* availableWidth= */ 360, + /* expectedWidth= */ 360, + /* expectedHeight= */ 1336 + ); + runTest( + /* availableWidth= */ 320, + /* expectedWidth= */ 320, + /* expectedHeight= */ 1200 + ); + runTest( + /* availableWidth= */ 300, + /* expectedWidth= */ 300, + /* expectedHeight= */ 1131 + ); + runTest( + /* availableWidth= */ 200, + /* expectedWidth= */ 200, + /* expectedHeight= */ 791 + ); + runTest( + /* availableWidth= */ 120, + /* expectedWidth= */ 120, + /* expectedHeight= */ 519 + ); + }); it('should use image_sidebyside on narrow slots on desktop', function() { const runTest = (availableWidth, expectedWidth, expectedHeight) => { - expect(getAutoConfig(availableWidth, /* isMobile= */ false)) - .to.deep.equal({ - layoutType: LayoutType.IMAGE_SIDEBYSIDE, - numberOfColumns: 1, - numberOfRows: 13, - slotWidth: expectedWidth, - slotHeight: expectedHeight, - }); + expect( + getAutoConfig(availableWidth, /* isMobile= */ false) + ).to.deep.equal({ + layoutType: LayoutType.IMAGE_SIDEBYSIDE, + numberOfColumns: 1, + numberOfRows: 13, + slotWidth: expectedWidth, + slotHeight: expectedHeight, + }); }; runTest( - /* availableWidth= */ 467, /* expectedWidth= */ 467, - /* expectedHeight= */ 1606); + /* availableWidth= */ 467, + /* expectedWidth= */ 467, + /* expectedHeight= */ 1606 + ); runTest( - /* availableWidth= */ 414, /* expectedWidth= */ 414, - /* expectedHeight= */ 1424); + /* availableWidth= */ 414, + /* expectedWidth= */ 414, + /* expectedHeight= */ 1424 + ); runTest( - /* availableWidth= */ 360, /* expectedWidth= */ 360, - /* expectedHeight= */ 1238); + /* availableWidth= */ 360, + /* expectedWidth= */ 360, + /* expectedHeight= */ 1238 + ); runTest( - /* availableWidth= */ 320, /* expectedWidth= */ 320, - /* expectedHeight= */ 1100); + /* availableWidth= */ 320, + /* expectedWidth= */ 320, + /* expectedHeight= */ 1100 + ); runTest( - /* availableWidth= */ 300, /* expectedWidth= */ 300, - /* expectedHeight= */ 1032); + /* availableWidth= */ 300, + /* expectedWidth= */ 300, + /* expectedHeight= */ 1032 + ); runTest( - /* availableWidth= */ 250, /* expectedWidth= */ 250, - /* expectedHeight= */ 860); + /* availableWidth= */ 250, + /* expectedWidth= */ 250, + /* expectedHeight= */ 860 + ); runTest( - /* availableWidth= */ 200, /* expectedWidth= */ 200, - /* expectedHeight= */ 688); + /* availableWidth= */ 200, + /* expectedWidth= */ 200, + /* expectedHeight= */ 688 + ); runTest( - /* availableWidth= */ 120, /* expectedWidth= */ 120, - /* expectedHeight= */ 412); + /* availableWidth= */ 120, + /* expectedWidth= */ 120, + /* expectedHeight= */ 412 + ); }); }); - describe('getPubControlConfig', function() { it('should use setting when only one provided', function() { const rawPubControlParams = { @@ -131,80 +172,112 @@ describe('getPubControlConfig', function() { layoutType: LayoutType.IMAGE_STACKED, }; const runTest = (availableWidth, expectedWidth, expectedHeight) => { - expect(getPubControlConfig(availableWidth, rawPubControlParams)) - .to.deep.equal({ - layoutType: LayoutType.PUB_CONTROL_IMAGE_STACKED, - numberOfColumns: 4, - numberOfRows: 2, - slotWidth: expectedWidth, - slotHeight: expectedHeight, - }); + expect( + getPubControlConfig(availableWidth, rawPubControlParams) + ).to.deep.equal({ + layoutType: LayoutType.PUB_CONTROL_IMAGE_STACKED, + numberOfColumns: 4, + numberOfRows: 2, + slotWidth: expectedWidth, + slotHeight: expectedHeight, + }); }; runTest( - /* availableWidth= */ 1300, /* expectedWidth= */ 1300, - /* expectedHeight= */ 513); + /* availableWidth= */ 1300, + /* expectedWidth= */ 1300, + /* expectedHeight= */ 513 + ); runTest( - /* availableWidth= */ 1200, /* expectedWidth= */ 1200, - /* expectedHeight= */ 487); + /* availableWidth= */ 1200, + /* expectedWidth= */ 1200, + /* expectedHeight= */ 487 + ); runTest( - /* availableWidth= */ 800, /* expectedWidth= */ 800, - /* expectedHeight= */ 382); + /* availableWidth= */ 800, + /* expectedWidth= */ 800, + /* expectedHeight= */ 382 + ); runTest( - /* availableWidth= */ 500, /* expectedWidth= */ 500, - /* expectedHeight= */ 304); + /* availableWidth= */ 500, + /* expectedWidth= */ 500, + /* expectedHeight= */ 304 + ); runTest( - /* availableWidth= */ 400, /* expectedWidth= */ 400, - /* expectedHeight= */ 278); + /* availableWidth= */ 400, + /* expectedWidth= */ 400, + /* expectedHeight= */ 278 + ); }); - it('should use different settings for mobile and desktop when two provided', - function() { - const rawPubControlParams = { - numberOfColumns: '1,4', - numberOfRows: '3,2', - layoutType: - `${LayoutType.IMAGE_SIDEBYSIDE},${LayoutType.IMAGE_STACKED}`, - }; - const expectedDesktopConfig = { - layoutType: LayoutType.PUB_CONTROL_IMAGE_STACKED, - numberOfColumns: 4, - numberOfRows: 2, - }; - const expectedMobileConfig = { - layoutType: LayoutType.PUB_CONTROL_IMAGE_SIDEBYSIDE, - numberOfColumns: 1, - numberOfRows: 3, - }; - const runTest = - (availableWidth, expectedWidth, expectedHeight, expectedConfig) => { - expectedConfig.slotWidth = expectedWidth; - expectedConfig.slotHeight = expectedHeight; - expect(getPubControlConfig(availableWidth, rawPubControlParams)) - .to.deep.equal(expectedConfig); - }; + it('should use different settings for mobile and desktop when two provided', function() { + const rawPubControlParams = { + numberOfColumns: '1,4', + numberOfRows: '3,2', + layoutType: `${LayoutType.IMAGE_SIDEBYSIDE},${LayoutType.IMAGE_STACKED}`, + }; + const expectedDesktopConfig = { + layoutType: LayoutType.PUB_CONTROL_IMAGE_STACKED, + numberOfColumns: 4, + numberOfRows: 2, + }; + const expectedMobileConfig = { + layoutType: LayoutType.PUB_CONTROL_IMAGE_SIDEBYSIDE, + numberOfColumns: 1, + numberOfRows: 3, + }; + const runTest = ( + availableWidth, + expectedWidth, + expectedHeight, + expectedConfig + ) => { + expectedConfig.slotWidth = expectedWidth; + expectedConfig.slotHeight = expectedHeight; + expect( + getPubControlConfig(availableWidth, rawPubControlParams) + ).to.deep.equal(expectedConfig); + }; - // Above 468px the logic should use desktop setting. - runTest( - /* availableWidth= */ 1300, /* expectedWidth= */ 1300, - /* expectedHeight= */ 513, expectedDesktopConfig); - runTest( - /* availableWidth= */ 1200, /* expectedWidth= */ 1200, - /* expectedHeight= */ 487, expectedDesktopConfig); - runTest( - /* availableWidth= */ 800, /* expectedWidth= */ 800, - /* expectedHeight= */ 382, expectedDesktopConfig); - runTest( - /* availableWidth= */ 500, /* expectedWidth= */ 500, - /* expectedHeight= */ 304, expectedDesktopConfig); + // Above 468px the logic should use desktop setting. + runTest( + /* availableWidth= */ 1300, + /* expectedWidth= */ 1300, + /* expectedHeight= */ 513, + expectedDesktopConfig + ); + runTest( + /* availableWidth= */ 1200, + /* expectedWidth= */ 1200, + /* expectedHeight= */ 487, + expectedDesktopConfig + ); + runTest( + /* availableWidth= */ 800, + /* expectedWidth= */ 800, + /* expectedHeight= */ 382, + expectedDesktopConfig + ); + runTest( + /* availableWidth= */ 500, + /* expectedWidth= */ 500, + /* expectedHeight= */ 304, + expectedDesktopConfig + ); - // Below 468px the logic should use mobile setting. - runTest( - /* availableWidth= */ 400, /* expectedWidth= */ 400, - /* expectedHeight= */ 333, expectedMobileConfig); - runTest( - /* availableWidth= */ 300, /* expectedWidth= */ 300, - /* expectedHeight= */ 255, expectedMobileConfig); - }); + // Below 468px the logic should use mobile setting. + runTest( + /* availableWidth= */ 400, + /* expectedWidth= */ 400, + /* expectedHeight= */ 333, + expectedMobileConfig + ); + runTest( + /* availableWidth= */ 300, + /* expectedWidth= */ 300, + /* expectedHeight= */ 255, + expectedMobileConfig + ); + }); it('should return different sizes for different layouts', function() { // sanity check that when publisher provides different layouts we use @@ -237,45 +310,53 @@ describe('getPubControlConfig', function() { }; // One of pub control params is missing. runTest( - {numberOfRows: '1', numberOfColumns: '1'}, - /Tags .* should be set together/); + {numberOfRows: '1', numberOfColumns: '1'}, + /Tags .* should be set together/ + ); runTest( - {numberOfColumns: '1', layoutType: 'foo'}, - /Tags .* should be set together/); + {numberOfColumns: '1', layoutType: 'foo'}, + /Tags .* should be set together/ + ); runTest( - {numberOfRows: '1', layoutType: 'foo'}, - /Tags .* should be set together/); + {numberOfRows: '1', layoutType: 'foo'}, + /Tags .* should be set together/ + ); // Length of parameters doesn't match runTest( - {numberOfRows: '1', numberOfColumns: '1', layoutType: 'foo,bar'}, - /Lengths of parameters .* must match/); + {numberOfRows: '1', numberOfColumns: '1', layoutType: 'foo,bar'}, + /Lengths of parameters .* must match/ + ); runTest( - {numberOfRows: '1', numberOfColumns: '1,2', layoutType: 'foo'}, - /Lengths of parameters .* must match/); + {numberOfRows: '1', numberOfColumns: '1,2', layoutType: 'foo'}, + /Lengths of parameters .* must match/ + ); runTest( - {numberOfRows: '1,2', numberOfColumns: '1', layoutType: 'foo'}, - /Lengths of parameters .* must match/); + {numberOfRows: '1,2', numberOfColumns: '1', layoutType: 'foo'}, + /Lengths of parameters .* must match/ + ); // Length is more than 2. runTest( - { - numberOfRows: '1,2,3', - numberOfColumns: '1,2,3', - layoutType: 'foo,bar,baz', - }, - /At most 2 parameters for each attribute are needed/); + { + numberOfRows: '1,2,3', + numberOfColumns: '1,2,3', + layoutType: 'foo,bar,baz', + }, + /At most 2 parameters for each attribute are needed/ + ); // Passed non-number for rows/columns. runTest( - {numberOfRows: 'foo', numberOfColumns: '1', layoutType: 'foo'}, - /Wrong value 'foo' for/); + {numberOfRows: 'foo', numberOfColumns: '1', layoutType: 'foo'}, + /Wrong value 'foo' for/ + ); runTest( - {numberOfRows: '1', numberOfColumns: 'foo', layoutType: 'foo'}, - /Wrong value 'foo' for/); + {numberOfRows: '1', numberOfColumns: 'foo', layoutType: 'foo'}, + /Wrong value 'foo' for/ + ); }); - it('limits number of columns if publisher chose too many', function() { const rawPubControlParams = { numberOfColumns: '5', // want 5 columns. diff --git a/ads/google/a4a/shared/test/test-url-builder.js b/ads/google/a4a/shared/test/test-url-builder.js index 1904f5e24062..2c99a7300791 100644 --- a/ads/google/a4a/shared/test/test-url-builder.js +++ b/ads/google/a4a/shared/test/test-url-builder.js @@ -18,7 +18,8 @@ import {buildUrl} from '../url-builder'; describe('buildUrl', () => { it('should build a simple URL', () => { - expect(buildUrl('https://example.com', {'key': 'value'}, Infinity)) - .to.equal('https://example.com?key=value'); + expect( + buildUrl('https://example.com', {'key': 'value'}, Infinity) + ).to.equal('https://example.com?key=value'); }); }); diff --git a/ads/google/a4a/shared/url-builder.js b/ads/google/a4a/shared/url-builder.js index e60a04503f35..95a8e7daef35 100644 --- a/ads/google/a4a/shared/url-builder.js +++ b/ads/google/a4a/shared/url-builder.js @@ -29,15 +29,22 @@ export let QueryParameterDef; * @return {string} the fully constructed URL. */ export function buildUrl( - baseUrl, queryParams, maxLength, opt_truncationQueryParam) { + baseUrl, + queryParams, + maxLength, + opt_truncationQueryParam +) { const encodedParams = []; const encodedTruncationParam = - opt_truncationQueryParam && - !(opt_truncationQueryParam.value == null || - opt_truncationQueryParam.value === '') ? - encodeURIComponent(opt_truncationQueryParam.name) + '=' + - encodeURIComponent(String(opt_truncationQueryParam.value)) : - null; + opt_truncationQueryParam && + !( + opt_truncationQueryParam.value == null || + opt_truncationQueryParam.value === '' + ) + ? encodeURIComponent(opt_truncationQueryParam.name) + + '=' + + encodeURIComponent(String(opt_truncationQueryParam.value)) + : null; let capacity = maxLength - baseUrl.length; if (encodedTruncationParam) { capacity -= encodedTruncationParam.length + 1; @@ -54,9 +61,9 @@ export function buildUrl( const fullLength = encodedNameAndSep.length + encodedValue.length + 1; if (fullLength > capacity) { const truncatedValue = encodedValue - .substr(0, capacity - encodedNameAndSep.length - 1) - // Don't end with a partially truncated escape sequence - .replace(/%\w?$/, ''); + .substr(0, capacity - encodedNameAndSep.length - 1) + // Don't end with a partially truncated escape sequence + .replace(/%\w?$/, ''); if (truncatedValue) { encodedParams.push(encodedNameAndSep + truncatedValue); } diff --git a/ads/google/a4a/test/test-line-delimited-response-handler.js b/ads/google/a4a/test/test-line-delimited-response-handler.js index 2742274b5eaf..029943ad6080 100644 --- a/ads/google/a4a/test/test-line-delimited-response-handler.js +++ b/ads/google/a4a/test/test-line-delimited-response-handler.js @@ -20,7 +20,6 @@ import { } from '../line-delimited-response-handler'; describe('#line-delimited-response-handler', () => { - let chunkHandlerStub; let slotData; let sandbox; @@ -34,8 +33,10 @@ describe('#line-delimited-response-handler', () => { let slotDataString = ''; slotData.forEach(slot => { // TODO: escape creative returns - const creative = slot.creative.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\n').replace(/\r/g, '\\r'); + const creative = slot.creative + .replace(/\\/g, '\\\\') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r'); slotDataString += `${JSON.stringify(slot.headers)}\n${creative}\n`; }); return slotDataString; @@ -45,7 +46,7 @@ describe('#line-delimited-response-handler', () => { // Streamed response calls chunk handlers after returning so need to // wait on chunks. let chunkResolver; - const chunkPromise = new Promise(resolver => chunkResolver = resolver); + const chunkPromise = new Promise(resolver => (chunkResolver = resolver)); const chunkHandlerWrapper = (creative, metaData) => { chunkHandlerStub(creative, metaData); if (chunkHandlerStub.callCount == slotData.length) { @@ -58,27 +59,35 @@ describe('#line-delimited-response-handler', () => { chunkResolver(); } lineDelimitedStreamer( - win, response, metaJsonCreativeGrouper(chunkHandlerWrapper)); + win, + response, + metaJsonCreativeGrouper(chunkHandlerWrapper) + ); return chunkPromise.then(() => { expect(chunkHandlerStub.callCount).to.equal(slotData.length); // Could have duplicate responses so need to iterate and get counts. // TODO: can't use objects as keys :( const calls = {}; slotData.forEach(slot => { - const normalizedHeaderNames = - Object.keys(slot.headers).map(s => [s.toLowerCase(), s]); + const normalizedHeaderNames = Object.keys(slot.headers).map(s => [ + s.toLowerCase(), + s, + ]); slot.normalizedHeaders = {}; normalizedHeaderNames.forEach( - namePair => - slot.normalizedHeaders[namePair[0]] = slot.headers[namePair[1]]); + namePair => + (slot.normalizedHeaders[namePair[0]] = slot.headers[namePair[1]]) + ); const key = slot.creative + JSON.stringify(slot.normalizedHeaders); calls[key] ? calls[key]++ : (calls[key] = 1); }); slotData.forEach(slot => { - expect(chunkHandlerStub.withArgs( - slot.creative, slot.normalizedHeaders).callCount) - .to.equal(calls[slot.creative + - JSON.stringify(slot.normalizedHeaders)]); + expect( + chunkHandlerStub.withArgs(slot.creative, slot.normalizedHeaders) + .callCount + ).to.equal( + calls[slot.creative + JSON.stringify(slot.normalizedHeaders)] + ); }); }); } @@ -99,7 +108,9 @@ describe('#line-delimited-response-handler', () => { }; win = { // TextDecoder should exist but not be called - TextDecoder: () => { throw new Error('fail'); }, + TextDecoder: () => { + throw new Error('fail'); + }, }; }); @@ -119,8 +130,10 @@ describe('#line-delimited-response-handler', () => { it('should fallback to text if no TextDecoder', () => { slotData = [ {headers: {foo: 'bar', hello: 'world'}, creative: 'baz\n'}, - {headers: {hello: 'world'}, - creative: '\r\n\nchunk\b me\r\b\n\r'}, + { + headers: {hello: 'world'}, + creative: '\r\n\nchunk\b me\r\b\n\r', + }, {headers: {foo: 'bar'}, creative: ''}, {headers: {}, creative: '\r\n\r\b\n\r'}, ]; @@ -138,8 +151,10 @@ describe('#line-delimited-response-handler', () => { const CHUNK_SIZE = 5; let chunk = 0; do { - const value = textEncoder.encode(responseString.substr( - chunk * CHUNK_SIZE, CHUNK_SIZE), {'stream': true}); + const value = textEncoder.encode( + responseString.substr(chunk * CHUNK_SIZE, CHUNK_SIZE), + {'stream': true} + ); const done = chunk * CHUNK_SIZE >= responseString.length - 1; readStub.onCall(chunk).returns(Promise.resolve({value, done})); } while (chunk++ * CHUNK_SIZE < responseString.length); @@ -168,12 +183,14 @@ describe('#line-delimited-response-handler', () => { }); // TODO(lannka, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should handle empty streamed ' + - 'response properly', () => { - slotData = []; - setup(); - return executeAndVerifyResponse(); - }); + it.configure().skipSafari( + 'should handle empty streamed response properly', + () => { + slotData = []; + setup(); + return executeAndVerifyResponse(); + } + ); // TODO(lannka, #15748): Fails on Safari 11.1.0. it.configure().skipSafari('should handle no fill response properly', () => { @@ -183,31 +200,40 @@ describe('#line-delimited-response-handler', () => { }); // TODO(lannka, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should handle multiple no fill responses ' + - 'properly', () => { - slotData = [ - {headers: {}, creative: ''}, - {headers: {}, creative: ''}, - ]; - setup(); - return executeAndVerifyResponse(); - }); + it.configure().skipSafari( + 'should handle multiple no fill responses properly', + () => { + slotData = [{headers: {}, creative: ''}, {headers: {}, creative: ''}]; + setup(); + return executeAndVerifyResponse(); + } + ); // TODO(lannka, #15748): Fails on Safari 11.1.0. it.configure().skipSafari('should stream properly', () => { slotData = [ {headers: {}, creative: ''}, - {headers: {foo: 'bar', hello: 'world'}, - creative: '\t\n\r\bbaz\r\n\n'}, - {headers: {Foo: 'bar', hello: 'world'}, - creative: '\t\n\r\bbaz\r\n\n'}, + { + headers: {foo: 'bar', hello: 'world'}, + creative: '\t\n\r\bbaz\r\n\n', + }, + { + headers: {Foo: 'bar', hello: 'world'}, + creative: '\t\n\r\bbaz\r\n\n', + }, {headers: {}, creative: ''}, - {headers: {Foo: 'bar', HELLO: 'Le Monde'}, - creative: '\t\n\r\bbaz\r\n\n'}, - {headers: {FOO: 'bar', Hello: 'Le Monde'}, - creative: '\t\n\r\bbaz\r\n\n'}, - {headers: {hello: 'world'}, - creative: '\nchu\nnk me'}, + { + headers: {Foo: 'bar', HELLO: 'Le Monde'}, + creative: '\t\n\r\bbaz\r\n\n', + }, + { + headers: {FOO: 'bar', Hello: 'Le Monde'}, + creative: '\t\n\r\bbaz\r\n\n', + }, + { + headers: {hello: 'world'}, + creative: '\nchu\nnk me', + }, {headers: {}, creative: ''}, ]; setup(); diff --git a/ads/google/a4a/test/test-traffic-experiments.js b/ads/google/a4a/test/test-traffic-experiments.js index 416e4b671483..3c09a1ffdbe4 100644 --- a/ads/google/a4a/test/test-traffic-experiments.js +++ b/ads/google/a4a/test/test-traffic-experiments.js @@ -72,7 +72,8 @@ describe('all-traffic-experiments-tests', () => { element.setAttribute(EXPERIMENT_ATTRIBUTE, '99,77,11,0122345'); addExperimentIdToElement('3', element); expect(element.getAttribute(EXPERIMENT_ATTRIBUTE)).to.equal( - '99,77,11,0122345,3'); + '99,77,11,0122345,3' + ); }); it('should should replace existing invalid experiments', () => { diff --git a/ads/google/a4a/test/test-utils.js b/ads/google/a4a/test/test-utils.js index 624ddd8d1156..50b9e71a9bec 100644 --- a/ads/google/a4a/test/test-utils.js +++ b/ads/google/a4a/test/test-utils.js @@ -38,17 +38,13 @@ import { mergeExperimentIds, } from '../utils'; import {CONSENT_POLICY_STATE} from '../../../../src/consent-state'; -import { - MockA4AImpl, -} from '../../../../extensions/amp-a4a/0.1/test/utils'; +import {MockA4AImpl} from '../../../../extensions/amp-a4a/0.1/test/utils'; import {Services} from '../../../../src/services'; import {buildUrl} from '../shared/url-builder'; import {createElementWithAttributes} from '../../../../src/dom'; import {createIframePromise} from '../../../../testing/iframe'; import {installDocService} from '../../../../src/service/ampdoc-impl'; -import { - installExtensionsService, -} from '../../../../src/service/extensions-impl'; +import {installExtensionsService} from '../../../../src/service/extensions-impl'; import {installXhrService} from '../../../../src/service/xhr-impl'; import {toggleExperiment} from '../../../../src/experiments'; @@ -68,9 +64,19 @@ function setupForAdTesting(fixture) { // Because of the way the element is constructed, it doesn't have all of the // machinery that AMP expects it to have, so just no-op the irrelevant // functions. -function noopMethods(impl, doc, sandbox, pageLayoutBox = { - top: 11, left: 12, right: 0, bottom: 0, width: 0, height: 0, -}) { +function noopMethods( + impl, + doc, + sandbox, + pageLayoutBox = { + top: 11, + left: 12, + right: 0, + bottom: 0, + width: 0, + height: 0, + } +) { const noop = () => {}; impl.element.build = noop; impl.element.getPlaceholder = noop; @@ -80,7 +86,6 @@ function noopMethods(impl, doc, sandbox, pageLayoutBox = { } describe('Google A4A utils', () => { - //TODO: Add tests for other utils functions. describe('#additionalDimensions', () => { @@ -102,7 +107,8 @@ describe('Google A4A utils', () => { height: '101px', }; return expect(additionalDimensions(fakeWin, fakeSize)).to.equal( - '3,4,1,2,11,12,5,6,100px,101px'); + '3,4,1,2,11,12,5,6,100px,101px' + ); }); }); @@ -167,10 +173,12 @@ describe('Google A4A utils', () => { }); const a4a = new MockA4AImpl(element); url = 'not an array'; - allowConsoleError(() => - expect(extractAmpAnalyticsConfig(a4a, headers)).to.not.be.ok); - allowConsoleError(() => - expect(extractAmpAnalyticsConfig(a4a, headers)).to.be.null); + allowConsoleError( + () => expect(extractAmpAnalyticsConfig(a4a, headers)).to.not.be.ok + ); + allowConsoleError( + () => expect(extractAmpAnalyticsConfig(a4a, headers)).to.be.null + ); url = []; expect(extractAmpAnalyticsConfig(a4a, headers)).to.not.be.ok; expect(extractAmpAnalyticsConfig(a4a, headers)).to.be.null; @@ -203,15 +211,21 @@ describe('Google A4A utils', () => { }; const qqid = 'qqid_string'; let newConfig = addCsiSignalsToAmpAnalyticsConfig( - window, mockElement, builtConfig, qqid, - /* isVerifiedAmpCreative */ true); + window, + mockElement, + builtConfig, + qqid, + /* isVerifiedAmpCreative */ true + ); expect(newConfig.requests.iniLoadCsi).to.not.be.null; expect(newConfig.requests.renderStartCsi).to.not.be.null; - expect(newConfig.triggers.continuousVisibleIniLoad.request) - .to.equal('iniLoadCsi'); - expect(newConfig.triggers.continuousVisibleRenderStart.request) - .to.equal('renderStartCsi'); + expect(newConfig.triggers.continuousVisibleIniLoad.request).to.equal( + 'iniLoadCsi' + ); + expect(newConfig.triggers.continuousVisibleRenderStart.request).to.equal( + 'renderStartCsi' + ); const getRegExps = metricName => [ /^https:\/\/csi\.gstatic\.com\/csi\?/, /(\?|&)s=a4a(&|$)/, @@ -234,48 +248,62 @@ describe('Google A4A utils', () => { expect(newConfig.requests.renderStartCsi).to.match(regExp); }); newConfig = addCsiSignalsToAmpAnalyticsConfig( - window, mockElement, builtConfig, qqid, - /* isVerifiedAmpCreative */ false, - /* lifecycle time events; not relevant here */ -1, -1); + window, + mockElement, + builtConfig, + qqid, + /* isVerifiedAmpCreative */ false, + /* lifecycle time events; not relevant here */ -1, + -1 + ); getRegExps('iniLoadCsiCrossDomain').forEach(regExp => { expect(newConfig.requests.iniLoadCsi).to.match(regExp); }); getRegExps('renderStartCsiCrossDomain').forEach(regExp => { expect(newConfig.requests.renderStartCsi).to.match(regExp); }); - }); }); describe('#getAmpRuntimeTypeParameter', () => { it('should specify that this is canary', () => { - expect(getAmpRuntimeTypeParameter({ - AMP_CONFIG: {type: 'canary'}, - location: {origin: 'https://www-example-com.cdn.ampproject.org'}, - })).to.equal('2'); + expect( + getAmpRuntimeTypeParameter({ + AMP_CONFIG: {type: 'canary'}, + location: {origin: 'https://www-example-com.cdn.ampproject.org'}, + }) + ).to.equal('2'); }); it('should specify that this is control', () => { - expect(getAmpRuntimeTypeParameter({ - AMP_CONFIG: {type: 'control'}, - location: {origin: 'https://www-example-com.cdn.ampproject.org'}, - })).to.equal('1'); + expect( + getAmpRuntimeTypeParameter({ + AMP_CONFIG: {type: 'control'}, + location: {origin: 'https://www-example-com.cdn.ampproject.org'}, + }) + ).to.equal('1'); }); it('should not have `art` parameter when AMP_CONFIG is undefined', () => { - expect(getAmpRuntimeTypeParameter({ - location: {origin: 'https://www-example-com.cdn.ampproject.org'}, - })).to.be.null; + expect( + getAmpRuntimeTypeParameter({ + location: {origin: 'https://www-example-com.cdn.ampproject.org'}, + }) + ).to.be.null; }); it('should not have `art` parameter when binary type is production', () => { - expect(getAmpRuntimeTypeParameter({ - AMP_CONFIG: {type: 'production'}, - location: {origin: 'https://www-example-com.cdn.ampproject.org'}, - })).to.be.null; + expect( + getAmpRuntimeTypeParameter({ + AMP_CONFIG: {type: 'production'}, + location: {origin: 'https://www-example-com.cdn.ampproject.org'}, + }) + ).to.be.null; }); it('should not have `art` parameter when canonical', () => { - expect(getAmpRuntimeTypeParameter({ - AMP_CONFIG: {type: 'canary'}, - location: {origin: 'https://www.example.com'}, - })).to.be.null; + expect( + getAmpRuntimeTypeParameter({ + AMP_CONFIG: {type: 'canary'}, + location: {origin: 'https://www.example.com'}, + }) + ).to.be.null; }); }); @@ -325,8 +353,12 @@ describe('Google A4A utils', () => { }); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); - const getRect = () => { return {'width': 100, 'height': 200}; }; - const getSize = () => { return {'width': 100, 'height': 200}; }; + const getRect = () => { + return {'width': 100, 'height': 200}; + }; + const getSize = () => { + return {'width': 100, 'height': 200}; + }; const getScrollLeft = () => 12; const getScrollTop = () => 34; const viewportStub = sandbox.stub(Services, 'viewportForDoc'); @@ -418,8 +450,10 @@ describe('Google A4A utils', () => { }); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); - const createElementStub = - sandbox.stub(impl.win.document, 'createElement'); + const createElementStub = sandbox.stub( + impl.win.document, + 'createElement' + ); createElementStub.withArgs('iframe').returns({ sandbox: { supports: () => true, @@ -427,7 +461,8 @@ describe('Google A4A utils', () => { }); return fixture.addElement(elem).then(() => { return expect(googleAdUrl(impl, '', 0, {}, [])).to.eventually.match( - /[&?]bc=7[&$]/); + /[&?]bc=7[&$]/ + ); }); }); }); @@ -444,14 +479,17 @@ describe('Google A4A utils', () => { }); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); - const createElementStub = - sandbox.stub(impl.win.document, 'createElement'); + const createElementStub = sandbox.stub( + impl.win.document, + 'createElement' + ); createElementStub.withArgs('iframe').returns({ sandbox: {}, }); return fixture.addElement(elem).then(() => { return expect(googleAdUrl(impl, '', 0, {}, [])).to.eventually.match( - /[&?]bc=1[&$]/); + /[&?]bc=1[&$]/ + ); }); }); }); @@ -469,8 +507,10 @@ describe('Google A4A utils', () => { const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); impl.win.SVGElement = undefined; - const createElementStub = - sandbox.stub(impl.win.document, 'createElement'); + const createElementStub = sandbox.stub( + impl.win.document, + 'createElement' + ); createElementStub.withArgs('iframe').returns({ sandbox: { supports: () => false, @@ -478,8 +518,8 @@ describe('Google A4A utils', () => { }); return fixture.addElement(elem).then(() => { return expect( - googleAdUrl(impl, '', 0, {}, [])).to.eventually.not.match( - /[&?]bc=1[&$]/); + googleAdUrl(impl, '', 0, {}, []) + ).to.eventually.not.match(/[&?]bc=1[&$]/); }); }); }); @@ -496,10 +536,13 @@ describe('Google A4A utils', () => { }); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); - sandbox.stub(Services.viewerForDoc(impl.getAmpDoc()), 'getReferrerUrl') - .returns(new Promise(() => {})); - const createElementStub = - sandbox.stub(impl.win.document, 'createElement'); + sandbox + .stub(Services.viewerForDoc(impl.getAmpDoc()), 'getReferrerUrl') + .returns(new Promise(() => {})); + const createElementStub = sandbox.stub( + impl.win.document, + 'createElement' + ); createElementStub.withArgs('iframe').returns({ sandbox: { supports: () => false, @@ -508,8 +551,8 @@ describe('Google A4A utils', () => { expectAsyncConsoleError(/Referrer timeout/, 1); return fixture.addElement(elem).then(() => { return expect( - googleAdUrl(impl, '', 0, {}, [])).to.eventually.not.match( - /[&?]ref=[&$]/); + googleAdUrl(impl, '', 0, {}, []) + ).to.eventually.not.match(/[&?]ref=[&$]/); }); }); }); @@ -538,19 +581,27 @@ describe('Google A4A utils', () => { const elem = createElementWithAttributes(doc, 'amp-a4a', {}); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox, { - top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0, + top: 0, + left: 0, + right: 0, + bottom: 0, + width: 0, + height: 0, }); return fixture.addElement(elem).then(() => googleAdUrl(impl, '', Date.now(), [], []).then(url => { expect(url).to.match(/[&?]adx=0[&$]/); expect(url).to.match(/[&?]adx=0[&$]/); elem.setAttribute( - 'data-experiment-id', `123,${ADX_ADY_EXP.experiment},789`,); + 'data-experiment-id', + `123,${ADX_ADY_EXP.experiment},789` + ); return googleAdUrl(impl, '', Date.now(), [], []).then(url => { expect(url).to.match(/[&?]adx=1[&$]/); expect(url).to.match(/[&?]adx=1[&$]/); }); - })); + }) + ); }); }); }); @@ -560,12 +611,14 @@ describe('Google A4A utils', () => { expect(mergeExperimentIds(['12345'])).to.equal('12345'); }); it('should merge a single ID to a list', () => { - expect(mergeExperimentIds(['12345'], '3,4,5,6')) - .to.equal('3,4,5,6,12345'); + expect(mergeExperimentIds(['12345'], '3,4,5,6')).to.equal( + '3,4,5,6,12345' + ); }); it('should merge multiple IDs into a list', () => { - expect(mergeExperimentIds(['12345','6789'], '3,4,5,6')) - .to.equal('3,4,5,6,12345,6789'); + expect(mergeExperimentIds(['12345', '6789'], '3,4,5,6')).to.equal( + '3,4,5,6,12345,6789' + ); }); it('should discard invalid ID', () => { expect(mergeExperimentIds(['frob'], '3,4,5,6')).to.equal('3,4,5,6'); @@ -585,7 +638,11 @@ describe('Google A4A utils', () => { }); it('should not append parameter if truncated', () => { const truncUrl = buildUrl( - 'https://foo.com/bar', {hello: 'world'}, 15, TRUNCATION_PARAM); + 'https://foo.com/bar', + {hello: 'world'}, + 15, + TRUNCATION_PARAM + ); expect(truncUrl.indexOf(TRUNCATION_PARAM.name)).to.not.equal(-1); expect(maybeAppendErrorParameter(truncUrl, 'n')).to.not.be.ok; }); @@ -593,8 +650,10 @@ describe('Google A4A utils', () => { describes.realWin('#getEnclosingContainerTypes', {}, env => { it('should return empty if no containers', () => { - expect(getEnclosingContainerTypes( - env.win.document.createElement('amp-ad')).length).to.equal(0); + expect( + getEnclosingContainerTypes(env.win.document.createElement('amp-ad')) + .length + ).to.equal(0); }); Object.keys(ValidAdContainerTypes).forEach(container => { @@ -603,8 +662,9 @@ describe('Google A4A utils', () => { env.win.document.body.appendChild(containerElem); const ampAdElem = env.win.document.createElement('amp-ad'); containerElem.appendChild(ampAdElem); - expect(getEnclosingContainerTypes(ampAdElem)) - .to.deep.equal([ValidAdContainerTypes[container]]); + expect(getEnclosingContainerTypes(ampAdElem)).to.deep.equal([ + ValidAdContainerTypes[container], + ]); }); }); @@ -617,12 +677,14 @@ describe('Google A4A utils', () => { }); const ampAdElem = env.win.document.createElement('amp-ad'); prevContainer.appendChild(ampAdElem); - const ValidAdContainerTypeValues = - Object.keys(ValidAdContainerTypes).map(function(key) { - return ValidAdContainerTypes[key]; - }); - expect(getEnclosingContainerTypes(ampAdElem).sort()) - .to.deep.equal(ValidAdContainerTypeValues.sort()); + const ValidAdContainerTypeValues = Object.keys(ValidAdContainerTypes).map( + function(key) { + return ValidAdContainerTypes[key]; + } + ); + expect(getEnclosingContainerTypes(ampAdElem).sort()).to.deep.equal( + ValidAdContainerTypeValues.sort() + ); }); }); @@ -633,44 +695,49 @@ describe('Google A4A utils', () => { const documentInfoStub = sandbox.stub(Services, 'documentInfoForDoc'); doc = {}; fakeWin = {location: {}}; - documentInfoStub.withArgs(doc) - .returns({canonicalUrl: 'http://f.blah.com?some_site'}); + documentInfoStub + .withArgs(doc) + .returns({canonicalUrl: 'http://f.blah.com?some_site'}); }); it('should use google.com if at top', () => { fakeWin.top = fakeWin; fakeWin.location.ancestorOrigins = ['foo.google.com.eu']; expect(getIdentityTokenRequestUrl(fakeWin, doc)).to.equal( - 'https://adservice.google.com/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.com/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); it('should use google.com if no ancestorOrigins', () => { expect(getIdentityTokenRequestUrl(fakeWin, doc)).to.equal( - 'https://adservice.google.com/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.com/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); it('should use google.com if non-google top', () => { fakeWin.location.ancestorOrigins = ['foo.google2.com']; expect(getIdentityTokenRequestUrl(fakeWin, doc)).to.equal( - 'https://adservice.google.com/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.com/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); it('should use google ancestor origin based top domain', () => { - fakeWin.location.ancestorOrigins = - ['foo.google.eu', 'blah.google.fr']; + fakeWin.location.ancestorOrigins = ['foo.google.eu', 'blah.google.fr']; expect(getIdentityTokenRequestUrl(fakeWin, doc)).to.equal( - 'https://adservice.google.fr/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.fr/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); it('should use supplied domain', () => { fakeWin.location.ancestorOrigins = ['foo.google.fr']; expect(getIdentityTokenRequestUrl(fakeWin, doc, '.google.eu')).to.equal( - 'https://adservice.google.eu/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.eu/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); }); @@ -678,8 +745,9 @@ describe('Google A4A utils', () => { beforeEach(() => { installXhrService(env.win); const documentInfoStub = sandbox.stub(Services, 'documentInfoForDoc'); - documentInfoStub.withArgs(env.ampdoc) - .returns({canonicalUrl: 'http://f.blah.com?some_site'}); + documentInfoStub + .withArgs(env.ampdoc) + .returns({canonicalUrl: 'http://f.blah.com?some_site'}); }); afterEach(() => { @@ -688,9 +756,11 @@ describe('Google A4A utils', () => { }); const getUrl = domain => { - domain = domain || 'google\.com'; - return `https:\/\/adservice\.${domain}\/adsid\/integrator\.json\?` + - 'domain=f\.blah\.com'; + domain = domain || 'google.com'; + return ( + `https:\/\/adservice\.${domain}\/adsid\/integrator\.json\?` + + 'domain=f.blah.com' + ); }; it('should ignore response if required fields are missing', () => { @@ -706,13 +776,16 @@ describe('Google A4A utils', () => { }); it('should fetch full token as expected', () => { - env.expectFetch(getUrl(), JSON.stringify({ - newToken: 'abc', - '1p_jar': 'some_jar', - pucrd: 'some_pucrd', - freshLifetimeSecs: '1234', - validLifetimeSecs: '5678', - })); + env.expectFetch( + getUrl(), + JSON.stringify({ + newToken: 'abc', + '1p_jar': 'some_jar', + pucrd: 'some_pucrd', + freshLifetimeSecs: '1234', + validLifetimeSecs: '5678', + }) + ); return getIdentityToken(env.win, env.ampdoc).then(result => { expect(result.token).to.equal('abc'); expect(result.jar).to.equal('some_jar'); @@ -725,11 +798,14 @@ describe('Google A4A utils', () => { it('should redirect as expected', () => { env.expectFetch(getUrl(), JSON.stringify({altDomain: '.google.fr'})); - env.expectFetch(getUrl('google\.fr'), JSON.stringify({ - newToken: 'abc', - freshLifetimeSecs: '1234', - validLifetimeSecs: '5678', - })); + env.expectFetch( + getUrl('google.fr'), + JSON.stringify({ + newToken: 'abc', + freshLifetimeSecs: '1234', + validLifetimeSecs: '5678', + }) + ); return getIdentityToken(env.win, env.ampdoc, '').then(result => { expect(result.token).to.equal('abc'); expect(result.jar).to.equal(''); @@ -743,7 +819,9 @@ describe('Google A4A utils', () => { it('should stop after 1 redirect', () => { env.expectFetch(getUrl(), JSON.stringify({altDomain: '.google.fr'})); env.expectFetch( - getUrl('google\.fr'), JSON.stringify({altDomain: '.google.com'})); + getUrl('google.fr'), + JSON.stringify({altDomain: '.google.com'}) + ); return getIdentityToken(env.win, env.ampdoc).then(result => { expect(result.token).to.not.be.ok; expect(result.jar).to.not.be.ok; @@ -759,49 +837,61 @@ describe('Google A4A utils', () => { validLifetimeSecs: '5678', }; env.win['goog_identity_prom'] = Promise.resolve(ident); - return getIdentityToken(env.win, env.ampdoc) - .then(result => expect(result).to.jsonEqual(ident)); + return getIdentityToken(env.win, env.ampdoc).then(result => + expect(result).to.jsonEqual(ident) + ); }); it('should handle fetch error', () => { - sandbox.stub(Services, 'xhrFor').returns( - {fetchJson: () => Promise.reject('some network failure')}); - return getIdentityToken(env.win, env.ampdoc) - .then(result => expect(result).to.jsonEqual({})); + sandbox + .stub(Services, 'xhrFor') + .returns({fetchJson: () => Promise.reject('some network failure')}); + return getIdentityToken(env.win, env.ampdoc).then(result => + expect(result).to.jsonEqual({}) + ); }); it('should fetch if SUFFICIENT consent', () => { - env.expectFetch(getUrl(), JSON.stringify({ - newToken: 'abc', - '1p_jar': 'some_jar', - pucrd: 'some_pucrd', - freshLifetimeSecs: '1234', - validLifetimeSecs: '5678', - })); + env.expectFetch( + getUrl(), + JSON.stringify({ + newToken: 'abc', + '1p_jar': 'some_jar', + pucrd: 'some_pucrd', + freshLifetimeSecs: '1234', + validLifetimeSecs: '5678', + }) + ); sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( - Promise.resolve({ - whenPolicyResolved: () => CONSENT_POLICY_STATE.SUFFICIENT, - })); - return getIdentityToken(env.win, env.ampdoc, 'default').then( - result => expect(result.token).to.equal('abc')); + Promise.resolve({ + whenPolicyResolved: () => CONSENT_POLICY_STATE.SUFFICIENT, + }) + ); + return getIdentityToken(env.win, env.ampdoc, 'default').then(result => + expect(result.token).to.equal('abc') + ); }); it('should not fetch if INSUFFICIENT consent', () => { sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( - Promise.resolve({ - whenPolicyResolved: () => CONSENT_POLICY_STATE.INSUFFICIENT, - })); - return expect(getIdentityToken(env.win, env.ampdoc, 'default')) - .to.eventually.jsonEqual({}); + Promise.resolve({ + whenPolicyResolved: () => CONSENT_POLICY_STATE.INSUFFICIENT, + }) + ); + return expect( + getIdentityToken(env.win, env.ampdoc, 'default') + ).to.eventually.jsonEqual({}); }); it('should not fetch if UNKNOWN consent', () => { sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( - Promise.resolve({ - whenPolicyResolved: () => CONSENT_POLICY_STATE.UNKNOWN, - })); - return expect(getIdentityToken(env.win, env.ampdoc, 'default')) - .to.eventually.jsonEqual({}); + Promise.resolve({ + whenPolicyResolved: () => CONSENT_POLICY_STATE.UNKNOWN, + }) + ); + return expect( + getIdentityToken(env.win, env.ampdoc, 'default') + ).to.eventually.jsonEqual({}); }); }); @@ -851,8 +941,7 @@ describe('Google A4A utils', () => { it('should include scheduleTime for ad render start triggers', () => { a4a.element.layoutScheduleTime = 200; - const vars = getCsiAmpAnalyticsVariables( - 'ad-render-start', a4a, null); + const vars = getCsiAmpAnalyticsVariables('ad-render-start', a4a, null); expect(vars['scheduleTime']).to.be.a('number'); expect(vars['scheduleTime']).not.to.equal(0); }); @@ -892,7 +981,8 @@ describe('Google A4A utils', () => { {in: 'hello.com', out: 'hello.com'}, {in: '', out: ''}, ].forEach(test => - it(test.in, () => expect(extractHost(test.in)).to.equal(test.out))); + it(test.in, () => expect(extractHost(test.in)).to.equal(test.out)) + ); }); describes.realWin('#getCorrelator', {}, env => { @@ -946,17 +1036,21 @@ describes.realWin('#groupAmpAdsByType', {amp: true}, env => { } it('should find amp-ad of only given type', () => { - const resources = [createResource({type: 'doubleclick'}), - createResource({type: 'blah'}), createResource({}, 'amp-foo')]; - sandbox.stub(Services.resourcesForDoc(doc), 'getMeasuredResources') - .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); + const resources = [ + createResource({type: 'doubleclick'}), + createResource({type: 'blah'}), + createResource({}, 'amp-foo'), + ]; + sandbox + .stub(Services.resourcesForDoc(doc), 'getMeasuredResources') + .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); return groupAmpAdsByType(win, 'doubleclick', () => 'foo').then(result => { expect(Object.keys(result).length).to.equal(1); expect(result['foo']).to.be.ok; expect(result['foo'].length).to.equal(1); return result['foo'][0].then(baseElement => - expect(baseElement.element.getAttribute('type')) - .to.equal('doubleclick')); + expect(baseElement.element.getAttribute('type')).to.equal('doubleclick') + ); }); }); @@ -967,49 +1061,63 @@ describes.realWin('#groupAmpAdsByType', {amp: true}, env => { // as its owned by amp-sticky-ad. It will locate associated element // and block on whenUpgradedToCustomElement so override createdCallback // to cause it to return immediately. - const ampAdResource = - createResource({type: 'doubleclick'}, 'amp-ad', stickyResource.element); + const ampAdResource = createResource( + {type: 'doubleclick'}, + 'amp-ad', + stickyResource.element + ); ampAdResource.element.createdCallback = true; - sandbox.stub(Services.resourcesForDoc(doc), 'getMeasuredResources') - .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); - return groupAmpAdsByType(win, 'doubleclick', () => 'foo').then( - result => { - expect(Object.keys(result).length).to.equal(1); - expect(result['foo']).to.be.ok; - expect(result['foo'].length).to.equal(1); - return result['foo'][0].then(baseElement => - expect(baseElement.element.getAttribute('type')) - .to.equal('doubleclick')); - }); + sandbox + .stub(Services.resourcesForDoc(doc), 'getMeasuredResources') + .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); + return groupAmpAdsByType(win, 'doubleclick', () => 'foo').then(result => { + expect(Object.keys(result).length).to.equal(1); + expect(result['foo']).to.be.ok; + expect(result['foo'].length).to.equal(1); + return result['foo'][0].then(baseElement => + expect(baseElement.element.getAttribute('type')).to.equal('doubleclick') + ); + }); }); it('should find and group multiple, some in containers', () => { const stickyResource = createResource({}, 'amp-sticky-ad'); - const resources = [stickyResource, createResource({}, 'amp-foo'), + const resources = [ + stickyResource, + createResource({}, 'amp-foo'), createResource({type: 'doubleclick', foo: 'bar'}), - createResource({type: 'doubleclick', foo: 'hello'})]; + createResource({type: 'doubleclick', foo: 'hello'}), + ]; // Do not expect ampAdResource to be returned by getMeasuredResources // as its owned by amp-sticky-ad. It will locate associated element // and block on whenUpgradedToCustomElement so override createdCallback // to cause it to return immediately. - const ampAdResource = createResource({type: 'doubleclick', foo: 'bar'}, - 'amp-ad', stickyResource.element); + const ampAdResource = createResource( + {type: 'doubleclick', foo: 'bar'}, + 'amp-ad', + stickyResource.element + ); ampAdResource.element.createdCallback = true; - sandbox.stub(Services.resourcesForDoc(doc), 'getMeasuredResources') - .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); - return groupAmpAdsByType( - win, 'doubleclick', element => element.getAttribute('foo')).then( - result => { - expect(Object.keys(result).length).to.equal(2); - expect(result['bar']).to.be.ok; - expect(result['bar'].length).to.equal(2); - expect(result['hello']).to.be.ok; - expect(result['hello'].length).to.equal(1); - return Promise.all(result['bar'].concat(result['hello'])).then( - baseElements => baseElements.forEach(baseElement => - expect(baseElement.element.getAttribute('type')) - .to.equal('doubleclick'))); - }); + sandbox + .stub(Services.resourcesForDoc(doc), 'getMeasuredResources') + .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); + return groupAmpAdsByType(win, 'doubleclick', element => + element.getAttribute('foo') + ).then(result => { + expect(Object.keys(result).length).to.equal(2); + expect(result['bar']).to.be.ok; + expect(result['bar'].length).to.equal(2); + expect(result['hello']).to.be.ok; + expect(result['hello'].length).to.equal(1); + return Promise.all(result['bar'].concat(result['hello'])).then( + baseElements => + baseElements.forEach(baseElement => + expect(baseElement.element.getAttribute('type')).to.equal( + 'doubleclick' + ) + ) + ); + }); }); }); @@ -1021,7 +1129,11 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { }); function createResource( - config, layout, tagName = 'amp-ad', parent = doc.body) { + config, + layout, + tagName = 'amp-ad', + parent = doc.body + ) { config['layout'] = layout; const element = createElementWithAttributes(doc, tagName, config); parent.appendChild(element); @@ -1062,7 +1174,7 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { expect(getContainerWidth(win, element)).to.equal(300); }); - it('should return parent\'s fixed width for FILL layout', () => { + it("should return parent's fixed width for FILL layout", () => { const parent = document.createElement('div'); parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); @@ -1071,20 +1183,23 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { expect(getContainerWidth(win, element)).to.equal(300); }); - it('should return the max-width, if present, for FIXED_HEIGHT layout', - () => { - const element = createResource({height: 300}, 'fixed-height'); - element.style.maxWidth = '250px'; - expect(getContainerWidth(win, element)).to.equal(250); - }); + it('should return the max-width, if present, for FIXED_HEIGHT layout', () => { + const element = createResource({height: 300}, 'fixed-height'); + element.style.maxWidth = '250px'; + expect(getContainerWidth(win, element)).to.equal(250); + }); - it('should return parent\'s fixed width for FIXED_HEIGHT layout', () => { + it("should return parent's fixed width for FIXED_HEIGHT layout", () => { const parent = document.createElement('div'); parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); doc.body.appendChild(parent); const element = createResource( - {height: 250}, 'fixed-height', 'amp-ad', parent); + {height: 250}, + 'fixed-height', + 'amp-ad', + parent + ); expect(getContainerWidth(win, element)).to.equal(300); }); @@ -1094,13 +1209,12 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { expect(getContainerWidth(win, element)).to.equal(250); }); - it('should return parent\'s fixed width for FLUID layout', () => { + it("should return parent's fixed width for FLUID layout", () => { const parent = document.createElement('div'); parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); doc.body.appendChild(parent); - const element = createResource( - {height: 250}, 'fluid', 'amp-ad', parent); + const element = createResource({height: 250}, 'fluid', 'amp-ad', parent); expect(getContainerWidth(win, element)).to.equal(300); }); @@ -1110,20 +1224,25 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { expect(getContainerWidth(win, element)).to.equal(250); }); - it('should return parent\'s fixed width for RESPONSIVE layout', () => { + it("should return parent's fixed width for RESPONSIVE layout", () => { const parent = document.createElement('div'); parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); doc.body.appendChild(parent); const element = createResource( - {height: 250, width: 250}, 'responsive', 'amp-ad', parent); + {height: 250, width: 250}, + 'responsive', + 'amp-ad', + parent + ); expect(getContainerWidth(win, element)).to.equal(300); }); it('should return the viewport width for CONTAINER layout', () => { const element = createResource({} /* config */, 'container'); - sandbox.stub(Services.viewportForDoc(element), 'getSize') - .returns({width: 300}); + sandbox + .stub(Services.viewportForDoc(element), 'getSize') + .returns({width: 300}); expect(getContainerWidth(win, element)).to.equal(300); }); @@ -1133,8 +1252,7 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); doc.body.appendChild(parent); - const element = createResource( - {height: 250}, layout, 'amp-ad', parent); + const element = createResource({height: 250}, layout, 'amp-ad', parent); expect(getContainerWidth(win, element, 1)).to.equal(-1); }); }); diff --git a/ads/google/a4a/traffic-experiments.js b/ads/google/a4a/traffic-experiments.js index 8695aaff6349..658e857c4be0 100644 --- a/ads/google/a4a/traffic-experiments.js +++ b/ads/google/a4a/traffic-experiments.js @@ -22,10 +22,7 @@ * impacts on click-throughs. */ -import { - EXPERIMENT_ATTRIBUTE, - mergeExperimentIds, -} from './utils'; +import {EXPERIMENT_ATTRIBUTE, mergeExperimentIds} from './utils'; import { ExperimentInfo, // eslint-disable-line no-unused-vars isExperimentOn, @@ -42,7 +39,6 @@ export let A4aExperimentBranches; /** @type {string} @private */ export const MANUAL_EXPERIMENT_ID = '117152632'; - /** * Experiment IDs used to identify single pass experiments. * @@ -59,7 +55,8 @@ export const SINGLE_PASS_EXPERIMENT_IDS = { * @return {?string} experiment extracted from page url. */ export function extractUrlExperimentId(win, element) { - const expParam = Services.viewerForDoc(element).getParam('exp') || + const expParam = + Services.viewerForDoc(element).getParam('exp') || parseQueryString(win.location.search)['exp']; if (!expParam) { return null; @@ -67,15 +64,20 @@ export function extractUrlExperimentId(win, element) { // Allow for per type experiment control with Doubleclick key set for 'da' // and AdSense using 'aa'. Fallback to 'a4a' if type specific is missing. const expKeys = [ - (element.getAttribute('type') || '').toLowerCase() == 'doubleclick' ? - 'da' : 'aa', + (element.getAttribute('type') || '').toLowerCase() == 'doubleclick' + ? 'da' + : 'aa', 'a4a', ]; let arg; let match; - expKeys.forEach(key => arg = arg || - ((match = new RegExp(`(?:^|,)${key}:(-?\\d+)`).exec(expParam)) && - match[1])); + expKeys.forEach( + key => + (arg = + arg || + ((match = new RegExp(`(?:^|,)${key}:(-?\\d+)`).exec(expParam)) && + match[1])) + ); return arg || null; } @@ -111,7 +113,10 @@ export function parseExperimentIds(idString) { */ export function isInExperiment(element, id) { return parseExperimentIds(element.getAttribute(EXPERIMENT_ATTRIBUTE)).some( - x => { return x === id; }); + x => { + return x === id; + } + ); } /** @@ -157,7 +162,9 @@ export function hasLaunched(win, element) { * @return {boolean} Whether all list elements are valid experiment IDs. */ export function validateExperimentIds(idList) { - return idList.every(id => { return !isNaN(parseInt(id, 10)); }); + return idList.every(id => { + return !isNaN(parseInt(id, 10)); + }); } /** @@ -173,8 +180,10 @@ export function addExperimentIdToElement(experimentId, element) { } const currentEids = element.getAttribute(EXPERIMENT_ATTRIBUTE); if (currentEids && validateExperimentIds(parseExperimentIds(currentEids))) { - element.setAttribute(EXPERIMENT_ATTRIBUTE, - mergeExperimentIds([experimentId], currentEids)); + element.setAttribute( + EXPERIMENT_ATTRIBUTE, + mergeExperimentIds([experimentId], currentEids) + ); } else { element.setAttribute(EXPERIMENT_ATTRIBUTE, experimentId); } diff --git a/ads/google/a4a/utils.js b/ads/google/a4a/utils.js index 88b419fad463..097936308bc6 100644 --- a/ads/google/a4a/utils.js +++ b/ads/google/a4a/utils.js @@ -120,8 +120,12 @@ export const ADX_ADY_EXP = { * @return {number} */ function getNavigationTiming(win, timingEvent) { - return (win['performance'] && win['performance']['timing'] && - win['performance']['timing'][timingEvent]) || 0; + return ( + (win['performance'] && + win['performance']['timing'] && + win['performance']['timing'][timingEvent]) || + 0 + ); } /** @@ -135,8 +139,10 @@ function getNavigationTiming(win, timingEvent) { * pathway. */ export function isGoogleAdsA4AValidEnvironment(win) { - return supportsNativeCrypto(win) && ( - !!isCdnProxy(win) || getMode(win).localDev || getMode(win).test); + return ( + supportsNativeCrypto(win) && + (!!isCdnProxy(win) || getMode(win).localDev || getMode(win).test) + ); } /** @@ -170,8 +176,10 @@ export function isReportingEnabled(ampElement) { if (getMode(ampElement.win).localDev && !getMode(ampElement.win).test) { toggleExperiment(win, 'a4aProfilingRate', true, true); } - return (type == 'doubleclick' || type == 'adsense') && - isExperimentOn(win, 'a4aProfilingRate'); + return ( + (type == 'doubleclick' || type == 'adsense') && + isExperimentOn(win, 'a4aProfilingRate') + ); } /** @@ -219,12 +227,14 @@ export function groupAmpAdsByType(win, type, groupFn) { // TODO(keithwrightbos): what about slots that become measured due to removal // of display none (e.g. user resizes viewport and media selector makes // visible). - const ampAdSelector = - r => r.element./*OK*/querySelector(`amp-ad[type=${type}]`); + const ampAdSelector = r => + r.element./*OK*/ querySelector(`amp-ad[type=${type}]`); const {documentElement} = win.document; - return Services.resourcesForDoc(documentElement).getMeasuredResources(win, - r => { - const isAmpAdType = r.element.tagName == 'AMP-AD' && + return ( + Services.resourcesForDoc(documentElement) + .getMeasuredResources(win, r => { + const isAmpAdType = + r.element.tagName == 'AMP-AD' && r.element.getAttribute('type') == type; if (isAmpAdType) { return true; @@ -236,22 +246,29 @@ export function groupAmpAdsByType(win, type, groupFn) { }) // Need to wait on any contained element resolution followed by build // of child ad. - .then(resources => Promise.all(resources.map( - resource => { + .then(resources => + Promise.all( + resources.map(resource => { if (resource.element.tagName == 'AMP-AD') { return resource.element; } // Must be container element so need to wait for child amp-ad to // be upgraded. return whenUpgradedToCustomElement( - dev().assertElement(ampAdSelector(resource))); - }))) + dev().assertElement(ampAdSelector(resource)) + ); + }) + ) + ) // Group by networkId. - .then(elements => elements.reduce((result, element) => { - const groupId = groupFn(element); - (result[groupId] || (result[groupId] = [])).push(element.getImpl()); - return result; - }, {})); + .then(elements => + elements.reduce((result, element) => { + const groupId = groupFn(element); + (result[groupId] || (result[groupId] = [])).push(element.getImpl()); + return result; + }, {}) + ) + ); } /** @@ -264,63 +281,62 @@ export function googlePageParameters(a4a, startTime) { const ampDoc = a4a.getAmpDoc(); // Do not wait longer than 1 second to retrieve referrer to ensure // viewer integration issues do not cause ad requests to hang indefinitely. - const referrerPromise = Services.timerFor(win).timeoutPromise( - 1000, Services.viewerForDoc(ampDoc).getReferrerUrl()) - .catch(() => { - dev().expectedError('AMP-A4A', 'Referrer timeout!'); - return ''; - }); + const referrerPromise = Services.timerFor(win) + .timeoutPromise(1000, Services.viewerForDoc(ampDoc).getReferrerUrl()) + .catch(() => { + dev().expectedError('AMP-A4A', 'Referrer timeout!'); + return ''; + }); const domLoading = getNavigationTiming(win, 'domLoading'); return Promise.all([ - getOrCreateAdCid(ampDoc, 'AMP_ECID_GOOGLE', '_ga'), referrerPromise]) - .then(promiseResults => { - const clientId = promiseResults[0]; - const referrer = promiseResults[1]; - const {pageViewId, canonicalUrl} = Services.documentInfoForDoc(ampDoc); - // Read by GPT for GA/GPT integration. - win.gaGlobal = win.gaGlobal || {cid: clientId, hid: pageViewId}; - const {screen} = win; - const viewport = Services.viewportForDoc(ampDoc); - const viewportRect = viewport.getRect(); - const viewportSize = viewport.getSize(); - const visibilityState = Services.viewerForDoc(ampDoc) - .getVisibilityState(); - return { - 'is_amp': a4a.isXhrAllowed() ? - AmpAdImplementation.AMP_AD_XHR_TO_IFRAME_OR_AMP : - AmpAdImplementation.AMP_AD_IFRAME_GET, - 'amp_v': internalRuntimeVersion(), - 'd_imp': '1', - 'c': getCorrelator(win, ampDoc, clientId), - 'ga_cid': win.gaGlobal.cid || null, - 'ga_hid': win.gaGlobal.hid || null, - 'dt': startTime, - 'biw': viewportRect.width, - 'bih': viewportRect.height, - 'u_aw': screen ? screen.availWidth : null, - 'u_ah': screen ? screen.availHeight : null, - 'u_cd': screen ? screen.colorDepth : null, - 'u_w': screen ? screen.width : null, - 'u_h': screen ? screen.height : null, - 'u_tz': -new Date().getTimezoneOffset(), - 'u_his': getHistoryLength(win), - 'isw': win != win.top ? viewportSize.width : null, - 'ish': win != win.top ? viewportSize.height : null, - 'art': getAmpRuntimeTypeParameter(win), - 'vis': visibilityStateCodes[visibilityState] || '0', - 'scr_x': viewport.getScrollLeft(), - 'scr_y': viewport.getScrollTop(), - 'bc': getBrowserCapabilitiesBitmap(win) || null, - 'debug_experiment_id': - (/(?:#|,)deid=([\d,]+)/i.exec(win.location.hash) || [])[1] || - null, - 'url': canonicalUrl || null, - 'top': win != win.top ? topWindowUrlOrDomain(win) : null, - 'loc': win.location.href == canonicalUrl ? null : win.location.href, - 'ref': referrer || null, - 'bdt': domLoading ? startTime - domLoading : null, - }; - }); + getOrCreateAdCid(ampDoc, 'AMP_ECID_GOOGLE', '_ga'), + referrerPromise, + ]).then(promiseResults => { + const clientId = promiseResults[0]; + const referrer = promiseResults[1]; + const {pageViewId, canonicalUrl} = Services.documentInfoForDoc(ampDoc); + // Read by GPT for GA/GPT integration. + win.gaGlobal = win.gaGlobal || {cid: clientId, hid: pageViewId}; + const {screen} = win; + const viewport = Services.viewportForDoc(ampDoc); + const viewportRect = viewport.getRect(); + const viewportSize = viewport.getSize(); + const visibilityState = Services.viewerForDoc(ampDoc).getVisibilityState(); + return { + 'is_amp': a4a.isXhrAllowed() + ? AmpAdImplementation.AMP_AD_XHR_TO_IFRAME_OR_AMP + : AmpAdImplementation.AMP_AD_IFRAME_GET, + 'amp_v': internalRuntimeVersion(), + 'd_imp': '1', + 'c': getCorrelator(win, ampDoc, clientId), + 'ga_cid': win.gaGlobal.cid || null, + 'ga_hid': win.gaGlobal.hid || null, + 'dt': startTime, + 'biw': viewportRect.width, + 'bih': viewportRect.height, + 'u_aw': screen ? screen.availWidth : null, + 'u_ah': screen ? screen.availHeight : null, + 'u_cd': screen ? screen.colorDepth : null, + 'u_w': screen ? screen.width : null, + 'u_h': screen ? screen.height : null, + 'u_tz': -new Date().getTimezoneOffset(), + 'u_his': getHistoryLength(win), + 'isw': win != win.top ? viewportSize.width : null, + 'ish': win != win.top ? viewportSize.height : null, + 'art': getAmpRuntimeTypeParameter(win), + 'vis': visibilityStateCodes[visibilityState] || '0', + 'scr_x': viewport.getScrollLeft(), + 'scr_y': viewport.getScrollTop(), + 'bc': getBrowserCapabilitiesBitmap(win) || null, + 'debug_experiment_id': + (/(?:#|,)deid=([\d,]+)/i.exec(win.location.hash) || [])[1] || null, + 'url': canonicalUrl || null, + 'top': win != win.top ? topWindowUrlOrDomain(win) : null, + 'loc': win.location.href == canonicalUrl ? null : win.location.href, + 'ref': referrer || null, + 'bdt': domLoading ? startTime - domLoading : null, + }; + }); } /** @@ -334,14 +350,18 @@ export function googlePageParameters(a4a, startTime) { * @return {!Promise} */ export function googleAdUrl( - a4a, baseUrl, startTime, parameters, opt_experimentIds) { + a4a, + baseUrl, + startTime, + parameters, + opt_experimentIds +) { // TODO: Maybe add checks in case these promises fail. const blockLevelParameters = googleBlockParameters(a4a, opt_experimentIds); - return googlePageParameters(a4a, startTime) - .then(pageLevelParameters => { - Object.assign(parameters, blockLevelParameters, pageLevelParameters); - return truncAndTimeUrl(baseUrl, parameters, startTime); - }); + return googlePageParameters(a4a, startTime).then(pageLevelParameters => { + Object.assign(parameters, blockLevelParameters, pageLevelParameters); + return truncAndTimeUrl(baseUrl, parameters, startTime); + }); } /** @@ -351,9 +371,11 @@ export function googleAdUrl( * @return {string} */ export function truncAndTimeUrl(baseUrl, parameters, startTime) { - return buildUrl( - baseUrl, parameters, MAX_URL_LENGTH - 10, TRUNCATION_PARAM) - + '&dtd=' + elapsedTimeWithCeiling(Date.now(), startTime); + return ( + buildUrl(baseUrl, parameters, MAX_URL_LENGTH - 10, TRUNCATION_PARAM) + + '&dtd=' + + elapsedTimeWithCeiling(Date.now(), startTime) + ); } /** @@ -406,9 +428,11 @@ function topWindowUrlOrDomain(win) { return win.top.location.hostname; } const secondFromTop = secondWindowFromTop(win); - if (secondFromTop == win || - origin == ancestorOrigins[ancestorOrigins.length - 2]) { - return extractHost(secondFromTop./*OK*/document.referrer); + if ( + secondFromTop == win || + origin == ancestorOrigins[ancestorOrigins.length - 2] + ) { + return extractHost(secondFromTop./*OK*/ document.referrer); } return extractHost(topOrigin); } else { @@ -417,7 +441,7 @@ function topWindowUrlOrDomain(win) { } catch (e) {} const secondFromTop = secondWindowFromTop(win); try { - return extractHost(secondFromTop./*OK*/document.referrer); + return extractHost(secondFromTop./*OK*/ document.referrer); } catch (e) {} return null; } @@ -430,8 +454,7 @@ function topWindowUrlOrDomain(win) { function secondWindowFromTop(win) { let secondFromTop = win; let depth = 0; - while (secondFromTop.parent != secondFromTop.parent.parent && - depth < 100) { + while (secondFromTop.parent != secondFromTop.parent.parent && depth < 100) { secondFromTop = secondFromTop.parent; depth++; } @@ -463,10 +486,12 @@ function elapsedTimeWithCeiling(time, start) { */ export function getCorrelator(win, elementOrAmpDoc, opt_cid) { if (!win.ampAdPageCorrelator) { - win.ampAdPageCorrelator = isExperimentOn(win, 'exp-new-correlator') ? - Math.floor(4503599627370496 * Math.random()) : - makeCorrelator( - Services.documentInfoForDoc(elementOrAmpDoc).pageViewId, opt_cid); + win.ampAdPageCorrelator = isExperimentOn(win, 'exp-new-correlator') + ? Math.floor(4503599627370496 * Math.random()) + : makeCorrelator( + Services.documentInfoForDoc(elementOrAmpDoc).pageViewId, + opt_cid + ); } return win.ampAdPageCorrelator; } @@ -479,7 +504,7 @@ export function getCorrelator(win, elementOrAmpDoc, opt_cid) { function makeCorrelator(pageViewId, opt_clientId) { const pageViewIdNumeric = Number(pageViewId || 0); if (opt_clientId) { - return pageViewIdNumeric + ((opt_clientId.replace(/\D/g, '') % 1e6) * 1e6); + return pageViewIdNumeric + (opt_clientId.replace(/\D/g, '') % 1e6) * 1e6; } else { // In this case, pageViewIdNumeric is only 4 digits => too low entropy // to be useful as a page correlator. So synthesize one from scratch. @@ -489,7 +514,6 @@ function makeCorrelator(pageViewId, opt_clientId) { } } - /** * Collect additional dimensions for the brdim parameter. * @param {!Window} win The window for which we read the browser dimensions. @@ -512,7 +536,8 @@ export function additionalDimensions(win, viewportSize) { innerWidth = viewportSize.width; innerHeight = viewportSize.height; } catch (e) {} - return [win.screenLeft, + return [ + win.screenLeft, win.screenTop, screenX, screenY, @@ -521,7 +546,8 @@ export function additionalDimensions(win, viewportSize) { outerWidth, outerHeight, innerWidth, - innerHeight].join(); + innerHeight, + ].join(); } /** @@ -570,7 +596,7 @@ export function getCsiAmpAnalyticsConfig() { // ast => ad schedule time // ars => ad render start 'met.a4a': - 'ast.${scheduleTime}~ars_lvt.${viewerLastVisibleTime}~ars.${time}', + 'ast.${scheduleTime}~ars_lvt.${viewerLastVisibleTime}~ars.${time}', 'qqid': '${qqid}', }), 'adIframeLoaded': csiTrigger('ad-iframe-loaded', { @@ -631,8 +657,9 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { return null; } try { - const analyticsConfig = - parseJson(responseHeaders.get(AMP_ANALYTICS_HEADER)); + const analyticsConfig = parseJson( + responseHeaders.get(AMP_ANALYTICS_HEADER) + ); devAssert(Array.isArray(analyticsConfig['url'])); const urls = analyticsConfig['url']; if (!urls.length) { @@ -662,12 +689,15 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { } // Security review needed here. config['requests'] = requests; - config['triggers']['continuousVisible']['request'] = - Object.keys(requests); + config['triggers']['continuousVisible']['request'] = Object.keys(requests); return config; } catch (err) { - dev().error('AMP-A4A', 'Invalid analytics', err, - responseHeaders.get(AMP_ANALYTICS_HEADER)); + dev().error( + 'AMP-A4A', + 'Invalid analytics', + err, + responseHeaders.get(AMP_ANALYTICS_HEADER) + ); } return null; } @@ -690,8 +720,9 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { export function mergeExperimentIds(newIds, currentIdString) { const newIdString = newIds.filter(newId => Number(newId)).join(','); currentIdString = currentIdString || ''; - return currentIdString + (currentIdString && newIdString ? ',' : '') - + newIdString; + return ( + currentIdString + (currentIdString && newIdString ? ',' : '') + newIdString + ); } /** @@ -706,22 +737,31 @@ export function mergeExperimentIds(newIds, currentIdString) { * @return {?JsonObject} config or null if invalid/missing. */ export function addCsiSignalsToAmpAnalyticsConfig( - win, element, config, qqid, isVerifiedAmpCreative) { + win, + element, + config, + qqid, + isVerifiedAmpCreative +) { // Add CSI pingbacks. const correlator = getCorrelator(win, element); const slotId = Number(element.getAttribute('data-amp-slot-index')); - const eids = encodeURIComponent( - element.getAttribute(EXPERIMENT_ATTRIBUTE)); + const eids = encodeURIComponent(element.getAttribute(EXPERIMENT_ATTRIBUTE)); const adType = element.getAttribute('type'); - const initTime = - Number(getTimingDataSync(win, 'navigationStart') || Date.now()); - const deltaTime = Math.round(win.performance && win.performance.now ? - win.performance.now() : (Date.now() - initTime)); - const baseCsiUrl = 'https://csi.gstatic.com/csi?s=a4a' + - `&c=${correlator}&slotId=${slotId}&qqid.${slotId}=${qqid}` + - `&dt=${initTime}` + - (eids != 'null' ? `&e.${slotId}=${eids}` : '') + - `&rls=${internalRuntimeVersion()}&adt.${slotId}=${adType}`; + const initTime = Number( + getTimingDataSync(win, 'navigationStart') || Date.now() + ); + const deltaTime = Math.round( + win.performance && win.performance.now + ? win.performance.now() + : Date.now() - initTime + ); + const baseCsiUrl = + 'https://csi.gstatic.com/csi?s=a4a' + + `&c=${correlator}&slotId=${slotId}&qqid.${slotId}=${qqid}` + + `&dt=${initTime}` + + (eids != 'null' ? `&e.${slotId}=${eids}` : '') + + `&rls=${internalRuntimeVersion()}&adt.${slotId}=${adType}`; const isAmpSuffix = isVerifiedAmpCreative ? 'Friendly' : 'CrossDomain'; config['triggers']['continuousVisibleIniLoad'] = { 'on': 'ini-load', @@ -735,14 +775,14 @@ export function addCsiSignalsToAmpAnalyticsConfig( 'selectionMethod': 'closest', 'request': 'renderStartCsi', }; - config['requests']['iniLoadCsi'] = baseCsiUrl + - `&met.a4a.${slotId}=iniLoadCsi${isAmpSuffix}.${deltaTime}`; - config['requests']['renderStartCsi'] = baseCsiUrl + - `&met.a4a.${slotId}=renderStartCsi${isAmpSuffix}.${deltaTime}`; + config['requests']['iniLoadCsi'] = + baseCsiUrl + `&met.a4a.${slotId}=iniLoadCsi${isAmpSuffix}.${deltaTime}`; + config['requests']['renderStartCsi'] = + baseCsiUrl + `&met.a4a.${slotId}=renderStartCsi${isAmpSuffix}.${deltaTime}`; // Add CSI ping for visibility. - config['requests']['visibilityCsi'] = baseCsiUrl + - `&met.a4a.${slotId}=visibilityCsi.${deltaTime}`; + config['requests']['visibilityCsi'] = + baseCsiUrl + `&met.a4a.${slotId}=visibilityCsi.${deltaTime}`; config['triggers']['continuousVisible']['request'].push('visibilityCsi'); return config; } @@ -756,8 +796,11 @@ export function addCsiSignalsToAmpAnalyticsConfig( */ export function getEnclosingContainerTypes(adElement) { const containerTypeSet = {}; - for (let el = adElement.parentElement, counter = 0; - el && counter < 20; el = el.parentElement, counter++) { + for ( + let el = adElement.parentElement, counter = 0; + el && counter < 20; + el = el.parentElement, counter++ + ) { const tagName = el.tagName.toUpperCase(); if (ValidAdContainerTypes[tagName]) { containerTypeSet[ValidAdContainerTypes[tagName]] = true; @@ -779,9 +822,12 @@ export function maybeAppendErrorParameter(adUrl, parameterValue) { // truncated and error parameter is not already present. Note that we assume // that added, error parameter length will be less than truncation parameter // so adding will not cause length to exceed maximum. - if (new RegExp(`[?|&](${encodeURIComponent(TRUNCATION_PARAM.name)}=` + - `${encodeURIComponent(String(TRUNCATION_PARAM.value))}|aet=[^&]*)$`) - .test(adUrl)) { + if ( + new RegExp( + `[?|&](${encodeURIComponent(TRUNCATION_PARAM.name)}=` + + `${encodeURIComponent(String(TRUNCATION_PARAM.value))}|aet=[^&]*)$` + ).test(adUrl) + ) { return; } const modifiedAdUrl = adUrl + `&aet=${parameterValue}`; @@ -795,12 +841,14 @@ export function maybeAppendErrorParameter(adUrl, parameterValue) { * @return {?string} */ export function getBinaryTypeNumericalCode(type) { - return { - 'production': '0', - 'control': '1', - 'canary': '2', - 'rc': '3', - }[type] || null; + return ( + { + 'production': '0', + 'control': '1', + 'canary': '2', + 'rc': '3', + }[type] || null + ); } /** @const {!RegExp} */ @@ -825,16 +873,18 @@ export let IdentityToken; export function getIdentityToken(win, ampDoc, consentPolicyId) { // If configured to use amp-consent, delay request until consent state is // resolved. - win['goog_identity_prom'] = win['goog_identity_prom'] || - (consentPolicyId - ? getConsentPolicyState(ampDoc.getHeadNode(), consentPolicyId) - : Promise.resolve(CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED)) - .then(consentState => - consentState == CONSENT_POLICY_STATE.INSUFFICIENT || - consentState == CONSENT_POLICY_STATE.UNKNOWN ? - /** @type{!IdentityToken} */({}) : - executeIdentityTokenFetch(win, ampDoc)); - return /** @type {!Promise} */(win['goog_identity_prom']); + win['goog_identity_prom'] = + win['goog_identity_prom'] || + (consentPolicyId + ? getConsentPolicyState(ampDoc.getHeadNode(), consentPolicyId) + : Promise.resolve(CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED) + ).then(consentState => + consentState == CONSENT_POLICY_STATE.INSUFFICIENT || + consentState == CONSENT_POLICY_STATE.UNKNOWN + ? /** @type {!IdentityToken} */ ({}) + : executeIdentityTokenFetch(win, ampDoc) + ); + return /** @type {!Promise} */ (win['goog_identity_prom']); } /** @@ -845,42 +895,63 @@ export function getIdentityToken(win, ampDoc, consentPolicyId) { * @param {number=} startTime * @return {!Promise} */ -function executeIdentityTokenFetch(win, ampDoc, redirectsRemaining = 1, - domain = undefined, startTime = Date.now()) { +function executeIdentityTokenFetch( + win, + ampDoc, + redirectsRemaining = 1, + domain = undefined, + startTime = Date.now() +) { const url = getIdentityTokenRequestUrl(win, ampDoc, domain); - return Services.xhrFor(win).fetchJson(url, { - mode: 'cors', - method: 'GET', - ampCors: false, - credentials: 'include', - }).then(res => res.json()) - .then(obj => { - const token = obj['newToken']; - const jar = obj['1p_jar'] || ''; - const pucrd = obj['pucrd'] || ''; - const freshLifetimeSecs = parseInt(obj['freshLifetimeSecs'] || '', 10); - const validLifetimeSecs = parseInt(obj['validLifetimeSecs'] || '', 10); - const altDomain = obj['altDomain']; - const fetchTimeMs = Date.now() - startTime; - if (IDENTITY_DOMAIN_REGEXP_.test(altDomain)) { - if (!redirectsRemaining--) { - // Max redirects, log? - return {fetchTimeMs}; - } - return executeIdentityTokenFetch( - win, ampDoc, redirectsRemaining, altDomain, startTime); - } else if (freshLifetimeSecs > 0 && validLifetimeSecs > 0 && - typeof token == 'string') { - return {token, jar, pucrd, freshLifetimeSecs, validLifetimeSecs, - fetchTimeMs}; + return Services.xhrFor(win) + .fetchJson(url, { + mode: 'cors', + method: 'GET', + ampCors: false, + credentials: 'include', + }) + .then(res => res.json()) + .then(obj => { + const token = obj['newToken']; + const jar = obj['1p_jar'] || ''; + const pucrd = obj['pucrd'] || ''; + const freshLifetimeSecs = parseInt(obj['freshLifetimeSecs'] || '', 10); + const validLifetimeSecs = parseInt(obj['validLifetimeSecs'] || '', 10); + const altDomain = obj['altDomain']; + const fetchTimeMs = Date.now() - startTime; + if (IDENTITY_DOMAIN_REGEXP_.test(altDomain)) { + if (!redirectsRemaining--) { + // Max redirects, log? + return {fetchTimeMs}; } - // returning empty - return {fetchTimeMs}; - }) - .catch(unusedErr => { - // TODO log? - return {}; - }); + return executeIdentityTokenFetch( + win, + ampDoc, + redirectsRemaining, + altDomain, + startTime + ); + } else if ( + freshLifetimeSecs > 0 && + validLifetimeSecs > 0 && + typeof token == 'string' + ) { + return { + token, + jar, + pucrd, + freshLifetimeSecs, + validLifetimeSecs, + fetchTimeMs, + }; + } + // returning empty + return {fetchTimeMs}; + }) + .catch(unusedErr => { + // TODO log? + return {}; + }); } /** @@ -893,12 +964,14 @@ function executeIdentityTokenFetch(win, ampDoc, redirectsRemaining = 1, export function getIdentityTokenRequestUrl(win, ampDoc, domain = undefined) { if (!domain && win != win.top && win.location.ancestorOrigins) { const matches = IDENTITY_DOMAIN_REGEXP_.exec( - win.location.ancestorOrigins[win.location.ancestorOrigins.length - 1]); + win.location.ancestorOrigins[win.location.ancestorOrigins.length - 1] + ); domain = (matches && matches[0]) || undefined; } domain = domain || '.google.com'; - const canonical = - extractHost(Services.documentInfoForDoc(ampDoc).canonicalUrl); + const canonical = extractHost( + Services.documentInfoForDoc(ampDoc).canonicalUrl + ); return `https://adservice${domain}/adsid/integrator.json?domain=${canonical}`; } diff --git a/ads/google/adsense.js b/ads/google/adsense.js index f41569d5b921..8d69b59fb9de 100644 --- a/ads/google/adsense.js +++ b/ads/google/adsense.js @@ -33,21 +33,43 @@ import {validateData} from '../../3p/3p'; */ export function adsense(global, data) { // TODO: check mandatory fields - validateData(data, [], - ['adClient', 'adSlot', 'adHost', 'adtest', 'tagOrigin', 'experimentId', - 'ampSlotIndex', 'adChannel', 'autoFormat', 'fullWidth', 'package', - 'npaOnUnknownConsent', 'matchedContentUiType', 'matchedContentRowsNum', - 'matchedContentColumnsNum']); + validateData( + data, + [], + [ + 'adClient', + 'adSlot', + 'adHost', + 'adtest', + 'tagOrigin', + 'experimentId', + 'ampSlotIndex', + 'adChannel', + 'autoFormat', + 'fullWidth', + 'package', + 'npaOnUnknownConsent', + 'matchedContentUiType', + 'matchedContentRowsNum', + 'matchedContentColumnsNum', + ] + ); - if (data['autoFormat'] == ADSENSE_RSPV_TAG || - data['autoFormat'] == ADSENSE_MCRSPV_TAG) { - userAssert(hasOwn(data, 'fullWidth'), - 'Responsive AdSense ad units require the attribute data-full-width.'); + if ( + data['autoFormat'] == ADSENSE_RSPV_TAG || + data['autoFormat'] == ADSENSE_MCRSPV_TAG + ) { + userAssert( + hasOwn(data, 'fullWidth'), + 'Responsive AdSense ad units require the attribute data-full-width.' + ); - userAssert(data['height'] == ADSENSE_RSPV_WHITELISTED_HEIGHT, - `Specified height ${data['height']} in tag is not equal to ` + - `the required height of ${ADSENSE_RSPV_WHITELISTED_HEIGHT} for ` + - 'responsive AdSense ad units.'); + userAssert( + data['height'] == ADSENSE_RSPV_WHITELISTED_HEIGHT, + `Specified height ${data['height']} in tag is not equal to ` + + `the required height of ${ADSENSE_RSPV_WHITELISTED_HEIGHT} for ` + + 'responsive AdSense ad units.' + ); } if (global.context.clientId) { @@ -62,14 +84,22 @@ export function adsense(global, data) { global.document.body.appendChild(s); const i = global.document.createElement('ins'); - ['adChannel', 'adClient', 'adSlot', 'adHost', 'adtest', 'tagOrigin', - 'package', 'matchedContentUiType', 'matchedContentRowsNum', - 'matchedContentColumnsNum'] - .forEach(datum => { - if (data[datum]) { - i.setAttribute('data-' + camelCaseToDash(datum), data[datum]); - } - }); + [ + 'adChannel', + 'adClient', + 'adSlot', + 'adHost', + 'adtest', + 'tagOrigin', + 'package', + 'matchedContentUiType', + 'matchedContentRowsNum', + 'matchedContentColumnsNum', + ].forEach(datum => { + if (data[datum]) { + i.setAttribute('data-' + camelCaseToDash(datum), data[datum]); + } + }); i.setAttribute('data-page-url', global.context.canonicalUrl); i.setAttribute('class', 'adsbygoogle'); setStyles(i, { @@ -85,8 +115,9 @@ export function adsense(global, data) { return; } case CONSENT_POLICY_STATE.INSUFFICIENT: - (global.adsbygoogle = global.adsbygoogle || []) - ['requestNonPersonalizedAds'] = true; + (global.adsbygoogle = global.adsbygoogle || [])[ + 'requestNonPersonalizedAds' + ] = true; break; } if (data['experimentId']) { diff --git a/ads/google/csa.js b/ads/google/csa.js index f1ed923418be..0eea294220f3 100644 --- a/ads/google/csa.js +++ b/ads/google/csa.js @@ -39,7 +39,6 @@ export const AD_TYPE = { AFSH_BACKFILL: 3, }; - /** * Request Custom Search Ads (Adsense for Search or AdSense for Shopping). * @param {!Window} global The window object of the iframe @@ -47,15 +46,19 @@ export const AD_TYPE = { */ export function csa(global, data) { // Get parent width in case we want to override - const width = global.document.body./*OK*/clientWidth; + const width = global.document.body./*OK*/ clientWidth; - validateData(data, [], [ - 'afshPageOptions', - 'afshAdblockOptions', - 'afsPageOptions', - 'afsAdblockOptions', - 'ampSlotIndex', - ]); + validateData( + data, + [], + [ + 'afshPageOptions', + 'afshAdblockOptions', + 'afsPageOptions', + 'afsAdblockOptions', + 'ampSlotIndex', + ] + ); // Add the ad container to the document const containerDiv = global.document.createElement('div'); @@ -68,13 +71,21 @@ export function csa(global, data) { // Parse all the options const afshPage = Object.assign( - Object(tryParseJson(data['afshPageOptions'])), pageOptions); + Object(tryParseJson(data['afshPageOptions'])), + pageOptions + ); const afsPage = Object.assign( - Object(tryParseJson(data['afsPageOptions'])), pageOptions); + Object(tryParseJson(data['afsPageOptions'])), + pageOptions + ); const afshAd = Object.assign( - Object(tryParseJson(data['afshAdblockOptions'])), adblockOptions); + Object(tryParseJson(data['afshAdblockOptions'])), + adblockOptions + ); const afsAd = Object.assign( - Object(tryParseJson(data['afsAdblockOptions'])), adblockOptions); + Object(tryParseJson(data['afsAdblockOptions'])), + adblockOptions + ); // Special case for AFSh when "auto" is the requested width if (afshAd['width'] == 'auto') { @@ -82,18 +93,25 @@ export function csa(global, data) { } // Event listener needed for iOS9 bug - global.addEventListener('orientationchange', - orientationChangeHandler.bind(null, global, containerDiv)); + global.addEventListener( + 'orientationchange', + orientationChangeHandler.bind(null, global, containerDiv) + ); // Register resize callbacks global.context.onResizeSuccess( - resizeSuccessHandler.bind(null, global, containerDiv)); + resizeSuccessHandler.bind(null, global, containerDiv) + ); global.context.onResizeDenied( - resizeDeniedHandler.bind(null, global, containerDiv)); + resizeDeniedHandler.bind(null, global, containerDiv) + ); // Only call for ads once the script has loaded - loadScript(global, 'https://www.google.com/adsense/search/ads.js', - requestCsaAds.bind(null, global, data, afsPage, afsAd, afshPage, afshAd)); + loadScript( + global, + 'https://www.google.com/adsense/search/ads.js', + requestCsaAds.bind(null, global, data, afsPage, afsAd, afshPage, afshAd) + ); } /** @@ -109,7 +127,7 @@ function orientationChangeHandler(global, containerDiv) { global.setTimeout(() => { // Force DOM reflow and repaint. // eslint-disable-next-line no-unused-vars - const ignore = global.document.body./*OK*/offsetHeight; + const ignore = global.document.body./*OK*/ offsetHeight; // Capture new height. let newHeight = getStyle(containerDiv, 'height'); // In older versions of iOS, this height will be different because the @@ -122,8 +140,8 @@ function orientationChangeHandler(global, containerDiv) { // Also update the onclick function to resize to the right height. const overflow = global.document.getElementById('overflow'); if (overflow) { - overflow.onclick = - () => global.context.requestResize(undefined, newHeight); + overflow.onclick = () => + global.context.requestResize(undefined, newHeight); } // Resize the container to the correct height. global.context.requestResize(undefined, newHeight); @@ -218,8 +236,7 @@ function getAdType(data) { } if (data['afsPageOptions'] != null && data['afshPageOptions'] != null) { return AD_TYPE.AFSH_BACKFILL; - } - else { + } else { return AD_TYPE.UNSUPPORTED; } } @@ -249,7 +266,7 @@ export function callbackWithNoBackfill(global, containerName, hasAd) { * @param {string} containerName The name of the CSA container * @param {boolean} hasAd Whether or not CSA returned an ad * @visibleForTesting -*/ + */ export function callbackWithBackfill(global, page, ad, containerName, hasAd) { if (hasAd) { resizeIframe(global, containerName); @@ -268,10 +285,10 @@ export function callbackWithBackfill(global, page, ad, containerName, hasAd) { export function resizeIframe(global, containerName) { // Get actual height of container const container = global.document.getElementById(containerName); - const height = container./*OK*/offsetHeight; + const height = container./*OK*/ offsetHeight; // Set initial AMP height currentAmpHeight = - global.context.initialIntersection.boundingClientRect.height; + global.context.initialIntersection.boundingClientRect.height; // If the height of the container is larger than the height of the // initially requested AMP tag, add the overflow element @@ -336,10 +353,11 @@ function getOverflowLine(global) { * @return {!Element} */ function getOverflowChevron(global) { - const svg = '' + - ' '; + const svg = + '' + + ' '; const chevron = global.document.createElement('div'); setStyles(chevron, { @@ -349,7 +367,7 @@ function getOverflowChevron(global) { marginRight: 'auto', display: 'block', }); - chevron./*OK*/innerHTML = svg; + chevron./*OK*/ innerHTML = svg; return chevron; } diff --git a/ads/google/doubleclick.js b/ads/google/doubleclick.js index da1191d910d7..17bf71a20c2e 100644 --- a/ads/google/doubleclick.js +++ b/ads/google/doubleclick.js @@ -21,8 +21,10 @@ const TAG = 'DOUBLECLICK - DEPRECATED'; * @param {!Object} opt_data */ export function doubleclick(opt_global, opt_data) { - dev().error(TAG, 'The use of doubleclick.js has been deprecated. Please ' + - 'switch to Fast Fetch. See documentation here: ' + - 'https://github.com/ampproject/amphtml/issues/11834'); - + dev().error( + TAG, + 'The use of doubleclick.js has been deprecated. Please ' + + 'switch to Fast Fetch. See documentation here: ' + + 'https://github.com/ampproject/amphtml/issues/11834' + ); } diff --git a/ads/google/ima-player-data.js b/ads/google/ima-player-data.js index e0064557036a..64e884740a66 100644 --- a/ads/google/ima-player-data.js +++ b/ads/google/ima-player-data.js @@ -15,7 +15,6 @@ */ export class ImaPlayerData { - /** * Create a new ImaPlayerData object. */ diff --git a/ads/google/imaVideo.js b/ads/google/imaVideo.js index 167ef1d5cfa7..c2a0883e9edf 100644 --- a/ads/google/imaVideo.js +++ b/ads/google/imaVideo.js @@ -23,7 +23,6 @@ import {loadScript} from '../../3p/3p'; import {throttle} from '../../src/utils/rate-limit'; import {tryParseJson} from '../../src/json'; - /** * Possible player states. * @enum {number} @@ -227,9 +226,8 @@ let showControlsThrottled = throttle(window, showControls, 1000); * @param {!Object} data */ export function imaVideo(global, data) { - - videoWidth = global./*OK*/innerWidth; - videoHeight = global./*OK*/innerHeight; + videoWidth = global./*OK*/ innerWidth; + videoHeight = global./*OK*/ innerHeight; adLabel = data.adLabel || 'Ad (%s of %s)'; // Wraps *everything*. @@ -432,7 +430,9 @@ export function imaVideo(global, data) { } videoPlayer.setAttribute('playsinline', true); videoPlayer.setAttribute( - 'controlsList', 'nodownload nofullscreen noremoteplayback'); + 'controlsList', + 'nodownload nofullscreen noremoteplayback' + ); if (data.src) { const sourceElement = document.createElement('source'); sourceElement.setAttribute('src', data.src); @@ -469,9 +469,11 @@ export function imaVideo(global, data) { mouseDownEvent = 'mousedown'; mouseMoveEvent = 'mousemove'; mouseUpEvent = 'mouseup'; - if (navigator.userAgent.match(/iPhone/i) || - navigator.userAgent.match(/iPad/i) || - navigator.userAgent.match(/Android/i)) { + if ( + navigator.userAgent.match(/iPhone/i) || + navigator.userAgent.match(/iPad/i) || + navigator.userAgent.match(/Android/i) + ) { mobileBrowser = true; interactEvent = 'touchend'; mouseDownEvent = 'touchstart'; @@ -483,18 +485,22 @@ export function imaVideo(global, data) { bigPlayDiv.addEventListener(mouseMoveEvent, onBigPlayTouchMove); bigPlayDiv.addEventListener(mouseUpEvent, onBigPlayTouchEnd); bigPlayDiv.addEventListener( - 'tapwithoutdrag', - onBigPlayClick.bind(null, global)); + 'tapwithoutdrag', + onBigPlayClick.bind(null, global) + ); } else { bigPlayDiv.addEventListener( - interactEvent, - onBigPlayClick.bind(null, global)); + interactEvent, + onBigPlayClick.bind(null, global) + ); } playPauseDiv.addEventListener(interactEvent, onPlayPauseClick); progressBarWrapperDiv.addEventListener(mouseDownEvent, onProgressClick); muteUnmuteDiv.addEventListener(interactEvent, onMuteUnmuteClick); - fullscreenDiv.addEventListener(interactEvent, - toggleFullscreen.bind(null, global)); + fullscreenDiv.addEventListener( + interactEvent, + toggleFullscreen.bind(null, global) + ); // Timeout is 1s, because showControls will hide after 3s showControlsThrottled = throttle(window, showControls, 1000); @@ -502,25 +508,31 @@ export function imaVideo(global, data) { const fullScreenEvents = [ 'fullscreenchange', 'mozfullscreenchange', - 'webkitfullscreenchange']; + 'webkitfullscreenchange', + ]; fullScreenEvents.forEach(fsEvent => { - global.document.addEventListener(fsEvent, - onFullscreenChange.bind(null, global), - false); + global.document.addEventListener( + fsEvent, + onFullscreenChange.bind(null, global), + false + ); }); consentState = global.context.initialConsentState; - if (consentState == 4) { // UNKNOWN + if (consentState == 4) { + // UNKNOWN // On unknown consent state, do not load IMA. Treat this the same as if IMA // failed to load. onImaLoadFail(); } else { // Set-up code that can't run until the IMA lib loads. loadScript( - /** @type {!Window} */ (global), - 'https://imasdk.googleapis.com/js/sdkloader/ima3.js', - () => onImaLoadSuccess(global, data), onImaLoadFail); + /** @type {!Window} */ (global), + 'https://imasdk.googleapis.com/js/sdkloader/ima3.js', + () => onImaLoadSuccess(global, data), + onImaLoadFail + ); } } @@ -571,8 +583,10 @@ function onImaLoadSuccess(global, data) { } } - adDisplayContainer = - new global.google.ima.AdDisplayContainer(adContainerDiv, videoPlayer); + adDisplayContainer = new global.google.ima.AdDisplayContainer( + adContainerDiv, + videoPlayer + ); adsLoader = new global.google.ima.AdsLoader(adDisplayContainer); adsLoader.getSettings().setPlayerType('amp-ima'); @@ -582,8 +596,12 @@ function onImaLoadSuccess(global, data) { // an AdDisplayContainer. // playerType and playerVersion are used by the developers to track usage, // so we do not want to allow users to overwrite those values. - const skippedSettings = - ['locale', 'vpaidMode', 'playerType', 'playerVersion']; + const skippedSettings = [ + 'locale', + 'vpaidMode', + 'playerType', + 'playerVersion', + ]; for (const setting in imaSettings) { if (!skippedSettings.includes(setting)) { // Change e.g. 'ppid' to 'setPpid'. @@ -594,13 +612,15 @@ function onImaLoadSuccess(global, data) { } } adsLoader.addEventListener( - global.google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, - onAdsManagerLoaded.bind(null, global), - false); + global.google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, + onAdsManagerLoaded.bind(null, global), + false + ); adsLoader.addEventListener( - global.google.ima.AdErrorEvent.Type.AD_ERROR, - onAdsLoaderError, - false); + global.google.ima.AdErrorEvent.Type.AD_ERROR, + onAdsLoaderError, + false + ); videoPlayer.addEventListener('ended', onContentEnded); @@ -625,7 +645,10 @@ function onImaLoadSuccess(global, data) { function onImaLoadFail() { // Something blocked ima3.js from loading - ignore all IMA stuff and just play // content. - addHoverEventToElement(/** @type !Element */(videoPlayer), showControlsThrottled); + addHoverEventToElement( + /** @type {!Element} */ (videoPlayer), + showControlsThrottled + ); imaLoadAllowed = false; postMessage({event: VideoEvents.LOAD}); } @@ -636,7 +659,7 @@ function onImaLoadFail() { */ function htmlToElement(html) { const template = document.createElement('template'); - template./*OK*/innerHTML = html; + template./*OK*/ innerHTML = html; return template.content.firstChild; } @@ -654,7 +677,7 @@ function createIcon(global, name, fill = '#FFFFFF') { icon.setAttributeNS(null, 'width', '100%'); icon.setAttributeNS(null, 'viewBox', '0 0 24 24'); setStyle(icon, 'filter', 'drop-shadow(0px 0px 14px rgba(0,0,0,0.4))'); - icon./*OK*/innerHTML = icons[name]; + icon./*OK*/ innerHTML = icons[name]; return icon; } @@ -664,7 +687,7 @@ function createIcon(global, name, fill = '#FFFFFF') { * @param {string} [fill='#FFFFFF'] */ function changeIcon(element, name, fill = '#FFFFFF') { - element./*OK*/innerHTML = icons[name]; + element./*OK*/ innerHTML = icons[name]; if (fill != element.getAttributeNS(null, 'fill')) { element.setAttributeNS(null, 'fill', fill); } @@ -752,7 +775,10 @@ export function playAds(global) { // Ad request resolved. try { adsManager.init( - videoWidth, videoHeight, global.google.ima.ViewMode.NORMAL); + videoWidth, + videoHeight, + global.google.ima.ViewMode.NORMAL + ); adsManager.start(); } catch (adError) { playVideo(); @@ -796,25 +822,31 @@ export function onContentEnded() { export function onAdsManagerLoaded(global, adsManagerLoadedEvent) { const adsRenderingSettings = new global.google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; - adsManager = adsManagerLoadedEvent.getAdsManager(videoPlayer, - adsRenderingSettings); - adsManager.addEventListener(global.google.ima.AdErrorEvent.Type.AD_ERROR, - onAdError); + adsManager = adsManagerLoadedEvent.getAdsManager( + videoPlayer, + adsRenderingSettings + ); adsManager.addEventListener( - global.google.ima.AdEvent.Type.LOADED, - onAdLoad); + global.google.ima.AdErrorEvent.Type.AD_ERROR, + onAdError + ); + adsManager.addEventListener(global.google.ima.AdEvent.Type.LOADED, onAdLoad); adsManager.addEventListener( - global.google.ima.AdEvent.Type.AD_PROGRESS, - onAdProgress); + global.google.ima.AdEvent.Type.AD_PROGRESS, + onAdProgress + ); adsManager.addEventListener( - global.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, - onContentPauseRequested.bind(null, global)); + global.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, + onContentPauseRequested.bind(null, global) + ); adsManager.addEventListener( - global.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, - onContentResumeRequested); + global.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, + onContentResumeRequested + ); adsManager.addEventListener( - global.google.ima.AdEvent.Type.ALL_ADS_COMPLETED, - onAllAdsCompleted); + global.google.ima.AdEvent.Type.ALL_ADS_COMPLETED, + onAllAdsCompleted + ); if (muteAdsManagerOnLoaded) { adsManager.setVolume(0); } @@ -832,7 +864,10 @@ export function onAdsLoaderError() { // failing to load an ad is just as good as loading one as far as starting // playback is concerned because our content will be ready to play. postMessage({event: VideoEvents.LOAD}); - addHoverEventToElement(/** @type !Element */(videoPlayer), showControlsThrottled); + addHoverEventToElement( + /** @type {!Element} */ (videoPlayer), + showControlsThrottled + ); if (playbackStarted) { playVideo(); } @@ -849,7 +884,10 @@ export function onAdError() { if (adsManager) { adsManager.destroy(); } - addHoverEventToElement(/** @type !Element */(videoPlayer), showControlsThrottled); + addHoverEventToElement( + /** @type {!Element} */ (videoPlayer), + showControlsThrottled + ); playVideo(); } @@ -876,8 +914,7 @@ export function onAdProgress(global) { remainingSeconds = '0' + remainingSeconds; } const label = adLabel.replace('%s', adPosition).replace('%s', totalAds); - countdownDiv.textContent - = `${label}: ${remainingMinutes}:${remainingSeconds}`; + countdownDiv.textContent = `${label}: ${remainingMinutes}:${remainingSeconds}`; } /** @@ -888,15 +925,19 @@ export function onAdProgress(global) { export function onContentPauseRequested(global) { if (adsManagerWidthOnLoad) { adsManager.resize( - adsManagerWidthOnLoad, - adsManagerHeightOnLoad, - global.google.ima.ViewMode.NORMAL); + adsManagerWidthOnLoad, + adsManagerHeightOnLoad, + global.google.ima.ViewMode.NORMAL + ); adsManagerWidthOnLoad = null; adsManagerHeightOnLoad = null; } adsActive = true; postMessage({event: VideoEvents.AD_START}); - removeHoverEventFromElement(/** @type !Element */(videoPlayer), showControlsThrottled); + removeHoverEventFromElement( + /** @type {!Element} */ (videoPlayer), + showControlsThrottled + ); setStyle(adContainerDiv, 'display', 'block'); videoPlayer.removeEventListener('ended', onContentEnded); showAdControls(); @@ -910,7 +951,10 @@ export function onContentPauseRequested(global) { */ export function onContentResumeRequested() { adsActive = false; - addHoverEventToElement(/** @type !Element */(videoPlayer), showControlsThrottled); + addHoverEventToElement( + /** @type {!Element} */ (videoPlayer), + showControlsThrottled + ); postMessage({event: VideoEvents.AD_END}); resetControlsAfterAd(); if (!contentComplete) { @@ -965,12 +1009,10 @@ function playerDataTick() { * @visibleForTesting */ export function updateUi(currentTime, duration) { - timeNode.textContent = - formatTime(currentTime) + ' / ' + formatTime(duration); - const progressPercent = - Math.floor((currentTime / duration) * 100); + timeNode.textContent = formatTime(currentTime) + ' / ' + formatTime(duration); + const progressPercent = Math.floor((currentTime / duration) * 100); setStyle(progressLine, 'width', progressPercent + '%'); - setStyle(progressMarkerDiv, 'left', (progressPercent - 1) + '%'); + setStyle(progressMarkerDiv, 'left', progressPercent - 1 + '%'); } /** @@ -994,7 +1036,7 @@ export function formatTime(time) { } else { timeString += minutes + ':'; } - const seconds = Math.floor(time - ((hours * 3600) + (minutes * 60))); + const seconds = Math.floor(time - (hours * 3600 + minutes * 60)); timeString += zeroPad(seconds); return timeString; } @@ -1044,7 +1086,7 @@ function onProgressClickEnd() { function onProgressMove(event) { const progressWrapperPosition = getPagePosition(progressBarWrapperDiv); const progressListStart = progressWrapperPosition.x; - const progressListWidth = progressBarWrapperDiv./*OK*/offsetWidth; + const progressListWidth = progressBarWrapperDiv./*OK*/ offsetWidth; // Handle Android Chrome touch events. const eventX = event.clientX || event.touches[0].pageX; @@ -1065,12 +1107,14 @@ function onProgressMove(event) { */ function getPagePosition(el) { let lx, ly; - for (lx = 0, ly = 0; + for ( + lx = 0, ly = 0; el != null; - lx += el./*OK*/offsetLeft, ly += el./*OK*/offsetTop, - el = el./*OK*/offsetParent) - {} - return {x: lx,y: ly}; + lx += el./*OK*/ offsetLeft, + ly += el./*OK*/ offsetTop, + el = el./*OK*/ offsetParent + ) {} + return {x: lx, y: ly}; } /** @@ -1168,34 +1212,33 @@ export function unmuteVideo() { } } - /** * @param {Object} global */ function exitFullscreen(global) { // The video is currently in fullscreen mode - const cancelFullscreen = global.document.exitFullscreen || - global.document.exitFullScreen || - global.document.webkitCancelFullScreen || - global.document.mozCancelFullScreen; + const cancelFullscreen = + global.document.exitFullscreen || + global.document.exitFullScreen || + global.document.webkitCancelFullScreen || + global.document.mozCancelFullScreen; if (cancelFullscreen) { cancelFullscreen.call(document); } } - /** * @param {Object} global */ function enterFullscreen(global) { // Try to enter fullscreen mode in the browser const requestFullscreen = - global.document.documentElement.requestFullscreen || - global.document.documentElement.webkitRequestFullscreen || - global.document.documentElement.mozRequestFullscreen || - global.document.documentElement.requestFullScreen || - global.document.documentElement.webkitRequestFullScreen || - global.document.documentElement.mozRequestFullScreen; + global.document.documentElement.requestFullscreen || + global.document.documentElement.webkitRequestFullscreen || + global.document.documentElement.mozRequestFullscreen || + global.document.documentElement.requestFullScreen || + global.document.documentElement.webkitRequestFullScreen || + global.document.documentElement.mozRequestFullScreen; if (requestFullscreen) { fullscreenWidth = window.screen.width; fullscreenHeight = window.screen.height; @@ -1212,7 +1255,6 @@ function enterFullscreen(global) { } } - /** * @param {Object} global */ @@ -1224,7 +1266,6 @@ function toggleFullscreen(global) { enterFullscreen(global); } - /** * Called when the fullscreen mode of the browser or content player changes. * @param {Object} global @@ -1234,7 +1275,10 @@ function onFullscreenChange(global) { if (adsManager) { // Resize the ad container adsManager.resize( - videoWidth, videoHeight, global.google.ima.ViewMode.NORMAL); + videoWidth, + videoHeight, + global.google.ima.ViewMode.NORMAL + ); adsManagerWidthOnLoad = null; adsManagerHeightOnLoad = null; } @@ -1248,8 +1292,10 @@ function onFullscreenChange(global) { if (adsManager) { // Resize the ad container adsManager.resize( - fullscreenWidth, fullscreenHeight, - global.google.ima.ViewMode.FULLSCREEN); + fullscreenWidth, + fullscreenHeight, + global.google.ima.ViewMode.FULLSCREEN + ); adsManagerWidthOnLoad = null; adsManagerHeightOnLoad = null; } @@ -1286,9 +1332,12 @@ export function showAdControls() { 'height': miniControls ? '18px' : '22px', }; setStyles(fullscreenDiv, buttonDefaults); - setStyles(muteUnmuteDiv, Object.assign(buttonDefaults, { - 'margin-right': '10px', - })); + setStyles( + muteUnmuteDiv, + Object.assign(buttonDefaults, { + 'margin-right': '10px', + }) + ); // show ad controls setStyle(countdownWrapperDiv, 'display', 'flex'); showControls(); @@ -1310,9 +1359,12 @@ export function resetControlsAfterAd() { }); const buttonDefaults = {'height': '30px'}; setStyles(fullscreenDiv, buttonDefaults); - setStyles(muteUnmuteDiv, Object.assign(buttonDefaults, { - 'margin-right': '20px', - })); + setStyles( + muteUnmuteDiv, + Object.assign(buttonDefaults, { + 'margin-right': '20px', + }) + ); // show non-ad controls const showElement = button => setStyle(button, 'display', 'block'); [playPauseDiv, timeDiv, progressBarWrapperDiv].forEach(showElement); @@ -1416,8 +1468,10 @@ function onMessage(global, event) { }); if (adsActive && !fullscreen) { adsManager.resize( - args.width, args.height, - global.google.ima.ViewMode.NORMAL); + args.width, + args.height, + global.google.ima.ViewMode.NORMAL + ); } else { adsManagerWidthOnLoad = args.width; adsManagerHeightOnLoad = args.height; @@ -1445,15 +1499,13 @@ function onMessage(global, event) { } } - /** * @param {!Object} data */ function postMessage(data) { - window.parent./*OK*/postMessage(data, '*'); + window.parent./*OK*/ postMessage(data, '*'); } - /** * Returns the properties we need to access for testing. * diff --git a/ads/google/test/test-adsense.js b/ads/google/test/test-adsense.js index 402bf0a9aabb..c78bf40a3323 100644 --- a/ads/google/test/test-adsense.js +++ b/ads/google/test/test-adsense.js @@ -17,7 +17,6 @@ import {CONSENT_POLICY_STATE} from '../../../src/consent-state'; import {adsense} from '../adsense'; describes.realWin('adsenseDelayedFetch', {}, env => { - let containerElem, data; const canonicalUrl = 'https://foo.com?some=page'; const clientId = 'some_clientId'; @@ -42,7 +41,8 @@ describes.realWin('adsenseDelayedFetch', {}, env => { env.win.context = {canonicalUrl, clientId, pageViewId}; data = {}; Object.keys(elementAttributes).forEach( - attr => data[attr] = `some-${attr}`); + attr => (data[attr] = `some-${attr}`) + ); data['experimentId'] = '1234,567,890'; }); @@ -55,15 +55,20 @@ describes.realWin('adsenseDelayedFetch', {}, env => { }, }; adsense(env.win, data); - expect(env.win.document.querySelector('script[src=' + - '"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"]')) - .to.be.ok; + expect( + env.win.document.querySelector( + 'script[src=' + + '"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"]' + ) + ).to.be.ok; const insElement = env.win.document.querySelector('ins.adsbygoogle'); expect(insElement).to.be.ok; expect(insElement.getAttribute('data-page-url')).to.equal(canonicalUrl); Object.keys(elementAttributes).forEach(attr => - expect(insElement.getAttribute(elementAttributes[attr])) - .to.equal(`some-${attr}`)); + expect(insElement.getAttribute(elementAttributes[attr])).to.equal( + `some-${attr}` + ) + ); expect(pushArg).to.be.ok; expect(pushArg).to.jsonEqual({ params: { @@ -91,7 +96,8 @@ describes.realWin('adsenseDelayedFetch', {}, env => { data['height'] = '666'; allowConsoleError(() => { expect(() => adsense(env.win, data)).to.throw( - /Specified height 666 in tag is not equal to the required/); + /Specified height 666 in tag is not equal to the required/ + ); }); }); @@ -100,13 +106,12 @@ describes.realWin('adsenseDelayedFetch', {}, env => { data['height'] = '320'; allowConsoleError(() => { expect(() => adsense(env.win, data)).to.throw( - /Responsive AdSense ad units require the attribute data-full-width.​/ + /Responsive AdSense ad units require the attribute data-full-width.​/ ); }); }); describe('amp-consent integration', () => { - let pushCount = 0; beforeEach(() => { pushCount = 0; @@ -175,17 +180,17 @@ describes.realWin('adsenseDelayedFetch', {}, env => { if (result == RESULT_STATE.NO_REQUEST) { expect(insElement).to.not.be.ok; expect(pushCount).to.equal(0); - expect(env.win.adsbygoogle['requestNonPersonalizedAds']) - .to.be.undefined; + expect(env.win.adsbygoogle['requestNonPersonalizedAds']).to.be + .undefined; } else { expect(insElement).to.be.ok; expect(pushCount).to.equal(1); expect(env.win.adsbygoogle).to.be.ok; expect(env.win.adsbygoogle['requestNonPersonalizedAds']).to.equal( - result == RESULT_STATE.NPA ? true : undefined); + result == RESULT_STATE.NPA ? true : undefined + ); } }); } - }); }); diff --git a/ads/google/test/test-utils.js b/ads/google/test/test-utils.js index e8e249388d6b..2818e1ce9d78 100644 --- a/ads/google/test/test-utils.js +++ b/ads/google/test/test-utils.js @@ -35,93 +35,131 @@ describe('#getMultiSizeDimensions', () => { const multiSizeDataStr = '300x300,300x250,250x250,250x200,150x50'; function verifyArray(actual, lower, upper) { - expect(multiSizes.filter((size, index) => index < upper && index >= lower)) - .to.deep.equal(actual); + expect( + multiSizes.filter((size, index) => index < upper && index >= lower) + ).to.deep.equal(actual); } it('should return all sizes', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 300, 300, - /* Ignore lowerbound */ false); + multiSizeDataStr, + 300, + 300, + /* Ignore lowerbound */ false + ); verifyArray(actual, 0, multiSizes.length); }); it('should return a smaller array', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 300, 250, - /* Ignore lowerbound */ false); + multiSizeDataStr, + 300, + 250, + /* Ignore lowerbound */ false + ); verifyArray(actual, 1, multiSizes.length); }); it('should return an even smaller array', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 250, 250, - /* Ignore lowerbound */ false); + multiSizeDataStr, + 250, + 250, + /* Ignore lowerbound */ false + ); verifyArray(actual, 2, multiSizes.length); }); it('should return an empty array', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 100, 50, - /* Ignore lowerbound */ false); + multiSizeDataStr, + 100, + 50, + /* Ignore lowerbound */ false + ); verifyArray(actual, 0, 0); }); it('should return a smaller array due to lowerbound', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 300, 300, - /* Use lowerbound */ true); + multiSizeDataStr, + 300, + 300, + /* Use lowerbound */ true + ); verifyArray(actual, 0, multiSizes.length - 1); }); - it('should return a smaller array due to lowerbound + smaller primary size', - () => { - const actual = getMultiSizeDimensions( - multiSizeDataStr, 300, 250, - /* Use lowerbound */ true); - verifyArray(actual, 1, multiSizes.length - 1); - }); + it('should return a smaller array due to lowerbound + smaller primary size', () => { + const actual = getMultiSizeDimensions( + multiSizeDataStr, + 300, + 250, + /* Use lowerbound */ true + ); + verifyArray(actual, 1, multiSizes.length - 1); + }); it('should return all positive sizes', () => { const actual = getMultiSizeDimensions( - '-1x300,' + multiSizeDataStr, 300, 300, - /* Ignore lowerbound */ false); + '-1x300,' + multiSizeDataStr, + 300, + 300, + /* Ignore lowerbound */ false + ); verifyArray(actual, 0, multiSizes.length); }); it('should add dummy size for fluid', () => { - expect(getMultiSizeDimensions('fluid', 300, 300, /* useLowerBound */ false)) - .to.deep.equal([[320, 50]]); + expect( + getMultiSizeDimensions('fluid', 300, 300, /* useLowerBound */ false) + ).to.deep.equal([[320, 50]]); }); it('should not add dummy size for fluid if fluid is primary size', () => { - expect(getMultiSizeDimensions( - 'fluid', 300, 300, + expect( + getMultiSizeDimensions( + 'fluid', + 300, + 300, /* useLowerBound */ false, - /* isFluidPrimary */ true)) - .to.deep.equal([]); + /* isFluidPrimary */ true + ) + ).to.deep.equal([]); }); it('should allow fluid with fixed sizes', () => { - expect(getMultiSizeDimensions( - 'fluid,300x300', 300, 300, /* useLowerBound */ false)) - .to.deep.equal([[320, 50], [300, 300]]); - expect(getMultiSizeDimensions( - '300x300,fluid', 300, 300, /* useLowerBound */ false)) - .to.deep.equal([[300, 300], [320, 50]]); + expect( + getMultiSizeDimensions( + 'fluid,300x300', + 300, + 300, + /* useLowerBound */ false + ) + ).to.deep.equal([[320, 50], [300, 300]]); + expect( + getMultiSizeDimensions( + '300x300,fluid', + 300, + 300, + /* useLowerBound */ false + ) + ).to.deep.equal([[300, 300], [320, 50]]); }); }); describe('#getMatchedContentResponsiveHeightAndUpdatePubParams', () => { it('should use auto logic when no pub params present', () => { const element = document.createElement('div'); - expect(getMatchedContentResponsiveHeightAndUpdatePubParams(400, element)) - .to.equal(1472); + expect( + getMatchedContentResponsiveHeightAndUpdatePubParams(400, element) + ).to.equal(1472); expect(element.getAttribute(ExternalCorePubVars.ROWS_NUM)).to.equal('12'); expect(element.getAttribute(ExternalCorePubVars.COLUMNS_NUM)).to.equal('1'); - expect(element.getAttribute(ExternalCorePubVars.UI_TYPE)) - .to.equal(LayoutType.MOBILE_BANNER_IMAGE_SIDEBYSIDE); + expect(element.getAttribute(ExternalCorePubVars.UI_TYPE)).to.equal( + LayoutType.MOBILE_BANNER_IMAGE_SIDEBYSIDE + ); }); it('should use pub control logic when pub params present', () => { @@ -129,14 +167,17 @@ describe('#getMatchedContentResponsiveHeightAndUpdatePubParams', () => { element.setAttribute(ExternalCorePubVars.ROWS_NUM, '1,2'); element.setAttribute(ExternalCorePubVars.COLUMNS_NUM, '3,4'); element.setAttribute( - ExternalCorePubVars.UI_TYPE, - `${LayoutType.IMAGE_SIDEBYSIDE},${LayoutType.IMAGE_STACKED}`); + ExternalCorePubVars.UI_TYPE, + `${LayoutType.IMAGE_SIDEBYSIDE},${LayoutType.IMAGE_STACKED}` + ); - expect(getMatchedContentResponsiveHeightAndUpdatePubParams(800, element)) - .to.equal(382); + expect( + getMatchedContentResponsiveHeightAndUpdatePubParams(800, element) + ).to.equal(382); expect(element.getAttribute(ExternalCorePubVars.ROWS_NUM)).to.equal('2'); expect(element.getAttribute(ExternalCorePubVars.COLUMNS_NUM)).to.equal('4'); - expect(element.getAttribute(ExternalCorePubVars.UI_TYPE)) - .to.equal(LayoutType.PUB_CONTROL_IMAGE_STACKED); + expect(element.getAttribute(ExternalCorePubVars.UI_TYPE)).to.equal( + LayoutType.PUB_CONTROL_IMAGE_STACKED + ); }); }); diff --git a/ads/google/utils.js b/ads/google/utils.js index b2d60a3394a4..b113cd90df73 100644 --- a/ads/google/utils.js +++ b/ads/google/utils.js @@ -47,8 +47,9 @@ export const DUMMY_FLUID_SIZE = '320x50'; * Required size to be sent with fluid requests in array format. * @const {!Array} */ -const DUMMY_FLUID_SIZE_ARR = - DUMMY_FLUID_SIZE.split('x').map(dim => Number(dim)); +const DUMMY_FLUID_SIZE_ARR = DUMMY_FLUID_SIZE.split('x').map(dim => + Number(dim) +); /** * Given the amp-ad data attribute containing the multi-size dimensions, and a @@ -71,13 +72,12 @@ export function getMultiSizeDimensions( primaryWidth, primaryHeight, multiSizeValidation, - isFluidPrimary = false) { - + isFluidPrimary = false +) { const dimensions = []; const arrayOfSizeStrs = multiSizeDataStr.split(','); for (let i = 0; i < arrayOfSizeStrs.length; i++) { - const sizeStr = arrayOfSizeStrs[i]; if (sizeStr.toLowerCase() == 'fluid') { if (!isFluidPrimary) { @@ -99,22 +99,43 @@ export function getMultiSizeDimensions( const height = Number(size[1]); // Make sure that both dimensions given are positive numbers. - if (!validateDimensions(width, height, + if ( + !validateDimensions( + width, + height, w => isNaN(w) || w <= 0, h => isNaN(h) || h <= 0, - badParams => badParams.map(badParam => - `Invalid ${badParam.dim} of ${badParam.val} ` + - 'given for secondary size.').join(' '))) { + badParams => + badParams + .map( + badParam => + `Invalid ${badParam.dim} of ${badParam.val} ` + + 'given for secondary size.' + ) + .join(' ') + ) + ) { continue; } // Check that secondary size is not larger than primary size. - if (!isFluidPrimary && !validateDimensions(width, height, + if ( + !isFluidPrimary && + !validateDimensions( + width, + height, w => w > primaryWidth, h => h > primaryHeight, - badParams => badParams.map(badParam => - `Secondary ${badParam.dim} ${badParam.val} ` + - `can't be larger than the primary ${badParam.dim}.`).join(' '))) { + badParams => + badParams + .map( + badParam => + `Secondary ${badParam.dim} ${badParam.val} ` + + `can't be larger than the primary ${badParam.dim}.` + ) + .join(' ') + ) + ) { continue; } @@ -126,13 +147,22 @@ export function getMultiSizeDimensions( const minRatio = 2 / 3; const minWidth = minRatio * primaryWidth; const minHeight = minRatio * primaryHeight; - if (!validateDimensions(width, height, + if ( + !validateDimensions( + width, + height, w => w < minWidth, h => h < minHeight, - badParams => badParams.map(badParam => - `Secondary ${badParam.dim} ${badParam.val} is ` + - `smaller than 2/3rds of the primary ${badParam.dim}.`) - .join(' '))) { + badParams => + badParams + .map( + badParam => + `Secondary ${badParam.dim} ${badParam.val} is ` + + `smaller than 2/3rds of the primary ${badParam.dim}.` + ) + .join(' ') + ) + ) { continue; } } @@ -162,8 +192,13 @@ export function getMultiSizeDimensions( * A function that will produce an informative error message. * @return {boolean} */ -function validateDimensions(width, height, widthCond, heightCond, errorBuilder) -{ +function validateDimensions( + width, + height, + widthCond, + heightCond, + errorBuilder +) { const badParams = []; if (widthCond(width)) { badParams.push({dim: 'width', val: width}); @@ -188,23 +223,29 @@ function validateDimensions(width, height, widthCond, heightCond, errorBuilder) * @return {number} height to use for the matched content slot. */ export function getMatchedContentResponsiveHeightAndUpdatePubParams( - availableWidth, element) { + availableWidth, + element +) { const pubControlParams = { numberOfRows: element.getAttribute(ExternalCorePubVars.ROWS_NUM), numberOfColumns: element.getAttribute(ExternalCorePubVars.COLUMNS_NUM), layoutType: element.getAttribute(ExternalCorePubVars.UI_TYPE), }; let config; - if (pubControlParams.numberOfRows || - pubControlParams.numberOfColumns || - pubControlParams.layoutType) { + if ( + pubControlParams.numberOfRows || + pubControlParams.numberOfColumns || + pubControlParams.layoutType + ) { // Publisher provided at least 1 param which means we are in // "pub controlled matched content" mode. config = getPubControlConfig(availableWidth, pubControlParams); } else { // Publisher didn't provide any matched content params so use auto mode. config = getAutoConfig( - availableWidth, availableWidth <= MIN_PUB_CONTROL_WIDTH_OF_DESKTOP); + availableWidth, + availableWidth <= MIN_PUB_CONTROL_WIDTH_OF_DESKTOP + ); } if (config.validationError) { user().error('AMP-AD', config.validationError); diff --git a/ads/gumgum.js b/ads/gumgum.js index 8f229cd99191..615ac9a2f87d 100644 --- a/ads/gumgum.js +++ b/ads/gumgum.js @@ -24,32 +24,29 @@ import {setStyles} from '../src/style'; export function gumgum(global, data) { validateData(data, ['zone', 'slot']); - const - win = window, - ctx = win.context, - dom = global.document.getElementById('c'), - ampWidth = parseInt(data.width || '0', 10), - ampHeight = parseInt(data.height || '0', 10), - ggevents = global.ggevents || []; + const win = window, + ctx = win.context, + dom = global.document.getElementById('c'), + ampWidth = parseInt(data.width || '0', 10), + ampHeight = parseInt(data.height || '0', 10), + ggevents = global.ggevents || []; - const - {max} = Math, - slotId = parseInt(data.slot, 10), - onLoad = function(type) { - return function(evt) { - const - ad = Object.assign({width: 0, height: 0}, evt.ad || {}), - identifier = ['GUMGUM', type, evt.id].join('_'); - ctx.reportRenderedEntityIdentifier(identifier); - ctx.renderStart({ - width: max(ampWidth, ad.width), - height: max(ampHeight, ad.height), - }); - }; - }, - noFill = function() { - ctx.noContentAvailable(); + const {max} = Math, + slotId = parseInt(data.slot, 10), + onLoad = function(type) { + return function(evt) { + const ad = Object.assign({width: 0, height: 0}, evt.ad || {}), + identifier = ['GUMGUM', type, evt.id].join('_'); + ctx.reportRenderedEntityIdentifier(identifier); + ctx.renderStart({ + width: max(ampWidth, ad.width), + height: max(ampHeight, ad.height), + }); }; + }, + noFill = function() { + ctx.noContentAvailable(); + }; // Ads logic starts global.ggv2id = data.zone; diff --git a/ads/holder.js b/ads/holder.js index e35519abde0a..dc5e5c14ab21 100644 --- a/ads/holder.js +++ b/ads/holder.js @@ -24,10 +24,10 @@ export function holder(global, data) { validateData(data, ['block'], []); const wcl = global.context.location; const n = navigator.userAgent; - let l = '&r' + Math.round((Math.random() * 10000000)) + '&h' + wcl.href; + let l = '&r' + Math.round(Math.random() * 10000000) + '&h' + wcl.href; if (!(n.indexOf('Safari') != -1 && n.indexOf('Chrome') == -1)) { l += '&c1'; } data.queue = l; - writeScript(global,'https://i.holder.com.ua/js2/holder/ajax/ampv1.js'); + writeScript(global, 'https://i.holder.com.ua/js2/holder/ajax/ampv1.js'); } diff --git a/ads/ibillboard.js b/ads/ibillboard.js index aa1be9434746..93bec6106ff6 100644 --- a/ads/ibillboard.js +++ b/ads/ibillboard.js @@ -29,7 +29,6 @@ const validHosts = [ * @param {!Object} data */ export function ibillboard(global, data) { - validateData(data, ['src']); const {src} = data; validateSrcPrefix(validHosts, src); diff --git a/ads/idealmedia.js b/ads/idealmedia.js index 443db3567bd1..ef1466d6db20 100644 --- a/ads/idealmedia.js +++ b/ads/idealmedia.js @@ -28,11 +28,12 @@ export function idealmedia(global, data) { document.body.appendChild(scriptRoot); - const url = `https://jsc.idealmedia.io/${encodeURIComponent(data.publisher[0])}/` - + `${encodeURIComponent(data.publisher[1])}/` - + `${encodeURIComponent(data.publisher)}.` - + `${encodeURIComponent(data.widget)}.js?t=` - + Math.floor(Date.now() / 36e5); + const url = + `https://jsc.idealmedia.io/${encodeURIComponent(data.publisher[0])}/` + + `${encodeURIComponent(data.publisher[1])}/` + + `${encodeURIComponent(data.publisher)}.` + + `${encodeURIComponent(data.widget)}.js?t=` + + Math.floor(Date.now() / 36e5); loadScript(global, data.url || url); } diff --git a/ads/imedia.js b/ads/imedia.js index ef34f61d2453..8da74128d79e 100644 --- a/ads/imedia.js +++ b/ads/imedia.js @@ -36,41 +36,48 @@ export function imedia(global, data) { } mW.inPagePositions.push({parentElement, context: global.context}); - computeInMasterFrame(global, 'imedia-load', done => { - loadScript(global, 'https://i.imedia.cz/js/im3.js', () => { - if (global.im != null) { - mW.im = global.im; - mW.im.conf.referer = context.canonicalUrl; + computeInMasterFrame( + global, + 'imedia-load', + done => { + loadScript(global, 'https://i.imedia.cz/js/im3.js', () => { + if (global.im != null) { + mW.im = global.im; + mW.im.conf.referer = context.canonicalUrl; - // send request to get all ads - mW.im.getAds(positions, {AMPcallback: ads => { - mW.ads = ads; - done(null); - }}); - }}); - }, () => { - mW.inPagePositions = mW.inPagePositions.filter(inPagePostion => { - let used = true; - positions.filter((position, index) => { - - // match right element and zone to write advert from adserver - if (inPagePostion.parentElement.id == position.id) { - used = false; - position.id = inPagePostion.parentElement; // right element "c" to position obj. - if (mW.im.writeAd) { - mW.im.writeAd(mW.ads[index], position); + // send request to get all ads + mW.im.getAds(positions, { + AMPcallback: ads => { + mW.ads = ads; + done(null); + }, + }); + } + }); + }, + () => { + mW.inPagePositions = mW.inPagePositions.filter(inPagePostion => { + let used = true; + positions.filter((position, index) => { + // match right element and zone to write advert from adserver + if (inPagePostion.parentElement.id == position.id) { + used = false; + position.id = inPagePostion.parentElement; // right element "c" to position obj. + if (mW.im.writeAd) { + mW.im.writeAd(mW.ads[index], position); - // inform AMP runtime when the ad starts rendering - if (mW.ads[index].impress) { - inPagePostion.context.renderStart(); - } else { - inPagePostion.context.noContentAvailable(); + // inform AMP runtime when the ad starts rendering + if (mW.ads[index].impress) { + inPagePostion.context.renderStart(); + } else { + inPagePostion.context.noContentAvailable(); + } } + return false; } - return false; - } + }); + return used; // remove (filter) element filled with add }); - return used; // remove (filter) element filled with add - }); - }); + } + ); } diff --git a/ads/imonomy.js b/ads/imonomy.js index 26b4d7d51375..c92969e029bf 100644 --- a/ads/imonomy.js +++ b/ads/imonomy.js @@ -33,14 +33,17 @@ export function imonomy(global, data) { if (!('slot' in data)) { global.CasaleArgs = data; writeScript(global, `//tag.imonomy.com/${data.pid}/indexJTag.js`); - } else { //DFP ad request call + } else { + //DFP ad request call let calledDoubleclick = false; data.timeout = isNaN(data.timeout) ? DEFAULT_TIMEOUT : data.timeout; const timer = setTimeout(() => { callDoubleclick(EVENT_TIMEOUT); }, data.timeout); const callDoubleclick = function(code) { - if (calledDoubleclick) { return; } + if (calledDoubleclick) { + return; + } calledDoubleclick = true; clearTimeout(timer); reportStats(data, code); @@ -60,11 +63,15 @@ export function imonomy(global, data) { }; loadScript( - global, `//tag.imonomy.com/amp/${data.pid}/amp.js`, () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + global, + `//tag.imonomy.com/amp/${data.pid}/amp.js`, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } } @@ -87,7 +94,9 @@ function prepareData(data) { */ function reportStats(data, code) { try { - if (code == EVENT_BADTAG) { return; } + if (code == EVENT_BADTAG) { + return; + } const xhttp = new XMLHttpRequest(); xhttp.withCredentials = true; let unitFormat = ''; @@ -96,19 +105,19 @@ function reportStats(data, code) { pageLocation = encodeURIComponent(window.context.location.href); } const {subId, pid} = data, - trackId = 'AMP', - notFirst = true, - cid = '', - abLabel = '', - rand = Math.random(); + trackId = 'AMP', + notFirst = true, + cid = '', + abLabel = '', + rand = Math.random(); if (!isNaN(data.width) && !isNaN(data.height)) { unitFormat = `${data.width}x${data.height}`; } const uid = '', - isLocked = false, - isTrackable = false, - isClient = false, - tier = 0; + isLocked = false, + isTrackable = false, + isClient = false, + tier = 0; const baseUrl = '//srv.imonomy.com/internal/reporter'; let unitCodeUrl = `${baseUrl}?v=2&subid=${subId}&sid=${pid}&`; unitCodeUrl = unitCodeUrl + `format=${unitFormat}&ai=`; @@ -134,6 +143,5 @@ function reportStats(data, code) { xhttp.open('GET', unitCodeUrl, true); xhttp.setRequestHeader('Content-Type', 'application/json'); xhttp.send(); - } catch (e) {} } diff --git a/ads/improvedigital.js b/ads/improvedigital.js index 8452f26ec981..16419918b010 100755 --- a/ads/improvedigital.js +++ b/ads/improvedigital.js @@ -23,13 +23,19 @@ import {validateData, writeScript} from '../3p/3p'; export function improvedigital(global, data) { validateData(data, ['placement'], ['width', 'height', 'optin', 'keyvalue']); - let url = 'https://ad.360yield.com' + - '/adj?' + - 'p=' + encodeURIComponent(data.placement) + - '&w=' + encodeURIComponent(data.width) + - '&h=' + encodeURIComponent(data.height) + - '&optin=' + encodeURIComponent(data.optin) + - '&tz=' + new Date().getTimezoneOffset(); + let url = + 'https://ad.360yield.com' + + '/adj?' + + 'p=' + + encodeURIComponent(data.placement) + + '&w=' + + encodeURIComponent(data.width) + + '&h=' + + encodeURIComponent(data.height) + + '&optin=' + + encodeURIComponent(data.optin) + + '&tz=' + + new Date().getTimezoneOffset(); const value = data.keyvalue; let newData = ''; @@ -38,11 +44,12 @@ export function improvedigital(global, data) { if (value && value.length > 0) { const keys = value.split('&'); - for (let i = 0; i < keys.length; i++) - { - if (!keys[i]) {continue;} + for (let i = 0; i < keys.length; i++) { + if (!keys[i]) { + continue; + } const segment = keys[i].split('='); - const segment1 = (segment[1] ? encodeURIComponent(segment[1]) : ''); + const segment1 = segment[1] ? encodeURIComponent(segment[1]) : ''; if (validKey > 0) { newData += amps; } diff --git a/ads/inabox/frame-overlay-helper.js b/ads/inabox/frame-overlay-helper.js index 4752def46b13..3f3eadb1f8f6 100644 --- a/ads/inabox/frame-overlay-helper.js +++ b/ads/inabox/frame-overlay-helper.js @@ -26,7 +26,6 @@ import { import {resetStyles, setImportantStyles} from '../../src/style'; import {restrictedVsync, timer} from './util'; - const CENTER_TRANSITION_TIME_MS = 150; const CENTER_TRANSITION_END_WAIT_TIME_MS = 50; @@ -38,38 +37,47 @@ const CENTER_TRANSITION_END_WAIT_TIME_MS = 50; * @private */ const expandFrameImpl = function(win, iframe, onFinish) { - restrictedVsync(win, { - measure(state) { - state.viewportSize = { - width: win./*OK*/innerWidth, - height: win./*OK*/innerHeight, - }; - state.rect = layoutRectFromDomRect(iframe./*OK*/getBoundingClientRect()); + restrictedVsync( + win, + { + measure(state) { + state.viewportSize = { + width: win./*OK*/ innerWidth, + height: win./*OK*/ innerHeight, + }; + state.rect = layoutRectFromDomRect( + iframe./*OK*/ getBoundingClientRect() + ); + }, + mutate(state) { + const {width, height} = state.viewportSize; + const expandedRect = layoutRectLtwh(0, 0, width, height); + + centerFrameUnderVsyncMutate( + iframe, + state.rect, + state.viewportSize, + CENTER_TRANSITION_TIME_MS + ); + + // To prevent double click during transition; + setImportantStyles(iframe, {'pointer-events': 'none'}); + + timer(() => { + restrictedVsync(win, { + mutate() { + resetStyles(iframe, ['pointer-events']); + expandFrameUnderVsyncMutate(iframe); + onFinish(state.rect, expandedRect); + }, + }); + }, CENTER_TRANSITION_TIME_MS + CENTER_TRANSITION_END_WAIT_TIME_MS); + }, }, - mutate(state) { - const {width, height} = state.viewportSize; - const expandedRect = layoutRectLtwh(0, 0, width, height); - - centerFrameUnderVsyncMutate(iframe, state.rect, state.viewportSize, - CENTER_TRANSITION_TIME_MS); - - // To prevent double click during transition; - setImportantStyles(iframe, {'pointer-events': 'none'}); - - timer(() => { - restrictedVsync(win, { - mutate() { - resetStyles(iframe, ['pointer-events']); - expandFrameUnderVsyncMutate(iframe); - onFinish(state.rect, expandedRect); - }, - }); - }, CENTER_TRANSITION_TIME_MS + CENTER_TRANSITION_END_WAIT_TIME_MS); - }, - }, {}); + {} + ); }; - /** * Resets the frame from full overlay mode. * @param {!Window} win Host window. @@ -89,14 +97,14 @@ const collapseFrameImpl = function(win, iframe, onFinish, onMeasure) { restrictedVsync(win, { measure() { onMeasure( - layoutRectFromDomRect(iframe./*OK*/getBoundingClientRect())); + layoutRectFromDomRect(iframe./*OK*/ getBoundingClientRect()) + ); }, }); }, }); }; - /** * Places the child frame in full overlay mode. * @param {!Window} win Host window. @@ -105,7 +113,6 @@ const collapseFrameImpl = function(win, iframe, onFinish, onMeasure) { */ export let expandFrame = expandFrameImpl; - /** * @param {!Function} implFn * @visibleForTesting @@ -114,7 +121,6 @@ export function stubExpandFrameForTesting(implFn) { expandFrame = implFn; } - /** * @visibleForTesting */ @@ -122,7 +128,6 @@ export function resetExpandFrameForTesting() { expandFrame = expandFrameImpl; } - /** * Places the child frame in full overlay mode. * @param {!Window} win Host window. @@ -132,7 +137,6 @@ export function resetExpandFrameForTesting() { */ export let collapseFrame = collapseFrameImpl; - /** * @param {!Function} implFn * @visibleForTesting @@ -141,7 +145,6 @@ export function stubCollapseFrameForTesting(implFn) { collapseFrame = implFn; } - /** * @visibleForTesting */ diff --git a/ads/inabox/frame-overlay-manager.js b/ads/inabox/frame-overlay-manager.js index 58105f872e5a..77683409567b 100644 --- a/ads/inabox/frame-overlay-manager.js +++ b/ads/inabox/frame-overlay-manager.js @@ -16,12 +16,10 @@ import {collapseFrame, expandFrame} from './frame-overlay-helper'; - /** * Inabox host manager for full overlay frames. */ export class FrameOverlayManager { - /** * @param {!Window} win */ @@ -82,18 +80,23 @@ export class FrameOverlayManager { // We know what the collapsed box was. If the viewport has not changed while // expanded, we can immediately notify the consumer of the collapsed // box rect since it should be the same. Otherwise, we wait for remeasure. - collapseFrame(this.win_, iframe, () => { - this.isExpanded_ = false; + collapseFrame( + this.win_, + iframe, + () => { + this.isExpanded_ = false; - if (!this.viewportChangedSinceExpand_) { - callback(this.collapsedRect_); - } - }, collapsedRect => { - this.collapsedRect_ = collapsedRect; + if (!this.viewportChangedSinceExpand_) { + callback(this.collapsedRect_); + } + }, + collapsedRect => { + this.collapsedRect_ = collapsedRect; - if (this.viewportChangedSinceExpand_) { - callback(this.collapsedRect_); + if (this.viewportChangedSinceExpand_) { + callback(this.collapsedRect_); + } } - }); + ); } } diff --git a/ads/inabox/inabox-host.js b/ads/inabox/inabox-host.js index becb491d54d4..93a347caa8ef 100644 --- a/ads/inabox/inabox-host.js +++ b/ads/inabox/inabox-host.js @@ -69,7 +69,7 @@ export class InaboxHost { win.AMP[INABOX_UNREGISTER_IFRAME] = host.unregisterIframe.bind(host); } const queuedMsgs = win[PENDING_MESSAGES]; - const processMessageFn = /** @type function(Event)*/(evt => { + const processMessageFn = /** @type {function(Event)} */ (evt => { try { host.processMessage(evt); } catch (err) { @@ -107,9 +107,10 @@ export class InaboxHost { function validateMessage(message) { const valid = !!(message.source && message.source.postMessage); if (!valid) { - user().error(TAG, - 'Missing message.source. message.data=' - + JSON.stringify(getData(message))); + user().error( + TAG, + 'Missing message.source. message.data=' + JSON.stringify(getData(message)) + ); } return valid; } diff --git a/ads/inabox/inabox-messaging-host.js b/ads/inabox/inabox-messaging-host.js index 537082e284da..9a546b6954de 100644 --- a/ads/inabox/inabox-messaging-host.js +++ b/ads/inabox/inabox-messaging-host.js @@ -34,7 +34,6 @@ const READ_ONLY_MESSAGES = [MessageType.SEND_POSITIONS]; /** Simple helper for named callbacks. */ class NamedObservable { - /** * Creates an instance of NamedObservable. */ @@ -76,7 +75,6 @@ class NamedObservable { let AdFrameDef; export class InaboxMessagingHost { - /** * @param {!Window} win * @param {!Array} iframes @@ -98,13 +96,19 @@ export class InaboxMessagingHost { this.frameOverlayManager_ = new FrameOverlayManager(win); this.msgObservable_.listen( - MessageType.SEND_POSITIONS, this.handleSendPositions_); + MessageType.SEND_POSITIONS, + this.handleSendPositions_ + ); this.msgObservable_.listen( - MessageType.FULL_OVERLAY_FRAME, this.handleEnterFullOverlay_); + MessageType.FULL_OVERLAY_FRAME, + this.handleEnterFullOverlay_ + ); this.msgObservable_.listen( - MessageType.CANCEL_FULL_OVERLAY_FRAME, this.handleCancelFullOverlay_); + MessageType.CANCEL_FULL_OVERLAY_FRAME, + this.handleCancelFullOverlay_ + ); } /** @@ -125,24 +129,29 @@ export class InaboxMessagingHost { return false; } - const adFrame = - this.getFrameElement_(message.source, request['sentinel']); + const adFrame = this.getFrameElement_(message.source, request['sentinel']); if (!adFrame) { dev().info(TAG, 'Ignored message from untrusted iframe:', message); return false; } const allowedTypes = adFrame.iframe.dataset['ampAllowed']; - const allowedTypesList = allowedTypes ? - allowedTypes.split(/\s*,\s*/) : - READ_ONLY_MESSAGES; + const allowedTypesList = allowedTypes + ? allowedTypes.split(/\s*,\s*/) + : READ_ONLY_MESSAGES; if (allowedTypesList.indexOf(request['type']) === -1) { dev().info(TAG, 'Ignored non-whitelisted message type:', message); return false; } - if (!this.msgObservable_.fire(request['type'], this, - [adFrame.measurableFrame, request, message.source, message.origin])) { + if ( + !this.msgObservable_.fire(request['type'], this, [ + adFrame.measurableFrame, + request, + message.source, + message.origin, + ]) + ) { dev().warn(TAG, 'Unprocessed AMP message:', message); return false; } @@ -160,16 +169,27 @@ export class InaboxMessagingHost { handleSendPositions_(iframe, request, source, origin) { const viewportRect = this.positionObserver_.getViewportRect(); const targetRect = this.positionObserver_.getTargetRect(iframe); - this.sendPosition_(request, source, origin, dict({ - 'viewportRect': viewportRect, - 'targetRect': targetRect, - })); + this.sendPosition_( + request, + source, + origin, + dict({ + 'viewportRect': viewportRect, + 'targetRect': targetRect, + }) + ); devAssert(this.iframeMap_[request.sentinel]); this.iframeMap_[request.sentinel].observeUnregisterFn = - this.iframeMap_[request.sentinel].observeUnregisterFn || - this.positionObserver_.observe(iframe, data => - this.sendPosition_(request, source, origin, /** @type ?JsonObject */(data))); + this.iframeMap_[request.sentinel].observeUnregisterFn || + this.positionObserver_.observe(iframe, data => + this.sendPosition_( + request, + source, + origin, + /** @type {?JsonObject} */ (data) + ) + ); return true; } @@ -182,9 +202,10 @@ export class InaboxMessagingHost { */ sendPosition_(request, source, origin, data) { dev().fine(TAG, 'Sent position data to [%s] %s', request.sentinel, data); - source./*OK*/postMessage( - serializeMessage(MessageType.POSITION, request.sentinel, data), - origin); + source./*OK*/ postMessage( + serializeMessage(MessageType.POSITION, request.sentinel, data), + origin + ); } /** @@ -199,15 +220,17 @@ export class InaboxMessagingHost { */ handleEnterFullOverlay_(iframe, request, source, origin) { this.frameOverlayManager_.expandFrame(iframe, boxRect => { - source./*OK*/postMessage( - serializeMessage( - MessageType.FULL_OVERLAY_FRAME_RESPONSE, - request.sentinel, - dict({ - 'success': true, - 'boxRect': boxRect, - })), - origin); + source./*OK*/ postMessage( + serializeMessage( + MessageType.FULL_OVERLAY_FRAME_RESPONSE, + request.sentinel, + dict({ + 'success': true, + 'boxRect': boxRect, + }) + ), + origin + ); }); return true; @@ -222,15 +245,17 @@ export class InaboxMessagingHost { */ handleCancelFullOverlay_(iframe, request, source, origin) { this.frameOverlayManager_.collapseFrame(iframe, boxRect => { - source./*OK*/postMessage( - serializeMessage( - MessageType.CANCEL_FULL_OVERLAY_FRAME_RESPONSE, - request.sentinel, - dict({ - 'success': true, - 'boxRect': boxRect, - })), - origin); + source./*OK*/ postMessage( + serializeMessage( + MessageType.CANCEL_FULL_OVERLAY_FRAME_RESPONSE, + request.sentinel, + dict({ + 'success': true, + 'boxRect': boxRect, + }) + ), + origin + ); }); return true; @@ -271,8 +296,11 @@ export class InaboxMessagingHost { const measurableWin = measurableFrame.contentWindow; for (let i = 0; i < this.iframes_.length; i++) { const iframe = this.iframes_[i]; - for (let j = 0, tempWin = measurableWin; - j < 10; j++, tempWin = tempWin.parent) { + for ( + let j = 0, tempWin = measurableWin; + j < 10; + j++, tempWin = tempWin.parent + ) { if (iframe.contentWindow == tempWin) { this.iframeMap_[sentinel] = {iframe, measurableFrame}; return this.iframeMap_[sentinel]; @@ -304,9 +332,11 @@ export class InaboxMessagingHost { // hierarchy. If win is not nested within x-domain framing, then // this loop breaks immediately. let topXDomainWin; - for (let j = 0, tempWin = win; + for ( + let j = 0, tempWin = win; j < 10 && tempWin != tempWin.top && !canInspectWindow(tempWin); - j++, topXDomainWin = tempWin, tempWin = tempWin.parent) {} + j++, topXDomainWin = tempWin, tempWin = tempWin.parent + ) {} // If topXDomainWin exists, we know that the frame we want to measure // is a x-domain frame. Unfortunately, you can not access properties // on a x-domain window, so we can not do window.frameElement, and @@ -314,18 +344,20 @@ export class InaboxMessagingHost { // over that parent's child iframes until we find the frame element // that corresponds to topXDomainWin. if (!!topXDomainWin) { - const iframes = - topXDomainWin.parent.document.querySelectorAll('iframe'); - for (let k = 0, frame = iframes[k]; k < iframes.length; - k++, frame = iframes[k]) { + const iframes = topXDomainWin.parent.document.querySelectorAll('iframe'); + for ( + let k = 0, frame = iframes[k]; + k < iframes.length; + k++, frame = iframes[k] + ) { if (frame.contentWindow == topXDomainWin) { - return /** @type {!HTMLIFrameElement} */(frame); + return /** @type {!HTMLIFrameElement} */ (frame); } } } // If topXDomainWin does not exist, then win is friendly, and we can // just return its frameElement directly. - return /** @type {!HTMLIFrameElement} */(win.frameElement); + return /** @type {!HTMLIFrameElement} */ (win.frameElement); } /** diff --git a/ads/inabox/position-observer.js b/ads/inabox/position-observer.js index 0247de991748..8f069cbd1ef3 100644 --- a/ads/inabox/position-observer.js +++ b/ads/inabox/position-observer.js @@ -35,7 +35,6 @@ let PositionEntryDef; const MIN_EVENT_INTERVAL_IN_MS = 100; export class PositionObserver { - /** * @param {!Window} win */ @@ -60,10 +59,14 @@ export class PositionObserver { observe(element, callback) { if (!this.positionObservable_) { this.positionObservable_ = new Observable(); - const listener = throttle(this.win_, () => { - this.update_(); - this.positionObservable_.fire(); - }, MIN_EVENT_INTERVAL_IN_MS); + const listener = throttle( + this.win_, + () => { + this.update_(); + this.positionObservable_.fire(); + }, + MIN_EVENT_INTERVAL_IN_MS + ); this.update_(); this.win_.addEventListener('scroll', listener, true); this.win_.addEventListener('resize', listener, true); @@ -89,7 +92,7 @@ export class PositionObserver { */ getPositionEntry_(element) { return { - viewportRect: /** @type {!LayoutRectDef} */(this.viewportRect_), + viewportRect: /** @type {!LayoutRectDef} */ (this.viewportRect_), // relative position to viewport targetRect: this.getTargetRect(element), }; @@ -101,15 +104,16 @@ export class PositionObserver { getViewportRect() { const {scrollingElement_: scrollingElement, win_: win} = this; - const scrollLeft = scrollingElement./*OK*/scrollLeft || - win./*OK*/pageXOffset; - const scrollTop = scrollingElement./*OK*/scrollTop || - win./*OK*/pageYOffset; + const scrollLeft = + scrollingElement./*OK*/ scrollLeft || win./*OK*/ pageXOffset; + const scrollTop = + scrollingElement./*OK*/ scrollTop || win./*OK*/ pageYOffset; return layoutRectLtwh( - Math.round(scrollLeft), - Math.round(scrollTop), - win./*OK*/innerWidth, - win./*OK*/innerHeight); + Math.round(scrollLeft), + Math.round(scrollTop), + win./*OK*/ innerWidth, + win./*OK*/ innerHeight + ); } /** @@ -122,16 +126,23 @@ export class PositionObserver { * @return {!LayoutRectDef} */ getTargetRect(element) { - let targetRect = - layoutRectFromDomRect(element./*OK*/getBoundingClientRect()); + let targetRect = layoutRectFromDomRect( + element./*OK*/ getBoundingClientRect() + ); const parentWin = element.ownerDocument.defaultView; - for (let j = 0, tempWin = parentWin; + for ( + let j = 0, tempWin = parentWin; j < 10 && tempWin != this.win_ && tempWin != this.win_.top; - j++, tempWin = tempWin.parent) { + j++, tempWin = tempWin.parent + ) { const parentFrameRect = layoutRectFromDomRect( - tempWin.frameElement./*OK*/getBoundingClientRect()); - targetRect = moveLayoutRect(targetRect, - parentFrameRect.left, parentFrameRect.top); + tempWin.frameElement./*OK*/ getBoundingClientRect() + ); + targetRect = moveLayoutRect( + targetRect, + parentFrameRect.left, + parentFrameRect.top + ); } return targetRect; } @@ -143,16 +154,18 @@ export class PositionObserver { */ function getScrollingElement(win) { const doc = win.document; - if (doc./*OK*/scrollingElement) { - return doc./*OK*/scrollingElement; + if (doc./*OK*/ scrollingElement) { + return doc./*OK*/ scrollingElement; } - if (doc.body - // Due to https://bugs.webkit.org/show_bug.cgi?id=106133, WebKit - // browsers have to use `body` and NOT `documentElement` for - // scrolling purposes. This has mostly being resolved via - // `scrollingElement` property, but this branch is still necessary - // for backward compatibility purposes. - && isWebKit(win.navigator.userAgent)) { + if ( + doc.body && + // Due to https://bugs.webkit.org/show_bug.cgi?id=106133, WebKit + // browsers have to use `body` and NOT `documentElement` for + // scrolling purposes. This has mostly being resolved via + // `scrollingElement` property, but this branch is still necessary + // for backward compatibility purposes. + isWebKit(win.navigator.userAgent) + ) { return doc.body; } return doc.documentElement; diff --git a/ads/inabox/util.js b/ads/inabox/util.js index c8aaea5a4893..daf2ebdd1c68 100644 --- a/ads/inabox/util.js +++ b/ads/inabox/util.js @@ -14,7 +14,6 @@ * limitations under the License. */ - /** * Executes a "restricted" read/write vsync cycle. * This function exists mainly since the vsync service is not available for the @@ -42,7 +41,6 @@ export function restrictedVsync(win, task, opt_state) { }); } - /** * Executes a function after a certain time. * The timer service is not available for the inabox host script, hence this diff --git a/ads/inmobi.js b/ads/inmobi.js index 063b027685c9..77f4e9d64c23 100644 --- a/ads/inmobi.js +++ b/ads/inmobi.js @@ -22,7 +22,7 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function inmobi(global, data) { - validateData(data, ['siteid','slotid'], []); + validateData(data, ['siteid', 'slotid'], []); const inmobiConf = { siteid: data.siteid, @@ -40,7 +40,7 @@ export function inmobi(global, data) { }; writeScript(global, 'https://cf.cdn.inmobi.com/ad/inmobi.secure.js', () => { - global.document.write('
'); + global.document.write("
"); global._inmobi.getNewAd(document.getElementById('my-ad-slot'), inmobiConf); }); } diff --git a/ads/innity.js b/ads/innity.js index 0006d9d0a6ba..26c1b88f95b5 100644 --- a/ads/innity.js +++ b/ads/innity.js @@ -24,9 +24,15 @@ export function innity(global, data) { validateData(data, ['pub', 'zone'], ['channel']); writeScript(global, 'https://cdn.innity.net/admanager.js', () => { const innityAMPZone = global.innity_adZone; - const innityAMPTag = new innityAMPZone(encodeURIComponent(data.pub), - encodeURIComponent(data.zone), {width: data.width, height: data.height, - channel: data.channel ? encodeURIComponent(data.channel) : ''}); + const innityAMPTag = new innityAMPZone( + encodeURIComponent(data.pub), + encodeURIComponent(data.zone), + { + width: data.width, + height: data.height, + channel: data.channel ? encodeURIComponent(data.channel) : '', + } + ); // AMP handling or noContentAvailable innityAMPTag.amp(global.context); // else renderStart (with at least house ad) diff --git a/ads/ix.js b/ads/ix.js index 6677ce90ddd4..1089899dfaf7 100644 --- a/ads/ix.js +++ b/ads/ix.js @@ -32,7 +32,8 @@ export function ix(global, data) { if (!('slot' in data)) { global.CasaleArgs = data; writeScript(global, 'https://js-sec.indexww.com/indexJTag.js'); - } else { //DFP ad request call + } else { + //DFP ad request call const start = Date.now(); let calledDoubleclick = false; @@ -42,7 +43,9 @@ export function ix(global, data) { }, data.ixTimeout); const callDoubleclick = function(code) { - if (calledDoubleclick) { return; } + if (calledDoubleclick) { + return; + } calledDoubleclick = true; clearTimeout(timer); reportStats(data.ixId, data.ixSlot, data.slot, start, code); @@ -61,9 +64,14 @@ export function ix(global, data) { ampError: EVENT_ERROR, }; - loadScript(global, 'https://js-sec.indexww.com/apl/amp.js', undefined, () => { - callDoubleclick(EVENT_ERROR); - }); + loadScript( + global, + 'https://js-sec.indexww.com/apl/amp.js', + undefined, + () => { + callDoubleclick(EVENT_ERROR); + } + ); } } @@ -89,13 +97,15 @@ function prepareData(data) { */ function reportStats(siteID, slotID, dfpSlot, start, code) { try { - if (code == EVENT_BADTAG) { return; } + if (code == EVENT_BADTAG) { + return; + } const xhttp = new XMLHttpRequest(); xhttp.withCredentials = true; const deltat = Date.now() - start; - const ts = start / 1000 >> 0; - const ets = Date.now() / 1000 >> 0; + const ts = (start / 1000) >> 0; + const ets = (Date.now() / 1000) >> 0; let url = 'https://as-sec.casalemedia.com/headerstats?s=' + siteID; if (typeof window.context.location.href !== 'undefined') { url += '&u=' + encodeURIComponent(window.context.location.href); @@ -112,7 +122,7 @@ function reportStats(siteID, slotID, dfpSlot, start, code) { stats += '"n":"amp-e",'; } stats += '"v":"' + deltat + '",'; - stats += '"b": "INDX","x": "' + dfpSlot.substring(0,64) + '"}]}]}'; + stats += '"b": "INDX","x": "' + dfpSlot.substring(0, 64) + '"}]}]}'; xhttp.open('POST', url, true); xhttp.setRequestHeader('Content-Type', 'application/json'); diff --git a/ads/jubna.js b/ads/jubna.js index d88b71eebf71..8661f0cb855a 100644 --- a/ads/jubna.js +++ b/ads/jubna.js @@ -17,9 +17,9 @@ import {loadScript, validateData} from '../3p/3p'; /** -* @param {!Window} global -* @param {!Object} data -*/ + * @param {!Window} global + * @param {!Object} data + */ export function jubna(global, data) { validateData(data, ['wid', 'pid']); global._jubna = global._jubna || { diff --git a/ads/kargo.js b/ads/kargo.js index edb7ef160bc2..75a7226f6f87 100644 --- a/ads/kargo.js +++ b/ads/kargo.js @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - computeInMasterFrame, - loadScript, - validateData, -} from '../3p/3p'; +import {computeInMasterFrame, loadScript, validateData} from '../3p/3p'; /** * @param {!Window} global @@ -30,7 +26,8 @@ export function kargo(global, data) { validateData(data, ['site', 'slot'], ['options']); // Kargo AdTag url - const kargoScriptUrl = 'https://storage.cloud.kargo.com/ad/network/tag/v3/' + data.site + '.js'; + const kargoScriptUrl = + 'https://storage.cloud.kargo.com/ad/network/tag/v3/' + data.site + '.js'; // parse extra ad call options (optional) let options = {}; @@ -43,28 +40,33 @@ export function kargo(global, data) { // Add window source reference to ad options options.source_window = global; - computeInMasterFrame(global, 'kargo-load', function(done) { - // load AdTag in Master window - loadScript(this, kargoScriptUrl, () => { - let success = false; - if (this.Kargo != null && this.Kargo.loaded) { - success = true; - } + computeInMasterFrame( + global, + 'kargo-load', + function(done) { + // load AdTag in Master window + loadScript(this, kargoScriptUrl, () => { + let success = false; + if (this.Kargo != null && this.Kargo.loaded) { + success = true; + } - done(success); - }); - }, success => { - if (success) { - const w = options.source_window; + done(success); + }); + }, + success => { + if (success) { + const w = options.source_window; - // Add reference to Kargo api to this window if it's not the Master window - if (!w.context.isMaster) { - w.Kargo = w.context.master.Kargo; - } + // Add reference to Kargo api to this window if it's not the Master window + if (!w.context.isMaster) { + w.Kargo = w.context.master.Kargo; + } - w.Kargo.getAd(data.slot, options); - } else { - throw new Error('Kargo AdTag failed to load'); + w.Kargo.getAd(data.slot, options); + } else { + throw new Error('Kargo AdTag failed to load'); + } } - }); + ); } diff --git a/ads/kiosked.js b/ads/kiosked.js index 2aa5d57c06d6..235df8a09fab 100644 --- a/ads/kiosked.js +++ b/ads/kiosked.js @@ -27,14 +27,24 @@ export function kiosked(global, data) { if (hasOwn(data, 'scriptid')) { scriptId = data['scriptid']; } - window.addEventListener('kioskedAdRender', function() { - global.context.renderStart(); - }, false); + window.addEventListener( + 'kioskedAdRender', + function() { + global.context.renderStart(); + }, + false + ); - window.addEventListener('kioskedAdNoFill', function() { - global.context.noContentAvailable(); - }, false); - - writeScript(global, 'https://scripts.kiosked.com/loader/kiosked-ad.js?staticTagId=' + scriptId); + window.addEventListener( + 'kioskedAdNoFill', + function() { + global.context.noContentAvailable(); + }, + false + ); + writeScript( + global, + 'https://scripts.kiosked.com/loader/kiosked-ad.js?staticTagId=' + scriptId + ); } diff --git a/ads/kixer.js b/ads/kixer.js index a8cbdf59516f..3ef34177e954 100644 --- a/ads/kixer.js +++ b/ads/kixer.js @@ -54,11 +54,13 @@ export function kixer(global, data) { const kxviewCheck = function(intersectionEntry) { inView = intersectionEntry.intersectionRatio > 0.5; // Half of the unit is in the viewport if (inView) { - if (!viewed && viewTimer == null) { // If the ad hasn't been viewed and the timer is not set + if (!viewed && viewTimer == null) { + // If the ad hasn't been viewed and the timer is not set viewTimer = setTimeout(kxviewFire, 900); // Set a Timeout to check the ad in 900ms and fire the view } } else { - if (viewTimer) { // If the Timeout is set + if (viewTimer) { + // If the Timeout is set clearTimeout(viewTimer); // Clear the Timeout viewTimer = null; } @@ -66,7 +68,8 @@ export function kixer(global, data) { }; const kxviewFire = function() { - if (inView) { // if the ad is still in the viewport + if (inView) { + // if the ad is still in the viewport if (typeof __kx_viewability.process_locked === 'function') { viewed = true; __kx_viewability.process_locked(data.adslot); // Fire kixer view diff --git a/ads/kuadio.js b/ads/kuadio.js index 0c0f9aec7456..94f13ea47142 100644 --- a/ads/kuadio.js +++ b/ads/kuadio.js @@ -21,8 +21,11 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function kuadio(global, data) { - validateData(data, ['widgetId'], - ['region', 'baseUrl', 'betaMode', 'debugMode', 'fastParse', 'ref']); + validateData( + data, + ['widgetId'], + ['region', 'baseUrl', 'betaMode', 'debugMode', 'fastParse', 'ref'] + ); global._pvmax = { region: data.region, diff --git a/ads/lentainform.js b/ads/lentainform.js index 698cdc4d2be1..40bcc3a9ac7d 100644 --- a/ads/lentainform.js +++ b/ads/lentainform.js @@ -28,11 +28,12 @@ export function lentainform(global, data) { document.body.appendChild(scriptRoot); - const url = `https://jsc.lentainform.com/${encodeURIComponent(data.publisher[0])}/` - + `${encodeURIComponent(data.publisher[1])}/` - + `${encodeURIComponent(data.publisher)}.` - + `${encodeURIComponent(data.widget)}.js?t=` - + Math.floor(Date.now() / 36e5); + const url = + `https://jsc.lentainform.com/${encodeURIComponent(data.publisher[0])}/` + + `${encodeURIComponent(data.publisher[1])}/` + + `${encodeURIComponent(data.publisher)}.` + + `${encodeURIComponent(data.widget)}.js?t=` + + Math.floor(Date.now() / 36e5); loadScript(global, data.url || url); } diff --git a/ads/lockerdome.js b/ads/lockerdome.js index 4058d18d676b..e15c76077991 100644 --- a/ads/lockerdome.js +++ b/ads/lockerdome.js @@ -17,9 +17,9 @@ import {loadScript, validateData} from '../3p/3p'; /** -* @param {!Window} global -* @param {!Object} data -*/ + * @param {!Window} global + * @param {!Object} data + */ export function lockerdome(global, data) { validateData(data, ['slot']); global.SLOT = data.slot; diff --git a/ads/mantis.js b/ads/mantis.js index 9c52a19e9051..5712ea12522b 100644 --- a/ads/mantis.js +++ b/ads/mantis.js @@ -24,9 +24,13 @@ export function mantisDisplay(global, data) { validateData(data, ['property', 'zone'], []); global.mantis = global.mantis || []; - global.mantis.push(['display', 'load', { - property: data['property'], - }]); + global.mantis.push([ + 'display', + 'load', + { + property: data['property'], + }, + ]); const d = global.document.createElement('div'); d.setAttribute('data-mantis-zone', data['zone']); @@ -43,11 +47,15 @@ export function mantisRecommend(global, data) { validateData(data, ['property'], ['css']); global.mantis = global.mantis || []; - global.mantis.push(['recommend', 'load', { - property: data['property'], - render: 'recommended', - css: data['css'], - }]); + global.mantis.push([ + 'recommend', + 'load', + { + property: data['property'], + render: 'recommended', + css: data['css'], + }, + ]); const d = global.document.createElement('div'); d.setAttribute('id', 'recommended'); diff --git a/ads/mediaimpact.js b/ads/mediaimpact.js index 2c001e062304..e26709c5f93f 100644 --- a/ads/mediaimpact.js +++ b/ads/mediaimpact.js @@ -25,10 +25,11 @@ export function mediaimpact(global, data) { /* eslint google-camelcase/google-camelcase: 0 */ global.sas_loadHandler = function(f) { if (f.hasAd) { - f.crea1 || (f.crea1 = { - width: 300, - height: 250, - }); + f.crea1 || + (f.crea1 = { + width: 300, + height: 250, + }); global.context.renderStart({ width: f.crea1.width, height: f.crea1.height, @@ -37,23 +38,35 @@ export function mediaimpact(global, data) { global.context.noContentAvailable(); } }; - window.addEventListener('load', function() { - asmi.sas.call(data.site + '/(' + data.page + ')', // eslint-disable-line no-undef + window.addEventListener( + 'load', + function() { + // eslint-disable-next-line no-undef + asmi.sas.call( + data.site + '/(' + data.page + ')', data.format, data.target + ';googleAMP=1;', '', - 'sas_' + data.slot.replace('sas_',''), - 1); - }, false); - asmiSetup = { // eslint-disable-line no-unused-vars, no-undef + 'sas_' + data.slot.replace('sas_', ''), + 1 + ); + }, + false + ); + // eslint-disable-next-line no-unused-vars, no-undef + asmiSetup = { view: 'm', async: true, }; - loadScript(global, 'https://ec-ns.sascdn.com/diff/251/pages/amp_default.js', () => { - if (!document.getElementById('sas_' + data.slot.replace('sas_',''))) { - const adContainer = global.document.createElement('div'); - adContainer.id = 'sas_' + data.slot.replace('sas_',''); - global.document.body.appendChild(adContainer); + loadScript( + global, + 'https://ec-ns.sascdn.com/diff/251/pages/amp_default.js', + () => { + if (!document.getElementById('sas_' + data.slot.replace('sas_', ''))) { + const adContainer = global.document.createElement('div'); + adContainer.id = 'sas_' + data.slot.replace('sas_', ''); + global.document.body.appendChild(adContainer); + } } - }); + ); } diff --git a/ads/medianet.js b/ads/medianet.js index 9c49c2ea13b3..19206f93ee2c 100644 --- a/ads/medianet.js +++ b/ads/medianet.js @@ -19,14 +19,24 @@ import {getSourceUrl, parseUrlDeprecated} from '../src/url'; import {hasOwn} from '../src/utils/object'; const mandatoryParams = ['tagtype', 'cid'], - optionalParams = [ - 'timeout', 'crid', 'misc', - 'slot', 'targeting', 'categoryExclusions', - 'tagForChildDirectedTreatment', 'cookieOptions', - 'overrideWidth', 'overrideHeight', 'loadingStrategy', - 'consentNotificationId', 'useSameDomainRenderingUntilDeprecated', - 'experimentId', 'multiSize', 'multiSizeValidation', - ]; + optionalParams = [ + 'timeout', + 'crid', + 'misc', + 'slot', + 'targeting', + 'categoryExclusions', + 'tagForChildDirectedTreatment', + 'cookieOptions', + 'overrideWidth', + 'overrideHeight', + 'loadingStrategy', + 'consentNotificationId', + 'useSameDomainRenderingUntilDeprecated', + 'experimentId', + 'multiSize', + 'multiSizeValidation', + ]; // useSameDomainRenderingUntilDeprecated is included to ensure publisher // amp-tags don't break before 29th March @@ -37,11 +47,12 @@ const mandatoryParams = ['tagtype', 'cid'], export function medianet(global, data) { validateData(data, mandatoryParams, optionalParams); - const publisherUrl = global.context.canonicalUrl || - getSourceUrl(global.context.location.href), - referrerUrl = global.context.referrer; + const publisherUrl = + global.context.canonicalUrl || getSourceUrl(global.context.location.href), + referrerUrl = global.context.referrer; - if (data.tagtype === 'headerbidder') { //parameter tagtype is used to identify the product the publisher is using. Going ahead we plan to support more product types. + if (data.tagtype === 'headerbidder') { + //parameter tagtype is used to identify the product the publisher is using. Going ahead we plan to support more product types. loadHBTag(global, data, publisherUrl, referrerUrl); } else if (data.tagtype === 'cm' && data.crid) { loadCMTag(global, data, publisherUrl, referrerUrl); @@ -143,7 +154,6 @@ function loadCMTag(global, data, publisherUrl, referrerUrl) { * @param {?string} referrerUrl */ function loadHBTag(global, data, publisherUrl, referrerUrl) { - /** * Loads MNETAd. */ @@ -162,8 +172,10 @@ function loadHBTag(global, data, publisherUrl, referrerUrl) { data.targeting = data.targeting || {}; - if (global.advBidxc && - typeof global.advBidxc.setAmpTargeting === 'function') { + if ( + global.advBidxc && + typeof global.advBidxc.setAmpTargeting === 'function' + ) { global.advBidxc.setAmpTargeting(global, data); } global.advBidxc.loadAmpAd(global, data); @@ -174,8 +186,10 @@ function loadHBTag(global, data, publisherUrl, referrerUrl) { */ function mnetHBHandle() { global.advBidxc = global.context.master.advBidxc; - if (global.advBidxc && - typeof global.advBidxc.registerAmpSlot === 'function') { + if ( + global.advBidxc && + typeof global.advBidxc.registerAmpSlot === 'function' + ) { global.advBidxc.registerAmpSlot({ cb: loadMNETAd, data, @@ -184,22 +198,34 @@ function loadHBTag(global, data, publisherUrl, referrerUrl) { } } - computeInMasterFrame(global, 'medianet-hb-load', done => { - /*eslint "google-camelcase/google-camelcase": 0*/ - global.advBidxc_requrl = publisherUrl; - global.advBidxc_refurl = referrerUrl; - global.advBidxc = { - registerAmpSlot: () => {}, - setAmpTargeting: () => {}, - renderAmpAd: () => {}, - loadAmpAd: () => { - global.context.noContentAvailable(); - }, - }; - global.advBidxc.amp = getCallbacksObject(); - const publisherDomain = parseUrlDeprecated(publisherUrl).hostname; - writeScript(global, 'https://contextual.media.net/bidexchange.js?https=1&=1&cid=' + encodeURIComponent(data.cid) + '&dn=' + encodeURIComponent(publisherDomain), () => { - done(null); - }); - }, mnetHBHandle); + computeInMasterFrame( + global, + 'medianet-hb-load', + done => { + /*eslint "google-camelcase/google-camelcase": 0*/ + global.advBidxc_requrl = publisherUrl; + global.advBidxc_refurl = referrerUrl; + global.advBidxc = { + registerAmpSlot: () => {}, + setAmpTargeting: () => {}, + renderAmpAd: () => {}, + loadAmpAd: () => { + global.context.noContentAvailable(); + }, + }; + global.advBidxc.amp = getCallbacksObject(); + const publisherDomain = parseUrlDeprecated(publisherUrl).hostname; + writeScript( + global, + 'https://contextual.media.net/bidexchange.js?https=1&=1&cid=' + + encodeURIComponent(data.cid) + + '&dn=' + + encodeURIComponent(publisherDomain), + () => { + done(null); + } + ); + }, + mnetHBHandle + ); } diff --git a/ads/medyanet.js b/ads/medyanet.js index 5fb1dff2749b..a291e6e91012 100644 --- a/ads/medyanet.js +++ b/ads/medyanet.js @@ -46,13 +46,15 @@ function medyanetAds(global, data) { f.setAttribute('allowfullscreen', 'true'); f.setAttribute('scrolling', 'no'); setStyles(f, { - border: '0 none transparent' , - position: 'relative' , + border: '0 none transparent', + position: 'relative', }); f.onload = function() { window.context.renderStart(); }; - f.src = `https://app.medyanetads.com/amp/medyanetads.html?bidderData=${global.domain}&adunit=${global.adunit}&size=${global.size}`; + f.src = `https://app.medyanetads.com/amp/medyanetads.html?bidderData=${ + global.domain + }&adunit=${global.adunit}&size=${global.size}`; const url = window.top.location.search.substring(1); if (url && url.indexOf('hb=true') !== -1) { f.src = f.src + '&hb=true'; diff --git a/ads/meg.js b/ads/meg.js index a0b60d69d2f2..1355673c6f3c 100644 --- a/ads/meg.js +++ b/ads/meg.js @@ -34,10 +34,15 @@ export function meg(global, data) { global.context.noContentAvailable(); }, }; - loadScript(global, url, () => { - // Meg has been loaded - }, () => { - // Cannot load meg embed.js - global.context.noContentAvailable(); - }); + loadScript( + global, + url, + () => { + // Meg has been loaded + }, + () => { + // Cannot load meg embed.js + global.context.noContentAvailable(); + } + ); } diff --git a/ads/mgid.js b/ads/mgid.js index 6755a7bf03b5..52034330f4dd 100644 --- a/ads/mgid.js +++ b/ads/mgid.js @@ -28,11 +28,12 @@ export function mgid(global, data) { document.body.appendChild(scriptRoot); - const url = `https://jsc.mgid.com/${encodeURIComponent(data.publisher[0])}/` - + `${encodeURIComponent(data.publisher[1])}/` - + `${encodeURIComponent(data.publisher)}.` - + `${encodeURIComponent(data.widget)}.js?t=` - + Math.floor(Date.now() / 36e5); + const url = + `https://jsc.mgid.com/${encodeURIComponent(data.publisher[0])}/` + + `${encodeURIComponent(data.publisher[1])}/` + + `${encodeURIComponent(data.publisher)}.` + + `${encodeURIComponent(data.widget)}.js?t=` + + Math.floor(Date.now() / 36e5); loadScript(global, data.url || url); } diff --git a/ads/miximedia.js b/ads/miximedia.js index 7a46854bf7ad..96cd9ea2755f 100644 --- a/ads/miximedia.js +++ b/ads/miximedia.js @@ -22,7 +22,7 @@ import {loadScript, validateData} from '../3p/3p'; */ export function miximedia(global, data) { validateData(data, ['blockid']); - (global._miximedia = global._miximedia || { + global._miximedia = global._miximedia || { viewId: global.context.pageViewId, blockId: data['blockid'], htmlURL: data['canonical'] || global.context.canonicalUrl, @@ -34,14 +34,17 @@ export function miximedia(global, data) { domFingerprint: window.context.domFingerprint, location: window.context.location, startTime: window.context.startTime, - - }); + }; global._miximedia.AMPCallbacks = { renderStart: global.context.renderStart, noContentAvailable: global.context.noContentAvailable, }; // load the miximedia AMP JS file script asynchronously const rand = Math.round(Math.random() * 100000000); - loadScript(global, 'https://amp.mixi.media/ampclient/mixi.js?rand=' + rand, () => {}, - global.context.noContentAvailable); + loadScript( + global, + 'https://amp.mixi.media/ampclient/mixi.js?rand=' + rand, + () => {}, + global.context.noContentAvailable + ); } diff --git a/ads/mixpo.js b/ads/mixpo.js index 67e4b0173172..a8367dc513fe 100644 --- a/ads/mixpo.js +++ b/ads/mixpo.js @@ -14,22 +14,18 @@ * limitations under the License. */ -import {validateData,writeScript} from '../3p/3p'; +import {validateData, writeScript} from '../3p/3p'; /** * @param {!Window} global * @param {!Object} data */ export function mixpo(global, data) { - validateData(data, [ - 'guid', - 'subdomain', - ]); + validateData(data, ['guid', 'subdomain']); const g = global, - cdnSubdomain = (data.subdomain == 'www') ? - 'cdn' : data.subdomain + '-cdn', - url = data.loader || `https://${cdnSubdomain}.mixpo.com/js/loader.js`; + cdnSubdomain = data.subdomain == 'www' ? 'cdn' : data.subdomain + '-cdn', + url = data.loader || `https://${cdnSubdomain}.mixpo.com/js/loader.js`; g.mixpoAd = { amp: true, diff --git a/ads/nativo.js b/ads/nativo.js index 8b78bcfb42ed..1633187e3b0a 100644 --- a/ads/nativo.js +++ b/ads/nativo.js @@ -22,11 +22,11 @@ import {loadScript} from '../3p/3p'; export function nativo(global, data) { let ntvAd; (function(ntvAd, global, data) { - global - .history - .replaceState(null, - '', - location.pathname + location.hash.replace(/({).*(})/, '')); + global.history.replaceState( + null, + '', + location.pathname + location.hash.replace(/({).*(})/, '') + ); // Private let delayedAdLoad = false; let percentageOfadViewed; @@ -36,26 +36,27 @@ export function nativo(global, data) { * @return {boolean} */ function isValidDelayTime(delay) { - return ((typeof delay != 'undefined' - && !isNaN(delay) - && parseInt(delay,10) >= 0)); + return ( + typeof delay != 'undefined' && !isNaN(delay) && parseInt(delay, 10) >= 0 + ); } /** * @param {!Object} data * @return {boolean} */ function isDelayedTimeStart(data) { - return (isValidDelayTime(data.delayByTime) - && ('delay' in data) - && !('delayByView' in data)); + return ( + isValidDelayTime(data.delayByTime) && + 'delay' in data && + !('delayByView' in data) + ); } /** * @param {!Object} data * @return {boolean} */ function isDelayedViewStart(data) { - return (isValidDelayTime(data.delayByTime) - && ('delayByView' in data)); + return isValidDelayTime(data.delayByTime) && 'delayByView' in data; } /** * Loads ad when done. @@ -64,9 +65,11 @@ export function nativo(global, data) { const g = global; global.context.observeIntersection(function(positions) { const coordinates = getLastPositionCoordinates(positions); - if (typeof coordinates.rootBounds != 'undefined' - && (coordinates.intersectionRect.top == ( - coordinates.rootBounds.top + coordinates.boundingClientRect.y))) { + if ( + typeof coordinates.rootBounds != 'undefined' && + coordinates.intersectionRect.top == + coordinates.rootBounds.top + coordinates.boundingClientRect.y + ) { if (isDelayedViewStart(data) && !delayedAdLoad) { g.PostRelease.Start(); delayedAdLoad = true; @@ -104,10 +107,10 @@ export function nativo(global, data) { function viewabilityConfiguration(positions) { const coordinates = getLastPositionCoordinates(positions); setPercentageOfadViewed( - (((coordinates.intersectionRect - .height * 100) / coordinates - .boundingClientRect - .height) / 100)); + (coordinates.intersectionRect.height * 100) / + coordinates.boundingClientRect.height / + 100 + ); global.PostRelease.checkIsAdVisible(); } // Public @@ -124,11 +127,17 @@ export function nativo(global, data) { global._prx.push(['cfg.RequestUrl', data['requestUrl'] || loc.href]); for (const key in data) { switch (key) { - case 'premium': global._prx.push(['cfg.SetUserPremium']); break; - case 'debug': global._prx.push(['cfg.Debug']); break; - case 'delay': if (isValidDelayTime(data.delayByTime)) { - global._prx.push(['cfg.SetNoAutoStart']); - } break; + case 'premium': + global._prx.push(['cfg.SetUserPremium']); + break; + case 'debug': + global._prx.push(['cfg.Debug']); + break; + case 'delay': + if (isValidDelayTime(data.delayByTime)) { + global._prx.push(['cfg.SetNoAutoStart']); + } + break; } } }; diff --git a/ads/navegg.js b/ads/navegg.js index a576f98b5d88..5c7fc5252934 100644 --- a/ads/navegg.js +++ b/ads/navegg.js @@ -24,7 +24,8 @@ import {loadScript, validateData} from '../3p/3p'; export function navegg(global, data) { validateData(data, ['acc']); const {acc} = data; - let seg, nvg = function() {}; + let seg, + nvg = function() {}; delete data.acc; nvg.prototype.getProfile = function() {}; data.targeting = data.targeting || {}; diff --git a/ads/netletix.js b/ads/netletix.js index 7cda96409d54..635b636101fc 100644 --- a/ads/netletix.js +++ b/ads/netletix.js @@ -37,32 +37,39 @@ const DEFAULT_NX_SITE = 'none'; export function netletix(global, data) { /*eslint "google-camelcase/google-camelcase": 0*/ global._netletix_amp = { - allowed_data: ['nxasync','nxv','nxsite','nxid','nxscript'], - mandatory_data: ['nxkey','nxunit','nxwidth','nxheight'], + allowed_data: ['nxasync', 'nxv', 'nxsite', 'nxid', 'nxscript'], + mandatory_data: ['nxkey', 'nxunit', 'nxwidth', 'nxheight'], data, }; - validateData(data, - global._netletix_amp.mandatory_data, global._netletix_amp.allowed_data); + validateData( + data, + global._netletix_amp.mandatory_data, + global._netletix_amp.allowed_data + ); - const nxh = (data.nxheight || DEFAULT_NX_HEIGHT); - const nxw = (data.nxwidth || DEFAULT_NX_WIDTH); + const nxh = data.nxheight || DEFAULT_NX_HEIGHT; + const nxw = data.nxwidth || DEFAULT_NX_WIDTH; const url = assertHttpsUrl( - addParamsToUrl( - NX_URL_FULL + encodeURIComponent(data.nxkey || DEFAULT_NX_KEY), - dict({ - 'unit': data.nxunit || DEFAULT_NX_UNIT, - 'width': data.nxwidth || DEFAULT_NX_WIDTH, - 'height': data.nxheight || DEFAULT_NX_HEIGHT, - 'v': data.nxv || DEFAULT_NX_V, - 'site': data.nxsite || DEFAULT_NX_SITE, - 'ord': Math.round(Math.random() * 100000000), - })), - data.ampSlotIndex); + addParamsToUrl( + NX_URL_FULL + encodeURIComponent(data.nxkey || DEFAULT_NX_KEY), + dict({ + 'unit': data.nxunit || DEFAULT_NX_UNIT, + 'width': data.nxwidth || DEFAULT_NX_WIDTH, + 'height': data.nxheight || DEFAULT_NX_HEIGHT, + 'v': data.nxv || DEFAULT_NX_V, + 'site': data.nxsite || DEFAULT_NX_SITE, + 'ord': Math.round(Math.random() * 100000000), + }) + ), + data.ampSlotIndex + ); window.addEventListener('message', event => { - if (event.data.type && - startsWith(dev().assertString(event.data.type), 'nx-')) { + if ( + event.data.type && + startsWith(dev().assertString(event.data.type), 'nx-') + ) { switch (event.data.type) { case 'nx-resize': const renderconfig = { @@ -70,8 +77,11 @@ export function netletix(global, data) { 'height': event.data.height, }; global.context.renderStart(renderconfig); - if (event.data.width && event.data.height && - (event.data.width != nxw || event.data.height != nxh)) { + if ( + event.data.width && + event.data.height && + (event.data.width != nxw || event.data.height != nxh) + ) { global.context.requestResize(event.data.width, event.data.height); } break; diff --git a/ads/nokta.js b/ads/nokta.js index 3baf19aadf06..8f8ee985de4c 100644 --- a/ads/nokta.js +++ b/ads/nokta.js @@ -27,5 +27,8 @@ export function nokta(global, data) { global.zone = data.zone; global.iwidth = data.width; global.iheight = data.height; - writeScript(global, 'https://static.virgul.com/theme/mockups/noktaamp/ampjs.js'); + writeScript( + global, + 'https://static.virgul.com/theme/mockups/noktaamp/ampjs.js' + ); } diff --git a/ads/nws.js b/ads/nws.js index 779a8e98b7c4..c7ed163fdf3c 100644 --- a/ads/nws.js +++ b/ads/nws.js @@ -22,9 +22,14 @@ import {validateSrcPrefix, writeScript} from '../3p/3p'; */ export function nws(global, data) { const {src} = data; - validateSrcPrefix([ - 'https://tags.nws.ai/', 'https://echo.nws.press/', 'https://stories.nws.ai/', - ], src); + validateSrcPrefix( + [ + 'https://tags.nws.ai/', + 'https://echo.nws.press/', + 'https://stories.nws.ai/', + ], + src + ); writeScript(global, src); } @@ -36,8 +41,9 @@ export function nws(global, data) { */ export function chargeads(global, data) { const {src} = data; - validateSrcPrefix([ - 'https://www.chargeplatform.com/', 'https://tags.chargeplatform.com/', - ], src); + validateSrcPrefix( + ['https://www.chargeplatform.com/', 'https://tags.chargeplatform.com/'], + src + ); writeScript(global, src); } diff --git a/ads/onnetwork.js b/ads/onnetwork.js index 28c3f62021b6..4dc618ba3efe 100644 --- a/ads/onnetwork.js +++ b/ads/onnetwork.js @@ -42,9 +42,8 @@ export function onnetwork(global, data) { // Movie tag using "data-sid" attribute else if (sid) { url = hosts.video + '/embed.php?ampsrc=1&sid=' + encodeURIComponent(sid); - // Movie placement using "data-mid" attribute - } - else if (mid) { + // Movie placement using "data-mid" attribute + } else if (mid) { url = hosts.video + '/embed.php?ampsrc=1&mid=' + encodeURIComponent(mid); } diff --git a/ads/openadstream.js b/ads/openadstream.js index 53209047ffe5..cbd6bb0a2737 100644 --- a/ads/openadstream.js +++ b/ads/openadstream.js @@ -23,9 +23,15 @@ import {validateData, writeScript} from '../3p/3p'; export function openadstream(global, data) { validateData(data, ['adhost', 'sitepage', 'pos'], ['query']); - let url = 'https://' + encodeURIComponent(data.adhost) - + '/3/' + data.sitepage - + '/1' + String(Math.random()).substring(2, 11) + '@' + data.pos; + let url = + 'https://' + + encodeURIComponent(data.adhost) + + '/3/' + + data.sitepage + + '/1' + + String(Math.random()).substring(2, 11) + + '@' + + data.pos; if (data.query) { url = url + '?' + data.query; diff --git a/ads/openx.js b/ads/openx.js index c3f38b876802..28d5b89f2dde 100644 --- a/ads/openx.js +++ b/ads/openx.js @@ -57,7 +57,7 @@ export function openx(global, data) { if (startsWith(openxKey, 'dfp')) { // Remove 'dfp' prefix, lowercase the first letter. let fixKey = openxKey.substring(3); - fixKey = fixKey.substring(0,1).toLowerCase() + fixKey.substring(1); + fixKey = fixKey.substring(0, 1).toLowerCase() + fixKey.substring(1); dfpData[fixKey] = data[openxKey]; } delete dfpData[openxKey]; @@ -82,7 +82,8 @@ export function openx(global, data) { } else { standardImplementation(global, jssdk, dfpData); } - } else if (data.auid) { // Just show an ad. + } else if (data.auid) { + // Just show an ad. global.OX_cmds = [ () => { const oxRequest = OX(); @@ -102,7 +103,8 @@ export function openx(global, data) { ]; loadScript(global, jssdk); } - } else if (data.dfpSlot) { // Fall back to a DFP ad. + } else if (data.dfpSlot) { + // Fall back to a DFP ad. doubleclick(global, dfpData); } } @@ -136,8 +138,9 @@ function advanceImplementation(global, jssdk, dfpData, data) { callback: () => { const priceMap = global.oxhbjs && global.oxhbjs.getPriceMap(); const slot = priceMap && priceMap['c']; - const targeting = slot ? - `${slot.size}_${slot.price},hb-bid-${slot.bid_id}` : 'none_t'; + const targeting = slot + ? `${slot.size}_${slot.price},hb-bid-${slot.bid_id}` + : 'none_t'; dfpData.targeting = dfpData.targeting || {}; assign(dfpData.targeting, {oxb: targeting}); doubleclick(global, dfpData); @@ -171,8 +174,9 @@ function setCustomVars(oxRequest, customVars) { */ function filterCustomVar(customVars) { const filterPattern = /^[A-Za-z0-9._]{1,20}$/; - const filteredKeys = Object.keys(customVars) - .filter(key => filterPattern.test(key)); + const filteredKeys = Object.keys(customVars).filter(key => + filterPattern.test(key) + ); const filteredCustomVar = {}; filteredKeys.forEach(key => { filteredCustomVar[key.toLowerCase()] = customVars[key]; diff --git a/ads/opinary.js b/ads/opinary.js index 26fe050a76c1..8c2d9c81aa81 100644 --- a/ads/opinary.js +++ b/ads/opinary.js @@ -27,7 +27,7 @@ export function opinary(global, data) { div.setAttribute('id', 'opinary-automation-placeholder'); global.document.getElementById('c').appendChild(div); - if (!document.querySelector('link[rel=\'canonical\']')) { + if (!document.querySelector("link[rel='canonical']")) { const link = document.createElement('link'); link.rel = 'canonical'; link.href = global.context.canonicalUrl; diff --git a/ads/outbrain.js b/ads/outbrain.js index 53e2594e5cea..6251836949d9 100644 --- a/ads/outbrain.js +++ b/ads/outbrain.js @@ -21,11 +21,10 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function outbrain(global, data) { - // ensure we have valid widgetIds value validateData(data, ['widgetids']); - (global._outbrain = global._outbrain || { + global._outbrain = global._outbrain || { viewId: global.context.pageViewId, widgetIds: data['widgetids'], htmlURL: data['htmlurl'] || global.context.canonicalUrl, @@ -34,8 +33,11 @@ export function outbrain(global, data) { testMode: data['testmode'] || 'false', styleFile: data['stylefile'] || '', referrer: data['referrer'] || global.context.referrer, - }); + }; // load the Outbrain AMP JS file - loadScript(global, 'https://widgets.outbrain.com/widgetAMP/outbrainAMP.min.js'); + loadScript( + global, + 'https://widgets.outbrain.com/widgetAMP/outbrainAMP.min.js' + ); } diff --git a/ads/pixels.js b/ads/pixels.js index 94bf8f1267f6..afe513a47f48 100644 --- a/ads/pixels.js +++ b/ads/pixels.js @@ -21,19 +21,20 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function pixels(global, data) { - validateData(data, - ['origin', 'sid', 'tag'], - ['clickTracker', 'viewability'] - ); + validateData(data, ['origin', 'sid', 'tag'], ['clickTracker', 'viewability']); data.tag = data.tag.toString().toLowerCase(); global._pixelsParam = data; if (data.tag === 'sync') { - writeScript(global, 'https://cdn.adsfactor.net/amp/pixels-amp.min.js', () => { - const pixelsAMPAd = global.pixelsAd; - const pixelsAMPTag = new pixelsAMPAd(data); - pixelsAMPTag.renderAmp(global.context); - global.context.renderStart(); - }); + writeScript( + global, + 'https://cdn.adsfactor.net/amp/pixels-amp.min.js', + () => { + const pixelsAMPAd = global.pixelsAd; + const pixelsAMPTag = new pixelsAMPAd(data); + pixelsAMPTag.renderAmp(global.context); + global.context.renderStart(); + } + ); } else { global.context.noContentAvailable(); } diff --git a/ads/plista.js b/ads/plista.js index d7ffaf0521ca..b7a8390fa53a 100644 --- a/ads/plista.js +++ b/ads/plista.js @@ -22,20 +22,31 @@ import {loadScript, validateData} from '../3p/3p'; */ export function plista(global, data) { // TODO: check mandatory fields - validateData(data, [], [ - 'publickey', 'widgetname', 'urlprefix', - 'item', 'geo', 'categories', 'countrycode', - ]); + validateData( + data, + [], + [ + 'publickey', + 'widgetname', + 'urlprefix', + 'item', + 'geo', + 'categories', + 'countrycode', + ] + ); const div = global.document.createElement('div'); div.setAttribute('data-display', 'plista_widget_' + data.widgetname); // container with id "c" is provided by amphtml global.document.getElementById('c').appendChild(div); window.PLISTA = { publickey: data.publickey, - widgets: [{ - name: data.widgetname, - pre: data.urlprefix, - }], + widgets: [ + { + name: data.widgetname, + pre: data.urlprefix, + }, + ], item: data.item, geo: data.geo, categories: data.categories, @@ -45,5 +56,10 @@ export function plista(global, data) { }; // load the plista modules asynchronously - loadScript(global, 'https://static' + (data.countrycode ? '-' + encodeURIComponent(data.countrycode) : '') + '.plista.com/async.js'); + loadScript( + global, + 'https://static' + + (data.countrycode ? '-' + encodeURIComponent(data.countrycode) : '') + + '.plista.com/async.js' + ); } diff --git a/ads/popin.js b/ads/popin.js index 8981c3f76213..b16f4fc5bf97 100644 --- a/ads/popin.js +++ b/ads/popin.js @@ -17,9 +17,9 @@ import {loadScript, validateData} from '../3p/3p'; /** -* @param {!Window} global -* @param {!Object} data -*/ + * @param {!Window} global + * @param {!Object} data + */ export function popin(global, data) { validateData(data, ['mediaid']); @@ -27,7 +27,10 @@ export function popin(global, data) { d.id = '_popIn_amp_recommend'; global.document.getElementById('c').appendChild(d); - const url = 'https://api.popin.cc/searchbox/' + encodeURIComponent(data['mediaid']) + '.js'; + const url = + 'https://api.popin.cc/searchbox/' + + encodeURIComponent(data['mediaid']) + + '.js'; loadScript(global, url); } diff --git a/ads/postquare.js b/ads/postquare.js index 0366031db692..c4e50182fbf7 100644 --- a/ads/postquare.js +++ b/ads/postquare.js @@ -21,7 +21,6 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function postquare(global, data) { - validateData(data, ['widgetids']); global._postquare = global._postquare || { diff --git a/ads/pressboard.js b/ads/pressboard.js index 75f1f9afaf75..a68bc8fc969f 100644 --- a/ads/pressboard.js +++ b/ads/pressboard.js @@ -24,9 +24,14 @@ export function pressboard(global, data) { validateData(data, ['media']); data.baseUrl = 'https://adserver.pressboard.ca'; global.pbParams = data; - loadScript(global, data.baseUrl + '/js/amp-ad.js', () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + loadScript( + global, + data.baseUrl + '/js/amp-ad.js', + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/pubexchange.js b/ads/pubexchange.js index d1ea82a1475e..a002281e0024 100644 --- a/ads/pubexchange.js +++ b/ads/pubexchange.js @@ -21,7 +21,6 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function pubexchange(global, data) { - // ensure we have valid widgetIds value validateData(data, ['publication', 'moduleId', 'moduleNum'], ['test']); diff --git a/ads/pubguru.js b/ads/pubguru.js index 9c7ea34bbe1e..6d6c2fdb3638 100644 --- a/ads/pubguru.js +++ b/ads/pubguru.js @@ -29,6 +29,10 @@ export function pubguru(global, data) { el.setAttribute('id', 'the-ad-unit'); global.document.getElementById('c').appendChild(el); - loadScript(global, 'https://amp.pubguru.org/amp.' - + encodeURIComponent(data.publisher) + '.min.js'); + loadScript( + global, + 'https://amp.pubguru.org/amp.' + + encodeURIComponent(data.publisher) + + '.min.js' + ); } diff --git a/ads/pubmine.js b/ads/pubmine.js index 035f79a1c3f4..76a69bf11657 100644 --- a/ads/pubmine.js +++ b/ads/pubmine.js @@ -17,8 +17,8 @@ import {loadScript, validateData} from '../3p/3p'; const pubmineOptional = ['section', 'pt', 'ht'], - pubmineRequired = ['siteid'], - pubmineURL = 'https://s.pubmine.com/head.js'; + pubmineRequired = ['siteid'], + pubmineURL = 'https://s.pubmine.com/head.js'; /** * @param {!Object} data diff --git a/ads/pulsepoint.js b/ads/pulsepoint.js index 12ffe4fb8462..586756e329d9 100644 --- a/ads/pulsepoint.js +++ b/ads/pulsepoint.js @@ -23,9 +23,7 @@ import {loadScript, validateData, writeScript} from '../3p/3p'; */ export function pulsepoint(global, data) { // TODO: check mandatory fields - validateData(data, [], [ - 'pid', 'tagid', 'tagtype', 'slot', 'timeout', - ]); + validateData(data, [], ['pid', 'tagid', 'tagtype', 'slot', 'timeout']); if (data.tagtype === 'hb') { headerBidding(global, data); } else { @@ -38,10 +36,16 @@ export function pulsepoint(global, data) { * @param {!Object} data */ function tag(global, data) { - writeScript(global, 'https://tag.contextweb.com/getjs.aspx?action=VIEWAD' + - '&cwpid=' + encodeURIComponent(data.pid) - + '&cwtagid=' + encodeURIComponent(data.tagid) - + '&cwadformat=' + encodeURIComponent(data.width + 'X' + data.height)); + writeScript( + global, + 'https://tag.contextweb.com/getjs.aspx?action=VIEWAD' + + '&cwpid=' + + encodeURIComponent(data.pid) + + '&cwtagid=' + + encodeURIComponent(data.tagid) + + '&cwadformat=' + + encodeURIComponent(data.width + 'X' + data.height) + ); } /** @@ -52,13 +56,15 @@ function headerBidding(global, data) { loadScript(global, 'https://ads.contextweb.com/ht.js', () => { const hbConfig = { timeout: data.timeout || 1000, - slots: [{ - cp: data.pid, - ct: data.tagid, - cf: data.width + 'x' + data.height, - placement: data.slot, - elementId: 'c', - }], + slots: [ + { + cp: data.pid, + ct: data.tagid, + cf: data.width + 'x' + data.height, + placement: data.slot, + elementId: 'c', + }, + ], done(targeting) { doubleclick(global, { width: data.width, @@ -71,4 +77,3 @@ function headerBidding(global, data) { new window.PulsePointHeaderTag(hbConfig).init(); }); } - diff --git a/ads/purch.js b/ads/purch.js index ce03e15dc52a..64ed76c97ca5 100644 --- a/ads/purch.js +++ b/ads/purch.js @@ -14,18 +14,14 @@ * limitations under the License. */ -import { - validateData, - validateSrcPrefix, - writeScript} from '../3p/3p'; +import {validateData, validateSrcPrefix, writeScript} from '../3p/3p'; /** * @param {!Window} global * @param {!Object} data */ export function purch(global, data) { - validateData(data, [], - ['pid', 'divid', 'config']); + validateData(data, [], ['pid', 'divid', 'config']); global.data = data; const adsrc = 'https://ramp.purch.com/serve/creative_amp.js'; diff --git a/ads/rbinfox.js b/ads/rbinfox.js index 84ad70fade81..e0fb1a66aeb8 100644 --- a/ads/rbinfox.js +++ b/ads/rbinfox.js @@ -59,9 +59,9 @@ function addToQueue(global, src) { const ctx = n + blockId; global[ctx] = global[ctx] || []; global[ctx].push(() => { - const renderTo = 'infox_' + blockId; - // Create container - createContainer(global, renderTo); - global['INFOX' + blockId].renderTo(renderTo); + const renderTo = 'infox_' + blockId; + // Create container + createContainer(global, renderTo); + global['INFOX' + blockId].renderTo(renderTo); }); } diff --git a/ads/recomad.js b/ads/recomad.js index 117a5b9dc2df..911544fc65f0 100644 --- a/ads/recomad.js +++ b/ads/recomad.js @@ -57,13 +57,13 @@ export function recomad(global, data) { validateData(data, ['appId', 'widgetId', ['searchTerm', 'origin']]); createWidgetContainer( - window.document.createElement('div'), - data['appId'], - data['widgetId'], - data['searchTerm'] || '', - data['origin'] || '', - data['baseUrl'] || '', - data['puid'] || '' + window.document.createElement('div'), + data['appId'], + data['widgetId'], + data['searchTerm'] || '', + data['origin'] || '', + data['baseUrl'] || '', + data['puid'] || '' ); loadScript(window, 'https://widget.s24.com/js/s24widget.min.js'); diff --git a/ads/relap.js b/ads/relap.js index 2242c98661a0..51c2fb5cd0d9 100644 --- a/ads/relap.js +++ b/ads/relap.js @@ -63,7 +63,9 @@ export function relap(global, data) { anchorEl.id = data['anchorid']; global.document.getElementById('c').appendChild(anchorEl); - const url = `https://relap.io/api/v6/head.js?token=${encodeURIComponent(data['token'])}&url=${encodeURIComponent(urlParam)}`; + const url = `https://relap.io/api/v6/head.js?token=${encodeURIComponent( + data['token'] + )}&url=${encodeURIComponent(urlParam)}`; loadScript(global, url); } } diff --git a/ads/revcontent.js b/ads/revcontent.js index a42a54aaa350..97c96a640dda 100644 --- a/ads/revcontent.js +++ b/ads/revcontent.js @@ -21,13 +21,9 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function revcontent(global, data) { - const endpoint = 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js'; - const required = [ - 'id', - 'width', - 'height', - 'wrapper', - ]; + const endpoint = + 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js'; + const required = ['id', 'width', 'height', 'wrapper']; const optional = [ 'api', 'key', diff --git a/ads/revjet.js b/ads/revjet.js index b3045c27c565..417a8e8c19c5 100644 --- a/ads/revjet.js +++ b/ads/revjet.js @@ -26,10 +26,11 @@ export function revjet(global, data) { global._revjetData = Object.assign({}, data); loadScript( - global, - 'https://cdn.revjet.com/~cdn/JS/03/amp.js', - /* opt_cb */ undefined, - () => { - global.context.noContentAvailable(); - }); + global, + 'https://cdn.revjet.com/~cdn/JS/03/amp.js', + /* opt_cb */ undefined, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/rubicon.js b/ads/rubicon.js index cc73f11af8bf..2e331cbd3536 100644 --- a/ads/rubicon.js +++ b/ads/rubicon.js @@ -22,11 +22,21 @@ import {validateData, writeScript} from '../3p/3p'; */ export function rubicon(global, data) { // TODO: check mandatory fields - validateData(data, [], [ - 'account', 'site', 'zone', 'size', - 'kw', 'visitor', 'inventory', - 'method', 'callback', - ]); + validateData( + data, + [], + [ + 'account', + 'site', + 'zone', + 'size', + 'kw', + 'visitor', + 'inventory', + 'method', + 'callback', + ] + ); if (data.method === 'smartTag') { smartTag(global, data); @@ -50,6 +60,10 @@ function smartTag(global, data) { global.rp_amp = 'st'; global.rp_callback = data.callback; /* eslint-enable */ - writeScript(global, 'https://ads.rubiconproject.com/ad/' - + encodeURIComponent(data.account) + '.js'); + writeScript( + global, + 'https://ads.rubiconproject.com/ad/' + + encodeURIComponent(data.account) + + '.js' + ); } diff --git a/ads/runative.js b/ads/runative.js index 19e5364a01db..7d4b44fb0309 100644 --- a/ads/runative.js +++ b/ads/runative.js @@ -38,11 +38,7 @@ const adContainerId = 'runative_id'; */ export function runative(global, data) { // ensure we have valid widgetIds value - validateData( - data, - requiredParams, - optionsParams - ); + validateData(data, requiredParams, optionsParams); const adContainer = global.document.getElementById('c'); const adNativeContainer = getAdContainer(global); @@ -51,16 +47,9 @@ export function runative(global, data) { adContainer.appendChild(adNativeContainer); // load the RUNative AMP JS file - loadScript( - global, - '//cdn.run-syndicate.com/sdk/v1/n.js', - () => { - global - .document - .body - .appendChild(initScript); - } - ); + loadScript(global, '//cdn.run-syndicate.com/sdk/v1/n.js', () => { + global.document.body.appendChild(initScript); + }); } /** @@ -70,14 +59,13 @@ function getInitData(data) { const initKeys = requiredParams.concat(optionsParams); const initParams = {}; - initKeys - .forEach(key => { - if (key in data) { - const initKey = key === 'adType' ? 'type' : key; + initKeys.forEach(key => { + if (key in data) { + const initKey = key === 'adType' ? 'type' : key; - initParams[initKey] = data[key]; - } - }); + initParams[initKey] = data[key]; + } + }); initParams['element_id'] = adContainerId; @@ -102,9 +90,9 @@ function getAdContainer(global) { function getInitAdScript(global, data) { const scriptElement = global.document.createElement('script'); const initData = getInitData(data); - const initScript = global - .document - .createTextNode(`NativeAd(${ JSON.stringify(initData) });`); + const initScript = global.document.createTextNode( + `NativeAd(${JSON.stringify(initData)});` + ); scriptElement.appendChild(initScript); diff --git a/ads/sas.js b/ads/sas.js index 5fcf63f884d2..87e039207dd5 100644 --- a/ads/sas.js +++ b/ads/sas.js @@ -24,13 +24,15 @@ import {validateData, writeScript} from '../3p/3p'; export function sas(global, data) { let url, adHost; const fields = ['site', 'size', 'area']; - validateData(data, ['customerName'], - ['adHost', 'site', 'size', 'area', 'mid','tags']); + validateData( + data, + ['customerName'], + ['adHost', 'site', 'size', 'area', 'mid', 'tags'] + ); if (typeof data.adHost === 'undefined') { adHost = encodeURIComponent(data['customerName']) + '-ads.aimatch.com'; - } - else { + } else { adHost = encodeURIComponent(data['adHost']); } diff --git a/ads/sekindo.js b/ads/sekindo.js index 51b22032dedf..cd49c1df1e47 100644 --- a/ads/sekindo.js +++ b/ads/sekindo.js @@ -25,23 +25,31 @@ export function sekindo(global, data) { validateData(data, ['spaceid']); const pubUrl = encodeURIComponent(global.context.sourceUrl); const excludesSet = {ampSlotIndex: 1, type: 1}; - const customParamMap = {spaceid: 's',width: 'x',height: 'y'}; - let query = 'isAmpProject=1&pubUrl=' + pubUrl + '&cbuster=' + - global.context.startTime + '&'; + const customParamMap = {spaceid: 's', width: 'x', height: 'y'}; + let query = + 'isAmpProject=1&pubUrl=' + + pubUrl + + '&cbuster=' + + global.context.startTime + + '&'; let getParam = ''; for (const key in data) { if (hasOwn(data, key)) { if (typeof excludesSet[key] == 'undefined') { - getParam = (typeof customParamMap[key] == 'undefined') ? - key : customParamMap[key]; + getParam = + typeof customParamMap[key] == 'undefined' ? key : customParamMap[key]; query += getParam + '=' + encodeURIComponent(data[key]) + '&'; } } } - loadScript(global, - 'https://live.sekindo.com/live/liveView.php?' + query, () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + loadScript( + global, + 'https://live.sekindo.com/live/liveView.php?' + query, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/slimcutmedia.js b/ads/slimcutmedia.js index 93219ec7ba12..7622a77f8e10 100644 --- a/ads/slimcutmedia.js +++ b/ads/slimcutmedia.js @@ -28,8 +28,16 @@ export function slimcutmedia(global, data) { data, }; - validateData(data, - global._scm_amp.mandatory_data, global._scm_amp.allowed_data); + validateData( + data, + global._scm_amp.mandatory_data, + global._scm_amp.allowed_data + ); - loadScript(global, 'https://static.freeskreen.com/publisher/' + encodeURIComponent(data.pid) + '/freeskreen.min.js'); + loadScript( + global, + 'https://static.freeskreen.com/publisher/' + + encodeURIComponent(data.pid) + + '/freeskreen.min.js' + ); } diff --git a/ads/smartclip.js b/ads/smartclip.js index 48319134576e..e5a5594e24fb 100644 --- a/ads/smartclip.js +++ b/ads/smartclip.js @@ -28,12 +28,22 @@ export function smartclip(global, data) { data, }; - validateData(data, - global._smartclip_amp.mandatory_data, global._smartclip_amp.allowed_data); + validateData( + data, + global._smartclip_amp.mandatory_data, + global._smartclip_amp.allowed_data + ); const rand = Math.round(Math.random() * 100000000); - loadScript(global, 'https://des.smartclip.net/ads?type=dyn&plc=' - + encodeURIComponent(data.plc) + '&sz=' + encodeURIComponent(data.sz) - + (data.extra ? '&' + encodeURI(data.extra) : '') + '&rnd=' + rand); + loadScript( + global, + 'https://des.smartclip.net/ads?type=dyn&plc=' + + encodeURIComponent(data.plc) + + '&sz=' + + encodeURIComponent(data.sz) + + (data.extra ? '&' + encodeURI(data.extra) : '') + + '&rnd=' + + rand + ); } diff --git a/ads/smi2.js b/ads/smi2.js index 1f0cdef3a8e2..443321cf2e57 100644 --- a/ads/smi2.js +++ b/ads/smi2.js @@ -22,7 +22,7 @@ import {loadScript, validateData} from '../3p/3p'; */ export function smi2(global, data) { validateData(data, ['blockid']); - (global._smi2 = global._smi2 || { + global._smi2 = global._smi2 || { viewId: global.context.pageViewId, blockId: data['blockid'], htmlURL: data['canonical'] || global.context.canonicalUrl, @@ -34,14 +34,17 @@ export function smi2(global, data) { domFingerprint: window.context.domFingerprint, location: window.context.location, startTime: window.context.startTime, - - }); + }; global._smi2.AMPCallbacks = { renderStart: global.context.renderStart, noContentAvailable: global.context.noContentAvailable, }; // load the smi2 AMP JS file script asynchronously const rand = Math.round(Math.random() * 100000000); - loadScript(global, 'https://amp.smi2.ru/ampclient/ampfecth.js?rand=' + rand, () => {}, - global.context.noContentAvailable); + loadScript( + global, + 'https://amp.smi2.ru/ampclient/ampfecth.js?rand=' + rand, + () => {}, + global.context.noContentAvailable + ); } diff --git a/ads/sortable.js b/ads/sortable.js index 1a79b58e6586..35b7ec85b6d6 100644 --- a/ads/sortable.js +++ b/ads/sortable.js @@ -25,13 +25,14 @@ export function sortable(global, data) { const slot = global.document.getElementById('c'); const ad = global.document.createElement('div'); - const size = (data.responsive === 'true') ? - 'auto' - : data.width + 'x' + data.height; + const size = + data.responsive === 'true' ? 'auto' : data.width + 'x' + data.height; ad.className = 'ad-tag'; ad.setAttribute('data-ad-name', data.name); ad.setAttribute('data-ad-size', size); slot.appendChild(ad); - loadScript(global, 'https://tags-cdn.deployads.com/a/' - + encodeURIComponent(data.site) + '.js'); + loadScript( + global, + 'https://tags-cdn.deployads.com/a/' + encodeURIComponent(data.site) + '.js' + ); } diff --git a/ads/sovrn.js b/ads/sovrn.js index abc16baf74ce..183ad0c0951f 100644 --- a/ads/sovrn.js +++ b/ads/sovrn.js @@ -12,7 +12,7 @@ * 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. -*/ + */ /* ********* * Existing sovrn customers feel free to contact amp-implementations@sovrn.com diff --git a/ads/speakol.js b/ads/speakol.js index c6ab360dd704..37576833ab82 100644 --- a/ads/speakol.js +++ b/ads/speakol.js @@ -24,16 +24,21 @@ import {loadScript, validateData} from '../3p/3p'; */ export function speakol(global, data) { - validateData(data, ['widgetid']); + validateData(data, ['widgetid']); - global._speakol = global._speakol || { - widgetId: data['widgetid'], - }; + global._speakol = global._speakol || { + widgetId: data['widgetid'], + }; - const d = global.document.createElement('div'); - d.classList.add('speakol-widget'); - d.id = 'spk-wi-' + data['widgetid']; - global.document.getElementById('c').appendChild(d); + const d = global.document.createElement('div'); + d.classList.add('speakol-widget'); + d.id = 'spk-wi-' + data['widgetid']; + global.document.getElementById('c').appendChild(d); - loadScript(global, `https://crawler.speakol.com/sdk/speakol-widget.js?wid=wi-${data['widgetid']}&eid=spk-wi-${data['widgetid']}`); + loadScript( + global, + `https://crawler.speakol.com/sdk/speakol-widget.js?wid=wi-${ + data['widgetid'] + }&eid=spk-wi-${data['widgetid']}` + ); } diff --git a/ads/spotx.js b/ads/spotx.js index 9b20acf9c111..e09fd3b34ca1 100644 --- a/ads/spotx.js +++ b/ads/spotx.js @@ -32,8 +32,8 @@ export function spotx(global, data) { data['spotx_content_width'] = data.spotx_content_width || data.width; data['spotx_content_height'] = data.spotx_content_height || data.height; - data['spotx_content_page_url'] = global.context.location.href || - global.context.sourceUrl; + data['spotx_content_page_url'] = + global.context.location.href || global.context.sourceUrl; // Add data-* attribute for each data value passed in. for (const key in data) { diff --git a/ads/sunmedia.js b/ads/sunmedia.js index dddfcec17b19..4bf2c62f4d92 100644 --- a/ads/sunmedia.js +++ b/ads/sunmedia.js @@ -28,8 +28,14 @@ export function sunmedia(global, data) { data, }; - validateData(data, - global._sunmedia_amp.mandatory_data, global._sunmedia_amp.allowed_data); + validateData( + data, + global._sunmedia_amp.mandatory_data, + global._sunmedia_amp.allowed_data + ); - loadScript(global, 'https://vod.addevweb.com/sunmedia/amp/ads/SMIntextAMP.js'); + loadScript( + global, + 'https://vod.addevweb.com/sunmedia/amp/ads/SMIntextAMP.js' + ); } diff --git a/ads/svknative.js b/ads/svknative.js index 963085713fbc..a170f58fe098 100644 --- a/ads/svknative.js +++ b/ads/svknative.js @@ -21,13 +21,12 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function svknative(global, data) { - // ensure we have valid widgetid value validateData(data, ['widgetid']); const s = global.document.createElement('script'); - const scriptKey = 'svknativeampwidget_' + - Math.floor(Math.random() * 10000000); + const scriptKey = + 'svknativeampwidget_' + Math.floor(Math.random() * 10000000); s.setAttribute('data-key', scriptKey); global.document.getElementById('c').appendChild(s); diff --git a/ads/swoop.js b/ads/swoop.js index 0d77ead40d5c..61fe24d1da54 100644 --- a/ads/swoop.js +++ b/ads/swoop.js @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - computeInMasterFrame, - loadScript, - validateData, -} from '../3p/3p'; +import {computeInMasterFrame, loadScript, validateData} from '../3p/3p'; /** * @param {!Window} global @@ -26,25 +22,27 @@ import { */ export function swoop(global, data) { // Required properties - validateData(data, [ - 'layout', - 'placement', - 'publisher', - 'slot', - ]); + validateData(data, ['layout', 'placement', 'publisher', 'slot']); - computeInMasterFrame(global, 'swoop-load', done => { - global.swoopIabConfig = data; + computeInMasterFrame( + global, + 'swoop-load', + done => { + global.swoopIabConfig = data; - loadScript(global, 'https://www.swoop-amp.com/amp.js', () => done(global.Swoop != null)); - }, success => { - if (success) { - if (!global.context.isMaster) { - global.context.master.Swoop.announcePlace(global, data); + loadScript(global, 'https://www.swoop-amp.com/amp.js', () => + done(global.Swoop != null) + ); + }, + success => { + if (success) { + if (!global.context.isMaster) { + global.context.master.Swoop.announcePlace(global, data); + } + } else { + global.context.noContentAvailable(); + throw new Error('Swoop failed to load'); } - } else { - global.context.noContentAvailable(); - throw new Error('Swoop failed to load'); } - }); + ); } diff --git a/ads/taboola.js b/ads/taboola.js index 2d25c4c72a75..44307c2f5519 100644 --- a/ads/taboola.js +++ b/ads/taboola.js @@ -27,8 +27,12 @@ export function taboola(global, data) { // ensure we have vlid publisher, placement and mode // and exactly one page-type - validateData(data, ['publisher', 'placement', 'mode', - ['article', 'video', 'photo', 'search', 'category', 'homepage', 'other']]); + validateData(data, [ + 'publisher', + 'placement', + 'mode', + ['article', 'video', 'photo', 'search', 'category', 'homepage', 'other'], + ]); // setup default values for referrer and url const params = { @@ -44,17 +48,18 @@ export function taboola(global, data) { }); // push the two object into the '_taboola' global - (global._taboola = global._taboola || []).push([{ - viewId: global.context.pageViewId, - publisher: data.publisher, - placement: data.placement, - mode: data.mode, - framework: 'amp', - container: 'c', - }, - params, - {flush: true}] - ); + (global._taboola = global._taboola || []).push([ + { + viewId: global.context.pageViewId, + publisher: data.publisher, + placement: data.placement, + mode: data.mode, + framework: 'amp', + container: 'c', + }, + params, + {flush: true}, + ]); // install observation on entering/leaving the view global.context.observeIntersection(function(changes) { @@ -70,5 +75,10 @@ export function taboola(global, data) { }); // load the taboola loader asynchronously - loadScript(global, `https://cdn.taboola.com/libtrc/${encodeURIComponent(data.publisher)}/loader.js`); + loadScript( + global, + `https://cdn.taboola.com/libtrc/${encodeURIComponent( + data.publisher + )}/loader.js` + ); } diff --git a/ads/tcsemotion.js b/ads/tcsemotion.js index 4da17968c529..98d7c866f01b 100644 --- a/ads/tcsemotion.js +++ b/ads/tcsemotion.js @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {validateData,writeScript} from '../3p/3p'; +import {validateData, writeScript} from '../3p/3p'; /** * @param {!Window} global * @param {!Object} d */ export function tcsemotion(global, d) { - validateData(d, ['zone','delhost']); + validateData(d, ['zone', 'delhost']); global.djaxData = d; if (d.hb && d.hb == 'true') { global.djaxData.hb = true; } else { global.djaxData.hb = false; } - writeScript(global,'https://ads.tcsemotion.com/www/delivery/amphb.js'); + writeScript(global, 'https://ads.tcsemotion.com/www/delivery/amphb.js'); } diff --git a/ads/teads.js b/ads/teads.js index 7872d60cdb7c..391b978a53b5 100644 --- a/ads/teads.js +++ b/ads/teads.js @@ -29,16 +29,26 @@ export function teads(global, data) { data, }; - validateData(data, - global._teads_amp.mandatory_data, global._teads_amp.allowed_data); + validateData( + data, + global._teads_amp.mandatory_data, + global._teads_amp.allowed_data + ); if (data.tag) { validateData(data.tag, global._teads_amp.mandatory_tag_data); global._tta = data.tag.tta; global._ttp = data.tag.ttp; - loadScript(global, 'https://a.teads.tv/media/format/' + encodeURI(data.tag.js || 'v3/teads-format.min.js')); + loadScript( + global, + 'https://a.teads.tv/media/format/' + + encodeURI(data.tag.js || 'v3/teads-format.min.js') + ); } else { - loadScript(global, 'https://a.teads.tv/page/' + encodeURIComponent(data.pid) + '/tag'); + loadScript( + global, + 'https://a.teads.tv/page/' + encodeURIComponent(data.pid) + '/tag' + ); } } diff --git a/ads/torimochi.js b/ads/torimochi.js index 69cb88e39c49..2e628a5a54a6 100644 --- a/ads/torimochi.js +++ b/ads/torimochi.js @@ -22,7 +22,6 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function torimochi(global, data) { - validateData(data, ['area', 'adtype']); if (data.width < global.width) { @@ -36,7 +35,8 @@ export function torimochi(global, data) { global.extra = parseJson(data['extra'] || '{}'); global.context.renderStart({width: global.width, height: global.height}); - const url = 'https://asset.torimochi-ad.net/js/torimochi_ad_amp.min.js?v=' + Date.now(); + const url = + 'https://asset.torimochi-ad.net/js/torimochi_ad_amp.min.js?v=' + Date.now(); writeScript(global, url); } diff --git a/ads/uas.js b/ads/uas.js index 421d8a34614f..09476bdaade2 100644 --- a/ads/uas.js +++ b/ads/uas.js @@ -56,10 +56,17 @@ function centerAd(global) { */ export function uas(global, data) { validateData( - data, - ['accId', 'adUnit', 'sizes'], - ['locLat', 'locLon', 'locSrc', 'pageURL', 'targetings', 'extraParams', - 'visibility'] + data, + ['accId', 'adUnit', 'sizes'], + [ + 'locLat', + 'locLon', + 'locSrc', + 'pageURL', + 'targetings', + 'extraParams', + 'visibility', + ] ); global.Phoenix = {EQ: []}; const uasDivId = 'uas-amp-slot'; @@ -67,16 +74,22 @@ export function uas(global, data) { loadScript(global, 'https://ads.pubmatic.com/AdServer/js/phoenix.js', () => { global.Phoenix.EQ.push(function() { global.Phoenix.enableSingleRequestCallMode(); - global.Phoenix.setInfo('AMP', 1);// Need to set the AMP flag + global.Phoenix.setInfo('AMP', 1); // Need to set the AMP flag global.Phoenix.setInfo('ACCID', data.accId); // Reading PAGEURL from sourceUrl or location.href - global.Phoenix.setInfo('PAGEURL', (global.context.sourceUrl || global.context.location.href)); // eslint-disable-line max-len + global.Phoenix.setInfo( + 'PAGEURL', + global.context.sourceUrl || global.context.location.href + ); // eslint-disable-line max-len data.pageURL && global.Phoenix.setInfo('PAGEURL', data.pageURL); data.locLat && global.Phoenix.setInfo('LAT', data.locLat); data.locLon && global.Phoenix.setInfo('LON', data.locLon); data.locSrc && global.Phoenix.setInfo('LOC_SRC', data.locSrc); - const slot = global.Phoenix.defineAdSlot(data.adUnit, data.sizes, - uasDivId); + const slot = global.Phoenix.defineAdSlot( + data.adUnit, + data.sizes, + uasDivId + ); slot.setVisibility(1); forEachOnObject(data.targetings, function(key, value) { slot.setTargeting(key, value); diff --git a/ads/uzou.js b/ads/uzou.js index d69fa2a25f6b..a975253d158b 100644 --- a/ads/uzou.js +++ b/ads/uzou.js @@ -18,9 +18,9 @@ import {loadScript, validateData} from '../3p/3p'; import {parseJson} from '../src/json'; /** -* @param {!Window} global -* @param {!Object} data -*/ + * @param {!Window} global + * @param {!Object} data + */ export function uzou(global, data) { validateData(data, ['widgetParams'], []); @@ -35,7 +35,9 @@ export function uzou(global, data) { const akamaiHost = widgetParams['akamaiHost'] || 'speee-ad.akamaized.net'; const placementCode = widgetParams['placementCode']; const mode = widgetParams['mode'] || 'production'; - const entryPoint = `https://${prefixMap[mode]}${akamaiHost}/tag/${placementCode}/js/outer-frame.min.js`; + const entryPoint = `https://${ + prefixMap[mode] + }${akamaiHost}/tag/${placementCode}/js/outer-frame.min.js`; const d = global.document.createElement('div'); d.className = `uz-${placementCode} uz-ny`; @@ -45,9 +47,9 @@ export function uzou(global, data) { const uzouInjector = { url: fixedEncodeURIComponent( - widgetParams['url'] || - global.context.canonicalUrl || - global.context.sourceUrl + widgetParams['url'] || + global.context.canonicalUrl || + global.context.sourceUrl ), referer: widgetParams['referer'] || global.context.referrer, }; diff --git a/ads/valuecommerce.js b/ads/valuecommerce.js index e773acd81b15..6bcf751b385a 100644 --- a/ads/valuecommerce.js +++ b/ads/valuecommerce.js @@ -21,8 +21,7 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function valuecommerce(global, data) { - validateData(data, ['pid'], ['sid', 'vcptn','om']); + validateData(data, ['pid'], ['sid', 'vcptn', 'om']); global.vcParam = data; writeScript(global, 'https://amp.valuecommerce.com/amp_bridge.js'); } - diff --git a/ads/videointelligence.js b/ads/videointelligence.js index a5a097590c54..8f0b1090b918 100644 --- a/ads/videointelligence.js +++ b/ads/videointelligence.js @@ -21,7 +21,6 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function videointelligence(global, data) { - validateData(data, ['publisherId', 'channelId']); loadScript(global, 'https://s.vi-serve.com/tagLoaderAmp.js'); diff --git a/ads/videonow.js b/ads/videonow.js index de82a25601c4..c920ad93fbb3 100644 --- a/ads/videonow.js +++ b/ads/videonow.js @@ -30,18 +30,24 @@ export function videonow(global, data) { const kind = data.type || 'prod'; // production version by default - let script = (data.src && decodeURI(data.src)) || - ('https://static.videonow.ru/vn_init.js' + - '?amp=1&profileId=' + profileId); + let script = + (data.src && decodeURI(data.src)) || + 'https://static.videonow.ru/vn_init.js?amp=1&profileId=' + profileId; if (kind === 'local') { - script = 'https://localhost:8085/vn_init.js?amp=1' + - '?profileId=' + profileId + - '&url=' + encodeURIComponent('https://localhost:8085/init'); + script = + 'https://localhost:8085/vn_init.js?amp=1' + + '?profileId=' + + profileId + + '&url=' + + encodeURIComponent('https://localhost:8085/init'); } else if (kind === 'dev') { - script = 'https://static.videonow.ru/dev/vn_init_module.js' + - '?amp=1&profileId=' + profileId + - '&url=' + encodeURIComponent('https://data.videonow.ru/?init'); + script = + 'https://static.videonow.ru/dev/vn_init_module.js' + + '?amp=1&profileId=' + + profileId + + '&url=' + + encodeURIComponent('https://data.videonow.ru/?init'); } loadScript(global, script); diff --git a/ads/viralize.js b/ads/viralize.js index 8e82336ef100..8e3af774c044 100644 --- a/ads/viralize.js +++ b/ads/viralize.js @@ -44,7 +44,10 @@ export function viralize(global, data) { const scriptUrl = addParamsToUrl(endpoint, queryParams); - loadScript(global, scriptUrl, - () => global.context.renderStart(), - () => global.context.noContentAvailable()); + loadScript( + global, + scriptUrl, + () => global.context.renderStart(), + () => global.context.noContentAvailable() + ); } diff --git a/ads/webediads.js b/ads/webediads.js index c16845ca3f7c..c3942b607e3a 100644 --- a/ads/webediads.js +++ b/ads/webediads.js @@ -28,7 +28,7 @@ export function webediads(global, data) { 'site': data.site, 'page': data.page, 'position': data.position, - 'query': (data.query) ? data.query : '', + 'query': data.query ? data.query : '', }); }); } diff --git a/ads/weborama.js b/ads/weborama.js index eecf96612b88..86ad07de73d6 100644 --- a/ads/weborama.js +++ b/ads/weborama.js @@ -83,5 +83,8 @@ export function weboramaDisplay(global, data) { weak_encoding: data.wbo_weak_encoding, }; - writeScript(global, 'https://cstatic.weborama.fr/js/advertiserv2/adperf_launch_1.0.0_scrambled.js'); + writeScript( + global, + 'https://cstatic.weborama.fr/js/advertiserv2/adperf_launch_1.0.0_scrambled.js' + ); } diff --git a/ads/widespace.js b/ads/widespace.js index b0774d199051..8f8dd709f093 100644 --- a/ads/widespace.js +++ b/ads/widespace.js @@ -21,7 +21,6 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function widespace(global, data) { - const WS_AMP_CODE_VER = '1.0.1'; // Optional demography parameters. let demo = []; @@ -32,9 +31,12 @@ export function widespace(global, data) { validateData(data, ['sid'], demo); - const url = 'https://engine.widespace.com/map/engine/dynamic?isamp=1' - + '&ver=' + WS_AMP_CODE_VER - + '&#sid=' + encodeURIComponent(data.sid); + const url = + 'https://engine.widespace.com/map/engine/dynamic?isamp=1' + + '&ver=' + + WS_AMP_CODE_VER + + '&#sid=' + + encodeURIComponent(data.sid); writeScript(global, url); } diff --git a/ads/wisteria.js b/ads/wisteria.js index 44046c38ce6c..a37282f909d8 100644 --- a/ads/wisteria.js +++ b/ads/wisteria.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - loadScript, - validateData, -} from '../3p/3p'; +import {loadScript, validateData} from '../3p/3p'; /** * @param {!Window} global @@ -30,5 +27,13 @@ export function wisteria(global, data) { //get canonical url const originalUrl = global.context.canonicalUrl; validateData(data, ['siteId', 'templateNumber']); - loadScript(global, 'https://wisteria-js.excite.co.jp/wisteria.js?site_id=' + data['siteId'] + '&template_no=' + data['templateNumber'] + '&original_url=' + originalUrl); + loadScript( + global, + 'https://wisteria-js.excite.co.jp/wisteria.js?site_id=' + + data['siteId'] + + '&template_no=' + + data['templateNumber'] + + '&original_url=' + + originalUrl + ); } diff --git a/ads/wpmedia.js b/ads/wpmedia.js index 03a78cf0e17d..b6750561be5d 100644 --- a/ads/wpmedia.js +++ b/ads/wpmedia.js @@ -21,7 +21,7 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function wpmedia(global, data) { - validateData(data, ['slot','bunch'], ['sn','slots']); + validateData(data, ['slot', 'bunch'], ['sn', 'slots']); // const url = 'http://localhost/wpjslib.js'; const url = 'https://std.wpcdn.pl/wpjslib/wpjslib-amp.js'; diff --git a/ads/xlift.js b/ads/xlift.js index 9859a66b274f..c1cafffe7d51 100644 --- a/ads/xlift.js +++ b/ads/xlift.js @@ -39,14 +39,18 @@ export function xlift(global, data) { //assign XliftAmpHelper property to global(window) global.XliftAmpHelper = null; - loadScript(global, 'https://cdn.x-lift.jp/resources/common/xlift_amp.js', () => { - if (!global.XliftAmpHelper) { + loadScript( + global, + 'https://cdn.x-lift.jp/resources/common/xlift_amp.js', + () => { + if (!global.XliftAmpHelper) { + global.context.noContentAvailable(); + } else { + global.XliftAmpHelper.show(); + } + }, + () => { global.context.noContentAvailable(); } - else { - global.XliftAmpHelper.show(); - } - }, () => { - global.context.noContentAvailable(); - }); + ); } diff --git a/ads/yahoo.js b/ads/yahoo.js index d74d815095db..09ca195ff85e 100644 --- a/ads/yahoo.js +++ b/ads/yahoo.js @@ -17,9 +17,9 @@ import {validateData, writeScript} from '../3p/3p'; /** -* @param {!Window} global -* @param {!Object} data -*/ + * @param {!Window} global + * @param {!Object} data + */ export function yahoo(global, data) { validateData(data, ['sid', 'site', 'sa']); global.yadData = data; diff --git a/ads/yahoojp.js b/ads/yahoojp.js index 22d79ca7f9ef..a26a1c143c8e 100644 --- a/ads/yahoojp.js +++ b/ads/yahoojp.js @@ -23,6 +23,8 @@ import {validateData, writeScript} from '../3p/3p'; export function yahoojp(global, data) { validateData(data, ['yadsid'], []); global.yahoojpParam = data; - writeScript(global, - 'https://s.yimg.jp/images/listing/tool/yads/ydn/amp/amp.js'); + writeScript( + global, + 'https://s.yimg.jp/images/listing/tool/yads/ydn/amp/amp.js' + ); } diff --git a/ads/yandex.js b/ads/yandex.js index 19bc98ad6f6b..33bf5ced20dd 100644 --- a/ads/yandex.js +++ b/ads/yandex.js @@ -23,15 +23,13 @@ const renderTo = 'yandex_rtb'; * @param {!Object} data */ export function yandex(global, data) { - validateData( - data, - ['blockId'], - ['data', 'onRender', 'onError'] - ); + validateData(data, ['blockId'], ['data', 'onRender', 'onError']); addToQueue(global, data); - loadScript(global, - 'https://yastatic.net/partner-code/loaders/context_amp.js'); + loadScript( + global, + 'https://yastatic.net/partner-code/loaders/context_amp.js' + ); } /** @@ -41,30 +39,32 @@ export function yandex(global, data) { function addToQueue(global, data) { global[n] = global[n] || []; global[n].push(() => { - // Create container createContainer(global, renderTo); // Show Ad in container - global.Ya.Context.AdvManager.render({ - blockId: data.blockId, - statId: data.statId, - renderTo, - data: data.data, - async: true, - onRender: () => { - if (typeof data.onRender === 'function') { - data.onRender(); - } - global.context.renderStart(); + global.Ya.Context.AdvManager.render( + { + blockId: data.blockId, + statId: data.statId, + renderTo, + data: data.data, + async: true, + onRender: () => { + if (typeof data.onRender === 'function') { + data.onRender(); + } + global.context.renderStart(); + }, }, - }, () => { - if (typeof data.onError === 'function') { - data.onError(); - } else { - global.context.noContentAvailable(); + () => { + if (typeof data.onError === 'function') { + data.onError(); + } else { + global.context.noContentAvailable(); + } } - }); + ); }); } diff --git a/ads/yengo.js b/ads/yengo.js index 7c8cbf0198d9..ed4bd3651a14 100644 --- a/ads/yengo.js +++ b/ads/yengo.js @@ -23,13 +23,19 @@ import {loadScript, validateData} from '../3p/3p'; export function yengo(global, data) { validateData(data, ['blockId']); - const url = 'https://code.yengo.com/data/' + - encodeURIComponent(data['blockId']) + '.js?async=1&div=c'; - - loadScript(global, url, () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + const url = + 'https://code.yengo.com/data/' + + encodeURIComponent(data['blockId']) + + '.js?async=1&div=c'; + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/yieldbot.js b/ads/yieldbot.js index b0a2d456ae1d..69ebf23ed21e 100644 --- a/ads/yieldbot.js +++ b/ads/yieldbot.js @@ -36,10 +36,12 @@ export function yieldbot(global, data) { let dimensions; if (multiSizeDataStr) { - dimensions = getMultiSizeDimensions(multiSizeDataStr, - primaryWidth, - primaryHeight, - false); + dimensions = getMultiSizeDimensions( + multiSizeDataStr, + primaryWidth, + primaryHeight, + false + ); dimensions.unshift([primaryWidth, primaryHeight]); } else { dimensions = [[primaryWidth, primaryHeight]]; @@ -70,13 +72,16 @@ export function yieldbot(global, data) { } catch (e) { rethrowAsync(e); } - user().warn('AMP-AD', 'type="yieldbot" will no longer ' + - 'be supported starting on March 29, 2018.' + - ' Please use your amp-ad-network and RTC to configure a' + - ' Yieldbot callout vendor. Refer to' + - ' https://github.com/ampproject/amphtml/blob/master/' + - 'extensions/amp-a4a/rtc-publisher-implementation-guide.md' + - '#setting-up-rtc-config for more information.'); + user().warn( + 'AMP-AD', + 'type="yieldbot" will no longer ' + + 'be supported starting on March 29, 2018.' + + ' Please use your amp-ad-network and RTC to configure a' + + ' Yieldbot callout vendor. Refer to' + + ' https://github.com/ampproject/amphtml/blob/master/' + + 'extensions/amp-a4a/rtc-publisher-implementation-guide.md' + + '#setting-up-rtc-config for more information.' + ); }); }); } diff --git a/ads/yieldmo.js b/ads/yieldmo.js index da44aa770e9d..6cc66c83f1de 100644 --- a/ads/yieldmo.js +++ b/ads/yieldmo.js @@ -21,14 +21,13 @@ import {loadScript} from '../3p/3p'; * @param {!Object} data */ export function yieldmo(global, data) { - const ymElem = global.document.createElement('div'); ymElem.id = 'ym_' + data.ymid; ymElem.className = 'ym'; ymElem.dataset['ampEnabled'] = true; global.document.getElementById('c').appendChild(ymElem); - const swimLane = Math.round(5 * Math.random() / 3); + const swimLane = Math.round((5 * Math.random()) / 3); const ymJs = 'https://static.yieldmo.com/ym.' + swimLane + '.js'; loadScript(global, ymJs); diff --git a/ads/yieldone.js b/ads/yieldone.js index 7f4bb10e4243..14349502ef1a 100644 --- a/ads/yieldone.js +++ b/ads/yieldone.js @@ -1,29 +1,31 @@ /** - * 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. - */ + * 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 {validateData, writeScript} from '../3p/3p'; /** - * @param {!Window} global - * @param {!Object} data - */ + * @param {!Window} global + * @param {!Object} data + */ export function yieldone(global, data) { - validateData(data, ['pubid', 'pid','width', 'height'], []); + validateData(data, ['pubid', 'pid', 'width', 'height'], []); global.yieldoneParam = data; - writeScript(global, - 'https://img.ak.impact-ad.jp/ic/pone/commonjs/yone-amp.js'); + writeScript( + global, + 'https://img.ak.impact-ad.jp/ic/pone/commonjs/yone-amp.js' + ); } diff --git a/ads/yieldpro.js b/ads/yieldpro.js index f4935793609c..b46716bf2ee0 100644 --- a/ads/yieldpro.js +++ b/ads/yieldpro.js @@ -14,60 +14,63 @@ * limitations under the License. */ -import { - computeInMasterFrame, - loadScript, - validateData, -} from '../3p/3p'; +import {computeInMasterFrame, loadScript, validateData} from '../3p/3p'; /** * @param {!Window} global * @param {!Object} data */ export function yieldpro(global, data) { - - validateData(data, ['sectionId', 'slot', 'pubnetwork'], [ - 'instance', - 'custom', - 'adServerUrl', - 'cacheSafe', - 'pageIdModifier', - 'click3rd', - 'debugsrc', - ]); + validateData( + data, + ['sectionId', 'slot', 'pubnetwork'], + [ + 'instance', + 'custom', + 'adServerUrl', + 'cacheSafe', + 'pageIdModifier', + 'click3rd', + 'debugsrc', + ] + ); //TODO support dmp and cookie const SCRIPT_HOST = 'creatives.yieldpro.eu/showad_'; - let scriptUrl = 'https://' + SCRIPT_HOST + - data['pubnetwork'] + '.js'; + let scriptUrl = 'https://' + SCRIPT_HOST + data['pubnetwork'] + '.js'; if (data['debugsrc']) { scriptUrl = data['debugsrc']; } - computeInMasterFrame(global, 'yieldpro-request', done => { - let success = false; - const masterWin = this; - if (!masterWin.showadAMPAdapter) { - masterWin.showadAMPAdapter = { - registerSlot: () => {}, - }; - loadScript(this, scriptUrl, () => { - if (masterWin.showadAMPAdapter.inited) { - success = true; - } - done(success); - }); - } else { - done(true); - } - }, success => { - if (success) { - global.showadAMPAdapter = global.context.master.showadAMPAdapter; - global.showadAMPAdapter.registerSlot(data, global); - } else { - throw new Error('Yieldpro AdTag failed to load'); + computeInMasterFrame( + global, + 'yieldpro-request', + done => { + let success = false; + const masterWin = this; + if (!masterWin.showadAMPAdapter) { + masterWin.showadAMPAdapter = { + registerSlot: () => {}, + }; + loadScript(this, scriptUrl, () => { + if (masterWin.showadAMPAdapter.inited) { + success = true; + } + done(success); + }); + } else { + done(true); + } + }, + success => { + if (success) { + global.showadAMPAdapter = global.context.master.showadAMPAdapter; + global.showadAMPAdapter.registerSlot(data, global); + } else { + throw new Error('Yieldpro AdTag failed to load'); + } } - }); + ); } diff --git a/ads/zedo.js b/ads/zedo.js index 99486e469162..bb584ecd2387 100644 --- a/ads/zedo.js +++ b/ads/zedo.js @@ -16,31 +16,46 @@ import {loadScript, validateData} from '../3p/3p'; - /** * @param {!Window} global * @param {!Object} data */ export function zedo(global, data) { // check mandatory fields - validateData(data, - ['superId', 'network', 'placementId', 'channel', 'publisher', 'dim'], - ['charset', 'callback', 'renderer']); + validateData( + data, + ['superId', 'network', 'placementId', 'channel', 'publisher', 'dim'], + ['charset', 'callback', 'renderer'] + ); loadScript(global, 'https://ss3.zedo.com/gecko/tag/Gecko.amp.min.js', () => { const {ZGTag} = global; const charset = data.charset || ''; const callback = data.callback || function() {}; - const geckoTag = new ZGTag(data.superId, data.network, '', '', - charset, callback); + const geckoTag = new ZGTag( + data.superId, + data.network, + '', + '', + charset, + callback + ); geckoTag.setAMP(); // define placement - const placement = geckoTag.addPlacement(data.placementId, - data.channel, data.publisher, data.dim, data.width, data.height); + const placement = geckoTag.addPlacement( + data.placementId, + data.channel, + data.publisher, + data.dim, + data.width, + data.height + ); if (data.renderer) { for (const key in data.renderer) { - placement.includeRenderer(data.renderer[key].name, - data.renderer[key].value); + placement.includeRenderer( + data.renderer[key].name, + data.renderer[key].value + ); } } else { placement.includeRenderer('display', {}); diff --git a/ads/zen.js b/ads/zen.js index 38f81513ae24..0ea812b3dc67 100644 --- a/ads/zen.js +++ b/ads/zen.js @@ -24,9 +24,9 @@ const renderTo = 'zen-widget'; */ export function zen(global, data) { validateData( - data, - ['clid'], - ['size', 'orientation', 'successCallback', 'failCallback'] + data, + ['clid'], + ['size', 'orientation', 'successCallback', 'failCallback'] ); addToQueue(global, data); @@ -40,7 +40,6 @@ export function zen(global, data) { function addToQueue(global, data) { global[n] = global[n] || []; global[n].push(() => { - // Create container createContainer(global, renderTo); diff --git a/ads/zucks.js b/ads/zucks.js index 173812da0877..914bb393aebb 100644 --- a/ads/zucks.js +++ b/ads/zucks.js @@ -33,13 +33,13 @@ export function zucks(global, data) { container.appendChild(d); if (data['zoeMultiAd'] !== 'true') { - (global.gZgokZoeQueue = - (global.gZgokZoeQueue || [])).push({frameId}); + (global.gZgokZoeQueue = global.gZgokZoeQueue || []).push({frameId}); } - (global.gZgokZoeWidgetQueue = - (global.gZgokZoeWidgetQueue || [])) - .push({frameId, parent: `#${elementId}`}); + (global.gZgokZoeWidgetQueue = global.gZgokZoeWidgetQueue || []).push({ + frameId, + parent: `#${elementId}`, + }); }); } diff --git a/build-system/amp-cors.js b/build-system/amp-cors.js index 5b5d65c67f32..350b87db1c85 100644 --- a/build-system/amp-cors.js +++ b/build-system/amp-cors.js @@ -20,22 +20,31 @@ * https://goo.gl/F6uCAY * @type {RegExp} */ -const ORIGIN_REGEX = new RegExp('^http://localhost:8000|' + -'^http://.+\.localhost:8000|' + -'^https?://.+\.herokuapp\.com'); +const ORIGIN_REGEX = new RegExp( + '^http://localhost:8000|' + + '^http://.+.localhost:8000|' + + '^https?://.+.herokuapp.com' +); /** -* In practice this would be the publishers origin. -* Please see AMP CORS docs for more details: -* https://goo.gl/F6uCAY -* @type {RegExp} -*/ -const SOURCE_ORIGIN_REGEX = new RegExp('^http://localhost:8000|' + -'^http://.+\.localhost:8000|' + -'^https?://.+\.herokuapp\.com'); + * In practice this would be the publishers origin. + * Please see AMP CORS docs for more details: + * https://goo.gl/F6uCAY + * @type {RegExp} + */ +const SOURCE_ORIGIN_REGEX = new RegExp( + '^http://localhost:8000|' + + '^http://.+.localhost:8000|' + + '^https?://.+.herokuapp.com' +); -function assertCors(req, res, opt_validMethods, opt_exposeHeaders, - opt_ignoreMissingSourceOrigin) { +function assertCors( + req, + res, + opt_validMethods, + opt_exposeHeaders, + opt_ignoreMissingSourceOrigin +) { // Allow disable CORS check (iframe fixtures have origin 'about:srcdoc'). if (req.query.cors == '0') { return; @@ -62,8 +71,10 @@ function assertCors(req, res, opt_validMethods, opt_exposeHeaders, throw invalidOrigin; } - if (!opt_ignoreMissingSourceOrigin && - !SOURCE_ORIGIN_REGEX.test(req.query.__amp_source_origin)) { + if ( + !opt_ignoreMissingSourceOrigin && + !SOURCE_ORIGIN_REGEX.test(req.query.__amp_source_origin) + ) { res.statusCode = 500; res.end(JSON.stringify({message: invalidSourceOrigin})); throw invalidSourceOrigin; @@ -89,12 +100,17 @@ function enableCors(req, res, origin, opt_exposeHeaders) { if (origin) { res.setHeader('Access-Control-Allow-Origin', origin); } - res.setHeader('Access-Control-Expose-Headers', - ['AMP-Access-Control-Allow-Source-Origin'] - .concat(opt_exposeHeaders || []).join(', ')); + res.setHeader( + 'Access-Control-Expose-Headers', + ['AMP-Access-Control-Allow-Source-Origin'] + .concat(opt_exposeHeaders || []) + .join(', ') + ); if (req.query.__amp_source_origin) { - res.setHeader('AMP-Access-Control-Allow-Source-Origin', - req.query.__amp_source_origin); + res.setHeader( + 'AMP-Access-Control-Allow-Source-Origin', + req.query.__amp_source_origin + ); } } @@ -103,5 +119,6 @@ function getUrlPrefix(req) { } module.exports = { - enableCors, assertCors, + enableCors, + assertCors, }; diff --git a/build-system/amp.extern.js b/build-system/amp.extern.js index a2275189d096..437d1b2e9d6a 100644 --- a/build-system/amp.extern.js +++ b/build-system/amp.extern.js @@ -45,7 +45,7 @@ var FetchInitDef; */ var FetchRequestDef; -/** @constructor **/ +/** @constructor */ var FormDataWrapperInterface = function() {}; FormDataWrapperInterface.prototype.entries = function() {}; diff --git a/build-system/amp4test.js b/build-system/amp4test.js index 4e2a8d73c25e..9fe4504d4fed 100644 --- a/build-system/amp4test.js +++ b/build-system/amp4test.js @@ -18,8 +18,9 @@ const app = require('express').Router(); const cors = require('./amp-cors'); const minimist = require('minimist'); -const argv = minimist( - process.argv.slice(2), {boolean: ['strictBabelTransform']}); +const argv = minimist(process.argv.slice(2), { + boolean: ['strictBabelTransform'], +}); const multer = require('multer'); const path = require('path'); const {renderShadowViewer} = require('./shadow-viewer'); @@ -51,7 +52,7 @@ app.use('/compose-doc', function(req, res) { const {body, css, experiments, extensions, spec} = req.query; const compiled = process.env.SERVE_MODE == 'compiled'; - const frameHtml = (compiled) + const frameHtml = compiled ? 'dist.3p/current-min/frame.html' : 'dist.3p/current/frame.max.html'; @@ -60,8 +61,7 @@ app.use('/compose-doc', function(req, res) { const string = `"${experiments.split(',').join('","')}"`; // TODO: Why is setting localDev necessary? // `allow-doc-opt-in` enables any experiment to be enabled via doc opt-in. - experimentsBlock = - ` @@ -69,8 +69,7 @@ app.use('/compose-doc', function(req, res) { } // TODO: Do we need to inject amp-3p-iframe-src for non-ad tests? - const head = - `${experimentsBlock} + const head = `${experimentsBlock} `; const doc = composeDocument({ @@ -119,23 +118,20 @@ const bank = {}; * Deposit a request. An ID has to be specified. Will override previous request * if the same ID already exists. */ -app.use( - '/request-bank/:bid/deposit/:id/', - upload.array(), - (req, res) => { - cors.enableCors(req, res); - if (!bank[req.params.bid]) { - bank[req.params.bid] = {}; - } - const key = req.params.id; - log('SERVER-LOG [DEPOSIT]: ', key); - if (typeof bank[req.params.bid][key] === 'function') { - bank[req.params.bid][key](req); - } else { - bank[req.params.bid][key] = req; - } - res.end(); - }); +app.use('/request-bank/:bid/deposit/:id/', upload.array(), (req, res) => { + cors.enableCors(req, res); + if (!bank[req.params.bid]) { + bank[req.params.bid] = {}; + } + const key = req.params.id; + log('SERVER-LOG [DEPOSIT]: ', key); + if (typeof bank[req.params.bid][key] === 'function') { + bank[req.params.bid][key](req); + } else { + bank[req.params.bid][key] = req; + } + res.end(); +}); /** * Withdraw a request. If the request of the given ID is already in the bank, @@ -245,9 +241,9 @@ app.get('/a4a/:bid', (req, res) => { function composeDocument(config) { const {body, css, extensions, head, spec, mode} = config; - const m = (mode || process.env.SERVE_MODE); - const cdn = (m === 'cdn'); - const compiled = (m === 'compiled'); + const m = mode || process.env.SERVE_MODE; + const cdn = m === 'cdn'; + const compiled = m === 'compiled'; const cssTag = css ? `` : ''; @@ -258,22 +254,25 @@ function composeDocument(config) { switch (amp) { case 'amp': canonical = ''; - boilerplate = ''; - runtime = (cdn) + boilerplate = + ''; + runtime = cdn ? 'https://cdn.ampproject.org/v0.js' : `/dist/${compiled ? 'v0' : 'amp'}.js`; break; case 'amp4ads': canonical = ''; - boilerplate = ''; - runtime = (cdn) + boilerplate = + ''; + runtime = cdn ? 'https://cdn.ampproject.org/amp4ads-v0.js' : `/dist/${compiled ? 'amp4ads-v0' : 'amp-inabox'}.js`; break; case 'amp4email': canonical = ''; - boilerplate = ''; - runtime = (cdn) + boilerplate = + ''; + runtime = cdn ? 'https://cdn.ampproject.org/v0.js' : `/dist/${compiled ? 'v0' : 'amp'}.js`; break; @@ -285,19 +284,20 @@ function composeDocument(config) { // Generate extension `; - }).join('\n'); + extensionScripts = extensions + .map(extension => { + const tuple = extension.split(':'); + const name = tuple[0]; + const version = tuple[1] || '0.1'; + const src = cdn + ? `https://cdn.ampproject.org/v0/${name}-${version}.js` + : `/dist/v0/${name}-${version}.${compiled ? '' : 'max.'}js`; + return ``; + }) + .join('\n'); } - const topHalfOfHtml = - ` + const topHalfOfHtml = ` AMP TEST @@ -319,7 +319,8 @@ function composeDocument(config) { const start = topHalfOfHtml.indexOf(runtimeScript); let end = start + runtimeScript.length; - let customElements = [], extensionsMap = []; + let customElements = [], + extensionsMap = []; if (extensions) { end = topHalfOfHtml.indexOf(extensionScripts) + extensionScripts.length; // Filter out extensions that are not custom elements, e.g. amp-mustache. @@ -332,8 +333,7 @@ function composeDocument(config) { }; }); } - ampAdMeta = - ``; - + html` + + `; const AmpState = (id, state) => html` - `; - + +`; const ternaryExpr = (condition, onTrue, onFalse) => `${condition} ? ${onTrue} : ${onFalse}`; - const containsExpr = (haystack, needle, onTrue, onFalse) => ternaryExpr(`${haystack}.indexOf(${needle}) > -1`, onTrue, onFalse); - const ampStateKey = (...keys) => keys.join('.'); - const AmpDoc = ({body, css, head, canonical}) => { assert(canonical); return html` - + - - AMP Dev Server - - - ${css ? html`` : ''} - - ${boilerPlate} - - ${head || ''} - - - ${body} - - `; + + AMP Dev Server + + + ${css + ? html` + + ` + : ''} + + ${boilerPlate} + + ${head || ''} + + + ${body} + + + `; }; - const componentExtensionNameMapping = { 'amp-state': 'amp-bind', }; @@ -91,25 +95,27 @@ const componentExtensionNameMapping = { const componentExtensionName = tagName => componentExtensionNameMapping[tagName] || tagName; - -const addRequiredExtensionsToHead = (docStr, extensionConf = { - 'amp-mustache': {version: '0.2'}, -}) => { +const addRequiredExtensionsToHead = ( + docStr, + extensionConf = { + 'amp-mustache': {version: '0.2'}, + } +) => { const extensions = {}; const addExtension = (name, defaultConf = {}) => - extensions[name] = {name, ...defaultConf, ...(extensionConf[name] || {})}; + (extensions[name] = {name, ...defaultConf, ...(extensionConf[name] || {})}); const addTemplate = (name, defaultConf = {}) => addExtension(name, {isTemplate: true, ...defaultConf}); Array.from(matchIterator(componentTagNameRegex, docStr)) - .map(([unusedFullMatch, tagName]) => componentExtensionName(tagName)) - .forEach(addExtension); + .map(([unusedFullMatch, tagName]) => componentExtensionName(tagName)) + .forEach(addExtension); Array.from(matchIterator(templateTagTypeRegex, docStr)) - .map(([unusedFullMatch, type]) => type) - .forEach(addTemplate); + .map(([unusedFullMatch, type]) => type) + .forEach(addTemplate); // TODO(alanorozco): Too greedy. Parse "on" attributes instead. if (docStr.indexOf('AMP.setState') >= 0) { @@ -120,11 +126,13 @@ const addRequiredExtensionsToHead = (docStr, extensionConf = { addExtension('amp-form'); } - return docStr.replace(/\<\/head\>/i, headClosingTag => - joinFragments(Object.values(extensions), ExtensionScript) + headClosingTag); + return docStr.replace( + /\<\/head\>/i, + headClosingTag => + joinFragments(Object.values(extensions), ExtensionScript) + headClosingTag + ); }; - module.exports = { AmpDoc, AmpState, diff --git a/build-system/app-index/api/api.js b/build-system/app-index/api/api.js index 482257bc7793..a11cdd267bd3 100644 --- a/build-system/app-index/api/api.js +++ b/build-system/app-index/api/api.js @@ -23,11 +23,9 @@ const Fuse = require('fuse.js'); const path = require('path'); const {getListing} = require('../util/listing'); - // Sitting on /build-system/app-index/api, so we go back thrice for the root. const root = path.join(__dirname, '../../../'); - function searchListing(fileSet, opt_searchQuery) { if (!opt_searchQuery) { return fileSet; @@ -42,7 +40,6 @@ function searchListing(fileSet, opt_searchQuery) { return fuse.search(opt_searchQuery).map(i => fileSet[i]); } - async function handleListingRequest({query: {path, search}}, res) { try { assert(path); @@ -58,10 +55,8 @@ async function handleListingRequest({query: {path, search}}, res) { } } - function installExpressMiddleware(app) { app.get('/dashboard/api/listing', handleListingRequest); } - module.exports = {installExpressMiddleware}; diff --git a/build-system/app-index/boilerplate.js b/build-system/app-index/boilerplate.js index ddfea76a2672..337bfbe1e9d8 100644 --- a/build-system/app-index/boilerplate.js +++ b/build-system/app-index/boilerplate.js @@ -14,4 +14,5 @@ * limitations under the License. */ /* eslint-disable max-len */ -module.exports = ''; +module.exports = + ''; diff --git a/build-system/app-index/document-modes.js b/build-system/app-index/document-modes.js index 3038e461b3c4..9ed300ff1ac1 100644 --- a/build-system/app-index/document-modes.js +++ b/build-system/app-index/document-modes.js @@ -14,7 +14,6 @@ * limitations under the License. */ - module.exports = { 'standard': '/', 'shadow': '/shadow/', diff --git a/build-system/app-index/file-list.js b/build-system/app-index/file-list.js index 386aa48ae3b8..d36b58de1298 100644 --- a/build-system/app-index/file-list.js +++ b/build-system/app-index/file-list.js @@ -23,7 +23,6 @@ const {appendQueryParamsToUrl, replaceLeadingSlash} = require('./url'); const {html, joinFragments} = require('./html'); const {KeyValueOptions} = require('./form'); - const examplesPathRegex = /^\/examples\//; const htmlDocRegex = /\.html$/; @@ -37,74 +36,79 @@ const endpointStateId = 'listingEndpoint'; const endpointStateKey = 'src'; const endpointKey = ampStateKey(endpointStateId, endpointStateKey); - const FileListSearchInput = ({basepath}) => html` - `; - + })" + /> +`; const ExamplesDocumentModeSelect = () => html` `; - + +`; const linksToExample = (shouldContainBasepath, opt_name) => examplesPathRegex.test(shouldContainBasepath) && - htmlDocRegex.test(opt_name || shouldContainBasepath); - + htmlDocRegex.test(opt_name || shouldContainBasepath); const ExamplesSelectModeOptional = ({basepath, selectModePrefix}) => - !examplesPathRegex.test(basepath + '/') ? '' : ExamplesDocumentModeSelect({ - selectModePrefix, - }); - + !examplesPathRegex.test(basepath + '/') + ? '' + : ExamplesDocumentModeSelect({ + selectModePrefix, + }); const FileListItem = ({name, href, boundHref}) => - html``; - + html` + + `; const PlaceholderFileListItem = ({name, href, selectModePrefix}) => - linksToExample(href) ? - FileListItem({ - name, - href: selectModePrefix + replaceLeadingSlash(href, ''), - boundHref: `(${selectModeKey} || '${selectModePrefix}') + '${ - replaceLeadingSlash(href, '')}'`, - }) : - FileListItem({href, name}); - + linksToExample(href) + ? FileListItem({ + name, + href: selectModePrefix + replaceLeadingSlash(href, ''), + boundHref: `(${selectModeKey} || '${selectModePrefix}') + '${replaceLeadingSlash( + href, + '' + )}'`, + }) + : FileListItem({href, name}); const maybePrefixExampleDocHref = (basepath, name, selectModePrefix) => - (linksToExample(basepath, name) ? - replaceLeadingSlash(basepath, selectModePrefix) : - basepath) + - name; - + (linksToExample(basepath, name) + ? replaceLeadingSlash(basepath, selectModePrefix) + : basepath) + name; const FileListHeading = ({basepath, selectModePrefix}) => html`
@@ -119,66 +123,79 @@ const FileListHeading = ({basepath, selectModePrefix}) => html` ${ExamplesSelectModeOptional({basepath, selectModePrefix})} List root directory
- `; - + +`; const wrapFileList = rendered => html`
${rendered}
-
`; - + +`; const FileList = ({basepath, fileSet, selectModePrefix}) => - wrapFileList(joinFragments([ - AmpState(endpointStateId, { - [endpointStateKey]: endpoint({path: basepath}), - }), - - FileListHeading({basepath, selectModePrefix}), - - html` - -
Failed to load data.
- -
-
- ${joinFragments(fileSet, name => - PlaceholderFileListItem({ - name, - href: maybePrefixExampleDocHref(basepath, name, selectModePrefix), - selectModePrefix, - }))} -
-
- - + +
+ Show more +
+
+ `, + ]) + ); module.exports = {FileList}; diff --git a/build-system/app-index/form.js b/build-system/app-index/form.js index 94bcdd3b1c38..a4accb316a1f 100644 --- a/build-system/app-index/form.js +++ b/build-system/app-index/form.js @@ -18,15 +18,19 @@ const {html} = require('./html'); - -const Option = ({value, name}) => html``; - - -const KeyValueOptions = options => Object.keys(options).map(name => - Option({ - name, - value: options[name], - })).join(''); - +const Option = ({value, name}) => + html` + + `; + +const KeyValueOptions = options => + Object.keys(options) + .map(name => + Option({ + name, + value: options[name], + }) + ) + .join(''); module.exports = {Option, KeyValueOptions}; diff --git a/build-system/app-index/header-links.js b/build-system/app-index/header-links.js index 9586857fc624..e2956a9559ea 100644 --- a/build-system/app-index/header-links.js +++ b/build-system/app-index/header-links.js @@ -17,7 +17,8 @@ module.exports = [ { 'name': 'Developing', - 'href': 'https://' + + 'href': + 'https://' + 'github.com/ampproject/amphtml/blob/master/contributing/DEVELOPING.md', }, { diff --git a/build-system/app-index/html.js b/build-system/app-index/html.js index cf311af0a1e3..87c1786a1a98 100644 --- a/build-system/app-index/html.js +++ b/build-system/app-index/html.js @@ -16,7 +16,6 @@ const identity = a => a; - /** * Takes a set of HTML fragments and concatenates them. * @param {!Array} fragments @@ -27,7 +26,6 @@ const identity = a => a; const joinFragments = (fragments, renderer = identity) => fragments.map(renderer).join(''); - /** * pass-through for syntax highlighting * @param {!Array} strings @@ -37,5 +35,4 @@ const joinFragments = (fragments, renderer = identity) => const html = (strings, ...values) => joinFragments(strings, (string, i) => string + (values[i] || '')); - module.exports = {html, joinFragments}; diff --git a/build-system/app-index/proxy-form.js b/build-system/app-index/proxy-form.js index 20dcf64c2d10..830aaa551eb6 100644 --- a/build-system/app-index/proxy-form.js +++ b/build-system/app-index/proxy-form.js @@ -19,28 +19,34 @@ const documentModes = require('./document-modes'); const {html} = require('./html'); const {KeyValueOptions} = require('./form'); - -module.exports = () => html`
+module.exports = () => html` +
Takes canonical, AMPHTML and Google viewer URLs. - + What's this?
-
`; +
+`; diff --git a/build-system/app-index/regex.js b/build-system/app-index/regex.js index f42d89f1fb57..f4f202d75ea3 100644 --- a/build-system/app-index/regex.js +++ b/build-system/app-index/regex.js @@ -14,7 +14,6 @@ * limitations under the License. */ - function* matchIterator(regex, subject) { let match = regex.exec(subject); while (match != null) { @@ -23,5 +22,4 @@ function* matchIterator(regex, subject) { } } - module.exports = {matchIterator}; diff --git a/build-system/app-index/settings.js b/build-system/app-index/settings.js index 9fb4373476e1..b8363c0ed5ef 100644 --- a/build-system/app-index/settings.js +++ b/build-system/app-index/settings.js @@ -35,64 +35,75 @@ const serveModes = [ }, ]; - const SelectorBlock = ({id, value, selected, children}) => html` -
+
${children} -
`; - +
+`; const ServeModeSelector = ({serveMode}) => html`
+ id="serve-mode-form" + > + name="mode" + > ${joinFragments(serveModes, ({value, description}) => SelectorBlock({ id: `serve_mode_${value}`, value, selected: serveMode == value, - children: html`${value} -

${description}

`, - }))} + children: html` + ${value} +

${description}

+ `, + }) + )}
-
`; - + +`; -const SettingsOpenButton = () => html`
html` +
+ class="settings-cog-icon icon" + > Settings -
`; +
+`; - -const SettingsCloseButton = () => html`
html` +
+ class="close-icon icon" + > Close Settings -
`; - +
+`; -const SettingsModal = ({serveMode}) => html` -
+const SettingsModal = ({serveMode}) => html` + +
${SettingsCloseButton()} @@ -106,7 +117,7 @@ const SettingsModal = ({serveMode}) => html`
- `; - + +`; module.exports = {SettingsModal, SettingsOpenButton}; diff --git a/build-system/app-index/template.js b/build-system/app-index/template.js index 368dee2fd157..db23a5ed37d2 100644 --- a/build-system/app-index/template.js +++ b/build-system/app-index/template.js @@ -26,14 +26,13 @@ const {FileList} = require('./file-list'); const {html, joinFragments} = require('./html'); const {SettingsModal, SettingsOpenButton} = require('./settings'); - const HeaderLink = ({name, href, divider}) => html`
  • ${name} -
  • `; - + +`; const Header = ({isMainPage, links}) => html`
    @@ -43,31 +42,26 @@ const Header = ({isMainPage, links}) => html`
      ${joinFragments(links, ({name, href, divider}, i) => - HeaderLink({ - divider: divider || i == links.length - 1, - name, - href, - }))} + HeaderLink({ + divider: divider || i == links.length - 1, + name, + href, + }) + )}
    • ${SettingsOpenButton()}
    - `; - + +`; -const HeaderBackToMainLink = () => html`← Back to main`; - - -const ProxyFormOptional = ({isMainPage}) => isMainPage ? ProxyForm() : ''; +const HeaderBackToMainLink = () => + html` + ← Back to main + `; +const ProxyFormOptional = ({isMainPage}) => (isMainPage ? ProxyForm() : ''); function renderTemplate(opt_params) { - const { - basepath, - css, - isMainPage, - fileSet, - serveMode, - selectModePrefix, - } = { + const {basepath, css, isMainPage, fileSet, serveMode, selectModePrefix} = { basepath: '/', isMainPage: false, fileSet: [], @@ -77,17 +71,21 @@ function renderTemplate(opt_params) { }; const body = joinFragments([ - html`
    - ${Header({isMainPage, links: headerLinks})} - ${ProxyFormOptional({isMainPage})} -
    `, + html` +
    + ${Header({isMainPage, links: headerLinks})} + ${ProxyFormOptional({isMainPage})} +
    + `, FileList({basepath, selectModePrefix, fileSet}), - html`
    - Built with 💙 by - the AMP Project. -
    `, + html` +
    + Built with 💙 by + the AMP Project. +
    + `, SettingsModal({serveMode}), ]); @@ -97,5 +95,4 @@ function renderTemplate(opt_params) { return addRequiredExtensionsToHead(docWithoutExtensions); } - module.exports = {renderTemplate}; diff --git a/build-system/app-index/url.js b/build-system/app-index/url.js index 5c64de143b0c..91a5fa43e903 100644 --- a/build-system/app-index/url.js +++ b/build-system/app-index/url.js @@ -14,17 +14,17 @@ * limitations under the License. */ - const appendQueryParamsToUrl = (url, params) => - url + '?' + Object.keys(params).map(k => - `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`).join('&'); - + url + + '?' + + Object.keys(params) + .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`) + .join('&'); const leadingSlashRegex = /^\//; const replaceLeadingSlash = (subject, replacement) => subject.replace(leadingSlashRegex, replacement); - module.exports = { appendQueryParamsToUrl, replaceLeadingSlash, diff --git a/build-system/app-test.js b/build-system/app-test.js index b5a7a0cd5658..6c3b4dca32e0 100644 --- a/build-system/app-test.js +++ b/build-system/app-test.js @@ -47,10 +47,7 @@ function appTestEndpoints(app) { const nextWeek = new Date(new Date(date).setDate(date.getDate() + 7)); const twoWeeks = new Date(new Date(date).setDate(date.getDate() + 14)); - const blocked = [ - getISO8601Date(nextWeek), - getISO8601Date(twoWeeks), - ]; + const blocked = [getISO8601Date(nextWeek), getISO8601Date(twoWeeks)]; res.json({ blocked, diff --git a/build-system/app-utils.js b/build-system/app-utils.js index 6d6da6d4936f..a87a6570f4e1 100644 --- a/build-system/app-utils.js +++ b/build-system/app-utils.js @@ -27,24 +27,30 @@ const replaceUrls = (mode, file, hostName, inabox, storyV1) => { // TODO:(ccordry) remove this when story 0.1 is deprecated if (storyV1) { file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\/amp-story-0\.1\.js/g, - hostName + '/dist/v0/amp-story-1.0.max.js'); + /https:\/\/cdn\.ampproject\.org\/v0\/amp-story-0\.1\.js/g, + hostName + '/dist/v0/amp-story-1.0.max.js' + ); } file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\.js/g, - hostName + '/dist/amp.js'); + /https:\/\/cdn\.ampproject\.org\/v0\.js/g, + hostName + '/dist/amp.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/shadow-v0\.js/g, - hostName + '/dist/amp-shadow.js'); + /https:\/\/cdn\.ampproject\.org\/shadow-v0\.js/g, + hostName + '/dist/amp-shadow.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.js/g, - hostName + '/dist/amp-inabox.js'); + /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.js/g, + hostName + '/dist/amp-inabox.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.js/g, - hostName + '/dist/video-iframe-integration.js'); + /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.js/g, + hostName + '/dist/video-iframe-integration.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, - hostName + '/dist/v0/$1.max.js'); + /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, + hostName + '/dist/v0/$1.max.js' + ); if (inabox) { let filename; if (inabox == '1') { @@ -57,23 +63,29 @@ const replaceUrls = (mode, file, hostName, inabox, storyV1) => { } } else if (mode == 'compiled') { file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\.js/g, - hostName + '/dist/v0.js'); + /https:\/\/cdn\.ampproject\.org\/v0\.js/g, + hostName + '/dist/v0.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/shadow-v0\.js/g, - hostName + '/dist/shadow-v0.js'); + /https:\/\/cdn\.ampproject\.org\/shadow-v0\.js/g, + hostName + '/dist/shadow-v0.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.js/g, - hostName + '/dist/amp4ads-v0.js'); + /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.js/g, + hostName + '/dist/amp4ads-v0.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.js/g, - hostName + '/dist/video-iframe-integration-v0.js'); + /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.js/g, + hostName + '/dist/video-iframe-integration-v0.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, - hostName + '/dist/v0/$1.js'); + /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, + hostName + '/dist/v0/$1.js' + ); file = file.replace( - /\/dist.3p\/current\/(.*)\.max.html/g, - hostName + '/dist.3p/current-min/$1.html'); + /\/dist.3p\/current\/(.*)\.max.html/g, + hostName + '/dist.3p/current-min/$1.html' + ); if (inabox) { let filename; if (inabox == '1') { diff --git a/build-system/app.js b/build-system/app.js index f0ea7eee5cbb..1b7f052a7ad2 100644 --- a/build-system/app.js +++ b/build-system/app.js @@ -55,7 +55,8 @@ app.use('/list/', require('./routes/list')); app.use((req, res, next) => { if (req.query.csp) { res.set({ - 'content-security-policy': "default-src * blob: data:; script-src https://cdn.ampproject.org/rtv/ https://cdn.ampproject.org/v0.js https://cdn.ampproject.org/v0/ https://cdn.ampproject.org/viewer/ http://localhost:8000 https://localhost:8000; object-src 'none'; style-src 'unsafe-inline' https://cdn.ampproject.org/rtv/ https://cdn.materialdesignicons.com https://cloud.typography.com https://fast.fonts.net https://fonts.googleapis.com https://maxcdn.bootstrapcdn.com https://p.typekit.net https://use.fontawesome.com https://use.typekit.net; report-uri https://csp-collector.appspot.com/csp/amp", + 'content-security-policy': + "default-src * blob: data:; script-src https://cdn.ampproject.org/rtv/ https://cdn.ampproject.org/v0.js https://cdn.ampproject.org/v0/ https://cdn.ampproject.org/viewer/ http://localhost:8000 https://localhost:8000; object-src 'none'; style-src 'unsafe-inline' https://cdn.ampproject.org/rtv/ https://cdn.materialdesignicons.com https://cloud.typography.com https://fast.fonts.net https://fonts.googleapis.com https://maxcdn.bootstrapcdn.com https://p.typekit.net https://use.fontawesome.com https://use.typekit.net; report-uri https://csp-collector.appspot.com/csp/amp", }); } next(); @@ -85,7 +86,6 @@ if (!global.AMP_TESTING) { devDashboard.installExpressMiddleware(app); } - // Changes the current serve mode via query param // e.g. /serve_mode_change?mode=(default|compiled|cdn) // (See ./app-index/settings.js) @@ -103,7 +103,6 @@ app.get('/serve_mode_change', (req, res) => { res.status(400).json({ok: false}); }); - // Redirects to a proxied document with optional mode through query params. // // Mode can be one of: @@ -122,11 +121,10 @@ app.get('/serve_mode_change', (req, res) => { // // This passthrough is useful to generate the URL from
    values, // (See ./app-index/proxy-form.js) -app.get('/proxy', async(req, res, next) => { +app.get('/proxy', async (req, res, next) => { const {mode, url} = req.query; const urlSuffixClearPrefixReStr = - '^https?://' + - '((www\.)?google\.(com?|[a-z]{2}|com?\.[a-z]{2}|cat)/amp/s/)?'; + '^https?://((www.)?google.(com?|[a-z]{2}|com?.[a-z]{2}|cat)/amp/s/)?'; const urlSuffix = url.replace(new RegExp(urlSuffixClearPrefixReStr, 'i'), ''); try { @@ -141,7 +139,6 @@ app.get('/proxy', async(req, res, next) => { } }); - /** * Resolves an AMPHTML URL from a canonical URL. If AMPHTML is canonical, same * URL is returned. @@ -154,9 +151,10 @@ function requestAmphtmlDocUrl(urlSuffix, protocol = 'https') { console.log(`Fetching URL: ${defaultUrl}`); return new Promise((resolve, reject) => { request(defaultUrl, (error, response, body) => { - if (error || (response && ( - response.statusCode < 200 || response.statusCode >= 300))) { - + if ( + error || + (response && (response.statusCode < 200 || response.statusCode >= 300)) + ) { if (protocol == 'https') { return requestAmphtmlDocUrl(urlSuffix, 'http'); } @@ -181,25 +179,22 @@ function requestAmphtmlDocUrl(urlSuffix, protocol = 'https') { * integration tests. Using this to mock * out the recaptcha api. */ -app.get( - '/dist.3p/*/recaptcha.*html', - recaptchaFrameRequestHandler -); -app.use( - '/recaptcha', - recaptchaRouter -); +app.get('/dist.3p/*/recaptcha.*html', recaptchaFrameRequestHandler); +app.use('/recaptcha', recaptchaRouter); // Deprecate usage of .min.html/.max.html -app.get([ - '/examples/*.(min|max).html', - '/test/manual/*.(min|max).html', - '/test/fixtures/e2e/*/*.(min|max).html', - '/dist/cache-sw.(min|max).html', -], (req, res) => { - const filePath = req.url; - res.send(generateInfo(filePath)); -}); +app.get( + [ + '/examples/*.(min|max).html', + '/test/manual/*.(min|max).html', + '/test/fixtures/e2e/*/*.(min|max).html', + '/dist/cache-sw.(min|max).html', + ], + (req, res) => { + const filePath = req.url; + res.send(generateInfo(filePath)); + } +); app.use('/pwa', (req, res) => { let file; @@ -280,14 +275,12 @@ app.use('/form/html/post', (req, res) => { }); }); - app.use('/form/redirect-to/post', (req, res) => { cors.assertCors(req, res, ['POST'], ['AMP-Redirect-To']); res.setHeader('AMP-Redirect-To', 'https://google.com'); res.end('{}'); }); - app.use('/form/echo-json/post', (req, res) => { cors.assertCors(req, res, ['POST']); const form = new formidable.IncomingForm(); @@ -305,21 +298,28 @@ app.use('/form/json/poll1', (req, res) => { const form = new formidable.IncomingForm(); form.parse(req, () => { res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - result: [{ - answer: 'Penguins', - percentage: new Array(77), - }, { - answer: 'Ostriches', - percentage: new Array(8), - }, { - answer: 'Kiwis', - percentage: new Array(14), - }, { - answer: 'Wekas', - percentage: new Array(1), - }], - })); + res.end( + JSON.stringify({ + result: [ + { + answer: 'Penguins', + percentage: new Array(77), + }, + { + answer: 'Ostriches', + percentage: new Array(8), + }, + { + answer: 'Kiwis', + percentage: new Array(14), + }, + { + answer: 'Wekas', + percentage: new Array(1), + }, + ], + }) + ); }); }); @@ -351,7 +351,6 @@ app.use('/form/search-html/get', (req, res) => { `); }); - app.use('/form/search-json/get', (req, res) => { cors.assertCors(req, res, ['GET']); res.json({ @@ -361,8 +360,17 @@ app.use('/form/search-json/get', (req, res) => { }); }); -const autocompleteColors = ['red', 'orange', 'yellow', 'green', 'blue', - 'purple', 'pink', 'black', 'white']; +const autocompleteColors = [ + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'purple', + 'pink', + 'black', + 'white', +]; app.use('/form/autocomplete/query', (req, res) => { const query = req.query.q; @@ -370,32 +378,63 @@ app.use('/form/autocomplete/query', (req, res) => { res.json({items: autocompleteColors}); } else { const lowerCaseQuery = query.toLowerCase(); - const filtered = autocompleteColors.filter( - l => l.toLowerCase().includes(lowerCaseQuery)); + const filtered = autocompleteColors.filter(l => + l.toLowerCase().includes(lowerCaseQuery) + ); res.json({items: filtered}); } }); -const autosuggestLanguages = ['ActionScript', 'AppleScript', 'Asp', 'BASIC', - 'C', 'C++', 'Clojure', 'COBOL', 'ColdFusion', 'Erlang', 'Fortran', 'Go', - 'Groovy', 'Haskell', 'Java', 'JavaScript', 'Lisp', 'Perl', 'PHP', 'Python', - 'Ruby', 'Scala', 'Scheme']; +const autosuggestLanguages = [ + 'ActionScript', + 'AppleScript', + 'Asp', + 'BASIC', + 'C', + 'C++', + 'Clojure', + 'COBOL', + 'ColdFusion', + 'Erlang', + 'Fortran', + 'Go', + 'Groovy', + 'Haskell', + 'Java', + 'JavaScript', + 'Lisp', + 'Perl', + 'PHP', + 'Python', + 'Ruby', + 'Scala', + 'Scheme', +]; app.use('/form/autosuggest/query', (req, res) => { cors.assertCors(req, res, ['GET']); const MAX_RESULTS = 4; const query = req.query.q; if (!query) { - res.json({items: [{ - results: autosuggestLanguages.slice(0, MAX_RESULTS), - }]}); + res.json({ + items: [ + { + results: autosuggestLanguages.slice(0, MAX_RESULTS), + }, + ], + }); } else { const lowerCaseQuery = query.toLowerCase(); - const filtered = autosuggestLanguages.filter( - l => l.toLowerCase().includes(lowerCaseQuery)); - res.json({items: [{ - results: filtered.slice(0, MAX_RESULTS)}, - ]}); + const filtered = autosuggestLanguages.filter(l => + l.toLowerCase().includes(lowerCaseQuery) + ); + res.json({ + items: [ + { + results: filtered.slice(0, MAX_RESULTS), + }, + ], + }); } }); @@ -429,19 +468,21 @@ app.use('/form/verify-search-json/post', (req, res) => { if (fields.city !== 'Mountain View' || fields.zip !== '94043') { errors.push({ name: 'city', - message: 'City doesn\'t match zip (Mountain View and 94043)', + message: "City doesn't match zip (Mountain View and 94043)", }); } if (errors.length === 0) { - res.end(JSON.stringify({ - results: [ - {title: 'Result 1'}, - {title: 'Result 2'}, - {title: 'Result 3'}, - ], - committed: true, - })); + res.end( + JSON.stringify({ + results: [ + {title: 'Result 1'}, + {title: 'Result 2'}, + {title: 'Result 3'}, + ], + committed: true, + }) + ); } else { res.statusCode = 400; res.end(JSON.stringify({verifyErrors: errors})); @@ -450,8 +491,10 @@ app.use('/form/verify-search-json/post', (req, res) => { }); app.use('/share-tracking/get-outgoing-fragment', (req, res) => { - res.setHeader('AMP-Access-Control-Allow-Source-Origin', - req.protocol + '://' + req.headers.host); + res.setHeader( + 'AMP-Access-Control-Allow-Source-Origin', + req.protocol + '://' + req.headers.host + ); res.json({ fragment: '54321', }); @@ -460,17 +503,20 @@ app.use('/share-tracking/get-outgoing-fragment', (req, res) => { // Fetches an AMP document from the AMP proxy and replaces JS // URLs, so that they point to localhost. function proxyToAmpProxy(req, res, mode) { - const url = 'https://cdn.ampproject.org/' - + (req.query['amp_js_v'] ? 'v' : 'c') - + req.url; + const url = + 'https://cdn.ampproject.org/' + + (req.query['amp_js_v'] ? 'v' : 'c') + + req.url; console.log('Fetching URL: ' + url); request(url, function(error, response, body) { body = body - // Unversion URLs. - .replace(/https\:\/\/cdn\.ampproject\.org\/rtv\/\d+\//g, - 'https://cdn.ampproject.org/') - // href pointing to the proxy, so that images, etc. still work. - .replace('', ''); + // Unversion URLs. + .replace( + /https\:\/\/cdn\.ampproject\.org\/rtv\/\d+\//g, + 'https://cdn.ampproject.org/' + ) + // href pointing to the proxy, so that images, etc. still work. + .replace('', ''); const inabox = req.query['inabox']; // TODO(ccordry): Remove this when story v01 is depricated. const storyV1 = req.query['story_v'] === '1'; @@ -485,7 +531,6 @@ function proxyToAmpProxy(req, res, mode) { }); } - let itemCtr = 2; const doctype = '\n'; const liveListDocs = Object.create(null); @@ -500,7 +545,7 @@ app.use('/examples/live-list-update(-reverse)?.amp.html', (req, res, next) => { // When we already have state in memory and user refreshes page, we flush // the dom we maintain on the server. if (!('amp_latest_update_time' in req.query) && liveListDoc) { - let outerHTML = liveListDoc.documentElement./*OK*/outerHTML; + let outerHTML = liveListDoc.documentElement./*OK*/ outerHTML; outerHTML = replaceUrls(mode, outerHTML); res.send(`${doctype}${outerHTML}`); return; @@ -509,8 +554,9 @@ app.use('/examples/live-list-update(-reverse)?.amp.html', (req, res, next) => { const liveListUpdateFullPath = `${pc.cwd()}${req.baseUrl}`; console.log('liveListUpdateFullPath', liveListUpdateFullPath); const liveListFile = fs.readFileSync(liveListUpdateFullPath); - liveListDoc = liveListDocs[req.baseUrl] = new jsdom.JSDOM(liveListFile) - .window.document; + liveListDoc = liveListDocs[req.baseUrl] = new jsdom.JSDOM( + liveListFile + ).window.document; liveListDoc.ctr = 0; } const liveList = liveListDoc.querySelector('#my-live-list'); @@ -519,35 +565,38 @@ app.use('/examples/live-list-update(-reverse)?.amp.html', (req, res, next) => { const pagination = liveListDoc.querySelector('#my-live-list [pagination]'); const item1 = liveList.querySelector('#list-item-1'); if (liveListDoc.ctr != 0) { - if (Math.random() < .8) { + if (Math.random() < 0.8) { // Always run a replace on the first item liveListReplace(item1); - if (Math.random() < .5) { + if (Math.random() < 0.5) { liveListTombstone(liveList); } - if (Math.random() < .8) { + if (Math.random() < 0.8) { liveListInsert(liveList, item1); } pagination.textContent = ''; - const liveChildren = [].slice.call(items.children) - .filter(x => !x.hasAttribute('data-tombstone')); + const liveChildren = [].slice + .call(items.children) + .filter(x => !x.hasAttribute('data-tombstone')); const pageCount = Math.ceil(liveChildren.length / perPage); const pageListItems = Array.apply(null, Array(pageCount)) - .map((_, i) => `
  • ${i + 1}
  • `).join(''); - const newPagination = ''; - pagination./*OK*/innerHTML = newPagination; + .map((_, i) => `
  • ${i + 1}
  • `) + .join(''); + const newPagination = + ''; + pagination./*OK*/ innerHTML = newPagination; } else { // Sometimes we want an empty response to simulate no changes. res.send(`${doctype}`); return; } } - let outerHTML = liveListDoc.documentElement./*OK*/outerHTML; + let outerHTML = liveListDoc.documentElement./*OK*/ outerHTML; outerHTML = replaceUrls(mode, outerHTML); liveListDoc.ctr++; res.send(`${doctype}${outerHTML}`); @@ -577,19 +626,19 @@ function liveListTombstone(liveList) { // We can tombstone any list item except item-1 since we always do a // replace example on item-1. if (tombstoneId != 1) { - const item = liveList./*OK*/querySelector(`#list-item-${tombstoneId}`); + const item = liveList./*OK*/ querySelector(`#list-item-${tombstoneId}`); if (item) { item.setAttribute('data-tombstone', ''); } } } - // Generate a random number between min and max // Value is inclusive of both min and max values. function range(min, max) { - const values = - Array.apply(null, new Array(max - min + 1)).map((_, i) => min + i); + const values = Array.apply(null, new Array(max - min + 1)).map( + (_, i) => min + i + ); return values[Math.round(Math.random() * (max - min))]; } @@ -603,12 +652,18 @@ function getLiveBlogItem() { // Generate a 3 to 7 worded headline const headline = bacon(range(3, 7)); const numOfParagraphs = range(1, 2); - const body = Array.apply(null, new Array(numOfParagraphs)).map(() => { - return `

    ${bacon(range(50, 90))}

    `; - }).join('\n'); + const body = Array.apply(null, new Array(numOfParagraphs)) + .map(() => { + return `

    ${bacon(range(50, 90))}

    `; + }) + .join('\n'); const img = ` `; @@ -651,9 +706,11 @@ function getLiveBlogItemWithBindAttributes() { const now = Date.now(); // Generate a 3 to 7 worded headline const numOfParagraphs = range(1, 2); - const body = Array.apply(null, new Array(numOfParagraphs)).map(() => { - return `

    ${bacon(range(50, 90))}

    `; - }).join('\n'); + const body = Array.apply(null, new Array(numOfParagraphs)) + .map(() => { + return `

    ${bacon(range(50, 90))}

    `; + }) + .join('\n'); return ` @@ -670,25 +727,26 @@ function getLiveBlogItemWithBindAttributes() { `; } -app.use('/examples/live-blog(-non-floating-button)?.amp.html', - (req, res, next) => { - if ('amp_latest_update_time' in req.query) { - res.setHeader('Content-Type', 'text/html'); - res.end(getLiveBlogItem()); - return; - } - next(); - }); +app.use( + '/examples/live-blog(-non-floating-button)?.amp.html', + (req, res, next) => { + if ('amp_latest_update_time' in req.query) { + res.setHeader('Content-Type', 'text/html'); + res.end(getLiveBlogItem()); + return; + } + next(); + } +); -app.use('/examples/bind/live-list.amp.html', - (req, res, next) => { - if ('amp_latest_update_time' in req.query) { - res.setHeader('Content-Type', 'text/html'); - res.end(getLiveBlogItemWithBindAttributes()); - return; - } - next(); - }); +app.use('/examples/bind/live-list.amp.html', (req, res, next) => { + if ('amp_latest_update_time' in req.query) { + res.setHeader('Content-Type', 'text/html'); + res.end(getLiveBlogItemWithBindAttributes()); + return; + } + next(); +}); app.use('/impression-proxy/', (req, res) => { cors.assertCors(req, res, ['GET']); @@ -769,16 +827,19 @@ app.get('/a4a_template/*', (req, res) => { res.end('Invalid path: ' + req.path); return; } - const filePath = `${pc.cwd()}/extensions/amp-ad-network-${match[1]}-impl/` + - `0.1/data/${match[2]}.template`; - fs.readFileAsync(filePath).then(file => { - res.setHeader('Content-Type', 'application/json'); - res.setHeader('AMP-template-amp-creative', 'amp-mustache'); - res.end(file); - }).error(() => { - res.status(404); - res.end('Not found: ' + filePath); - }); + const filePath = + `${pc.cwd()}/extensions/amp-ad-network-${match[1]}-impl/` + + `0.1/data/${match[2]}.template`; + fs.readFileAsync(filePath) + .then(file => { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('AMP-template-amp-creative', 'amp-mustache'); + res.end(file); + }) + .error(() => { + res.status(404); + res.end('Not found: ' + filePath); + }); }); // Returns a document that echoes any post messages received from parent. @@ -789,7 +850,7 @@ app.get('/a4a_template/*', (req, res) => { app.get('/iframe-echo-message', (req, res) => { const {message} = req.query; res.send( - ` + ` - `); + ` + ); }); // A4A envelope. @@ -815,8 +877,7 @@ app.use('/a4a(|-3p)/', (req, res) => { let adUrl = req.url; const templatePath = '/build-system/server-a4a-template.html'; const urlPrefix = getUrlPrefix(req); - if (!adUrl.startsWith('/proxy') && - urlPrefix.indexOf('//localhost') != -1) { + if (!adUrl.startsWith('/proxy') && urlPrefix.indexOf('//localhost') != -1) { // This is a special case for testing. `localhost` URLs are transformed to // `ads.localhost` to ensure that the iframe is fully x-origin. adUrl = urlPrefix.replace('localhost', 'ads.localhost') + adUrl; @@ -824,12 +885,12 @@ app.use('/a4a(|-3p)/', (req, res) => { adUrl = addQueryParam(adUrl, 'inabox', 1); fs.readFileAsync(pc.cwd() + templatePath, 'utf8').then(template => { const result = template - .replace(/CHECKSIG/g, force3p || '') - .replace(/DISABLE3PFALLBACK/g, !force3p) - .replace(/OFFSET/g, req.query.offset || '0px') - .replace(/AD_URL/g, adUrl) - .replace(/AD_WIDTH/g, req.query.width || '300') - .replace(/AD_HEIGHT/g, req.query.height || '250'); + .replace(/CHECKSIG/g, force3p || '') + .replace(/DISABLE3PFALLBACK/g, !force3p) + .replace(/OFFSET/g, req.query.offset || '0px') + .replace(/AD_URL/g, adUrl) + .replace(/AD_WIDTH/g, req.query.width || '300') + .replace(/AD_HEIGHT/g, req.query.height || '250'); res.end(result); }); }); @@ -842,8 +903,10 @@ app.use('/inabox/:version/', (req, res) => { let adUrl = req.url; const templatePath = '/build-system/server-inabox-template.html'; const urlPrefix = getUrlPrefix(req); - if (!adUrl.startsWith('/proxy') && // Ignore /proxy - urlPrefix.indexOf('//localhost') != -1) { + if ( + !adUrl.startsWith('/proxy') && // Ignore /proxy + urlPrefix.indexOf('//localhost') != -1 + ) { // This is a special case for testing. `localhost` URLs are transformed to // `ads.localhost` to ensure that the iframe is fully x-origin. adUrl = urlPrefix.replace('localhost', 'ads.localhost') + adUrl; @@ -851,10 +914,10 @@ app.use('/inabox/:version/', (req, res) => { adUrl = addQueryParam(adUrl, 'inabox', req.params['version']); fs.readFileAsync(pc.cwd() + templatePath, 'utf8').then(template => { const result = template - .replace(/AD_URL/g, adUrl) - .replace(/OFFSET/g, req.query.offset || '0px') - .replace(/AD_WIDTH/g, req.query.width || '300') - .replace(/AD_HEIGHT/g, req.query.height || '250'); + .replace(/AD_URL/g, adUrl) + .replace(/OFFSET/g, req.query.offset || '0px') + .replace(/AD_WIDTH/g, req.query.width || '300') + .replace(/AD_HEIGHT/g, req.query.height || '250'); res.end(result); }); }); @@ -864,14 +927,16 @@ app.use('/examples/analytics.config.json', (req, res, next) => { next(); }); -app.use(['/examples/*', '/extensions/*', '/test/manual/*'], - (req, res, next) => { - const sourceOrigin = req.query['__amp_source_origin']; - if (sourceOrigin) { - res.setHeader('AMP-Access-Control-Allow-Source-Origin', sourceOrigin); - } - next(); - }); +app.use( + ['/examples/*', '/extensions/*', '/test/manual/*'], + (req, res, next) => { + const sourceOrigin = req.query['__amp_source_origin']; + if (sourceOrigin) { + res.setHeader('AMP-Access-Control-Allow-Source-Origin', sourceOrigin); + } + next(); + } +); /** * Append ?sleep=5 to any included JS file in examples to emulate delay in @@ -893,76 +958,92 @@ app.use(['/dist/v0/amp-*.js'], (req, res, next) => { */ app.get('/test/manual/amp-video.amp.html', runVideoTestBench); -app.get([ - '/examples/*.html', - '/test/manual/*.html', - '/test/fixtures/e2e/*/*.html'], (req, res, next) => { - const filePath = req.path; - const mode = pc.env.SERVE_MODE; - const inabox = req.query['inabox']; - const stream = Number(req.query['stream']); - fs.readFileAsync(pc.cwd() + filePath, 'utf8').then(file => { - if (req.query['amp_js_v']) { - file = addViewerIntegrationScript(req.query['amp_js_v'], file); - } - file = file.replace(/__TEST_SERVER_PORT__/g, TEST_SERVER_PORT); +app.get( + ['/examples/*.html', '/test/manual/*.html', '/test/fixtures/e2e/*/*.html'], + (req, res, next) => { + const filePath = req.path; + const mode = pc.env.SERVE_MODE; + const inabox = req.query['inabox']; + const stream = Number(req.query['stream']); + fs.readFileAsync(pc.cwd() + filePath, 'utf8') + .then(file => { + if (req.query['amp_js_v']) { + file = addViewerIntegrationScript(req.query['amp_js_v'], file); + } + file = file.replace(/__TEST_SERVER_PORT__/g, TEST_SERVER_PORT); - if (inabox && req.headers.origin && req.query.__amp_source_origin) { - // Allow CORS requests for A4A. - cors.enableCors(req, res, req.headers.origin); - } else { - file = replaceUrls(mode, file, '', inabox); - } + if (inabox && req.headers.origin && req.query.__amp_source_origin) { + // Allow CORS requests for A4A. + cors.enableCors(req, res, req.headers.origin); + } else { + file = replaceUrls(mode, file, '', inabox); + } - // Extract amp-ad for the given 'type' specified in URL query. - if (req.path.indexOf('/examples/ads.amp.html') == 0 && req.query.type) { - const ads = file.match( - elementExtractor('(amp-ad|amp-embed)', req.query.type)); - file = file.replace( - /[\s\S]+<\/body>/m, '' + ads.join('') + ''); - } + // Extract amp-ad for the given 'type' specified in URL query. + if (req.path.indexOf('/examples/ads.amp.html') == 0 && req.query.type) { + const ads = file.match( + elementExtractor('(amp-ad|amp-embed)', req.query.type) + ); + file = file.replace( + /[\s\S]+<\/body>/m, + '' + ads.join('') + '' + ); + } - // Extract amp-analytics for the given 'type' specified in URL query. - if (req.path.indexOf( - '/examples/analytics-vendors.amp.html') == 0 && req.query.type) { - const analytics = file.match( - elementExtractor('amp-analytics', req.query.type)); - file = file.replace( - /
    [\s\S]+<\/div>/m, - '
    ' + analytics.join('') + '
    '); - } + // Extract amp-analytics for the given 'type' specified in URL query. + if ( + req.path.indexOf('/examples/analytics-vendors.amp.html') == 0 && + req.query.type + ) { + const analytics = file.match( + elementExtractor('amp-analytics', req.query.type) + ); + file = file.replace( + /
    [\s\S]+<\/div>/m, + '
    ' + analytics.join('') + '
    ' + ); + } - // Extract amp-consent for the given 'type' specified in URL query. - if (req.path.indexOf( - '/examples/cmp-vendors.amp.html') == 0 && req.query.type) { - const consent = file.match( - elementExtractor('amp-consent', req.query.type)); - file = file.replace( - /
    [\s\S]+<\/div>/m, - '
    ' + consent.join('') + '
    '); - } + // Extract amp-consent for the given 'type' specified in URL query. + if ( + req.path.indexOf('/examples/cmp-vendors.amp.html') == 0 && + req.query.type + ) { + const consent = file.match( + elementExtractor('amp-consent', req.query.type) + ); + file = file.replace( + /
    [\s\S]+<\/div>/m, + '
    ' + consent.join('') + '
    ' + ); + } - if (stream > 0) { - res.writeHead(200, {'Content-Type': 'text/html'}); - let pos = 0; - const writeChunk = function() { - const chunk = file.substring(pos, Math.min(pos + stream, file.length)); - res.write(chunk); - pos += stream; - if (pos < file.length) { - setTimeout(writeChunk, 500); + if (stream > 0) { + res.writeHead(200, {'Content-Type': 'text/html'}); + let pos = 0; + const writeChunk = function() { + const chunk = file.substring( + pos, + Math.min(pos + stream, file.length) + ); + res.write(chunk); + pos += stream; + if (pos < file.length) { + setTimeout(writeChunk, 500); + } else { + res.end(); + } + }; + writeChunk(); } else { - res.end(); + res.send(file); } - }; - writeChunk(); - } else { - res.send(file); - } - }).catch(() => { - next(); - }); -}); + }) + .catch(() => { + next(); + }); + } +); appTestEndpoints(app); @@ -973,8 +1054,9 @@ function escapeRegExp(string) { function elementExtractor(tagName, type) { type = escapeRegExp(type); return new RegExp( - `<${tagName}[\\s][^>]*['"]${type}['"][^>]*>([\\s\\S]+?)`, - 'gm'); + `<${tagName}[\\s][^>]*['"]${type}['"][^>]*>([\\s\\S]+?)`, + 'gm' + ); } // Data for example: http://localhost:8000/examples/bind/xhr.amp.html @@ -1086,75 +1168,81 @@ app.get('/adzerk/*', (req, res) => { return; } const filePath = - pc.cwd() + '/extensions/amp-ad-network-adzerk-impl/0.1/data/' + match[1]; - fs.readFileAsync(filePath).then(file => { - res.setHeader('Content-Type', 'application/json'); - res.setHeader('AMP-Ad-Template-Extension', 'amp-mustache'); - res.setHeader('AMP-Ad-Response-Type', 'template'); - res.end(file); - }).error(() => { - res.status(404); - res.end('Not found: ' + filePath); - }); + pc.cwd() + '/extensions/amp-ad-network-adzerk-impl/0.1/data/' + match[1]; + fs.readFileAsync(filePath) + .then(file => { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('AMP-Ad-Template-Extension', 'amp-mustache'); + res.setHeader('AMP-Ad-Response-Type', 'template'); + res.end(file); + }) + .error(() => { + res.status(404); + res.end('Not found: ' + filePath); + }); }); /* * Serve extension scripts and their source maps. */ -app.get(['/dist/rtv/*/v0/*.js', '/dist/rtv/*/v0/*.js.map'], - (req, res, next) => { - const mode = pc.env.SERVE_MODE; - const fileName = path.basename(req.path).replace('.max.', '.'); - let filePath = 'https://cdn.ampproject.org/v0/' + fileName; - if (mode == 'cdn') { - // This will not be useful until extension-location.js change in prod - // Require url from cdn - request(filePath, (error, response) => { - if (error) { - res.status(404); - res.end(); - } else { - res.send(response); - } - }); - return; - } - const isJsMap = filePath.endsWith('.map'); - if (isJsMap) { - filePath = filePath.replace(/\.js\.map$/, '\.js'); - } - filePath = replaceUrls(mode, filePath); - req.url = filePath + (isJsMap ? '.map' : ''); - next(); - }); +app.get( + ['/dist/rtv/*/v0/*.js', '/dist/rtv/*/v0/*.js.map'], + (req, res, next) => { + const mode = pc.env.SERVE_MODE; + const fileName = path.basename(req.path).replace('.max.', '.'); + let filePath = 'https://cdn.ampproject.org/v0/' + fileName; + if (mode == 'cdn') { + // This will not be useful until extension-location.js change in prod + // Require url from cdn + request(filePath, (error, response) => { + if (error) { + res.status(404); + res.end(); + } else { + res.send(response); + } + }); + return; + } + const isJsMap = filePath.endsWith('.map'); + if (isJsMap) { + filePath = filePath.replace(/\.js\.map$/, '.js'); + } + filePath = replaceUrls(mode, filePath); + req.url = filePath + (isJsMap ? '.map' : ''); + next(); + } +); /** * Serve entry point script url */ -app.get(['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], - (req, res, next) => { - // Special case for entry point script url. Use compiled for testing - const mode = pc.env.SERVE_MODE; - const fileName = path.basename(req.path); - if (mode == 'cdn') { - // This will not be useful until extension-location.js change in prod - // Require url from cdn - const filePath = 'https://cdn.ampproject.org/' + fileName; - request(filePath, function(error, response) { - if (error) { - res.status(404); - res.end(); - } else { - res.send(response); - } - }); - return; - } - if (mode == 'default') { - req.url = req.url.replace(/\.js$/, '.max.js'); - } - next(); - }); +app.get( + ['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], + (req, res, next) => { + // Special case for entry point script url. Use compiled for testing + const mode = pc.env.SERVE_MODE; + const fileName = path.basename(req.path); + if (mode == 'cdn') { + // This will not be useful until extension-location.js change in prod + // Require url from cdn + const filePath = 'https://cdn.ampproject.org/' + fileName; + request(filePath, function(error, response) { + if (error) { + res.status(404); + res.end(); + } else { + res.send(response); + } + }); + return; + } + if (mode == 'default') { + req.url = req.url.replace(/\.js$/, '.max.js'); + } + next(); + } +); app.get('/dist/iframe-transport-client-lib.js', (req, res, next) => { req.url = req.url.replace(/dist/, 'dist.3p/current'); @@ -1174,19 +1262,26 @@ app.get('/dist/amp-inabox-host.js', (req, res, next) => { */ app.get('/dist/sw(.max)?.js', (req, res, next) => { const filePath = req.path; - fs.readFileAsync(pc.cwd() + filePath, 'utf8').then(file => { - let n = new Date(); - // Round down to the nearest 5 minutes. - n -= ((n.getMinutes() % 5) * 1000 * 60) - + (n.getSeconds() * 1000) + n.getMilliseconds(); - file = 'self.AMP_CONFIG = {v: "99' + n + '",' + - 'cdnUrl: "http://localhost:8000/dist"};' - + file; - res.setHeader('Content-Type', 'application/javascript'); - res.setHeader('Date', new Date().toUTCString()); - res.setHeader('Cache-Control', 'no-cache;max-age=150'); - res.end(file); - }).catch(next); + fs.readFileAsync(pc.cwd() + filePath, 'utf8') + .then(file => { + let n = new Date(); + // Round down to the nearest 5 minutes. + n -= + (n.getMinutes() % 5) * 1000 * 60 + + n.getSeconds() * 1000 + + n.getMilliseconds(); + file = + 'self.AMP_CONFIG = {v: "99' + + n + + '",' + + 'cdnUrl: "http://localhost:8000/dist"};' + + file; + res.setHeader('Content-Type', 'application/javascript'); + res.setHeader('Date', new Date().toUTCString()); + res.setHeader('Cache-Control', 'no-cache;max-age=150'); + res.end(file); + }) + .catch(next); }); app.get('/dist/rtv/9[89]*/*.js', (req, res, next) => { @@ -1198,10 +1293,12 @@ app.get('/dist/rtv/9[89]*/*.js', (req, res, next) => { // Cause a delay, to show the "stale-while-revalidate" if (req.path.includes('v0.js')) { const path = req.path.replace(/rtv\/\d+/, ''); - return fs.readFileAsync(pc.cwd() + path, 'utf8') - .then(file => { - res.end(file); - }).catch(next); + return fs + .readFileAsync(pc.cwd() + path, 'utf8') + .then(file => { + res.end(file); + }) + .catch(next); } res.end(` @@ -1214,30 +1311,36 @@ app.get('/dist/rtv/9[89]*/*.js', (req, res, next) => { app.get(['/dist/cache-sw.html'], (req, res, next) => { const filePath = '/test/manual/cache-sw.html'; - fs.readFileAsync(pc.cwd() + filePath, 'utf8').then(file => { - let n = new Date(); - // Round down to the nearest 5 minutes. - n -= ((n.getMinutes() % 5) * 1000 * 60) - + (n.getSeconds() * 1000) + n.getMilliseconds(); - const percent = parseFloat(req.query.canary) || 0.01; - let env = '99'; - if (Math.random() < percent) { - env = '98'; - n += 5 * 1000 * 60; - } - file = file.replace(/dist\/v0/g, `dist/rtv/${env}${n}/v0`); - file = file.replace(/CURRENT_RTV/, env + n); + fs.readFileAsync(pc.cwd() + filePath, 'utf8') + .then(file => { + let n = new Date(); + // Round down to the nearest 5 minutes. + n -= + (n.getMinutes() % 5) * 1000 * 60 + + n.getSeconds() * 1000 + + n.getMilliseconds(); + const percent = parseFloat(req.query.canary) || 0.01; + let env = '99'; + if (Math.random() < percent) { + env = '98'; + n += 5 * 1000 * 60; + } + file = file.replace(/dist\/v0/g, `dist/rtv/${env}${n}/v0`); + file = file.replace(/CURRENT_RTV/, env + n); - res.setHeader('Content-Type', 'text/html'); - res.end(file); - }).catch(next); + res.setHeader('Content-Type', 'text/html'); + res.end(file); + }) + .catch(next); }); app.get('/dist/diversions', (req, res) => { let n = new Date(); // Round down to the nearest 5 minutes. - n -= ((n.getMinutes() % 5) * 1000 * 60) - + (n.getSeconds() * 1000) + n.getMilliseconds(); + n -= + (n.getMinutes() % 5) * 1000 * 60 + + n.getSeconds() * 1000 + + n.getMilliseconds(); n += 5 * 1000 * 60; res.setHeader('Content-Type', 'application/json'); res.setHeader('Date', new Date().toUTCString()); @@ -1268,9 +1371,9 @@ app.use('/shadow/', (req, res) => { const {url} = req; const isProxyUrl = /^\/proxy\//.test(url); - const baseHref = isProxyUrl ? - 'https://cdn.ampproject.org/' : - `${path.dirname(url)}/`; + const baseHref = isProxyUrl + ? 'https://cdn.ampproject.org/' + : `${path.dirname(url)}/`; const viewerHtml = renderShadowViewer({ src: req.url.replace(/^\//, ''), @@ -1294,18 +1397,22 @@ function addViewerIntegrationScript(ampJsVersion, file) { return file; } let viewerScript; - if (Number.isInteger(ampJsVersion)) { // eslint-disable-line amphtml-internal/no-es2015-number-props + // eslint-disable-next-line amphtml-internal/no-es2015-number-props + if (Number.isInteger(ampJsVersion)) { // Viewer integration script from gws, such as // https://cdn.ampproject.org/viewer/google/v7.js viewerScript = - ''; + ''; } else { // Viewer integration script from runtime, such as // https://cdn.ampproject.org/v0/amp-viewer-integration-0.1.js - viewerScript = ''; + viewerScript = + ''; } file = file.replace(' ', viewerScript + ' '); return file; @@ -1323,7 +1430,7 @@ function getUrlPrefix(req) { */ function addQueryParam(url, param, value) { const paramValue = - encodeURIComponent(param) + '=' + encodeURIComponent(value); + encodeURIComponent(param) + '=' + encodeURIComponent(value); if (!url.includes('?')) { url += '?' + paramValue; } else { @@ -1332,21 +1439,25 @@ function addQueryParam(url, param, value) { return url; } - function generateInfo(filePath) { const mode = pc.env.SERVE_MODE; filePath = filePath.substr(0, filePath.length - 9) + '.html'; - return '

    Please note that .min/.max is no longer supported

    ' + - '

    Current serving mode is ' + mode + '

    ' + - '

    Please go to Unversioned Link to view the page

    ' + - '

    ' + - '

    ' + - 'Change to DEFAULT mode (unminified JS)

    ' + - '

    ' + - 'Change to COMPILED mode (minified JS)

    ' + - '

    Change to CDN mode (prod JS)

    '; + return ( + '

    Please note that .min/.max is no longer supported

    ' + + '

    Current serving mode is ' + + mode + + '

    ' + + '

    Please go to Unversioned Link to view the page

    ' + + '

    ' + + '

    ' + + 'Change to DEFAULT mode (unminified JS)

    ' + + '

    ' + + 'Change to COMPILED mode (minified JS)

    ' + + '

    Change to CDN mode (prod JS)

    ' + ); } module.exports = app; diff --git a/build-system/babel-plugins/babel-plugin-amp-mode-transformer/index.js b/build-system/babel-plugins/babel-plugin-amp-mode-transformer/index.js index 16d935759358..9009b65e0b6f 100644 --- a/build-system/babel-plugins/babel-plugin-amp-mode-transformer/index.js +++ b/build-system/babel-plugins/babel-plugin-amp-mode-transformer/index.js @@ -15,10 +15,10 @@ */ /** -* Changes the values of getMode().test, getMode().localDev to false -* and getMode().localDev to true. -* @param {Object} babelTypes -*/ + * Changes the values of getMode().test, getMode().localDev to false + * and getMode().localDev to true. + * @param {Object} babelTypes + */ const {resolve, dirname} = require('path'); module.exports = function({types: t}) { let getModeFound = false; @@ -32,7 +32,9 @@ module.exports = function({types: t}) { specifiers.forEach(specifier => { if (specifier.imported.name === 'getMode') { const filepath = resolve( - dirname(state.file.opts.filename), source.value); + dirname(state.file.opts.filename), + source.value + ); if (filepath.endsWith('/amphtml/src/mode')) { getModeFound = true; } @@ -54,7 +56,7 @@ module.exports = function({types: t}) { path.replaceWith(t.booleanLiteral(true)); } } - }, + }, }, }; }; diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js b/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js index c27d1c33feeb..3cad3ad8e414 100644 --- a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js +++ b/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js @@ -15,18 +15,20 @@ */ /** - * Changes the values of IS_DEV to false and IS_MINIFIED to true. - * The above said variables are in src/mode.js file. - * @param {Object} babelTypes - */ + * Changes the values of IS_DEV to false and IS_MINIFIED to true. + * The above said variables are in src/mode.js file. + * @param {Object} babelTypes + */ module.exports = function({types: t}) { return { visitor: { VariableDeclarator(path) { const {node} = path; const {id, init} = node; - if (t.isIdentifier(id, {name: 'IS_DEV'}) - && t.isBooleanLiteral(init, {value: true})) { + if ( + t.isIdentifier(id, {name: 'IS_DEV'}) && + t.isBooleanLiteral(init, {value: true}) + ) { node.init = t.booleanLiteral(false); } }, diff --git a/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js b/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js index 3d9f8daa5157..f57e1b497d36 100644 --- a/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js +++ b/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js @@ -15,23 +15,25 @@ */ /** - * Changes the values of IS_DEV to false and IS_MINIFIED to true. - * The above said variables are in src/mode.js file. - * @param {Object} babelTypes - */ + * Changes the values of IS_DEV to false and IS_MINIFIED to true. + * The above said variables are in src/mode.js file. + * @param {Object} babelTypes + */ module.exports = function(babelTypes) { const {types: t} = babelTypes; return { visitor: { VariableDeclarator(path) { const {id, init} = path.node; - if (t.isIdentifier(id, {name: 'IS_MINIFIED'}) - && t.isBooleanLiteral(init, {value: false})) { + if ( + t.isIdentifier(id, {name: 'IS_MINIFIED'}) && + t.isBooleanLiteral(init, {value: false}) + ) { path.replaceWith( - t.variableDeclarator( - t.identifier('IS_MINIFIED'), - t.booleanLiteral(true) - ) + t.variableDeclarator( + t.identifier('IS_MINIFIED'), + t.booleanLiteral(true) + ) ); } }, diff --git a/build-system/babel-plugins/babel-plugin-transform-amp-asserts/index.js b/build-system/babel-plugins/babel-plugin-transform-amp-asserts/index.js index 49d67155ad11..e7ca4a87cd4f 100644 --- a/build-system/babel-plugins/babel-plugin-transform-amp-asserts/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-amp-asserts/index.js @@ -43,7 +43,6 @@ const removableDevAsserts = [ const removableUserAsserts = ['fine']; - module.exports = function(babel) { const {types: t} = babel; return { @@ -52,8 +51,8 @@ module.exports = function(babel) { const {node} = path; const {callee} = node; const {parenthesized} = node.extra || {}; - const isMemberAndCallExpression = t.isMemberExpression(callee) - && t.isCallExpression(callee.object); + const isMemberAndCallExpression = + t.isMemberExpression(callee) && t.isCallExpression(callee.object); if (!isMemberAndCallExpression) { return; @@ -61,11 +60,13 @@ module.exports = function(babel) { const logCallee = callee.object.callee; const {property} = callee; - const isRemovableDevCall = t.isIdentifier(logCallee, {name: 'dev'}) && - isRemovableMethod(t, property, removableDevAsserts); + const isRemovableDevCall = + t.isIdentifier(logCallee, {name: 'dev'}) && + isRemovableMethod(t, property, removableDevAsserts); - const isRemovableUserCall = t.isIdentifier(logCallee, {name: 'user'}) && - isRemovableMethod(t, property, removableUserAsserts); + const isRemovableUserCall = + t.isIdentifier(logCallee, {name: 'user'}) && + isRemovableMethod(t, property, removableUserAsserts); if (!(isRemovableDevCall || isRemovableUserCall)) { return; @@ -81,9 +82,9 @@ module.exports = function(babel) { if (parenthesized) { path.replaceWith(t.parenthesizedExpression(args)); path.skip(); - // If is not an assert type, we won't need to do type annotation. - // If it has no type that we can cast to, then we also won't need to - // do type annotation. + // If is not an assert type, we won't need to do type annotation. + // If it has no type that we can cast to, then we also won't need to + // do type annotation. } else if (!property.name.startsWith('assert') || !type) { path.replaceWith(args); } else { diff --git a/build-system/babel-plugins/babel-plugin-transform-amp-extension-call/index.js b/build-system/babel-plugins/babel-plugin-transform-amp-extension-call/index.js index 1a0a2ab907a0..6b300d6e1762 100644 --- a/build-system/babel-plugins/babel-plugin-transform-amp-extension-call/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-amp-extension-call/index.js @@ -22,14 +22,17 @@ module.exports = function(babel) { CallExpression(path) { const {node} = path; const {callee} = node; - if (t.isIdentifier(callee.object, {name: 'AMP'}) && - t.isIdentifier(callee.property, {name: 'extension'})) { + if ( + t.isIdentifier(callee.object, {name: 'AMP'}) && + t.isIdentifier(callee.property, {name: 'extension'}) + ) { const func = node.arguments[node.arguments.length - 1]; const IIFE = t.expressionStatement( - t.callExpression(func, [ - t.memberExpression(t.identifier('self'), t.identifier('AMP')), - ])); + t.callExpression(func, [ + t.memberExpression(t.identifier('self'), t.identifier('AMP')), + ]) + ); path.replaceWith(IIFE); } }, diff --git a/build-system/babel-plugins/babel-plugin-transform-html-template/index.js b/build-system/babel-plugins/babel-plugin-transform-html-template/index.js index ea04b6750e47..6adde21d885b 100644 --- a/build-system/babel-plugins/babel-plugin-transform-html-template/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-html-template/index.js @@ -26,8 +26,11 @@ const INSERTED_TEMPLATES = new Map(); */ function optimizeLiteralOutput(templateLiteral) { if (templateLiteral.quasis.length !== 1) { - console/* OK */.log('Improperly formatted `html` tagged template literal' + - ', more than one template element present.'); + console /* OK */ + .log( + 'Improperly formatted `html` tagged template literal' + + ', more than one template element present.' + ); return null; } return minify(templateLiteral.quasis[0].value.cooked, { @@ -44,9 +47,11 @@ module.exports = function({types: t}) { TaggedTemplateExpression(path) { const {tag} = path.node; - if (t.isIdentifier(tag, {name: 'html'}) || - (t.isCallExpression(tag) && - t.isIdentifier(tag.callee, {name: 'htmlFor'}))) { + if ( + t.isIdentifier(tag, {name: 'html'}) || + (t.isCallExpression(tag) && + t.isIdentifier(tag.callee, {name: 'htmlFor'})) + ) { // Replace a matching TemplateExpression by either inlining a // transpiled template or hoisting the template and referring // to its value. @@ -54,13 +59,13 @@ module.exports = function({types: t}) { const template = optimizeLiteralOutput(path.node.quasi); if (template !== null) { - const templateArrayExpression = t.arrayExpression( - [t.stringLiteral(template)] - ); + const templateArrayExpression = t.arrayExpression([ + t.stringLiteral(template), + ]); if (t.isProgram(path.scope.block)) { path.replaceWith( - t.callExpression(tag, [templateArrayExpression]) + t.callExpression(tag, [templateArrayExpression]) ); } else { // Since the template is inline, and the block scope @@ -73,7 +78,7 @@ module.exports = function({types: t}) { } else { // Template not hoisted. Hoist it. hoistedIdentifier = path.scope.generateUidIdentifier( - 'template' + 'template' ); const program = path.findParent(path => path.isProgram()); diff --git a/build-system/babel-plugins/babel-plugin-transform-version-call/index.js b/build-system/babel-plugins/babel-plugin-transform-version-call/index.js index ca198d104c10..efe698bd05a0 100644 --- a/build-system/babel-plugins/babel-plugin-transform-version-call/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-version-call/index.js @@ -14,7 +14,7 @@ * limitations under the License. */ -const {VERSION} = require('../../internal-version') ; +const {VERSION} = require('../../internal-version'); module.exports = function(babel) { const {types: t} = babel; diff --git a/build-system/build.conf.js b/build-system/build.conf.js index ad06ec3f28ec..f1e0ebac0f2c 100644 --- a/build-system/build.conf.js +++ b/build-system/build.conf.js @@ -15,40 +15,37 @@ */ const defaultPlugins = [ + require.resolve('./babel-plugins/babel-plugin-transform-amp-asserts'), + require.resolve('./babel-plugins/babel-plugin-transform-html-template'), require.resolve( - './babel-plugins/babel-plugin-transform-amp-asserts'), + './babel-plugins/babel-plugin-transform-parenthesize-expression' + ), require.resolve( - './babel-plugins/babel-plugin-transform-html-template'), - require.resolve( - './babel-plugins/babel-plugin-transform-parenthesize-expression'), - require.resolve( - './babel-plugins/babel-plugin-is_minified-constant-transformer'), - require.resolve( - './babel-plugins/babel-plugin-transform-amp-extension-call'), - require.resolve( - './babel-plugins/babel-plugin-transform-version-call'), + './babel-plugins/babel-plugin-is_minified-constant-transformer' + ), + require.resolve('./babel-plugins/babel-plugin-transform-amp-extension-call'), + require.resolve('./babel-plugins/babel-plugin-transform-version-call'), ]; module.exports = { - plugins: ({ - isEsmBuild, - isCommonJsModule, - isForTesting, - }) => { + plugins: ({isEsmBuild, isCommonJsModule, isForTesting}) => { let pluginsToApply = defaultPlugins; if (isEsmBuild) { pluginsToApply = pluginsToApply.concat([ - [require.resolve('babel-plugin-filter-imports'), { - 'imports': { - './polyfills/fetch': ['installFetch'], - './polyfills/domtokenlist-toggle': ['installDOMTokenListToggle'], - './polyfills/document-contains': ['installDocContains'], - './polyfills/math-sign': ['installMathSign'], - './polyfills/object-assign': ['installObjectAssign'], - './polyfills/object-values': ['installObjectValues'], - './polyfills/promise': ['installPromise'], + [ + require.resolve('babel-plugin-filter-imports'), + { + 'imports': { + './polyfills/fetch': ['installFetch'], + './polyfills/domtokenlist-toggle': ['installDOMTokenListToggle'], + './polyfills/document-contains': ['installDocContains'], + './polyfills/math-sign': ['installMathSign'], + './polyfills/object-assign': ['installObjectAssign'], + './polyfills/object-values': ['installObjectValues'], + './polyfills/promise': ['installPromise'], + }, }, - }], + ], ]); } if (isCommonJsModule) { @@ -59,11 +56,9 @@ module.exports = { if (!isForTesting) { pluginsToApply = pluginsToApply.concat([ require.resolve( - './babel-plugins/babel-plugin-is_dev-constant-transformer' - ), - require.resolve( - './babel-plugins/babel-plugin-amp-mode-transformer' + './babel-plugins/babel-plugin-is_dev-constant-transformer' ), + require.resolve('./babel-plugins/babel-plugin-amp-mode-transformer'), ]); } return pluginsToApply; diff --git a/build-system/check-package-manager.js b/build-system/check-package-manager.js index c334396f1a29..e011a5335d05 100644 --- a/build-system/check-package-manager.js +++ b/build-system/check-package-manager.js @@ -25,9 +25,11 @@ const fs = require('fs'); const https = require('https'); const {getStdout} = require('./exec'); -const setupInstructionsUrl = 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-quick.md#one-time-setup'; +const setupInstructionsUrl = + 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-quick.md#one-time-setup'; const nodeDistributionsUrl = 'https://nodejs.org/dist/index.json'; -const gulpHelpUrl = 'https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467'; +const gulpHelpUrl = + 'https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467'; const yarnExecutable = 'npx yarn'; const gulpExecutable = 'npx gulp'; @@ -35,10 +37,18 @@ const gulpExecutable = 'npx gulp'; const updatesNeeded = []; // Color formatting libraries may not be available when this script is run. -function red(text) {return '\x1b[31m' + text + '\x1b[0m';} -function cyan(text) {return '\x1b[36m' + text + '\x1b[0m';} -function green(text) {return '\x1b[32m' + text + '\x1b[0m';} -function yellow(text) {return '\x1b[33m' + text + '\x1b[0m';} +function red(text) { + return '\x1b[31m' + text + '\x1b[0m'; +} +function cyan(text) { + return '\x1b[36m' + text + '\x1b[0m'; +} +function green(text) { + return '\x1b[32m' + text + '\x1b[0m'; +} +function yellow(text) { + return '\x1b[33m' + text + '\x1b[0m'; +} /** * @fileoverview Perform checks on the AMP toolchain. @@ -47,23 +57,33 @@ function yellow(text) {return '\x1b[33m' + text + '\x1b[0m';} // If npm is being run, print a message and cause 'npm install' to fail. function ensureYarn() { if (process.env.npm_execpath.indexOf('yarn') === -1) { - console.log(red( - '*** The AMP project uses yarn for package management ***'), '\n'); + console.log( + red('*** The AMP project uses yarn for package management ***'), + '\n' + ); console.log(yellow('To install all packages:')); console.log(cyan('$'), 'yarn', '\n'); console.log( - yellow('To install a new (runtime) package to "dependencies":')); + yellow('To install a new (runtime) package to "dependencies":') + ); console.log(cyan('$'), 'yarn add --exact [package_name@version]', '\n'); console.log( - yellow('To install a new (toolset) package to "devDependencies":')); - console.log(cyan('$'), - 'yarn add --dev --exact [package_name@version]', '\n'); + yellow('To install a new (toolset) package to "devDependencies":') + ); + console.log( + cyan('$'), + 'yarn add --dev --exact [package_name@version]', + '\n' + ); console.log(yellow('To upgrade a package:')); console.log(cyan('$'), 'yarn upgrade --exact [package_name@version]', '\n'); console.log(yellow('To remove a package:')); console.log(cyan('$'), 'yarn remove [package_name]', '\n'); - console.log(yellow('For detailed instructions, see'), - cyan(setupInstructionsUrl), '\n'); + console.log( + yellow('For detailed instructions, see'), + cyan(setupInstructionsUrl), + '\n' + ); process.exit(1); } } @@ -72,43 +92,64 @@ function ensureYarn() { function checkNodeVersion() { const nodeVersion = getStdout('node --version').trim(); return new Promise(resolve => { - https.get(nodeDistributionsUrl, res => { - res.setEncoding('utf8'); - let distributions = ''; - res.on('data', data => { - distributions += data; - }); - res.on('end', () => { - const distributionsJson = JSON.parse(distributions); - const latestLtsVersion = getNodeLatestLtsVersion(distributionsJson); - if (latestLtsVersion === '') { - console.log(yellow('WARNING: Something went wrong. ' + - 'Could not determine the latest LTS version of node.')); - } else if (nodeVersion !== latestLtsVersion) { - console.log(yellow('WARNING: Detected node version'), + https + .get(nodeDistributionsUrl, res => { + res.setEncoding('utf8'); + let distributions = ''; + res.on('data', data => { + distributions += data; + }); + res.on('end', () => { + const distributionsJson = JSON.parse(distributions); + const latestLtsVersion = getNodeLatestLtsVersion(distributionsJson); + if (latestLtsVersion === '') { + console.log( + yellow( + 'WARNING: Something went wrong. ' + + 'Could not determine the latest LTS version of node.' + ) + ); + } else if (nodeVersion !== latestLtsVersion) { + console.log( + yellow('WARNING: Detected node version'), cyan(nodeVersion) + - yellow('. Recommended (latest LTS) version is'), - cyan(latestLtsVersion) + yellow('.')); - console.log(yellow('⤷ To fix this, run'), - cyan('"nvm install --lts"'), yellow('or see'), + yellow('. Recommended (latest LTS) version is'), + cyan(latestLtsVersion) + yellow('.') + ); + console.log( + yellow('⤷ To fix this, run'), + cyan('"nvm install --lts"'), + yellow('or see'), cyan('https://nodejs.org/en/download/package-manager'), - yellow('for instructions.')); - updatesNeeded.push('node'); - } else { - console.log(green('Detected'), cyan('node'), green('version'), - cyan(nodeVersion + ' (latest LTS)') + - green('.')); - } + yellow('for instructions.') + ); + updatesNeeded.push('node'); + } else { + console.log( + green('Detected'), + cyan('node'), + green('version'), + cyan(nodeVersion + ' (latest LTS)') + green('.') + ); + } + resolve(); + }); + }) + .on('error', () => { + console.log( + yellow( + 'WARNING: Something went wrong. ' + + 'Could not download node version info from ' + + cyan(nodeDistributionsUrl) + + yellow('.') + ) + ); + console.log( + yellow('⤷ Detected node version'), + cyan(nodeVersion) + yellow('.') + ); resolve(); }); - }).on('error', () => { - console.log(yellow('WARNING: Something went wrong. ' + - 'Could not download node version info from ' + - cyan(nodeDistributionsUrl) + yellow('.'))); - console.log(yellow('⤷ Detected node version'), cyan(nodeVersion) + - yellow('.')); - resolve(); - }); }); } @@ -116,9 +157,11 @@ function getNodeLatestLtsVersion(distributionsJson) { if (distributionsJson) { // Versions are in descending order, so the first match is the latest lts. return distributionsJson.find(function(distribution) { - return distribution.hasOwnProperty('version') && - distribution.hasOwnProperty('lts') && - distribution.lts; + return ( + distribution.hasOwnProperty('version') && + distribution.hasOwnProperty('lts') && + distribution.lts + ); }).version; } else { return ''; @@ -132,28 +175,42 @@ function checkYarnVersion() { const yarnInfoJson = JSON.parse(yarnInfo.split('\n')[0]); // First line const stableVersion = getYarnStableVersion(yarnInfoJson); if (stableVersion === '') { - console.log(yellow('WARNING: Something went wrong. ' + - 'Could not determine the stable version of yarn.')); + console.log( + yellow( + 'WARNING: Something went wrong. ' + + 'Could not determine the stable version of yarn.' + ) + ); } else if (yarnVersion !== stableVersion) { - console.log(yellow('WARNING: Detected yarn version'), - cyan(yarnVersion) + yellow('. Recommended (stable) version is'), - cyan(stableVersion) + yellow('.')); - console.log(yellow('⤷ To fix this, run'), - cyan('"curl -o- -L https://yarnpkg.com/install.sh | bash"'), - yellow('or see'), cyan('https://yarnpkg.com/docs/install'), - yellow('for instructions.')); + console.log( + yellow('WARNING: Detected yarn version'), + cyan(yarnVersion) + yellow('. Recommended (stable) version is'), + cyan(stableVersion) + yellow('.') + ); + console.log( + yellow('⤷ To fix this, run'), + cyan('"curl -o- -L https://yarnpkg.com/install.sh | bash"'), + yellow('or see'), + cyan('https://yarnpkg.com/docs/install'), + yellow('for instructions.') + ); updatesNeeded.push('yarn'); } else { - console.log(green('Detected'), cyan('yarn'), green('version'), - cyan(yarnVersion + ' (stable)') + - green('. Installing packages...')); + console.log( + green('Detected'), + cyan('yarn'), + green('version'), + cyan(yarnVersion + ' (stable)') + green('. Installing packages...') + ); } } function getYarnStableVersion(infoJson) { - if (infoJson && - infoJson.hasOwnProperty('data') && - infoJson.data.hasOwnProperty('version')) { + if ( + infoJson && + infoJson.hasOwnProperty('data') && + infoJson.data.hasOwnProperty('version') + ) { return infoJson.data.version; } else { return ''; @@ -166,30 +223,51 @@ function checkGlobalGulp() { const globalGulp = globalPackages.match(/"gulp@.*" has binaries/); const globalGulpCli = globalPackages.match(/"gulp-cli@.*" has binaries/); if (globalGulp) { - console.log(yellow('WARNING: Detected a global install of'), - cyan('gulp') + yellow('. It is recommended that you use'), - cyan('gulp-cli'), yellow('instead.')); - console.log(yellow('⤷ To fix this, run'), - cyan('"yarn global remove gulp"'), yellow('followed by'), - cyan('"yarn global add gulp-cli"') + yellow('.')); - console.log(yellow('⤷ See'), cyan(gulpHelpUrl), - yellow('for more information.')); + console.log( + yellow('WARNING: Detected a global install of'), + cyan('gulp') + yellow('. It is recommended that you use'), + cyan('gulp-cli'), + yellow('instead.') + ); + console.log( + yellow('⤷ To fix this, run'), + cyan('"yarn global remove gulp"'), + yellow('followed by'), + cyan('"yarn global add gulp-cli"') + yellow('.') + ); + console.log( + yellow('⤷ See'), + cyan(gulpHelpUrl), + yellow('for more information.') + ); updatesNeeded.push('gulp'); } else if (!globalGulpCli) { - console.log(yellow('WARNING: Could not find'), - cyan('gulp-cli') + yellow('.')); - console.log(yellow('⤷ To install it, run'), - cyan('"yarn global add gulp-cli"') + yellow('.')); + console.log( + yellow('WARNING: Could not find'), + cyan('gulp-cli') + yellow('.') + ); + console.log( + yellow('⤷ To install it, run'), + cyan('"yarn global add gulp-cli"') + yellow('.') + ); } else if (!firstInstall) { const gulpVersions = getStdout(gulpExecutable + ' --version').trim(); const gulpVersion = gulpVersions.match(/Local version[:]? (.*?)$/); if (gulpVersion && gulpVersion.length == 2) { - console.log(green('Detected'), cyan('gulp'), green('version'), - cyan(gulpVersion[1]) + green('.')); + console.log( + green('Detected'), + cyan('gulp'), + green('version'), + cyan(gulpVersion[1]) + green('.') + ); } else { - console.log(yellow('WARNING: ' + - 'Could not determine the local version of gulp. ' + - '(This is normal during install / upgrade.)')); + console.log( + yellow( + 'WARNING: ' + + 'Could not determine the local version of gulp. ' + + '(This is normal during install / upgrade.)' + ) + ); } } } @@ -204,14 +282,24 @@ function main() { checkGlobalGulp(); checkYarnVersion(); if (!process.env.TRAVIS && updatesNeeded.length > 0) { - console.log(yellow('\nWARNING: Detected missing updates for'), - cyan(updatesNeeded.join(', '))); - console.log(yellow('⤷ Continuing install in'), cyan('5'), - yellow('seconds...')); - console.log(yellow('⤷ Press'), cyan('Ctrl + C'), - yellow('to abort and fix...')); + console.log( + yellow('\nWARNING: Detected missing updates for'), + cyan(updatesNeeded.join(', ')) + ); + console.log( + yellow('⤷ Continuing install in'), + cyan('5'), + yellow('seconds...') + ); + console.log( + yellow('⤷ Press'), + cyan('Ctrl + C'), + yellow('to abort and fix...') + ); let resolver; - const deferred = new Promise(resolverIn => {resolver = resolverIn;}); + const deferred = new Promise(resolverIn => { + resolver = resolverIn; + }); setTimeout(() => { console.log(yellow('\nAttempting to install packages...')); resolver(); diff --git a/build-system/compile-wrappers.js b/build-system/compile-wrappers.js index c146ddf060ff..2a2c5790012c 100644 --- a/build-system/compile-wrappers.js +++ b/build-system/compile-wrappers.js @@ -18,17 +18,22 @@ const {VERSION} = require('./internal-version'); // If there is a sync JS error during initial load, // at least try to unhide the body. -exports.mainBinary = 'var global=self;self.AMP=self.AMP||[];' + - 'try{(function(_){\n<%= contents %>})(AMP._=AMP._||{})}catch(e){' + - 'setTimeout(function(){' + - 'var s=document.body.style;' + - 's.opacity=1;' + - 's.visibility="visible";' + - 's.animation="none";' + - 's.WebkitAnimation="none;"},1000);throw e};'; +exports.mainBinary = + 'var global=self;self.AMP=self.AMP||[];' + + 'try{(function(_){\n<%= contents %>})(AMP._=AMP._||{})}catch(e){' + + 'setTimeout(function(){' + + 'var s=document.body.style;' + + 's.opacity=1;' + + 's.visibility="visible";' + + 's.animation="none";' + + 's.WebkitAnimation="none;"},1000);throw e};'; -exports.extension = function(name, loadPriority, intermediateDeps, - opt_splitMarker) { +exports.extension = function( + name, + loadPriority, + intermediateDeps, + opt_splitMarker +) { opt_splitMarker = opt_splitMarker || ''; let deps = ''; if (intermediateDeps) { @@ -50,9 +55,11 @@ exports.extension = function(name, loadPriority, intermediateDeps, } priority = 'p:"high",'; } - return `(self.AMP=self.AMP||[]).push({n:"${name}",${priority}${deps}` + - `v:"${VERSION}",f:(function(AMP,_){${opt_splitMarker}\n` + - '<%= contents %>\n})});'; + return ( + `(self.AMP=self.AMP||[]).push({n:"${name}",${priority}${deps}` + + `v:"${VERSION}",f:(function(AMP,_){${opt_splitMarker}\n` + + '<%= contents %>\n})});' + ); }; exports.none = '<%= contents %>'; diff --git a/build-system/compile/closure-compile.js b/build-system/compile/closure-compile.js index 369769f1a167..dd7370c96349 100644 --- a/build-system/compile/closure-compile.js +++ b/build-system/compile/closure-compile.js @@ -39,9 +39,10 @@ function formatClosureCompilerError(message) { exports.handleCompilerError = function(outputFilename) { handleError( - colors.red('Compilation failed for ') + + colors.red('Compilation failed for ') + colors.cyan(outputFilename) + - colors.red(':')); + colors.red(':') + ); }; exports.handleTypeCheckError = function() { @@ -57,8 +58,9 @@ exports.handleSinglePassCompilerError = function() { * @param {string} message */ function handleError(message) { - console./*OK*/error( - `${message}\n` + formatClosureCompilerError(compilerErrors)); + console./*OK*/ error( + `${message}\n` + formatClosureCompilerError(compilerErrors) + ); } /** @@ -71,15 +73,16 @@ exports.gulpClosureCompile = function(compilerOptions) { }; const pluginOptions = { platform: ['java'], // Override the binary used by closure compiler - logger: errors => compilerErrors = errors, // Capture compiler errors + logger: errors => (compilerErrors = errors), // Capture compiler errors }; if (compilerOptions.includes('SINGLE_FILE_COMPILATION=true')) { // For single-pass compilation, use the default compiler.jar // TODO(rsimha): Use the native compiler instead of compiler.jar once a fix // is checked in for https://github.com/google/closure-compiler/issues/3041 - closureCompiler.compiler.JAR_PATH = - require.resolve('../../third_party/closure-compiler/compiler.jar'); + closureCompiler.compiler.JAR_PATH = require.resolve( + '../../third_party/closure-compiler/compiler.jar' + ); } else { // On Mac OS and Linux, speed up compilation using nailgun. // See https://github.com/facebook/nailgun. @@ -94,8 +97,9 @@ exports.gulpClosureCompile = function(compilerOptions) { initOptions.extraArguments = null; // Already part of nailgun-server } else { // For other platforms, use AMP's custom runner.jar - closureCompiler.compiler.JAR_PATH = - require.resolve('../runner/dist/runner.jar'); + closureCompiler.compiler.JAR_PATH = require.resolve( + '../runner/dist/runner.jar' + ); } } diff --git a/build-system/compile/compile.js b/build-system/compile/compile.js index 99549239780d..30c6ff3c297b 100644 --- a/build-system/compile/compile.js +++ b/build-system/compile/compile.js @@ -23,11 +23,15 @@ const nop = require('gulp-nop'); const rename = require('gulp-rename'); const rimraf = require('rimraf'); const sourcemaps = require('gulp-sourcemaps'); -const {gulpClosureCompile, handleCompilerError, handleTypeCheckError} = require('./closure-compile'); +const { + gulpClosureCompile, + handleCompilerError, + handleTypeCheckError, +} = require('./closure-compile'); const {isTravisBuild} = require('../travis'); const {shortenLicense, shouldShortenLicense} = require('./shorten-license'); const {singlePassCompile} = require('./single-pass'); -const {VERSION: internalRuntimeVersion} = require('../internal-version') ; +const {VERSION: internalRuntimeVersion} = require('../internal-version'); const isProdBuild = !!argv.type; const queue = []; @@ -37,23 +41,29 @@ const MAX_PARALLEL_CLOSURE_INVOCATIONS = argv.single_pass ? 1 : 4; // Compiles AMP with the closure compiler. This is intended only for // production use. During development we intend to continue using // babel, as it has much faster incremental compilation. -exports.closureCompile = async function(entryModuleFilename, outputDir, - outputFilename, options) { +exports.closureCompile = async function( + entryModuleFilename, + outputDir, + outputFilename, + options +) { // Rate limit closure compilation to MAX_PARALLEL_CLOSURE_INVOCATIONS // concurrent processes. return new Promise(function(resolve, reject) { function start() { inProgress++; - compile(entryModuleFilename, outputDir, outputFilename, options) - .then(function() { - if (isTravisBuild()) { - // Print a progress dot after each task to avoid Travis timeouts. - process.stdout.write('.'); - } - inProgress--; - next(); - resolve(); - }, reason => reject(reason)); + compile(entryModuleFilename, outputDir, outputFilename, options).then( + function() { + if (isTravisBuild()) { + // Print a progress dot after each task to avoid Travis timeouts. + process.stdout.write('.'); + } + inProgress--; + next(); + resolve(); + }, + reason => reject(reason) + ); } function next() { if (!queue.length) { @@ -107,9 +117,7 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { 'third_party/moment/moment.extern.js', 'third_party/react-externs/externs.js', ]; - const define = [ - `VERSION=${internalRuntimeVersion}`, - ]; + const define = [`VERSION=${internalRuntimeVersion}`]; if (argv.pseudo_names) { define.push('PSEUDO_NAMES=true'); } @@ -129,7 +137,8 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { define.push('ESM_BUILD=true'); } - console/*OK*/.assert(typeof entryModuleFilenames == 'string'); + console /*OK*/ + .assert(typeof entryModuleFilenames == 'string'); return singlePassCompile(entryModuleFilenames, compilationOptions); } @@ -151,8 +160,10 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { let sourceMapBase = 'http://localhost:8000/'; if (isProdBuild) { // Point sourcemap to fetch files from correct GitHub tag. - sourceMapBase = 'https://raw.githubusercontent.com/ampproject/amphtml/' + - internalRuntimeVersion + '/'; + sourceMapBase = + 'https://raw.githubusercontent.com/ampproject/amphtml/' + + internalRuntimeVersion + + '/'; } const srcs = [ '3p/3p.js', @@ -230,9 +241,9 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { 'node_modules/web-activities/activity-ports.js', 'node_modules/@ampproject/animations/dist/animations.mjs', 'node_modules/@ampproject/worker-dom/dist/' + - 'unminified.index.safe.mjs.patched.js', + 'unminified.index.safe.mjs.patched.js', 'node_modules/document-register-element/build/' + - 'document-register-element.patched.js', + 'document-register-element.patched.js', // 'node_modules/core-js/modules/**.js', // Not sure what these files are, but they seem to duplicate code // one level below and confuse the compiler. @@ -252,9 +263,7 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { srcs.push.apply(srcs, options.extraGlobs); } if (options.include3pDirectories) { - srcs.push( - '3p/**/*.js', - 'ads/**/*.js'); + srcs.push('3p/**/*.js', 'ads/**/*.js'); } // Many files include the polyfills, but we only want to deliver them // once. Since all files automatically wait for the main binary to load @@ -266,42 +275,47 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { return p !== 'custom-elements.js'; }); srcs.push( - '!build/fake-module/src/polyfills.js', - '!build/fake-module/src/polyfills/**/*.js', - '!build/fake-polyfills/src/polyfills.js', - 'src/polyfills/custom-elements.js', - 'build/fake-polyfills/**/*.js'); + '!build/fake-module/src/polyfills.js', + '!build/fake-module/src/polyfills/**/*.js', + '!build/fake-polyfills/src/polyfills.js', + 'src/polyfills/custom-elements.js', + 'build/fake-polyfills/**/*.js' + ); polyfillsShadowList.forEach(polyfillFile => { srcs.push(`!src/polyfills/${polyfillFile}`); - fs.writeFileSync('build/fake-polyfills/src/polyfills/' + polyfillFile, - 'export function install() {}'); + fs.writeFileSync( + 'build/fake-polyfills/src/polyfills/' + polyfillFile, + 'export function install() {}' + ); }); } else if (options.includePolyfills) { srcs.push( - '!build/fake-module/src/polyfills.js', - '!build/fake-module/src/polyfills/**/*.js', - '!build/fake-polyfills/**/*.js', + '!build/fake-module/src/polyfills.js', + '!build/fake-module/src/polyfills/**/*.js', + '!build/fake-polyfills/**/*.js' ); } else { - srcs.push('!src/polyfills.js', '!build/fake-polyfills/**/*.js',); + srcs.push('!src/polyfills.js', '!build/fake-polyfills/**/*.js'); unneededFiles.push('build/fake-module/src/polyfills.js'); } // Negative globstars must come at the end. srcs.push( - // Don't include rollup configs - '!**/rollup.config.js', - // Don't include tests. - '!**_test.js', - '!**/test-*.js', - '!**/test-e2e/*.js', - // Don't include externs. - '!**/*.extern.js', + // Don't include rollup configs + '!**/rollup.config.js', + // Don't include tests. + '!**_test.js', + '!**/test-*.js', + '!**/test-e2e/*.js', + // Don't include externs. + '!**/*.extern.js' ); unneededFiles.forEach(function(fake) { if (!fs.existsSync(fake)) { - fs.writeFileSync(fake, - '// Not needed in closure compiler\n' + - 'export function deadCode() {}'); + fs.writeFileSync( + fake, + '// Not needed in closure compiler\n' + + 'export function deadCode() {}' + ); } }); @@ -358,13 +372,14 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { // it won't do strict type checking if its whitespace only. compilerOptions.define.push('TYPECHECK_ONLY=true'); compilerOptions.jscomp_error.push( - 'checkTypes', - 'accessControls', - 'const', - 'constantProperty', - 'globalThis'); + 'checkTypes', + 'accessControls', + 'const', + 'constantProperty', + 'globalThis' + ); compilerOptions.conformance_configs = - 'build-system/conformance-config.textproto'; + 'build-system/conformance-config.textproto'; } if (compilerOptions.define.length == 0) { @@ -388,27 +403,29 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { }); if (options.typeCheckOnly) { - return gulp.src(srcs, {base: '.'}) - .pipe(gulpClosureCompile(compilerOptionsArray)) - .on('error', err => { - handleTypeCheckError(); - reject(err); - }) - .pipe(nop()) - .on('end', resolve); + return gulp + .src(srcs, {base: '.'}) + .pipe(gulpClosureCompile(compilerOptionsArray)) + .on('error', err => { + handleTypeCheckError(); + reject(err); + }) + .pipe(nop()) + .on('end', resolve); } else { - return gulp.src(srcs, {base: '.'}) - .pipe(gulpIf(shouldShortenLicense, shortenLicense())) - .pipe(sourcemaps.init({loadMaps: true})) - .pipe(gulpClosureCompile(compilerOptionsArray)) - .on('error', err => { - handleCompilerError(outputFilename); - reject(err); - }) - .pipe(rename(outputFilename)) - .pipe(sourcemaps.write('.')) - .pipe(gulp.dest(outputDir)) - .on('end', resolve); + return gulp + .src(srcs, {base: '.'}) + .pipe(gulpIf(shouldShortenLicense, shortenLicense())) + .pipe(sourcemaps.init({loadMaps: true})) + .pipe(gulpClosureCompile(compilerOptionsArray)) + .on('error', err => { + handleCompilerError(outputFilename); + reject(err); + }) + .pipe(rename(outputFilename)) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest(outputDir)) + .on('end', resolve); } }); } diff --git a/build-system/compile/shorten-license.js b/build-system/compile/shorten-license.js index 643c9b3a1b78..2419aa72d308 100644 --- a/build-system/compile/shorten-license.js +++ b/build-system/compile/shorten-license.js @@ -62,15 +62,12 @@ const BSD_SHORT = [ /* eslint-enable */ -const LICENSES = [ - [MIT_FULL, MIT_SHORT], - [POLYMER_BSD_FULL, BSD_SHORT], -]; +const LICENSES = [[MIT_FULL, MIT_SHORT], [POLYMER_BSD_FULL, BSD_SHORT]]; const PATHS = [ 'third_party/webcomponentsjs/ShadowCSS.js', 'node_modules/document-register-element/build/' + - 'document-register-element.patched.js', + 'document-register-element.patched.js', ]; /** diff --git a/build-system/compile/single-pass.js b/build-system/compile/single-pass.js index 7da736fbf266..5acbfd761d21 100644 --- a/build-system/compile/single-pass.js +++ b/build-system/compile/single-pass.js @@ -32,18 +32,25 @@ const rename = require('gulp-rename'); const sourcemaps = require('gulp-sourcemaps'); const tempy = require('tempy'); const through = require('through2'); -const {extensionBundles, altMainBundles, TYPES} = require('../../bundles.config'); -const {gulpClosureCompile, handleSinglePassCompilerError} = require('./closure-compile'); +const { + extensionBundles, + altMainBundles, + TYPES, +} = require('../../bundles.config'); +const { + gulpClosureCompile, + handleSinglePassCompilerError, +} = require('./closure-compile'); const {isTravisBuild} = require('../travis'); const {shortenLicense, shouldShortenLicense} = require('./shorten-license'); const {TopologicalSort} = require('topological-sort'); const TYPES_VALUES = Object.keys(TYPES).map(x => TYPES[x]); const wrappers = require('../compile-wrappers'); -const {VERSION: internalRuntimeVersion} = require('../internal-version') ; +const {VERSION: internalRuntimeVersion} = require('../internal-version'); const argv = minimist(process.argv.slice(2)); -let singlePassDest = typeof argv.single_pass_dest === 'string' ? - argv.single_pass_dest : './dist/'; +let singlePassDest = + typeof argv.single_pass_dest === 'string' ? argv.single_pass_dest : './dist/'; if (!singlePassDest.endsWith('/')) { singlePassDest = `${singlePassDest}/`; @@ -67,24 +74,26 @@ const commonJsModules = [ const mainBundle = 'src/amp.js'; const extensionsInfo = {}; -let extensions = extensionBundles.concat(altMainBundles) - .filter(unsupportedExtensions).map(ext => { - const path = buildFullPathFromConfig(ext); - if (Array.isArray(path)) { - path.forEach((p, index) => { - extensionsInfo[p] = Object.create(ext); - extensionsInfo[p].filename = ext.name + '-' + ext.version[index]; - }); +let extensions = extensionBundles + .concat(altMainBundles) + .filter(unsupportedExtensions) + .map(ext => { + const path = buildFullPathFromConfig(ext); + if (Array.isArray(path)) { + path.forEach((p, index) => { + extensionsInfo[p] = Object.create(ext); + extensionsInfo[p].filename = ext.name + '-' + ext.version[index]; + }); + } else { + extensionsInfo[path] = Object.create(ext); + if (isAltMainBundle(ext.name) && ext.path) { + extensionsInfo[path].filename = ext.name; } else { - extensionsInfo[path] = Object.create(ext); - if (isAltMainBundle(ext.name) && ext.path) { - extensionsInfo[path].filename = ext.name; - } else { - extensionsInfo[path].filename = ext.name + '-' + ext.version; - } + extensionsInfo[path].filename = ext.name + '-' + ext.version; } - return path; - }); + } + return path; + }); // Flatten nested arrays to support multiple versions extensions = [].concat.apply([], extensions); @@ -133,24 +142,25 @@ exports.getFlags = function(config) { // Turn object into deterministically sorted array. const flagsArray = []; - Object.keys(flags).sort().forEach(function(flag) { - const val = flags[flag]; - if (val instanceof Array) { - val.forEach(function(item) { - flagsArray.push('--' + flag, item); - }); - } else { - if (val != null) { - flagsArray.push('--' + flag, val); + Object.keys(flags) + .sort() + .forEach(function(flag) { + const val = flags[flag]; + if (val instanceof Array) { + val.forEach(function(item) { + flagsArray.push('--' + flag, item); + }); } else { - flagsArray.push('--' + flag); + if (val != null) { + flagsArray.push('--' + flag, val); + } else { + flagsArray.push('--' + flag); + } } - } - }); + }); return exports.getGraph(config.modules, config).then(function(g) { - return flagsArray.concat( - exports.getBundleFlags(g, flagsArray)); + return flagsArray.concat(exports.getBundleFlags(g, flagsArray)); }); }; @@ -160,9 +170,11 @@ exports.getBundleFlags = function(g) { // Add all packages (directories with a package.json) to the srcs array. // Closure compiler reads the packages to resolve // non-relative module names. - Object.keys(g.packages).sort().forEach(function(pkg) { - srcs.push(pkg); - }); + Object.keys(g.packages) + .sort() + .forEach(function(pkg) { + srcs.push(pkg); + }); // Build up the weird flag structure that closure compiler calls // modules and we call bundles. @@ -197,8 +209,12 @@ exports.getBundleFlags = function(g) { } else { // TODO(@cramforce): Remove special case. if (!/_base/.test(bundle.name)) { - throw new Error('Unexpected missing extension info ' + bundle.name + - ',' + JSON.stringify(bundle)); + throw new Error( + 'Unexpected missing extension info ' + + bundle.name + + ',' + + JSON.stringify(bundle) + ); } name = bundle.name; info = { @@ -206,7 +222,7 @@ exports.getBundleFlags = function(g) { }; } // And now build --module $name:$numberOfJsFiles:$bundleDeps - let cmd = name + ':' + (bundle.modules.length); + let cmd = name + ':' + bundle.modules.length; const bundleDeps = []; if (!isMain) { const configEntry = getExtensionBundleConfig(originalName); @@ -226,8 +242,8 @@ exports.getBundleFlags = function(g) { flagsArray.push('--module', cmd); if (bundleKeys.length > 1) { function massageWrapper(w) { - return (w.replace('<%= contents %>', '%s') - /*+ '\n//# sourceMappingURL=%basename%.map\n'*/); + return w.replace('<%= contents %>', '%s'); + /*+ '\n//# sourceMappingURL=%basename%.map\n'*/ } // We need to post wrap the main bundles. We can't wrap v0.js either // since it would have the wrapper already when we read it and prepend @@ -236,11 +252,23 @@ exports.getBundleFlags = function(g) { jsFilesToWrap.push(name); } else { const configEntry = getExtensionBundleConfig(originalName); - const marker = configEntry && Array.isArray(configEntry.postPrepend) ? - SPLIT_MARKER : ''; - flagsArray.push('--module_wrapper', name + ':' + - massageWrapper(wrappers.extension( - info.name, info.loadPriority, bundleDeps, marker))); + const marker = + configEntry && Array.isArray(configEntry.postPrepend) + ? SPLIT_MARKER + : ''; + flagsArray.push( + '--module_wrapper', + name + + ':' + + massageWrapper( + wrappers.extension( + info.name, + info.loadPriority, + bundleDeps, + marker + ) + ) + ); } } else { throw new Error('Expect to build more than one bundle.'); @@ -297,72 +325,87 @@ exports.getGraph = function(entryModules, config) { deps: true, detectGlobals: false, }) - // The second stage are transforms that closure compiler supports - // directly and which we don't want to apply during deps finding. - .transform(babelify, { - compact: false, - plugins: [ - require.resolve('babel-plugin-transform-es2015-modules-commonjs'), - ], - }); + // The second stage are transforms that closure compiler supports + // directly and which we don't want to apply during deps finding. + .transform(babelify, { + compact: false, + plugins: [ + require.resolve('babel-plugin-transform-es2015-modules-commonjs'), + ], + }); // This gets us the actual deps. We collect them in an array, so // we can sort them prior to building the dep tree. Otherwise the tree // will not be stable. const depEntries = []; - b.pipeline.get('deps').push(through.obj(function(row, enc, next) { - row.source = null; // Release memory - depEntries.push(row); - next(); - })); - - b.bundle().on('end', function() { - const edges = {}; - depEntries.sort(function(a, b) { - return a.id < b.id; - }).forEach(function(row) { - const id = unifyPath(exports.maybeAddDotJs( - relativePath(process.cwd(), row.id))); - topo.addNode(id, id); - const deps = edges[id] = Object.keys(row.deps).sort().map(function(dep) { - return unifyPath(relativePath(process.cwd(), - row.deps[dep])); - }); - graph.deps[id] = deps; - if (row.entry) { - graph.depOf[id] = {}; - graph.depOf[id][id] = true; // Self edge. - deps.forEach(function(dep) { - graph.depOf[id][dep] = true; + b.pipeline.get('deps').push( + through.obj(function(row, enc, next) { + row.source = null; // Release memory + depEntries.push(row); + next(); + }) + ); + + b.bundle() + .on('end', function() { + const edges = {}; + depEntries + .sort(function(a, b) { + return a.id < b.id; + }) + .forEach(function(row) { + const id = unifyPath( + exports.maybeAddDotJs(relativePath(process.cwd(), row.id)) + ); + topo.addNode(id, id); + const deps = (edges[id] = Object.keys(row.deps) + .sort() + .map(function(dep) { + return unifyPath(relativePath(process.cwd(), row.deps[dep])); + })); + graph.deps[id] = deps; + if (row.entry) { + graph.depOf[id] = {}; + graph.depOf[id][id] = true; // Self edge. + deps.forEach(function(dep) { + graph.depOf[id][dep] = true; + }); + } }); - } - }); - Object.keys(edges).sort().forEach(function(id) { - edges[id].forEach(function(dep) { - topo.addEdge(id, dep); - }); - }); - graph.sorted = Array.from(topo.sort().keys()).reverse(); - - setupBundles(graph); - transformPathsToTempDir(graph, config); - resolve(graph); - fs.writeFileSync('deps.txt', JSON.stringify(graph, null, 2)); - }).on('error', reject).pipe(devnull()); + Object.keys(edges) + .sort() + .forEach(function(id) { + edges[id].forEach(function(dep) { + topo.addEdge(id, dep); + }); + }); + graph.sorted = Array.from(topo.sort().keys()).reverse(); + + setupBundles(graph); + transformPathsToTempDir(graph, config); + resolve(graph); + fs.writeFileSync('deps.txt', JSON.stringify(graph, null, 2)); + }) + .on('error', reject) + .pipe(devnull()); return promise; }; function setupBundles(graph) { // For each module, mark them as to whether any of the entry // modules depends on them (transitively). - Array.from(graph.sorted).reverse().forEach(function(id) { - graph.deps[id].forEach(function(dep) { - Object.keys(graph.depOf).sort().forEach(function(entry) { - if (graph.depOf[entry][id]) { - graph.depOf[entry][dep] = true; - } + Array.from(graph.sorted) + .reverse() + .forEach(function(id) { + graph.deps[id].forEach(function(dep) { + Object.keys(graph.depOf) + .sort() + .forEach(function(entry) { + if (graph.depOf[entry][id]) { + graph.depOf[entry][dep] = true; + } + }); }); }); - }); // Create the bundles. graph.sorted.forEach(function(id) { @@ -372,18 +415,26 @@ function setupBundles(graph) { // Bundles that this item must be available to. const bundleDestCandidates = []; // Count in how many bundles a modules wants to be. - Object.keys(graph.depOf).sort().forEach(function(entry) { - if (graph.depOf[entry][id]) { - inBundleCount++; - dest = entry; - const configEntry = getExtensionBundleConfig(entry); - const type = configEntry ? configEntry.type : mainBundle; - bundleDestCandidates.push(type); - } - }); - console/*OK*/.assert(inBundleCount >= 1, - 'Should be in at least 1 bundle', id, 'Bundle count', - inBundleCount, graph.depOf); + Object.keys(graph.depOf) + .sort() + .forEach(function(entry) { + if (graph.depOf[entry][id]) { + inBundleCount++; + dest = entry; + const configEntry = getExtensionBundleConfig(entry); + const type = configEntry ? configEntry.type : mainBundle; + bundleDestCandidates.push(type); + } + }); + console /*OK*/ + .assert( + inBundleCount >= 1, + 'Should be in at least 1 bundle', + id, + 'Bundle count', + inBundleCount, + graph.depOf + ); // If a module is in more than 1 bundle, it must go into _base. if (bundleDestCandidates.length > 1) { const first = bundleDestCandidates[0]; @@ -516,20 +567,21 @@ function isAltMainBundle(name) { } exports.singlePassCompile = async function(entryModule, options) { - return exports.getFlags({ - modules: [entryModule].concat(extensions), - writeTo: singlePassDest, - define: options.define, - externs: options.externs, - hideWarningsFor: options.hideWarningsFor, - }) - .then(compile) - .then(wrapMainBinaries) - .then(postProcessConcat) - .catch(err => { - err.showStack = false; // Useless node_modules stack - return Promise.reject(err); - }); + return exports + .getFlags({ + modules: [entryModule].concat(extensions), + writeTo: singlePassDest, + define: options.define, + externs: options.externs, + hideWarningsFor: options.hideWarningsFor, + }) + .then(compile) + .then(wrapMainBinaries) + .then(postProcessConcat) + .catch(err => { + err.showStack = false; // Useless node_modules stack + return Promise.reject(err); + }); }; /** @@ -551,8 +603,11 @@ function wrapMainBinaries() { const path = `dist/${x}.js`; const bootstrapCode = path === 'dist/v0.js' ? '' : mainFile; const isAmpAltstring = path === 'dist/v0.js' ? '' : 'self.IS_AMP_ALT=1;'; - fs.writeFileSync(path, `${isAmpAltstring}${prefix}${bootstrapCode}` + - `${fs.readFileSync(path).toString()}${suffix}`); + fs.writeFileSync( + path, + `${isAmpAltstring}${prefix}${bootstrapCode}` + + `${fs.readFileSync(path).toString()}${suffix}` + ); }); } @@ -562,8 +617,7 @@ function wrapMainBinaries() { * source maps. */ function postProcessConcat() { - const extensions = extensionBundles.filter( - x => Array.isArray(x.postPrepend)); + const extensions = extensionBundles.filter(x => Array.isArray(x.postPrepend)); extensions.forEach(extension => { const isAltMainBundle = altMainBundles.some(x => { return x.name === extension.name; @@ -582,11 +636,15 @@ function postProcessConcat() { targets.push(createFullPath(extension.version)); } targets.forEach(path => { - const prependContent = extension.postPrepend.map(x => { - return ';' + fs.readFileSync(x, 'utf8').toString(); - }).join(''); - const content = fs.readFileSync(path, 'utf8').toString() - .split(SPLIT_MARKER); + const prependContent = extension.postPrepend + .map(x => { + return ';' + fs.readFileSync(x, 'utf8').toString(); + }) + .join(''); + const content = fs + .readFileSync(path, 'utf8') + .toString() + .split(SPLIT_MARKER); const prefix = content[0]; const suffix = content[1]; fs.writeFileSync(path, prefix + prependContent + suffix, 'utf8'); @@ -597,17 +655,18 @@ function postProcessConcat() { function compile(flagsArray) { // TODO(@cramforce): Run the post processing step return new Promise(function(resolve, reject) { - return gulp.src(srcs, {base: transformDir}) - .pipe(gulpIf(shouldShortenLicense, shortenLicense())) - .pipe(sourcemaps.init({loadMaps: true})) - .pipe(gulpClosureCompile(flagsArray)) - .on('error', err => { - handleSinglePassCompilerError(); - reject(err); - }) - .pipe(sourcemaps.write('.')) - .pipe(gulpIf(/(\/amp-|\/_base)/, rename(path => path.dirname += '/v0'))) - .pipe(gulp.dest('.')) - .on('end', resolve); + return gulp + .src(srcs, {base: transformDir}) + .pipe(gulpIf(shouldShortenLicense, shortenLicense())) + .pipe(sourcemaps.init({loadMaps: true})) + .pipe(gulpClosureCompile(flagsArray)) + .on('error', err => { + handleSinglePassCompilerError(); + reject(err); + }) + .pipe(sourcemaps.write('.')) + .pipe(gulpIf(/(\/amp-|\/_base)/, rename(path => (path.dirname += '/v0')))) + .pipe(gulp.dest('.')) + .on('end', resolve); }); } diff --git a/build-system/config.js b/build-system/config.js index 37a3b30aea62..aa0bce4aa953 100644 --- a/build-system/config.js +++ b/build-system/config.js @@ -15,9 +15,7 @@ */ 'use strict'; -const initTestsPath = [ - 'test/_init_tests.js', -]; +const initTestsPath = ['test/_init_tests.js']; const fixturesExamplesPaths = [ 'test/fixtures/*.html', @@ -58,8 +56,10 @@ const builtRuntimePaths = [ const commonUnitTestPaths = initTestsPath.concat(fixturesExamplesPaths); -const commonIntegrationTestPaths = - initTestsPath.concat(fixturesExamplesPaths, builtRuntimePaths); +const commonIntegrationTestPaths = initTestsPath.concat( + fixturesExamplesPaths, + builtRuntimePaths +); const testPaths = commonIntegrationTestPaths.concat([ 'test/*/!(e2e)/**/*.js', @@ -73,9 +73,7 @@ const a4aTestPaths = initTestsPath.concat([ 'ads/google/a4a/test/*.js', ]); -const chaiAsPromised = [ - 'test/chai-as-promised/chai-as-promised.js', -]; +const chaiAsPromised = ['test/chai-as-promised/chai-as-promised.js']; const unitTestPaths = [ 'test/unit/**/*.js', @@ -83,24 +81,16 @@ const unitTestPaths = [ 'extensions/**/test/*.js', ]; -const unitTestOnSaucePaths = [ - 'test/unit/**/*.js', - 'ads/**/test/test-*.js', -]; +const unitTestOnSaucePaths = ['test/unit/**/*.js', 'ads/**/test/test-*.js']; const integrationTestPaths = [ 'test/integration/**/*.js', 'extensions/**/test/integration/**/*.js', ]; -const e2eTestPaths = [ - 'test/e2e/*.js', - 'extensions/**/test-e2e/*.js', -]; +const e2eTestPaths = ['test/e2e/*.js', 'extensions/**/test-e2e/*.js']; -const devDashboardTestPaths = [ - 'build-system/app-index/test/**/*.js', -]; +const devDashboardTestPaths = ['build-system/app-index/test/**/*.js']; const lintGlobs = [ '**/*.js', @@ -149,7 +139,7 @@ module.exports = { jsonGlobs: [ '**/*.json', '!{node_modules,build,dist,dist.3p,dist.tools,' + - 'third_party,build-system}/**/*.*', + 'third_party,build-system}/**/*.*', ], presubmitGlobs: [ '**/*.{css,js,go}', @@ -157,7 +147,7 @@ module.exports = { // built 3p binary. This is done, so we make sure our special 3p checks // run against the entire transitive closure of deps. '!{node_modules,build,dist,dist.tools,' + - 'dist.3p/[0-9]*,dist.3p/current,dist.3p/current-min}/**/*.*', + 'dist.3p/[0-9]*,dist.3p/current,dist.3p/current-min}/**/*.*', '!dist.3p/current/**/ampcontext-lib.js', '!dist.3p/current/**/iframe-transport-client-lib.js', '!out/**/*.*', diff --git a/build-system/ctrlcHandler.js b/build-system/ctrlcHandler.js index d7464da4c6ec..fd64d34cfbb2 100644 --- a/build-system/ctrlcHandler.js +++ b/build-system/ctrlcHandler.js @@ -21,9 +21,8 @@ const {isTravisBuild} = require('./travis'); const {green, cyan} = colors; -const killCmd = - (process.platform == 'win32') ? 'taskkill /f /pid' : 'kill -KILL'; -const killSuffix = (process.platform == 'win32') ? '>NUL' : ''; +const killCmd = process.platform == 'win32' ? 'taskkill /f /pid' : 'kill -KILL'; +const killSuffix = process.platform == 'win32' ? '>NUL' : ''; /** * Creates an async child process that handles Ctrl + C and immediately cancels @@ -33,11 +32,19 @@ const killSuffix = (process.platform == 'win32') ? '>NUL' : ''; */ exports.createCtrlcHandler = function(command) { if (!isTravisBuild()) { - log(green('Running'), cyan(command) + green('. Press'), cyan('Ctrl + C'), - green('to cancel...')); + log( + green('Running'), + cyan(command) + green('. Press'), + cyan('Ctrl + C'), + green('to cancel...') + ); } - const killMessage = green('\nDetected ') + cyan('Ctrl + C') + - green('. Canceling ') + cyan(command) + green('.'); + const killMessage = + green('\nDetected ') + + cyan('Ctrl + C') + + green('. Canceling ') + + cyan(command) + + green('.'); const listenerCmd = ` #!/bin/sh ctrlcHandler() { @@ -48,8 +55,9 @@ exports.createCtrlcHandler = function(command) { trap 'ctrlcHandler' INT read _ # Waits until the process is terminated `; - return execScriptAsync( - listenerCmd, {'stdio': [null, process.stdout, process.stderr]}).pid; + return execScriptAsync(listenerCmd, { + 'stdio': [null, process.stdout, process.stderr], + }).pid; }; /** diff --git a/build-system/dep-check-config.js b/build-system/dep-check-config.js index ebbba2f88086..df7f5c742f2a 100644 --- a/build-system/dep-check-config.js +++ b/build-system/dep-check-config.js @@ -75,13 +75,13 @@ exports.rules = [ mustNotDependOn: 'third_party/**/*.js', whitelist: [ 'extensions/amp-crypto-polyfill/**/*.js->' + - 'third_party/closure-library/sha384-generated.js', + 'third_party/closure-library/sha384-generated.js', 'extensions/amp-mustache/**/amp-mustache.js->' + - 'third_party/mustache/mustache.js', + 'third_party/mustache/mustache.js', 'extensions/amp-ad-network-adzerk-impl/0.1/' + - 'amp-ad-network-adzerk-impl.js->third_party/mustache/mustache.js', + 'amp-ad-network-adzerk-impl.js->third_party/mustache/mustache.js', 'extensions/amp-timeago/0.1/amp-timeago.js->' + - 'third_party/timeagojs/timeago.js', + 'third_party/timeagojs/timeago.js', '3p/polyfills.js->third_party/babel/custom-babel-helpers.js', 'src/sanitizer.js->third_party/caja/html-sanitizer.js', 'extensions/amp-viz-vega/**->third_party/vega/vega.js', @@ -89,21 +89,21 @@ exports.rules = [ 'src/css.js->third_party/css-escape/css-escape.js', 'src/shadow-embed.js->third_party/webcomponentsjs/ShadowCSS.js', 'third_party/timeagojs/timeago.js->' + - 'third_party/timeagojs/timeago-locales.js', + 'third_party/timeagojs/timeago-locales.js', 'extensions/amp-date-picker/**->third_party/react-dates/bundle.js', 'extensions/amp-date-picker/**->third_party/rrule/rrule.js', 'extensions/amp-subscriptions/**/*.js->' + - 'third_party/subscriptions-project/apis.js', + 'third_party/subscriptions-project/apis.js', 'extensions/amp-subscriptions/**/*.js->' + - 'third_party/subscriptions-project/config.js', + 'third_party/subscriptions-project/config.js', 'extensions/amp-subscriptions-google/**/*.js->' + - 'third_party/subscriptions-project/apis.js', + 'third_party/subscriptions-project/apis.js', 'extensions/amp-subscriptions-google/**/*.js->' + - 'third_party/subscriptions-project/config.js', + 'third_party/subscriptions-project/config.js', 'extensions/amp-subscriptions-google/**/*.js->' + - 'third_party/subscriptions-project/swg.js', + 'third_party/subscriptions-project/swg.js', 'extensions/amp-recaptcha-input/**/*.js->' + - 'third_party/amp-toolbox-cache-url/dist/amp-toolbox-cache-url.esm.js', + 'third_party/amp-toolbox-cache-url/dist/amp-toolbox-cache-url.esm.js', ], }, // Rules for 3p @@ -183,18 +183,18 @@ exports.rules = [ whitelist: [ // See todo note in ads/_a4a-config.js 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config.js', + 'extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-doubleclick-impl/0.1/' + - 'doubleclick-a4a-config.js', + 'extensions/amp-ad-network-doubleclick-impl/0.1/' + + 'doubleclick-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-fake-impl/0.1/fake-a4a-config.js', + 'extensions/amp-ad-network-fake-impl/0.1/fake-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-triplelift-impl/0.1/triplelift-a4a-config.js', + 'extensions/amp-ad-network-triplelift-impl/0.1/triplelift-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-cloudflare-impl/0.1/cloudflare-a4a-config.js', + 'extensions/amp-ad-network-cloudflare-impl/0.1/cloudflare-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-gmossp-impl/0.1/gmossp-a4a-config.js', + 'extensions/amp-ad-network-gmossp-impl/0.1/gmossp-a4a-config.js', ], }, // Rules for extensions and main src. @@ -278,404 +278,400 @@ exports.rules = [ mustNotDependOn: 'src/service/**/*.js', whitelist: [ 'extensions/amp-a4a/0.1/a4a-variable-source.js->' + - 'src/service/variable-source.js', + 'src/service/variable-source.js', 'extensions/amp-a4a/0.1/amp-a4a.js->' + - 'src/service/url-replacements-impl.js', + 'src/service/url-replacements-impl.js', 'extensions/amp-video-service/**->' + - 'src/service/video-service-interface.js', + 'src/service/video-service-interface.js', 'extensions/amp-video/0.1/amp-video.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-video-iframe/0.1/amp-video-iframe.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-ooyala-player/0.1/amp-ooyala-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-youtube/0.1/amp-youtube.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-viqeo-player/0.1/amp-viqeo-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-brightcove/0.1/amp-brightcove.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-powr-player/0.1/amp-powr-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-dailymotion/0.1/amp-dailymotion.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-brid-player/0.1/amp-brid-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-gfycat/0.1/amp-gfycat.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-a4a/0.1/amp-a4a.js->src/service/variable-source.js', 'extensions/amp-a4a/0.1/friendly-frame-util.js->' + - 'src/service/url-replacements-impl.js', + 'src/service/url-replacements-impl.js', 'extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-3q-player/0.1/amp-3q-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-ima-video/0.1/amp-ima-video.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-vimeo/0.1/amp-vimeo.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-wistia-player/0.1/amp-wistia-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-delight-player/0.1/amp-delight-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-analytics/0.1/iframe-transport.js->' + - 'src/service/extension-location.js', + 'src/service/extension-location.js', 'extensions/amp-analytics/0.1/iframe-transport.js->' + - 'src/service/jank-meter.js', + 'src/service/jank-meter.js', 'extensions/amp-position-observer/0.1/amp-position-observer.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-position-observer/0.1/amp-position-observer.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-fx-collection/0.1/providers/fx-provider.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-fx-collection/0.1/providers/fx-provider.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-list/0.1/amp-list.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-list/0.1/amp-list.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-video-docking/0.1/amp-video-docking.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-video-docking/0.1/amp-video-docking.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-analytics/0.1/amp-analytics.js->' + - 'src/service/cid-impl.js', + 'src/service/cid-impl.js', 'extensions/amp-analytics/0.1/cookie-writer.js->' + - 'src/service/cid-impl.js', + 'src/service/cid-impl.js', 'extensions/amp-next-page/0.1/next-page-service.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-next-page/0.1/next-page-service.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-user-notification/0.1/amp-user-notification.js->' + - 'src/service/notification-ui-manager.js', + 'src/service/notification-ui-manager.js', 'extensions/amp-consent/0.1/amp-consent.js->' + - 'src/service/notification-ui-manager.js', + 'src/service/notification-ui-manager.js', // For autoplay delegation: 'extensions/amp-story/0.1/amp-story-page.js->' + - 'src/service/video-service-sync-impl.js', + 'src/service/video-service-sync-impl.js', 'extensions/amp-story/1.0/amp-story-page.js->' + - 'src/service/video-service-sync-impl.js', + 'src/service/video-service-sync-impl.js', // Accessing USER_INTERACTED constant: 'extensions/amp-story/1.0/media-pool.js->' + - 'src/service/video-service-interface.js', + 'src/service/video-service-interface.js', 'extensions/amp-story/1.0/page-advancement.js->' + - 'src/service/action-impl.js', + 'src/service/action-impl.js', 'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js->' + - 'src/service/navigation.js', + 'src/service/navigation.js', 'extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js->' + - 'src/service/navigation.js', - 'extensions/amp-mowplayer/0.1/amp-mowplayer.js->' + - 'src/service/video-manager-impl.js', + 'src/service/navigation.js', + 'extensions/amp-mowplayer/0.1/amp-mowplayer.js->' + + 'src/service/video-manager-impl.js', 'extensions/amp-analytics/0.1/linker-manager.js->' + - 'src/service/navigation.js', + 'src/service/navigation.js', 'extensions/amp-skimlinks/0.1/link-rewriter/link-rewriter-manager.js->' + 'src/service/navigation.js', - 'extensions/amp-list/0.1/amp-list.js->' + - 'src/service/xhr-impl.js', - 'extensions/amp-form/0.1/amp-form.js->' + - 'src/service/xhr-impl.js', + 'extensions/amp-list/0.1/amp-list.js->src/service/xhr-impl.js', + 'extensions/amp-form/0.1/amp-form.js->src/service/xhr-impl.js', // Accessing extension-location.calculateExtensionScriptUrl(). 'extensions/amp-script/0.1/amp-script.js->' + - 'src/service/extension-location.js', + 'src/service/extension-location.js', // Origin experiments. 'extensions/amp-list/0.1/amp-list.js->' + - 'src/service/origin-experiments-impl.js', + 'src/service/origin-experiments-impl.js', 'extensions/amp-recaptcha-input/0.1/amp-recaptcha-input.js->' + - 'src/service/origin-experiments-impl.js', + 'src/service/origin-experiments-impl.js', 'extensions/amp-experiment/1.0/amp-experiment.js->' + - 'src/service/origin-experiments-impl.js', + 'src/service/origin-experiments-impl.js', 'extensions/amp-script/0.1/amp-script.js->' + - 'src/service/origin-experiments-impl.js', + 'src/service/origin-experiments-impl.js', // For action macros. 'extensions/amp-action-macro/0.1/amp-action-macro.js->' + - 'src/service/action-impl.js', + 'src/service/action-impl.js', 'extensions/amp-link-rewriter/0.1/amp-link-rewriter.js->' + - 'src/service/navigation.js', + 'src/service/navigation.js', // For localization. - 'extensions/amp-story/0.1/amp-story.js->' + - 'src/service/localization.js', - 'extensions/amp-story/1.0/amp-story.js->' + - 'src/service/localization.js', + 'extensions/amp-story/0.1/amp-story.js->src/service/localization.js', + 'extensions/amp-story/1.0/amp-story.js->src/service/localization.js', 'extensions/amp-story/1.0/_locales/af.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/am.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ar.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/bg.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/bn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/bs.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ca.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/cs.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/da.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/de.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/default.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/el.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/en-GB.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/en.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/es-419.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/es.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/et.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/eu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/fa.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/fi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/fil.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/fr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/gl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/gu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/hi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/hr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/hu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/id.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/is.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/it.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/iw.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ja.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ka.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/km.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/kn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ko.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/lo.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/lt.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/lv.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/mk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ml.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/mn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/mr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ms.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/my.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ne.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/nl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/no.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/pa.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/pt-BR.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/pt-PT.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ro.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ru.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/si.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sq.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sv.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sw.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ta.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/te.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/th.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/tr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/uk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ur.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/vi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/zh-CN.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/zh-TW.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/zu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/amp-story-auto-ads.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/af.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/am.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ar.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/bg.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/bn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/bs.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ca.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/cs.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/da.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/de.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/el.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/en-GB.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/en.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/es-419.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/es.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/et.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/eu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/fa.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/fi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/fil.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/fr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/gl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/gu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/hi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/hr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/hu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/id.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/is.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/it.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/iw.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ja.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ka.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/km.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/kn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ko.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/lo.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/lt.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/lv.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/mk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ml.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/mn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/mr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ms.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/my.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ne.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/nl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/no.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/pa.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/pt-BR.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/pt-PT.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ro.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ru.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/si.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sq.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sv.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sw.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ta.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/te.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/th.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/tr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/uk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ur.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/vi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/zh-CN.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/zh-TW.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/zu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', ], }, { @@ -734,7 +730,8 @@ exports.rules = [ 'src/3p-frame.js', 'src/iframe-helper.js', ], - whitelist: 'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js->src/3p-frame.js', + whitelist: + 'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js->src/3p-frame.js', }, { @@ -763,21 +760,19 @@ exports.rules = [ // Do not add any additional files to this whitelist without express // permission from @bradfrizzell, @keithwrightbos, or @robhazan. { - mustNotDependOn: [ - 'ads/google/doubleclick.js', - ], + mustNotDependOn: ['ads/google/doubleclick.js'], whitelist: [ - /** DO NOT ADD TO WHITELIST **/ + /** DO NOT ADD TO WHITELIST */ 'ads/ix.js->ads/google/doubleclick.js', 'ads/imonomy.js->ads/google/doubleclick.js', 'ads/medianet.js->ads/google/doubleclick.js', 'ads/navegg.js->ads/google/doubleclick.js', - /** DO NOT ADD TO WHITELIST **/ + /** DO NOT ADD TO WHITELIST */ 'ads/openx.js->ads/google/doubleclick.js', 'ads/pulsepoint.js->ads/google/doubleclick.js', 'ads/rubicon.js->ads/google/doubleclick.js', 'ads/yieldbot.js->ads/google/doubleclick.js', - /** DO NOT ADD TO WHITELIST **/ + /** DO NOT ADD TO WHITELIST */ ], }, ]; diff --git a/build-system/eslint-rules/query-selector.js b/build-system/eslint-rules/query-selector.js index 4aaa1c190620..b9a1d490b098 100644 --- a/build-system/eslint-rules/query-selector.js +++ b/build-system/eslint-rules/query-selector.js @@ -73,8 +73,8 @@ module.exports = function(context) { return; } - if (callee.trailingComments) { - const ok = callee.trailingComments.some(comment => { + if (node.leadingComments) { + const ok = node.leadingComments.some(comment => { return comment.value === 'OK'; }); if (ok) { diff --git a/build-system/exec.js b/build-system/exec.js index d9d9a4341814..82afff22cc29 100644 --- a/build-system/exec.js +++ b/build-system/exec.js @@ -21,8 +21,8 @@ const childProcess = require('child_process'); -const shellCmd = (process.platform == 'win32') ? 'cmd' : '/bin/sh'; -const shellFlag = (process.platform == 'win32') ? '/C' : '-c'; +const shellCmd = process.platform == 'win32' ? 'cmd' : '/bin/sh'; +const shellFlag = process.platform == 'win32' ? '/C' : '-c'; /** * Spawns the given command in a child process with the given options. @@ -77,14 +77,12 @@ exports.execOrDie = function(cmd, options) { * @return {!Object} */ function getOutput(cmd) { - const p = spawnProcess( - cmd, - { - 'cwd': process.cwd(), - 'env': process.env, - 'stdio': 'pipe', - 'encoding': 'utf-8', - }); + const p = spawnProcess(cmd, { + 'cwd': process.cwd(), + 'env': process.env, + 'stdio': 'pipe', + 'encoding': 'utf-8', + }); return p; } diff --git a/build-system/git.js b/build-system/git.js index 296f6f0c9df5..b7ea4a734384 100644 --- a/build-system/git.js +++ b/build-system/git.js @@ -67,7 +67,9 @@ exports.shortSha = function(sha) { */ exports.gitDiffNameOnlyMaster = function() { const masterBaseline = gitMasterBaseline(); - return getStdout(`git diff --name-only ${masterBaseline}`).trim().split('\n'); + return getStdout(`git diff --name-only ${masterBaseline}`) + .trim() + .split('\n'); }; /** @@ -100,7 +102,9 @@ ${colors.cyan(commitLogMaxCount)} commits. \ Branch ${colors.cyan(exports.gitBranchName())} may not have been forked from \ ${colors.cyan('master')}.`; commitLog += `\n${colors.yellow('WARNING:')} See \ -${colors.cyan('https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-quick.md')} \ +${colors.cyan( + 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-quick.md' +)} \ for how to fix this.`; } return commitLog; @@ -114,7 +118,8 @@ for how to fix this.`; exports.gitDiffAddedNameOnlyMaster = function() { const branchPoint = gitMergeBaseLocalMaster(); return getStdout(`git diff --name-only --diff-filter=ARC ${branchPoint}`) - .trim().split('\n'); + .trim() + .split('\n'); }; /** @@ -130,9 +135,9 @@ exports.gitDiffColor = function() { * @return {string} */ exports.gitBranchName = function() { - return isTravisPullRequestBuild() ? - travisPullRequestBranch() : - getStdout('git rev-parse --abbrev-ref HEAD').trim(); + return isTravisPullRequestBuild() + ? travisPullRequestBranch() + : getStdout('git rev-parse --abbrev-ref HEAD').trim(); }; /** @@ -160,8 +165,8 @@ exports.gitCommitterEmail = function() { */ exports.gitCommitFormattedTime = function() { return getStdout( - 'TZ=UTC git log -1 --pretty="%cd" --date=format-local:%y%m%d%H%M%S') - .trim(); + 'TZ=UTC git log -1 --pretty="%cd" --date=format-local:%y%m%d%H%M%S' + ).trim(); }; /** diff --git a/build-system/internal-version.js b/build-system/internal-version.js index ebba56324664..629f881fdf33 100644 --- a/build-system/internal-version.js +++ b/build-system/internal-version.js @@ -43,8 +43,9 @@ function getVersion() { lastDigit = parseInt(argv.custom_version_mark, 10); if (isNaN(lastDigit) || lastDigit < 0 || lastDigit > 9) { throw new Error( - `--custom_version_mark is set to ${argv.custom_version_mark}, ` + - 'expected value between 0 and 9!'); + `--custom_version_mark is set to ${argv.custom_version_mark}, ` + + 'expected value between 0 and 9!' + ); } } return `${lastCommitFormattedTime}${lastDigit}`; diff --git a/build-system/pr-check/build-targets.js b/build-system/pr-check/build-targets.js index a02fa2a47690..babf3355adc9 100644 --- a/build-system/pr-check/build-targets.js +++ b/build-system/pr-check/build-targets.js @@ -43,7 +43,8 @@ function isValidatorWebuiFile(filePath) { * @return {boolean} */ function isBuildSystemFile(filePath) { - return (filePath.startsWith('build-system') && + return ( + (filePath.startsWith('build-system') && // Exclude textproto from build-system since we want it to trigger // tests and type check. path.extname(filePath) != '.textproto' && @@ -55,9 +56,10 @@ function isBuildSystemFile(filePath) { !isDevDashboardFile(filePath) && // Exclude visual diff files from build-system since we want it to trigger // visual diff tests. - !isVisualDiffFile(filePath)) - // OWNERS.yaml files should trigger build system to run tests - || isOwnersFile(filePath); + !isVisualDiffFile(filePath)) || + // OWNERS.yaml files should trigger build system to run tests + isOwnersFile(filePath) + ); } /** @@ -67,13 +69,13 @@ function isBuildSystemFile(filePath) { * @return {boolean} */ function isBuildSystemAndRuntimeFile(filePath) { - return isBuildSystemFile(filePath) && - // These build system files are involved in the compilation/bundling and - // are likely to affect the runtime as well. - ( - filePath.startsWith('build-system/babel-plugins') || - filePath.startsWith('build-system/runner') - ); + return ( + isBuildSystemFile(filePath) && + // These build system files are involved in the compilation/bundling and + // are likely to affect the runtime as well. + (filePath.startsWith('build-system/babel-plugins') || + filePath.startsWith('build-system/runner')) + ); } /** @@ -101,9 +103,12 @@ function isValidatorFile(filePath) { // Validator files take the form of validator-.*\.(html|out|protoascii) const name = path.basename(filePath); - return name.startsWith('validator-') && - (name.endsWith('.out') || name.endsWith('.html') || - name.endsWith('.protoascii')); + return ( + name.startsWith('validator-') && + (name.endsWith('.out') || + name.endsWith('.html') || + name.endsWith('.protoascii')) + ); } /** @@ -131,9 +136,11 @@ function isDocFile(filePath) { */ function isVisualDiffFile(filePath) { const filename = path.basename(filePath); - return (filename == 'visual-diff.js' || - filename == 'visual-tests' || - filePath.startsWith('examples/visual-tests/')); + return ( + filename == 'visual-diff.js' || + filename == 'visual-tests' || + filePath.startsWith('examples/visual-tests/') + ); } /** @@ -155,8 +162,10 @@ function isUnitTest(filePath) { * @return {boolean} */ function isDevDashboardFile(filePath) { - return (filePath === 'build-system/app.js' || - filePath.startsWith('build-system/app-index/')); + return ( + filePath === 'build-system/app.js' || + filePath.startsWith('build-system/app-index/') + ); } /** @@ -178,7 +187,7 @@ function isIntegrationTest(filePath) { */ function isFlagConfig(filePath) { const filename = path.basename(filePath); - return (filename == 'prod-config.json' || filename == 'canary-config.json'); + return filename == 'prod-config.json' || filename == 'canary-config.json'; } /** @@ -191,17 +200,23 @@ function isFlagConfig(filePath) { function areValidBuildTargets(buildTargets, fileName) { const files = gitDiffNameOnlyMaster(); if (buildTargets.has('FLAG_CONFIG') && buildTargets.has('RUNTIME')) { - console.log(fileName, colors.red('ERROR:'), - 'Looks like your PR contains', - colors.cyan('{prod|canary}-config.json'), - 'in addition to some other files. Config and code are not kept in', - 'sync, and config needs to be backwards compatible with code for at', - 'least two weeks. See #8188'); + console.log( + fileName, + colors.red('ERROR:'), + 'Looks like your PR contains', + colors.cyan('{prod|canary}-config.json'), + 'in addition to some other files. Config and code are not kept in', + 'sync, and config needs to be backwards compatible with code for at', + 'least two weeks. See #8188' + ); const nonFlagConfigFiles = files.filter(file => !isFlagConfig(file)); const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); - console.log(fileLogPrefix, colors.red('ERROR:'), - 'Please move these files to a separate PR:', - colors.cyan(nonFlagConfigFiles.join(', '))); + console.log( + fileLogPrefix, + colors.red('ERROR:'), + 'Please move these files to a separate PR:', + colors.cyan(nonFlagConfigFiles.join(', ')) + ); return false; } return true; @@ -226,7 +241,8 @@ function determineBuildTargets() { 'INTEGRATION_TEST', 'DOCS', 'FLAG_CONFIG', - 'VISUAL_DIFF']); + 'VISUAL_DIFF', + ]); } const targetSet = new Set(); for (const p of filePaths) { diff --git a/build-system/pr-check/build.js b/build-system/pr-check/build.js index c0f1ab404898..a913293fe3d7 100644 --- a/build-system/pr-check/build.js +++ b/build-system/pr-check/build.js @@ -38,15 +38,16 @@ const {runYarnChecks} = require('./yarn-checks'); const FILENAME = 'build.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); - +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); const buildTargets = determineBuildTargets(); - if (!runYarnChecks(FILENAME) || - !areValidBuildTargets(buildTargets, FILENAME)) { + if ( + !runYarnChecks(FILENAME) || + !areValidBuildTargets(buildTargets, FILENAME) + ) { stopTimer(FILENAME, FILENAME, startTime); process.exitCode = 1; return; @@ -58,20 +59,24 @@ function main() { uploadBuildOutput(FILENAME); } else { printChangeSummary(FILENAME); - if (buildTargets.has('RUNTIME') || - buildTargets.has('UNIT_TEST') || - buildTargets.has('INTEGRATION_TEST') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('VISUAL_DIFF')) { - + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('FLAG_CONFIG') || + buildTargets.has('VISUAL_DIFF') + ) { timedExecOrDie('gulp update-packages'); timedExecOrDie('gulp build --fortesting'); uploadBuildOutput(FILENAME); } else { - console.log(`${FILELOGPREFIX} Skipping ` + colors.cyan('Build ') + + console.log( + `${FILELOGPREFIX} Skipping ` + + colors.cyan('Build ') + 'because this commit does not affect the runtime, build system, ' + - 'test files, or visual diff files'); + 'test files, or visual diff files' + ); } } diff --git a/build-system/pr-check/checks.js b/build-system/pr-check/checks.js index cb970c88ef50..c53bbaca5c2d 100644 --- a/build-system/pr-check/checks.js +++ b/build-system/pr-check/checks.js @@ -26,18 +26,19 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const {reportAllExpectedTests} = require('../tasks/runtime-test/status-report'); const FILENAME = 'checks.js'; -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function runCommonChecks() { - timedExecOrDie('gulp presubmit'); timedExecOrDie('gulp lint'); + timedExecOrDie('gulp presubmit'); timedExecOrDie('gulp ava'); timedExecOrDie('node node_modules/jest/bin/jest.js'); timedExecOrDie('gulp caches-json'); @@ -69,8 +70,7 @@ function main() { timedExecOrDie('gulp dev-dashboard-tests'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM')) { + if (buildTargets.has('RUNTIME') || buildTargets.has('BUILD_SYSTEM')) { timedExecOrDie('gulp dep-check'); timedExecOrDie('gulp check-types'); } diff --git a/build-system/pr-check/dist-bundle-size.js b/build-system/pr-check/dist-bundle-size.js index c90099c57272..d85ce4cf73b4 100644 --- a/build-system/pr-check/dist-bundle-size.js +++ b/build-system/pr-check/dist-bundle-size.js @@ -39,15 +39,16 @@ const {runYarnChecks} = require('./yarn-checks'); const FILENAME = 'dist-bundle-size.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); - +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); const buildTargets = determineBuildTargets(); - if (!runYarnChecks(FILENAME) || - !areValidBuildTargets(buildTargets, FILENAME)) { + if ( + !runYarnChecks(FILENAME) || + !areValidBuildTargets(buildTargets, FILENAME) + ) { stopTimer(FILENAME, FILENAME, startTime); process.exitCode = 1; return; @@ -60,21 +61,25 @@ function main() { uploadDistOutput(FILENAME); } else { printChangeSummary(FILENAME); - if (buildTargets.has('RUNTIME') || - buildTargets.has('UNIT_TEST') || - buildTargets.has('INTEGRATION_TEST') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('FLAG_CONFIG')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('FLAG_CONFIG') + ) { timedExecOrDie('gulp update-packages'); timedExecOrDie('gulp dist --fortesting'); timedExecOrDie('gulp bundle-size --on_pr_build'); uploadDistOutput(FILENAME); } else { timedExecOrDie('gulp bundle-size --on_skipped_build'); - console.log(`${FILELOGPREFIX} Skipping ` + + console.log( + `${FILELOGPREFIX} Skipping ` + colors.cyan('Dist, Bundle Size ') + 'because this commit does not affect the runtime, build system, ' + - 'test files, or visual diff files'); + 'test files, or visual diff files' + ); } } diff --git a/build-system/pr-check/e2e-tests.js b/build-system/pr-check/e2e-tests.js index b8269b5595eb..609316b7e94f 100644 --- a/build-system/pr-check/e2e-tests.js +++ b/build-system/pr-check/e2e-tests.js @@ -28,14 +28,15 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'e2e-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); async function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -47,21 +48,24 @@ async function main() { timedExecOrDie('gulp e2e --nobuild --headless'); } else { printChangeSummary(FILENAME); - if (buildTargets.has('RUNTIME') || - buildTargets.has('UNIT_TEST') || - buildTargets.has('INTEGRATION_TEST') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('VISUAL_DIFF')) { - + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('FLAG_CONFIG') || + buildTargets.has('VISUAL_DIFF') + ) { downloadBuildOutput(FILENAME); timedExecOrDie('gulp update-packages'); timedExecOrDie('gulp e2e --nobuild --headless'); } else { - console.log(`${FILELOGPREFIX} Skipping ` + + console.log( + `${FILELOGPREFIX} Skipping ` + colors.cyan('End to End Tests ') + 'because this commit does not affect the runtime, build system, ' + - 'test files, or visual diff files'); + 'test files, or visual diff files' + ); } } stopTimer(FILENAME, FILENAME, startTime); diff --git a/build-system/pr-check/local-tests.js b/build-system/pr-check/local-tests.js index 132cfb921a71..c4a2371df82f 100644 --- a/build-system/pr-check/local-tests.js +++ b/build-system/pr-check/local-tests.js @@ -27,15 +27,15 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'local-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); - +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -49,34 +49,46 @@ function main() { } else { printChangeSummary(FILENAME); - if (!(buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST') || - buildTargets.has('INTEGRATION_TEST'))) { + if ( + !( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST') + ) + ) { console.log( - `${FILELOGPREFIX} Skipping ` + colors.cyan('Local Tests ') + + `${FILELOGPREFIX} Skipping ` + + colors.cyan('Local Tests ') + 'because this commit not affect the runtime, build system, ' + - 'unit test files, or integration test files.'); + 'unit test files, or integration test files.' + ); stopTimer(FILENAME, FILENAME, startTime); return; } downloadBuildOutput(FILENAME); timedExecOrDie('gulp update-packages'); - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') + ) { timedExecOrDie('gulp test --unit --nobuild --headless --local-changes'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('INTEGRATION_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST') + ) { timedExecOrDie('gulp test --integration --nobuild --headless --coverage'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') + ) { timedExecOrDie('gulp test --unit --nobuild --headless --coverage'); } } diff --git a/build-system/pr-check/remote-tests.js b/build-system/pr-check/remote-tests.js index 6c101aed6570..7bbd74da9e9e 100644 --- a/build-system/pr-check/remote-tests.js +++ b/build-system/pr-check/remote-tests.js @@ -29,15 +29,15 @@ const { stopTimer, startSauceConnect, stopSauceConnect, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'remote-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); - +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); async function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -54,15 +54,20 @@ async function main() { stopSauceConnect(FILENAME); } else { printChangeSummary(FILENAME); - if (!(buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST') || - buildTargets.has('INTEGRATION_TEST'))) { + if ( + !( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST') + ) + ) { console.log( - `${FILELOGPREFIX} Skipping ` + + `${FILELOGPREFIX} Skipping ` + colors.cyan('Remote (Sauce Labs) Tests ') + 'because this commit does not affect the runtime, ' + - 'build system, or integration test files.'); + 'build system, or integration test files.' + ); stopTimer(FILENAME, FILENAME, startTime); return; } @@ -70,17 +75,22 @@ async function main() { timedExecOrDie('gulp update-packages'); await startSauceConnect(FILENAME); - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') + ) { timedExecOrDie('gulp test --unit --nobuild --saucelabs_lite'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('INTEGRATION_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST') + ) { timedExecOrDie( - 'gulp test --integration --nobuild --compiled --saucelabs'); + 'gulp test --integration --nobuild --compiled --saucelabs' + ); } stopSauceConnect(FILENAME); } diff --git a/build-system/pr-check/single-pass-tests.js b/build-system/pr-check/single-pass-tests.js index c0edd5655f02..354bff5a8968 100644 --- a/build-system/pr-check/single-pass-tests.js +++ b/build-system/pr-check/single-pass-tests.js @@ -26,15 +26,15 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'single-pass-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); - +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -43,22 +43,30 @@ function main() { if (!isTravisPullRequestBuild()) { timedExecOrDie('gulp update-packages'); timedExecOrDie('gulp dist --fortesting --single_pass --pseudo_names'); - timedExecOrDie('gulp test --integration ' + - '--nobuild --compiled --single_pass --headless'); + timedExecOrDie( + 'gulp test --integration ' + + '--nobuild --compiled --single_pass --headless' + ); } else { printChangeSummary(FILENAME); - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('INTEGRATION_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST') + ) { timedExecOrDie('gulp update-packages'); timedExecOrDie('gulp dist --fortesting --single_pass --pseudo_names'); - timedExecOrDie('gulp test --integration ' + - '--nobuild --compiled --single_pass --headless'); + timedExecOrDie( + 'gulp test --integration ' + + '--nobuild --compiled --single_pass --headless' + ); } else { - console.log(`${FILELOGPREFIX} Skipping ` + + console.log( + `${FILELOGPREFIX} Skipping ` + colors.cyan('Single Pass Tests ') + 'because this commit does not affect the runtime, build system, ' + - 'or integration test files.'); + 'or integration test files.' + ); } } diff --git a/build-system/pr-check/utils.js b/build-system/pr-check/utils.js index 509af2bfb292..e1f274059d91 100644 --- a/build-system/pr-check/utils.js +++ b/build-system/pr-check/utils.js @@ -26,19 +26,25 @@ const { gitTravisMasterBaseline, shortSha, } = require('../git'); +const { + isTravisBuild, + travisBuildNumber, + travisPullRequestSha, +} = require('../travis'); const {execOrDie, exec} = require('../exec'); -const {isTravisBuild, travisBuildNumber, travisPullRequestSha} = require('../travis'); -const BUILD_OUTPUT_FILE = - isTravisBuild() ? `amp_build_${travisBuildNumber()}.zip` : ''; -const DIST_OUTPUT_FILE = - isTravisBuild() ? `amp_dist_${travisBuildNumber()}.zip` : ''; +const BUILD_OUTPUT_FILE = isTravisBuild() + ? `amp_build_${travisBuildNumber()}.zip` + : ''; +const DIST_OUTPUT_FILE = isTravisBuild() + ? `amp_dist_${travisBuildNumber()}.zip` + : ''; const OUTPUT_DIRS = 'build/ dist/ dist.3p/ EXTENSIONS_CSS_MAP'; const OUTPUT_STORAGE_LOCATION = 'gs://amp-travis-builds'; const OUTPUT_STORAGE_KEY_FILE = 'sa-travis-key.json'; const OUTPUT_STORAGE_PROJECT_ID = 'amp-travis-build-storage'; const OUTPUT_STORAGE_SERVICE_ACCOUNT = - 'sa-travis@amp-travis-build-storage.iam.gserviceaccount.com'; + 'sa-travis@amp-travis-build-storage.iam.gserviceaccount.com'; /** * Prints a summary of files changed by, and commits included in the PR. @@ -50,24 +56,27 @@ function printChangeSummary(fileName) { if (isTravisBuild()) { console.log( - `${fileLogPrefix} ${colors.cyan('origin/master')} is currently at ` + - `commit ${colors.cyan(shortSha(gitTravisMasterBaseline()))}`); + `${fileLogPrefix} ${colors.cyan('origin/master')} is currently at ` + + `commit ${colors.cyan(shortSha(gitTravisMasterBaseline()))}` + ); commitSha = travisPullRequestSha(); } else { commitSha = gitCommitHash(); } console.log( - `${fileLogPrefix} Testing the following changes at commit ` + - `${colors.cyan(shortSha(commitSha))}`); + `${fileLogPrefix} Testing the following changes at commit ` + + `${colors.cyan(shortSha(commitSha))}` + ); const filesChanged = gitDiffStatMaster(); console.log(filesChanged); const branchPoint = gitMergeBaseMaster(); console.log( - `${fileLogPrefix} Commit log since branch ` + + `${fileLogPrefix} Commit log since branch ` + `${colors.cyan(gitBranchName())} was forked from ` + - `${colors.cyan('master')} at ${colors.cyan(shortSha(branchPoint))}:`); + `${colors.cyan('master')} at ${colors.cyan(shortSha(branchPoint))}:` + ); console.log(gitDiffCommitLog() + '\n'); } @@ -77,12 +86,17 @@ function printChangeSummary(fileName) { */ async function startSauceConnect(functionName) { process.env['SAUCE_USERNAME'] = 'amphtml'; - const response = await requestPromise('https://amphtml-sauce-token-dealer.appspot.com/getJwtToken'); + const response = await requestPromise( + 'https://amphtml-sauce-token-dealer.appspot.com/getJwtToken' + ); process.env['SAUCE_ACCESS_KEY'] = response.trim(); const startScCmd = 'build-system/sauce_connect/start_sauce_connect.sh'; const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - console.log('\n' + fileLogPrefix, - 'Starting Sauce Connect Proxy:', colors.cyan(startScCmd)); + console.log( + '\n' + fileLogPrefix, + 'Starting Sauce Connect Proxy:', + colors.cyan(startScCmd) + ); execOrDie(startScCmd); } @@ -93,8 +107,11 @@ async function startSauceConnect(functionName) { function stopSauceConnect(functionName) { const stopScCmd = 'build-system/sauce_connect/stop_sauce_connect.sh'; const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - console.log('\n' + fileLogPrefix, - 'Stopping Sauce Connect Proxy:', colors.cyan(stopScCmd)); + console.log( + '\n' + fileLogPrefix, + 'Stopping Sauce Connect Proxy:', + colors.cyan(stopScCmd) + ); execOrDie(stopScCmd); } @@ -108,7 +125,10 @@ function startTimer(functionName, fileName) { const startTime = Date.now(); const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); console.log( - '\n' + fileLogPrefix, 'Running', colors.cyan(functionName) + '...'); + '\n' + fileLogPrefix, + 'Running', + colors.cyan(functionName) + '...' + ); return startTime; } @@ -123,11 +143,15 @@ function stopTimer(functionName, fileName, startTime) { const endTime = Date.now(); const executionTime = endTime - startTime; const mins = Math.floor(executionTime / 60000); - const secs = Math.floor(executionTime % 60000 / 1000); + const secs = Math.floor((executionTime % 60000) / 1000); const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); console.log( - fileLogPrefix, 'Done running', colors.cyan(functionName), - 'Total time:', colors.green(mins + 'm ' + secs + 's')); + fileLogPrefix, + 'Done running', + colors.cyan(functionName), + 'Total time:', + colors.green(mins + 'm ' + secs + 's') + ); } /** @@ -163,19 +187,21 @@ function timedExecOrDie(cmd, fileName = 'utils.js') { */ async function downloadOutput_(functionName, outputFileName) { const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - const buildOutputDownloadUrl = - `${OUTPUT_STORAGE_LOCATION}/${outputFileName}`; + const buildOutputDownloadUrl = `${OUTPUT_STORAGE_LOCATION}/${outputFileName}`; console.log( - `${fileLogPrefix} Downloading build output from ` + - colors.cyan(buildOutputDownloadUrl) + '...'); + `${fileLogPrefix} Downloading build output from ` + + colors.cyan(buildOutputDownloadUrl) + + '...' + ); exec('echo travis_fold:start:download_results && echo'); authenticateWithStorageLocation_(); execOrDie(`gsutil cp ${buildOutputDownloadUrl} ${outputFileName}`); exec('echo travis_fold:end:download_results'); console.log( - `${fileLogPrefix} Extracting ` + colors.cyan(outputFileName) + '...'); + `${fileLogPrefix} Extracting ` + colors.cyan(outputFileName) + '...' + ); exec('echo travis_fold:start:unzip_results && echo'); execOrDie(`unzip -o ${outputFileName}`); exec('echo travis_fold:end:unzip_results'); @@ -196,16 +222,23 @@ async function uploadOutput_(functionName, outputFileName) { const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); console.log( - `\n${fileLogPrefix} Compressing ` + + `\n${fileLogPrefix} Compressing ` + colors.cyan(OUTPUT_DIRS.split(' ').join(', ')) + - ' into ' + colors.cyan(outputFileName) + '...'); + ' into ' + + colors.cyan(outputFileName) + + '...' + ); exec('echo travis_fold:start:zip_results && echo'); execOrDie(`zip -r ${outputFileName} ${OUTPUT_DIRS}`); exec('echo travis_fold:end:zip_results'); console.log( - `${fileLogPrefix} Uploading ` + colors.cyan(outputFileName) + ' to ' + - colors.cyan(OUTPUT_STORAGE_LOCATION) + '...'); + `${fileLogPrefix} Uploading ` + + colors.cyan(outputFileName) + + ' to ' + + colors.cyan(OUTPUT_STORAGE_LOCATION) + + '...' + ); exec('echo travis_fold:start:upload_results && echo'); authenticateWithStorageLocation_(); execOrDie(`gsutil -m cp -r ${outputFileName} ${OUTPUT_STORAGE_LOCATION}`); @@ -214,8 +247,10 @@ async function uploadOutput_(functionName, outputFileName) { function authenticateWithStorageLocation_() { decryptTravisKey_(); - execOrDie('gcloud auth activate-service-account ' + - `--key-file ${OUTPUT_STORAGE_KEY_FILE}`); + execOrDie( + 'gcloud auth activate-service-account ' + + `--key-file ${OUTPUT_STORAGE_KEY_FILE}` + ); execOrDie(`gcloud config set account ${OUTPUT_STORAGE_SERVICE_ACCOUNT}`); execOrDie('gcloud config set pass_credentials_to_gsutil true'); execOrDie(`gcloud config set project ${OUTPUT_STORAGE_PROJECT_ID}`); @@ -261,8 +296,10 @@ function decryptTravisKey_() { // -md sha256 is required due to encryption differences between // openssl 1.1.1a, which was used to encrypt the key, and // openssl 1.0.2g, which is used by Travis to decrypt. - execOrDie(`openssl aes-256-cbc -md sha256 -k ${process.env.GCP_TOKEN} -in ` + - `build-system/sa-travis-key.json.enc -out ${OUTPUT_STORAGE_KEY_FILE} -d`); + execOrDie( + `openssl aes-256-cbc -md sha256 -k ${process.env.GCP_TOKEN} -in ` + + `build-system/sa-travis-key.json.enc -out ${OUTPUT_STORAGE_KEY_FILE} -d` + ); } module.exports = { diff --git a/build-system/pr-check/validator-tests.js b/build-system/pr-check/validator-tests.js index a7746e8f163c..0d3160a54ea3 100644 --- a/build-system/pr-check/validator-tests.js +++ b/build-system/pr-check/validator-tests.js @@ -26,14 +26,15 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'validator-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -46,25 +47,31 @@ function main() { printChangeSummary(FILENAME); let ranTests = false; - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('VALIDATOR')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('VALIDATOR') + ) { timedExecOrDie('gulp validator'); ranTests = true; } - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('VALIDATOR_WEBUI')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('VALIDATOR_WEBUI') + ) { timedExecOrDie('gulp validator-webui'); ranTests = true; } if (!ranTests) { console.log( - `${FILELOGPREFIX} Skipping ` + colors.cyan('Validator Tests ') + + `${FILELOGPREFIX} Skipping ` + + colors.cyan('Validator Tests ') + 'because this commit does not affect the runtime, build system, ' + - 'validator, or validator web UI.'); + 'validator, or validator web UI.' + ); } } diff --git a/build-system/pr-check/visual-diff-tests.js b/build-system/pr-check/visual-diff-tests.js index 2ff344349b16..21e7d95747e8 100644 --- a/build-system/pr-check/visual-diff-tests.js +++ b/build-system/pr-check/visual-diff-tests.js @@ -28,14 +28,15 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'visual-diff-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -49,22 +50,25 @@ function main() { } else { printChangeSummary(FILENAME); process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('INTEGRATION_TEST') || - buildTargets.has('VISUAL_DIFF') || - buildTargets.has('FLAG_CONFIG')) { - + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST') || + buildTargets.has('VISUAL_DIFF') || + buildTargets.has('FLAG_CONFIG') + ) { downloadBuildOutput(FILENAME); timedExecOrDie('gulp update-packages'); timedExecOrDie('gulp visual-diff --nobuild'); } else { timedExecOrDie('gulp visual-diff --nobuild --empty'); console.log( - `${FILELOGPREFIX} Skipping ` + colors.cyan('Visual Diff Tests ') + + `${FILELOGPREFIX} Skipping ` + + colors.cyan('Visual Diff Tests ') + 'because this commit does not affect the ' + 'runtime, build system, integration test files, ' + - 'visual diff test files, or flag config files.'); + 'visual diff test files, or flag config files.' + ); } } diff --git a/build-system/pr-check/yarn-checks.js b/build-system/pr-check/yarn-checks.js index bd67e1532f67..38713f26c80e 100644 --- a/build-system/pr-check/yarn-checks.js +++ b/build-system/pr-check/yarn-checks.js @@ -34,19 +34,34 @@ function isYarnLockFileInSync(fileName = 'yarn-checks.js') { const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); const yarnIntegrityCheck = getStderr('yarn check --integrity').trim(); if (yarnIntegrityCheck.includes('error')) { - console.error(fileLogPrefix, colors.red('ERROR:'), - 'Found the following', colors.cyan('yarn'), 'errors:\n' + - colors.cyan(yarnIntegrityCheck)); - console.error(fileLogPrefix, colors.red('ERROR:'), - 'Updates to', colors.cyan('package.json'), - 'must be accompanied by a corresponding update to', - colors.cyan('yarn.lock')); - console.error(fileLogPrefix, colors.yellow('NOTE:'), - 'To update', colors.cyan('yarn.lock'), 'after changing', - colors.cyan('package.json') + ',', 'run', - '"' + colors.cyan('yarn install') + '"', - 'and include the updated', colors.cyan('yarn.lock'), - 'in your PR.'); + console.error( + fileLogPrefix, + colors.red('ERROR:'), + 'Found the following', + colors.cyan('yarn'), + 'errors:\n' + colors.cyan(yarnIntegrityCheck) + ); + console.error( + fileLogPrefix, + colors.red('ERROR:'), + 'Updates to', + colors.cyan('package.json'), + 'must be accompanied by a corresponding update to', + colors.cyan('yarn.lock') + ); + console.error( + fileLogPrefix, + colors.yellow('NOTE:'), + 'To update', + colors.cyan('yarn.lock'), + 'after changing', + colors.cyan('package.json') + ',', + 'run', + '"' + colors.cyan('yarn install') + '"', + 'and include the updated', + colors.cyan('yarn.lock'), + 'in your PR.' + ); return false; } return true; @@ -62,12 +77,20 @@ function isYarnLockFileProperlyUpdated(fileName = 'yarn-checks.js') { const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); if (localChanges.includes('yarn.lock')) { - console.error(fileLogPrefix, colors.red('ERROR:'), - 'This PR did not properly update', colors.cyan('yarn.lock') + '.'); - console.error(fileLogPrefix, colors.yellow('NOTE:'), - 'To fix this, sync your branch to', colors.cyan('upstream/master') + - ', run', colors.cyan('gulp update-packages') + - ', and push a new commit containing the changes.'); + console.error( + fileLogPrefix, + colors.red('ERROR:'), + 'This PR did not properly update', + colors.cyan('yarn.lock') + '.' + ); + console.error( + fileLogPrefix, + colors.yellow('NOTE:'), + 'To fix this, sync your branch to', + colors.cyan('upstream/master') + ', run', + colors.cyan('gulp update-packages') + + ', and push a new commit containing the changes.' + ); console.error(fileLogPrefix, 'Expected changes:'); console.log(localChanges); return false; @@ -81,8 +104,9 @@ function isYarnLockFileProperlyUpdated(fileName = 'yarn-checks.js') { * @return {boolean} */ function runYarnChecks(filename) { - return isYarnLockFileInSync(filename) && - isYarnLockFileProperlyUpdated(filename); + return ( + isYarnLockFileInSync(filename) && isYarnLockFileProperlyUpdated(filename) + ); } module.exports = { diff --git a/build-system/recaptcha-router.js b/build-system/recaptcha-router.js index 1b273ba3a931..64837f0d16dc 100644 --- a/build-system/recaptcha-router.js +++ b/build-system/recaptcha-router.js @@ -33,7 +33,10 @@ window.grecaptcha = { const recaptchaFrameRequestHandler = (req, res, next) => { if (global.AMP_TESTING) { fs.readFileAsync(pc.cwd() + req.path, 'utf8').then(file => { - file = file.replace(/initRecaptcha\(.*\)/g, 'initRecaptcha("/recaptcha/mock.js?sitekey=")'); + file = file.replace( + /initRecaptcha\(.*\)/g, + 'initRecaptcha("/recaptcha/mock.js?sitekey=")' + ); res.end(file); }); } else { @@ -45,34 +48,31 @@ recaptchaRouter.get('/mock.js', (req, res) => { res.end(recaptchaMock); }); -recaptchaRouter.post( - '/submit', - upload.array(), - (req, res) => { - cors.enableCors(req, res); +recaptchaRouter.post('/submit', upload.array(), (req, res) => { + cors.enableCors(req, res); - const responseJson = { - message: 'Success!', - }; + const responseJson = { + message: 'Success!', + }; - Object.keys(req.body).forEach(bodyKey => { - responseJson[bodyKey] = req.body[bodyKey]; - }); + Object.keys(req.body).forEach(bodyKey => { + responseJson[bodyKey] = req.body[bodyKey]; + }); - const containsRecaptchaInResponse = Object.keys(responseJson) - .some(responseJsonKey => { - return responseJsonKey.toLowerCase().includes('recaptcha'); - }); - - if (containsRecaptchaInResponse) { - res.status(200).json(responseJson); - } else { - res.status(400).json({ - message: 'Did not include a recaptcha token', - }); - } + const containsRecaptchaInResponse = Object.keys(responseJson).some( + responseJsonKey => { + return responseJsonKey.toLowerCase().includes('recaptcha'); } -); + ); + + if (containsRecaptchaInResponse) { + res.status(200).json(responseJson); + } else { + res.status(400).json({ + message: 'Did not include a recaptcha token', + }); + } +}); module.exports = { recaptchaFrameRequestHandler, diff --git a/build-system/routes/list.js b/build-system/routes/list.js index da5f0bd03867..9fdb868951f4 100644 --- a/build-system/routes/list.js +++ b/build-system/routes/list.js @@ -48,8 +48,9 @@ router.get('/search/countries', function(req, res) { if (req.query.hasOwnProperty('q')) { const query = req.query.q.toLowerCase(); - filtered = countries.items - .filter(country => country.name.toLowerCase().startsWith(query)); + filtered = countries.items.filter(country => + country.name.toLowerCase().startsWith(query) + ); } const results = { @@ -76,10 +77,14 @@ const squareImgUrl = width => { const randomFalsy = () => { const rand = randInt(4); switch (rand) { - case 1: return null; - case 2: return undefined; - case 3: return ''; - default: return false; + case 1: + return null; + case 2: + return undefined; + case 3: + return ''; + default: + return false; } }; @@ -108,8 +113,9 @@ const generateResults = (category, count = 2) => { } r.items = items; - r['load-more-src'] = - `/list/infinite-scroll-random/${category}?${randInt(10000)}`; + r['load-more-src'] = `/list/infinite-scroll-random/${category}?${randInt( + 10000 + )}`; return r; }; @@ -146,15 +152,18 @@ router.get('/infinite-scroll', function(req, res) { const items = generateJson(numberOfItems, pagesLeft); - const nextUrl = '/list/infinite-scroll?items=' + - numberOfItems + '&left=' + (pagesLeft - 1) + - '&latency=' + latency; + const nextUrl = + '/list/infinite-scroll?items=' + + numberOfItems + + '&left=' + + (pagesLeft - 1) + + '&latency=' + + latency; const next = pagesLeft == 0 ? randomFalsy() : nextUrl; - const results = next === false ? {items} - : {items, next, - 'loadMoreButtonText': 'test', - 'loadMoreEndText': 'end', - }; + const results = + next === false + ? {items} + : {items, next, 'loadMoreButtonText': 'test', 'loadMoreEndText': 'end'}; if (latency) { setTimeout(() => res.json(results), latency); @@ -178,8 +187,11 @@ router.get('/infinite-scroll-state', function(req, res) { const numberOfItems = query['items'] || 2; const pagesLeft = query['left'] || 0; const items = generateJsonWithState(numberOfItems, pagesLeft); - const next = '/list/infinite-scroll-state?left=' + (pagesLeft - 1) - + '&items=' + numberOfItems; + const next = + '/list/infinite-scroll-state?left=' + + (pagesLeft - 1) + + '&items=' + + numberOfItems; const results = { items, next, diff --git a/build-system/scope-require.js b/build-system/scope-require.js index a19cabd75d48..d814217d3dfc 100644 --- a/build-system/scope-require.js +++ b/build-system/scope-require.js @@ -39,9 +39,10 @@ function scopeRequire(src, scopeName) { const flatGlobals = globals.reduce((acc, g) => acc.concat(g.nodes), []); flatGlobals - .filter(node => isIdentifier(node) && isRequire(node)) - .forEach(node => - replaceIdentifier(node.parent, test => test === node, scopeName)); + .filter(node => isIdentifier(node) && isRequire(node)) + .forEach(node => + replaceIdentifier(node.parent, test => test === node, scopeName) + ); return escodegen.generate(ast, {format: {compact: true}}); } @@ -102,35 +103,44 @@ function createMemberNode(identifierNode, scopeName) { }; } - program - .description('Scope global `require` calls to an object.') - .option('-i, --infile [filename]', 'The path of the input file.' + - ' Reads from stdin if unspecified') - .option('-o, --outfile [filename]', 'The path for the output file.' + - ' Writes to stdout if unspecified.') - .option('-n --name [name]', 'The name to reference `require` calls from.' + - ' The default is `AMP`', 'AMP') - .parse(process.argv); + .description('Scope global `require` calls to an object.') + .option( + '-i, --infile [filename]', + 'The path of the input file. Reads from stdin if unspecified' + ) + .option( + '-o, --outfile [filename]', + 'The path for the output file. Writes to stdout if unspecified.' + ) + .option( + '-n --name [name]', + 'The name to reference `require` calls from. The default is `AMP`', + 'AMP' + ) + .parse(process.argv); -const inputStream = (program.infile && program.infile !== '-' ? - fs.createReadStream(program.infile) : - process.stdin); +const inputStream = + program.infile && program.infile !== '-' + ? fs.createReadStream(program.infile) + : process.stdin; inputStream.on('error', err => { - console./*OK*/error(colors.red('\nError reading file: ' + err.path)); + console./*OK*/ error(colors.red('\nError reading file: ' + err.path)); }); -const outputStream = (program.outfile && program.outfile !== '-' ? - fs.createWriteStream(program.outfile) : - process.stdout); +const outputStream = + program.outfile && program.outfile !== '-' + ? fs.createWriteStream(program.outfile) + : process.stdout; outputStream.on('error', err => { - console./*OK*/error(colors.red('\nError writing file: ' + err.path)); + console./*OK*/ error(colors.red('\nError writing file: ' + err.path)); }); const scopeRequireStream = es.map((inputFile, cb) => - cb(null, scopeRequire(inputFile.toString('utf8'), program.name))); + cb(null, scopeRequire(inputFile.toString('utf8'), program.name)) +); inputStream - .pipe(es.wait()) - .pipe(scopeRequireStream) - .pipe(outputStream); + .pipe(es.wait()) + .pipe(scopeRequireStream) + .pipe(outputStream); diff --git a/build-system/server.js b/build-system/server.js index deeb8811893c..32d8888bcb92 100644 --- a/build-system/server.js +++ b/build-system/server.js @@ -36,15 +36,19 @@ const { const useHttps = process.env.SERVE_USEHTTPS == 'true'; const quiet = process.env.SERVE_QUIET == 'true'; const sendCachingHeaders = process.env.SERVE_CACHING_HEADERS == 'true'; -const noCachingExtensions = process.env.SERVE_EXTENSIONS_WITHOUT_CACHING == - 'true'; +const noCachingExtensions = + process.env.SERVE_EXTENSIONS_WITHOUT_CACHING == 'true'; const header = require('connect-header'); // Exit if the port is in use. process.on('uncaughtException', function(err) { if (err.errno === 'EADDRINUSE') { - log(colors.red('ERROR:'), 'Port', colors.cyan(port), - 'in use, shutting down server'); + log( + colors.red('ERROR:'), + 'Port', + colors.cyan(port), + 'in use, shutting down server' + ); } else { log(colors.red(err)); } @@ -65,9 +69,11 @@ if (!quiet) { } middleware.push(app); if (sendCachingHeaders) { - middleware.push(header({ - 'cache-control': ' max-age=600', - })); + middleware.push( + header({ + 'cache-control': ' max-age=600', + }) + ); } if (noCachingExtensions) { @@ -81,11 +87,12 @@ if (noCachingExtensions) { } // Start gulp webserver -gulp.src(process.cwd()) - .pipe(webserver({ - port, - host, - directoryListing: true, - https: useHttps, - middleware, - })); +gulp.src(process.cwd()).pipe( + webserver({ + port, + host, + directoryListing: true, + https: useHttps, + middleware, + }) +); diff --git a/build-system/shadow-viewer.js b/build-system/shadow-viewer.js index 9713cb01a46d..559ae56a5d8d 100644 --- a/build-system/shadow-viewer.js +++ b/build-system/shadow-viewer.js @@ -144,21 +144,21 @@ const SCRIPT = ` }; `; - const renderShadowViewer = ({src, baseHref, port = 8000}) => - html` - - - - Shadow Viewer - - - - - - `; - + html` + + + + + Shadow Viewer + + + + + + + `; module.exports = {renderShadowViewer}; diff --git a/build-system/tasks/ava.js b/build-system/tasks/ava.js index c89db06fa926..04635c17c2a7 100644 --- a/build-system/tasks/ava.js +++ b/build-system/tasks/ava.js @@ -23,12 +23,13 @@ const {isTravisBuild} = require('../travis'); * Runs ava tests. */ function ava() { - return gulp.src([ - require.resolve('./csvify-size/test.js'), - require.resolve('./get-zindex/test.js'), - require.resolve('./prepend-global/test.js'), - ]) - .pipe(gulpAva({silent: isTravisBuild()})); + return gulp + .src([ + require.resolve('./csvify-size/test.js'), + require.resolve('./get-zindex/test.js'), + require.resolve('./prepend-global/test.js'), + ]) + .pipe(gulpAva({silent: isTravisBuild()})); } module.exports = { diff --git a/build-system/tasks/build.js b/build-system/tasks/build.js index 69ab3ef4d780..669a592dad93 100644 --- a/build-system/tasks/build.js +++ b/build-system/tasks/build.js @@ -31,7 +31,6 @@ const {maybeUpdatePackages} = require('./update-packages'); const {parseExtensionFlags} = require('./extension-helpers'); const {serve} = require('./serve'); - /** * Enables watching for file changes in css, extensions. * @return {!Promise} @@ -108,16 +107,15 @@ watch.flags = { with_inabox: ' Also watch and build the amp-inabox.js binary.', with_shadow: ' Also watch and build the amp-shadow.js binary.', extensions: ' Watches and builds only the listed extensions.', - extensions_from: ' Watches and builds only the extensions from the ' + - 'listed AMP(s).', + extensions_from: + ' Watches and builds only the extensions from the listed AMP(s).', noextensions: ' Watches and builds with no extensions.', }; defaultTask.description = 'Runs "watch" and then "serve"'; defaultTask.flags = { extensions: ' Watches and builds only the listed extensions.', - extensions_from: ' Watches and builds only the extensions from the ' + - 'listed AMP(s).', + extensions_from: + ' Watches and builds only the extensions from the listed AMP(s).', noextensions: ' Watches and builds with no extensions.', }; - diff --git a/build-system/tasks/bundle-size.js b/build-system/tasks/bundle-size.js index f7ed3655ff91..cdcfe81adac0 100644 --- a/build-system/tasks/bundle-size.js +++ b/build-system/tasks/bundle-size.js @@ -69,21 +69,32 @@ function getGzippedBundleSize() { */ function storeBundleSize() { if (!isTravisPushBuild()) { - log(yellow('Skipping'), cyan('--on_push_build') + ':', - 'this action can only be performed on `push` builds on Travis'); + log( + yellow('Skipping'), + cyan('--on_push_build') + ':', + 'this action can only be performed on `push` builds on Travis' + ); return; } if (travisRepoSlug() !== expectedGitHubRepoSlug) { - log(yellow('Skipping'), cyan('--on_push_build') + ':', - 'this action can only be performed on Travis builds on the', - cyan(expectedGitHubRepoSlug), 'repository'); + log( + yellow('Skipping'), + cyan('--on_push_build') + ':', + 'this action can only be performed on Travis builds on the', + cyan(expectedGitHubRepoSlug), + 'repository' + ); return; } if (!process.env.GITHUB_ARTIFACTS_RW_TOKEN) { - log(red('ERROR: Missing GITHUB_ARTIFACTS_RW_TOKEN, cannot store the ' + - 'bundle size in the artifacts repository on GitHub!')); + log( + red( + 'ERROR: Missing GITHUB_ARTIFACTS_RW_TOKEN, cannot store the ' + + 'bundle size in the artifacts repository on GitHub!' + ) + ); process.exitCode = 1; return; } @@ -98,23 +109,43 @@ function storeBundleSize() { auth: `token ${process.env.GITHUB_ARTIFACTS_RW_TOKEN}`, }); - return octokit.repos.getContents(githubApiCallOptions).then(() => { - log('The file', cyan(`bundle-size/${commitHash}`), 'already exists in the', - 'build artifacts repository on GitHub. Skipping...'); - }).catch(() => { - return octokit.repos.createFile(Object.assign(githubApiCallOptions, { - message: `bundle-size: ${commitHash} (${bundleSize})`, - content: Buffer.from(bundleSize).toString('base64'), - })).then(() => { - log('Stored the new bundle size of', cyan(bundleSize), 'in the artifacts', - 'repository on GitHub'); - }).catch(error => { - log(red(`ERROR: Failed to create the bundle-size/${commitHash} file in`), - red('the build artifacts repository on GitHub!')); - log(red('Error message was:'), error.message); - process.exitCode = 1; + return octokit.repos + .getContents(githubApiCallOptions) + .then(() => { + log( + 'The file', + cyan(`bundle-size/${commitHash}`), + 'already exists in the', + 'build artifacts repository on GitHub. Skipping...' + ); + }) + .catch(() => { + return octokit.repos + .createFile( + Object.assign(githubApiCallOptions, { + message: `bundle-size: ${commitHash} (${bundleSize})`, + content: Buffer.from(bundleSize).toString('base64'), + }) + ) + .then(() => { + log( + 'Stored the new bundle size of', + cyan(bundleSize), + 'in the artifacts', + 'repository on GitHub' + ); + }) + .catch(error => { + log( + red( + `ERROR: Failed to create the bundle-size/${commitHash} file in` + ), + red('the build artifacts repository on GitHub!') + ); + log(red('Error message was:'), error.message); + process.exitCode = 1; + }); }); - }); } /** @@ -124,12 +155,16 @@ async function skipBundleSize() { if (isTravisPullRequestBuild()) { const commitHash = gitCommitHash(); try { - const response = await requestPost(url.resolve(bundleSizeAppBaseUrl, - path.join('commit', commitHash, 'skip'))); + const response = await requestPost( + url.resolve( + bundleSizeAppBaseUrl, + path.join('commit', commitHash, 'skip') + ) + ); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error( - `${response.statusCode} ${response.statusMessage}: ` + - response.body); + `${response.statusCode} ${response.statusMessage}: ` + response.body + ); } } catch (error) { log(red('Could not report a skipped pull request')); @@ -138,8 +173,12 @@ async function skipBundleSize() { return; } } else { - log(yellow('Not marking this pull request to skip because that can only be ' - + 'done on Travis')); + log( + yellow( + 'Not marking this pull request to skip because that can only be ' + + 'done on Travis' + ) + ); } } @@ -153,8 +192,10 @@ async function reportBundleSize() { const commitHash = gitCommitHash(); try { const response = await requestPost({ - uri: url.resolve(bundleSizeAppBaseUrl, - path.join('commit', commitHash, 'report')), + uri: url.resolve( + bundleSizeAppBaseUrl, + path.join('commit', commitHash, 'report') + ), json: true, body: { baseSha, @@ -163,8 +204,8 @@ async function reportBundleSize() { }); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error( - `${response.statusCode} ${response.statusMessage}: ` + - response.body); + `${response.statusCode} ${response.statusMessage}: ` + response.body + ); } } catch (error) { log(red('Could not report the bundle size of this pull request')); @@ -173,8 +214,12 @@ async function reportBundleSize() { return; } } else { - log(yellow('Not reporting the bundle size of this pull request because ' - + 'that can only be done on Travis')); + log( + yellow( + 'Not reporting the bundle size of this pull request because ' + + 'that can only be done on Travis' + ) + ); } } @@ -196,12 +241,13 @@ module.exports = { }; bundleSize.description = - 'Checks if the minified AMP binary has exceeded its size cap'; + 'Checks if the minified AMP binary has exceeded its size cap'; bundleSize.flags = { - 'on_push_build': ' Store bundle size in AMP build artifacts repo ' - + '(also implies --on_pr_build)', - 'on_pr_build': ' Report the bundle size of this pull request to ' - + 'GitHub', - 'on_skipped_build': ' Set the status of this pull request\'s bundle ' - + 'size check in GitHub to `skipped`', + 'on_push_build': + ' Store bundle size in AMP build artifacts repo ' + + '(also implies --on_pr_build)', + 'on_pr_build': ' Report the bundle size of this pull request to GitHub', + 'on_skipped_build': + " Set the status of this pull request's bundle " + + 'size check in GitHub to `skipped`', }; diff --git a/build-system/tasks/changelog.js b/build-system/tasks/changelog.js index c6d09487813e..2bfc1662a4ef 100644 --- a/build-system/tasks/changelog.js +++ b/build-system/tasks/changelog.js @@ -100,12 +100,20 @@ let PrMetadataDef; async function changelog() { if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('Warning! You have not set the ' + - 'GITHUB_ACCESS_TOKEN env var. Aborting "changelog" task.')); - log(colors.green('See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.')); + log( + colors.red( + 'Warning! You have not set the ' + + 'GITHUB_ACCESS_TOKEN env var. Aborting "changelog" task.' + ) + ); + log( + colors.green( + 'See https://help.github.com/articles/' + + 'creating-an-access-token-for-command-line-use/ ' + + 'for instructions on how to create a github access token. We only ' + + 'need `public_repo` scope.' + ) + ); return; } @@ -127,25 +135,24 @@ function getGitMetadata() { branch: undefined, }; return getLastProdReleasedGitTag(gitMetadata) - .then(getCurrentBranchName) - .then(getGitLog) - .then(getGithubPullRequestsMetadata) - .then(getGithubFilesMetadata) - .then(getLastGitTag) - .then(buildChangelog) - .then(function(gitMetadata) { - log(colors.blue('\n' + gitMetadata.changelog)); - if (isDryrun) { - return; - } - return getCurrentSha().then( - submitReleaseNotes.bind(null, argv.tag, gitMetadata.changelog) - ); - }) - .catch(errHandler); + .then(getCurrentBranchName) + .then(getGitLog) + .then(getGithubPullRequestsMetadata) + .then(getGithubFilesMetadata) + .then(getLastGitTag) + .then(buildChangelog) + .then(function(gitMetadata) { + log(colors.blue('\n' + gitMetadata.changelog)); + if (isDryrun) { + return; + } + return getCurrentSha().then( + submitReleaseNotes.bind(null, argv.tag, gitMetadata.changelog) + ); + }) + .catch(errHandler); } - /** * Get the last git tag this current HEAD bases off of from. * @@ -234,39 +241,45 @@ function buildChangelog(gitMetadata) { let changelog = `## Version: ${argv.tag}\n\n`; if (gitMetadata.baseTag) { - changelog += `## Baseline: [${gitMetadata.baseTag}]` + - '(https://github.com/ampproject/amphtml/releases/' + - `tag/${gitMetadata.baseTag})\n\n`; + changelog += + `## Baseline: [${gitMetadata.baseTag}]` + + '(https://github.com/ampproject/amphtml/releases/' + + `tag/${gitMetadata.baseTag})\n\n`; } // Append all titles - changelog += gitMetadata.logs.filter(function(log) { - const {pr} = log; - if (!pr) { - return true; - } - // Ignore PRs that are just all docs changes. - return !pr.filenames.every(function(filename) { - return config.changelogIgnoreFileTypes.test(filename); - }); - }).map(function(log) { - const {pr} = log; - if (!pr) { - return ' - ' + log.title; - } - return ` - ${pr.title.trim()} (#${pr.id})`; - }).join('\n'); + changelog += gitMetadata.logs + .filter(function(log) { + const {pr} = log; + if (!pr) { + return true; + } + // Ignore PRs that are just all docs changes. + return !pr.filenames.every(function(filename) { + return config.changelogIgnoreFileTypes.test(filename); + }); + }) + .map(function(log) { + const {pr} = log; + if (!pr) { + return ' - ' + log.title; + } + return ` - ${pr.title.trim()} (#${pr.id})`; + }) + .join('\n'); changelog += '\n\n## Breakdown by component\n\n'; const sections = buildSections(gitMetadata); - Object.keys(sections).sort().forEach(function(section) { - changelog += `
    \n${section}\n`; - const uniqueItems = sections[section].filter(function(title, idx) { - return sections[section].indexOf(title) == idx; + Object.keys(sections) + .sort() + .forEach(function(section) { + changelog += `
    \n${section}\n`; + const uniqueItems = sections[section].filter(function(title, idx) { + return sections[section].indexOf(title) == idx; + }); + changelog += uniqueItems.join(''); + changelog += '
    \n'; }); - changelog += uniqueItems.join(''); - changelog += '
    \n'; - }); gitMetadata.changelog = changelog; return gitMetadata; @@ -352,15 +365,21 @@ function getLastProdReleasedGitTag(gitMetadata) { * @return {!Promise} */ function getGitLog(gitMetadata) { - const args = `log ${gitMetadata.branch}...${gitMetadata.tag} ` + - '--pretty=oneline --first-parent'; + const args = + `log ${gitMetadata.branch}...${gitMetadata.tag} ` + + '--pretty=oneline --first-parent'; const options = {args}; return gitExec(options).then(function(logs) { if (!logs) { - throw new Error('No logs found "git log ' + gitMetadata.branch + '...' + - gitMetadata.tag + '".\nIs it possible that there is no delta?\n' + + throw new Error( + 'No logs found "git log ' + + gitMetadata.branch + + '...' + + gitMetadata.tag + + '".\nIs it possible that there is no delta?\n' + 'Make sure to fetch and rebase (or reset --hard) the latest ' + - 'from remote upstream.'); + 'from remote upstream.' + ); } const commits = logs.split('\n').filter(log => !!log.length); gitMetadata.logs = commits.map(log => { @@ -383,31 +402,31 @@ function getGithubPullRequestsMetadata(gitMetadata) { getClosedPullRequests(2), getClosedPullRequests(3), ]) - .then(requests => [].concat.apply([], requests)) - .then(prs => { - gitMetadata.prs = prs; - const githubPrRequest = gitMetadata.logs.map(log => { - const pr = prs.filter(pr => pr.merge_commit_sha == log.sha)[0]; - if (pr) { - log.pr = buildPrMetadata(pr); - } else if (isPrIdInTitle(log.title)) { - const id = getPrIdFromCommit(log.title); - const prOptions = extend({}, pullOptions); - prOptions.url += `/${id}`; - const fileOptions = extend({}, prOptions); - fileOptions.url += '/files'; - // If we couldn't find the matching pull request from 3 pages - // of closed pull request try and fetch it through the id - // if we can retrieve it from the commit message (only available - // through github merge). - return getPullRequest(prOptions, log); - } - return BBPromise.resolve(); - }); - return BBPromise.all(githubPrRequest).then(() => { - return gitMetadata; - }); + .then(requests => [].concat.apply([], requests)) + .then(prs => { + gitMetadata.prs = prs; + const githubPrRequest = gitMetadata.logs.map(log => { + const pr = prs.filter(pr => pr.merge_commit_sha == log.sha)[0]; + if (pr) { + log.pr = buildPrMetadata(pr); + } else if (isPrIdInTitle(log.title)) { + const id = getPrIdFromCommit(log.title); + const prOptions = extend({}, pullOptions); + prOptions.url += `/${id}`; + const fileOptions = extend({}, prOptions); + fileOptions.url += '/files'; + // If we couldn't find the matching pull request from 3 pages + // of closed pull request try and fetch it through the id + // if we can retrieve it from the commit message (only available + // through github merge). + return getPullRequest(prOptions, log); + } + return BBPromise.resolve(); }); + return BBPromise.all(githubPrRequest).then(() => { + return gitMetadata; + }); + }); } /** @@ -476,8 +495,10 @@ function getPullRequestFiles(filesOption, pr) { return request(filesOption).then(function(res) { const body = JSON.parse(res.body); - assert(Array.isArray(body) && body.length > 0, - 'Pull request response must not be empty. ' + res.body); + assert( + Array.isArray(body) && body.length > 0, + 'Pull request response must not be empty. ' + res.body + ); const filenames = body.map(function(file) { return file.filename; }); @@ -501,7 +522,7 @@ function errHandler(err) { * @return {boolean} */ function isPrIdInTitle(str) { - return str./* OK*/indexOf('Merge pull request #') == 0; + return str./* OK*/ indexOf('Merge pull request #') == 0; } /** @@ -522,10 +543,9 @@ function getPrIdFromCommit(commit) { * @return {boolean} */ function isJs(str) { - return str./* OK*/endsWith('.js'); + return str./* OK*/ endsWith('.js'); } - /** * @param {!JSONValue} pr * @return {!PrMetadata} @@ -542,12 +562,20 @@ function buildPrMetadata(pr) { async function changelogUpdate() { if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('Warning! You have not set the ' + - 'GITHUB_ACCESS_TOKEN env var. Aborting "changelog" task.')); - log(colors.green('See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.')); + log( + colors.red( + 'Warning! You have not set the ' + + 'GITHUB_ACCESS_TOKEN env var. Aborting "changelog" task.' + ) + ); + log( + colors.green( + 'See https://help.github.com/articles/' + + 'creating-an-access-token-for-command-line-use/ ' + + 'for instructions on how to create a github access token. We only ' + + 'need `public_repo` scope.' + ) + ); return; } if (!argv.message) { @@ -557,8 +585,9 @@ async function changelogUpdate() { } function update() { - const url = 'https://api.github.com/repos/ampproject/amphtml/releases/tags/' + - `${argv.tag}`; + const url = + 'https://api.github.com/repos/ampproject/amphtml/releases/tags/' + + `${argv.tag}`; const tagsOptions = { url, method: 'GET', @@ -600,12 +629,13 @@ function update() { } else { releasesOptions.body.body = argv.message + release.body; } - return request(releasesOptions).then(() => { - log(colors.green('Update Successful.')); - }) - .catch(e => { - log(colors.red('Update Failed. ' + e.message)); - }); + return request(releasesOptions) + .then(() => { + log(colors.green('Update Successful.')); + }) + .catch(e => { + log(colors.red('Update Failed. ' + e.message)); + }); }); } @@ -621,8 +651,9 @@ changelog.flags = { tag: ' The git tag and github release label', }; -changelogUpdate.description = 'Update github release. Ex. prepend ' + - 'canary percentage changes to release'; +changelogUpdate.description = + 'Update github release. Ex. prepend ' + + 'canary percentage changes to release'; changelogUpdate.flags = { dryrun: ' Generate changelog but dont push it out', tag: ' The git tag and github release label', diff --git a/build-system/tasks/check-links.js b/build-system/tasks/check-links.js index bc56d3133282..e0e77fd03ae4 100644 --- a/build-system/tasks/check-links.js +++ b/build-system/tasks/check-links.js @@ -51,60 +51,65 @@ async function checkLinks() { const linkCheckers = markdownFiles.map(function(markdownFile) { return runLinkChecker(markdownFile); }); - return BBPromise.all(linkCheckers) - .then(function(allResults) { - let deadLinksFound = false; - const filesWithDeadLinks = []; - allResults.map(function(results, index) { - // Skip files that were deleted by the PR. - if (!fs.existsSync(markdownFiles[index])) { - return; - } - let deadLinksFoundInFile = false; - results.forEach(function(result) { - // Skip links to files that were introduced by the PR. - if (isLinkToFileIntroducedByPR(result.link)) { - return; - } - if (result.status === 'dead') { - deadLinksFound = true; - deadLinksFoundInFile = true; - log('[%s] %s', colors.red('✖'), result.link); - } else if (!isTravisBuild()) { - log('[%s] %s', colors.green('✔'), result.link); - } - }); - if (deadLinksFoundInFile) { - filesWithDeadLinks.push(markdownFiles[index]); - log( - colors.red('ERROR'), - 'Possible dead link(s) found in', - colors.magenta(markdownFiles[index])); - } else { - log( - colors.green('SUCCESS'), - 'All links in', - colors.magenta(markdownFiles[index]), 'are alive.'); - } - }); - if (deadLinksFound) { - log( - colors.red('ERROR'), - 'Please update dead link(s) in', - colors.magenta(filesWithDeadLinks.join(',')), - 'or whitelist them in build-system/tasks/check-links.js'); - log( - colors.yellow('NOTE'), - 'If the link(s) above are not meant to resolve to a real webpage', - 'surrounding them with backticks will exempt them from the link', - 'checker.'); - process.exit(1); - } else { - log( - colors.green('SUCCESS'), - 'All links in all markdown files in this branch are alive.'); + return BBPromise.all(linkCheckers).then(function(allResults) { + let deadLinksFound = false; + const filesWithDeadLinks = []; + allResults.map(function(results, index) { + // Skip files that were deleted by the PR. + if (!fs.existsSync(markdownFiles[index])) { + return; + } + let deadLinksFoundInFile = false; + results.forEach(function(result) { + // Skip links to files that were introduced by the PR. + if (isLinkToFileIntroducedByPR(result.link)) { + return; + } + if (result.status === 'dead') { + deadLinksFound = true; + deadLinksFoundInFile = true; + log('[%s] %s', colors.red('✖'), result.link); + } else if (!isTravisBuild()) { + log('[%s] %s', colors.green('✔'), result.link); } }); + if (deadLinksFoundInFile) { + filesWithDeadLinks.push(markdownFiles[index]); + log( + colors.red('ERROR'), + 'Possible dead link(s) found in', + colors.magenta(markdownFiles[index]) + ); + } else { + log( + colors.green('SUCCESS'), + 'All links in', + colors.magenta(markdownFiles[index]), + 'are alive.' + ); + } + }); + if (deadLinksFound) { + log( + colors.red('ERROR'), + 'Please update dead link(s) in', + colors.magenta(filesWithDeadLinks.join(',')), + 'or whitelist them in build-system/tasks/check-links.js' + ); + log( + colors.yellow('NOTE'), + 'If the link(s) above are not meant to resolve to a real webpage', + 'surrounding them with backticks will exempt them from the link', + 'checker.' + ); + process.exit(1); + } else { + log( + colors.green('SUCCESS'), + 'All links in all markdown files in this branch are alive.' + ); + } + }); } /** @@ -115,7 +120,7 @@ async function checkLinks() { */ function isLinkToFileIntroducedByPR(link) { return gitDiffAddedNameOnlyMaster().some(function(file) { - return (file.length > 0 && link.includes(path.parse(file).base)); + return file.length > 0 && link.includes(path.parse(file).base); }); } @@ -129,8 +134,10 @@ function filterWhitelistedLinks(markdown) { let filteredMarkdown = markdown; // localhost links optionally preceded by ( or [ (not served on Travis) - filteredMarkdown = - filteredMarkdown.replace(/(\(|\[)?http:\/\/localhost:8000/g, ''); + filteredMarkdown = filteredMarkdown.replace( + /(\(|\[)?http:\/\/localhost:8000/g, + '' + ); // Links in script tags (illustrative, and not always valid) filteredMarkdown = filteredMarkdown.replace(/src="http.*?"/g, ''); @@ -143,11 +150,15 @@ function filterWhitelistedLinks(markdown) { // The heroku nightly build page is not always acccessible by the checker. filteredMarkdown = filteredMarkdown.replace( - /\(http:\/\/amphtml-nightly\.herokuapp\.com\/\)/g, ''); + /\(http:\/\/amphtml-nightly\.herokuapp\.com\/\)/g, + '' + ); // The Googlebot help page is currently only available to signed-in users. filteredMarkdown = filteredMarkdown.replace( - /\(https:\/\/support\.google\.com\/webmasters\/answer\/182072\)/g, ''); + /\(https:\/\/support\.google\.com\/webmasters\/answer\/182072\)/g, + '' + ); // After all whitelisting is done, clean up any remaining empty blocks bounded // by backticks. Otherwise, `` will be treated as the start of a code block @@ -172,7 +183,7 @@ function runLinkChecker(markdownFile) { const markdown = fs.readFileSync(markdownFile).toString(); const filteredMarkdown = filterWhitelistedLinks(markdown); const opts = { - baseUrl: 'file://' + path.dirname(path.resolve((markdownFile))), + baseUrl: 'file://' + path.dirname(path.resolve(markdownFile)), }; return markdownLinkCheck(filteredMarkdown, opts); } diff --git a/build-system/tasks/check-types.js b/build-system/tasks/check-types.js index 82be2d3ae456..b6cee3968cdd 100644 --- a/build-system/tasks/check-types.js +++ b/build-system/tasks/check-types.js @@ -15,8 +15,12 @@ */ const log = require('fancy-log'); +const { + closureNailgunPort, + startNailgunServer, + stopNailgunServer, +} = require('./nailgun'); const {cleanupBuildDir, closureCompile} = require('../compile/compile'); -const {closureNailgunPort, startNailgunServer, stopNailgunServer} = require('./nailgun'); const {compileCss} = require('./css'); const {createCtrlcHandler, exitCtrlcHandler} = require('../ctrlcHandler'); const {extensions, maybeInitializeExtensions} = require('./extension-helpers'); @@ -44,59 +48,88 @@ async function checkTypes() { const extensionValues = Object.keys(extensions).map(function(key) { return extensions[key]; }); - const extensionSrcs = extensionValues.filter(function(extension) { - return !extension.noTypeCheck; - }).map(function(extension) { - return './extensions/' + extension.name + '/' + - extension.version + '/' + extension.name + '.js'; - }).sort(); + const extensionSrcs = extensionValues + .filter(function(extension) { + return !extension.noTypeCheck; + }) + .map(function(extension) { + return ( + './extensions/' + + extension.name + + '/' + + extension.version + + '/' + + extension.name + + '.js' + ); + }) + .sort(); return compileCss() - .then(async() => { - await startNailgunServer(closureNailgunPort, /* detached */ false); - }) - .then(() => { - if (!isTravisBuild()) { - log('Checking types...'); - } - return Promise.all([ - closureCompile(compileSrcs.concat(extensionSrcs), './dist', - 'check-types.js', { - include3pDirectories: true, - includePolyfills: true, - extraGlobs: ['src/inabox/*.js'], - typeCheckOnly: true, - }), - // Type check 3p/ads code. - closureCompile(['./3p/integration.js'], './dist', - 'integration-check-types.js', { - externs: ['ads/ads.extern.js'], - include3pDirectories: true, - includePolyfills: true, - typeCheckOnly: true, - }), - closureCompile(['./3p/ampcontext-lib.js'], './dist', - 'ampcontext-check-types.js', { - externs: ['ads/ads.extern.js'], - include3pDirectories: true, - includePolyfills: true, - typeCheckOnly: true, - }), - closureCompile(['./3p/iframe-transport-client-lib.js'], './dist', - 'iframe-transport-client-check-types.js', { - externs: ['ads/ads.extern.js'], - include3pDirectories: true, - includePolyfills: true, - typeCheckOnly: true, - }), - ]); - }).then(() => { - if (isTravisBuild()) { - // New line after all the compilation progress dots on Travis. - console.log('\n'); - } - }).then(async() => { - await stopNailgunServer(closureNailgunPort); - }).then(() => exitCtrlcHandler(handlerProcess)); + .then(async () => { + await startNailgunServer(closureNailgunPort, /* detached */ false); + }) + .then(() => { + if (!isTravisBuild()) { + log('Checking types...'); + } + return Promise.all([ + closureCompile( + compileSrcs.concat(extensionSrcs), + './dist', + 'check-types.js', + { + include3pDirectories: true, + includePolyfills: true, + extraGlobs: ['src/inabox/*.js'], + typeCheckOnly: true, + } + ), + // Type check 3p/ads code. + closureCompile( + ['./3p/integration.js'], + './dist', + 'integration-check-types.js', + { + externs: ['ads/ads.extern.js'], + include3pDirectories: true, + includePolyfills: true, + typeCheckOnly: true, + } + ), + closureCompile( + ['./3p/ampcontext-lib.js'], + './dist', + 'ampcontext-check-types.js', + { + externs: ['ads/ads.extern.js'], + include3pDirectories: true, + includePolyfills: true, + typeCheckOnly: true, + } + ), + closureCompile( + ['./3p/iframe-transport-client-lib.js'], + './dist', + 'iframe-transport-client-check-types.js', + { + externs: ['ads/ads.extern.js'], + include3pDirectories: true, + includePolyfills: true, + typeCheckOnly: true, + } + ), + ]); + }) + .then(() => { + if (isTravisBuild()) { + // New line after all the compilation progress dots on Travis. + console.log('\n'); + } + }) + .then(async () => { + await stopNailgunServer(closureNailgunPort); + }) + .then(() => exitCtrlcHandler(handlerProcess)); } module.exports = { diff --git a/build-system/tasks/clean.js b/build-system/tasks/clean.js index 7877f7b461e5..05017d645eca 100644 --- a/build-system/tasks/clean.js +++ b/build-system/tasks/clean.js @@ -21,13 +21,7 @@ const del = require('del'); * Clean up the build artifacts */ async function clean() { - return del([ - 'dist', - 'dist.3p', - 'dist.tools', - 'build', - '.amp-build', - ]); + return del(['dist', 'dist.3p', 'dist.tools', 'build', '.amp-build']); } module.exports = { diff --git a/build-system/tasks/compile-expr.js b/build-system/tasks/compile-expr.js index 19bca7ddacca..e771426bc847 100644 --- a/build-system/tasks/compile-expr.js +++ b/build-system/tasks/compile-expr.js @@ -36,18 +36,14 @@ function compileExpr(path, jisonFilename, imports, parserName, jsFilename) { const generator = new jison.Generator(bnf, settings); const jsModule = generator.generate(settings); - const license = fs.readFileSync( - 'build-system/tasks/js-license.txt', 'utf8'); - const suppressCheckTypes = '/** @fileoverview ' + - '@suppress {checkTypes, suspiciousCode, uselessCode} */'; + const license = fs.readFileSync('build-system/tasks/js-license.txt', 'utf8'); + const suppressCheckTypes = + '/** @fileoverview ' + + '@suppress {checkTypes, suspiciousCode, uselessCode} */'; const jsExports = 'export const ' + parserName + ' = parser;'; - const out = [ - license, - suppressCheckTypes, - imports, - jsModule, - jsExports] + const out = + [license, suppressCheckTypes, imports, jsModule, jsExports] .join('\n\n') // Required in order to support babel 7, since 'token-stack: true' will // adversely affect lexer performance. @@ -68,7 +64,7 @@ async function compileAccessExpr() { async function compileBindExpr() { const path = 'extensions/amp-bind/0.1/'; const jisonFilename = 'bind-expr-impl.jison'; - const imports = 'import {AstNode, AstNodeType} from \'./bind-expr-defines\';'; + const imports = "import {AstNode, AstNodeType} from './bind-expr-defines';"; const parserName = 'bindParser'; const jsFilename = 'bind-expr-impl.js'; compileExpr(path, jisonFilename, imports, parserName, jsFilename); @@ -77,7 +73,7 @@ async function compileBindExpr() { async function compileCssExpr() { const path = 'extensions/amp-animation/0.1/parsers/'; const jisonFilename = 'css-expr-impl.jison'; - const imports = 'import * as ast from \'./css-expr-ast\';'; + const imports = "import * as ast from './css-expr-ast';"; const parserName = 'cssParser'; const jsFilename = 'css-expr-impl.js'; compileExpr(path, jisonFilename, imports, parserName, jsFilename); diff --git a/build-system/tasks/create-module-compatible-es5-bundle.js b/build-system/tasks/create-module-compatible-es5-bundle.js index 31053f36b6f7..3e55ad05f587 100644 --- a/build-system/tasks/create-module-compatible-es5-bundle.js +++ b/build-system/tasks/create-module-compatible-es5-bundle.js @@ -30,11 +30,18 @@ const sourcemaps = require('gulp-sourcemaps'); * Changes `global?global:VARNAME}(this)` to `global?global:VARNAME}(self)` */ function createModuleCompatibleES5Bundle(src) { - return gulp.src('dist/' + src) - .pipe(sourcemaps.init({loadMaps: true})) - .pipe(regexpSourcemaps(/(window.global\?window.global:\w*)this/, '$1self', 'module-global')) - .pipe(sourcemaps.write('./')) - .pipe(gulp.dest('dist')); + return gulp + .src('dist/' + src) + .pipe(sourcemaps.init({loadMaps: true})) + .pipe( + regexpSourcemaps( + /(window.global\?window.global:\w*)this/, + '$1self', + 'module-global' + ) + ) + .pipe(sourcemaps.write('./')) + .pipe(gulp.dest('dist')); } module.exports = { diff --git a/build-system/tasks/css.js b/build-system/tasks/css.js index 9da7dad1260a..05db94632187 100644 --- a/build-system/tasks/css.js +++ b/build-system/tasks/css.js @@ -14,7 +14,6 @@ * limitations under the License. */ - const file = require('gulp-file'); const fs = require('fs-extra'); const gulp = require('gulp'); @@ -75,15 +74,17 @@ function compileCss(watch, opt_compileAll) { * @return {Promise} */ function writeCss(css, originalCssFilename, jsFilename, cssFilename) { - return toPromise(gulp.src(`css/${originalCssFilename}`) - .pipe(file(jsFilename, 'export const cssText = ' + - JSON.stringify(css))) + return toPromise( + gulp + .src(`css/${originalCssFilename}`) + .pipe(file(jsFilename, 'export const cssText = ' + JSON.stringify(css))) .pipe(gulp.dest('build')) .on('end', function() { mkdirSync('build'); mkdirSync('build/css'); fs.writeFileSync(`build/css/${cssFilename}`, css); - })); + }) + ); } /** @@ -92,8 +93,9 @@ function compileCss(watch, opt_compileAll) { * @param {string} outCss */ function writeCssEntryPoint(path, outJs, outCss) { - return jsifyCssAsync(`css/${path}`) - .then(css => writeCss(css, path, outJs, outCss)); + return jsifyCssAsync(`css/${path}`).then(css => + writeCss(css, path, outJs, outCss) + ); } const startTime = Date.now(); @@ -101,7 +103,6 @@ function compileCss(watch, opt_compileAll) { // Used by `gulp test --local-changes` to map CSS files to JS files. fs.writeFileSync('EXTENSIONS_CSS_MAP', JSON.stringify(extensions)); - let promise = Promise.resolve(); cssEntryPoints.forEach(entryPoint => { @@ -109,13 +110,17 @@ function compileCss(watch, opt_compileAll) { promise = promise.then(() => writeCssEntryPoint(path, outJs, outCss)); }); - return promise.then(() => buildExtensions({ - bundleOnlyIfListedInFiles: false, - compileOnlyCss: true, - compileAll: opt_compileAll, - })).then(() => { - endBuildStep('Recompiled all CSS files into', 'build/', startTime); - }); + return promise + .then(() => + buildExtensions({ + bundleOnlyIfListedInFiles: false, + compileOnlyCss: true, + compileAll: opt_compileAll, + }) + ) + .then(() => { + endBuildStep('Recompiled all CSS files into', 'build/', startTime); + }); } module.exports = { diff --git a/build-system/tasks/csvify-size/index.js b/build-system/tasks/csvify-size/index.js index 2b1941227bcf..d9375558c15b 100644 --- a/build-system/tasks/csvify-size/index.js +++ b/build-system/tasks/csvify-size/index.js @@ -15,7 +15,6 @@ */ 'use strict'; - const BBPromise = require('bluebird'); const childProcess = require('child_process'); const exec = BBPromise.promisify(childProcess.exec); @@ -23,7 +22,6 @@ const colors = require('ansi-colors'); const fs = BBPromise.promisifyAll(require('fs')); const log = require('fancy-log'); - const prettyBytesUnits = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; /** @@ -42,9 +40,7 @@ let FieldsDef; const filePath = 'test/size.txt'; -const tableHeaders = [ - ['"datetime"'], -]; +const tableHeaders = [['"datetime"']]; const dateTimes = []; @@ -53,8 +49,9 @@ const dateTimes = []; * @return {!Array} */ function getLog(format) { - return exec(`git log --format="${format}" ${filePath}`) - .then(logs => logs.trim().split('\n')); + return exec(`git log --format="${format}" ${filePath}`).then(logs => + logs.trim().split('\n') + ); } /** @@ -63,7 +60,10 @@ function getLog(format) { */ function parseSizeFile(file) { const lines = file.trim().split('\n'); - const headers = lines[0].trim().split('|').map(x => x.trim()); + const headers = lines[0] + .trim() + .split('|') + .map(x => x.trim()); let minPos = -1; // Find the "min" column which is the closure compiled or the "size" column // which was previously babelify compiled file. @@ -79,48 +79,58 @@ function parseSizeFile(file) { // Remove separator lines.shift(); - return lines.map(line => { - const columns = line.split('|').map(x => x.trim()); - let name = columns[columns.length - 1]; - - // Older size.txt files contained duplicate entries of the same "entity", - // for example a file had an entry for its .min and its .max file. - const shouldSkip = (name.endsWith('max.js') && - !name.endsWith('alp.max.js') && !/\s\/\s/.test(name)) - || name == 'current/integration.js' || name == 'amp.js' || - name == 'cc.js' || name.endsWith('-latest.js'); + return lines + .map(line => { + const columns = line.split('|').map(x => x.trim()); + let name = columns[columns.length - 1]; + // Older size.txt files contained duplicate entries of the same "entity", + // for example a file had an entry for its .min and its .max file. + const shouldSkip = + (name.endsWith('max.js') && + !name.endsWith('alp.max.js') && + !/\s\/\s/.test(name)) || + name == 'current/integration.js' || + name == 'amp.js' || + name == 'cc.js' || + name.endsWith('-latest.js'); - if (shouldSkip) { - return null; - } + if (shouldSkip) { + return null; + } - // Normalize names. We made mistakes at some point with duplicate entries - // or renamed entries so we make sure to identify these entities - // and put then into the same column. - if (name == 'v0.js / amp.js' || name == 'current-min/v0.js') { - name = 'v0.js'; - } else if (name == 'current-min/f.js / current/integration.js' || - name == 'current-min/f.js') { - name = 'f.js'; - } else if (name == 'alp.max.js' || name == 'alp.js / install-alp.js' || - name == 'alp.js / alp.max.js') { - name = 'alp.js'; - } else if (name == 'sw.js / sw.max.js') { - name = 'sw.js'; - } else if (name == 'sw-kill.js / sw-kill.max.js') { - name = 'sw-kill.js'; - } else if (name == 'a4a-host-v0.js / amp-inabox-host.js') { - name = 'amp4ads-host-v0.js / amp-inabox-host.js'; - } else if (name == 'a4a-v0.js / amp-inabox.js') { - name = 'amp4ads-v0.js / amp-inabox.js'; - } + // Normalize names. We made mistakes at some point with duplicate entries + // or renamed entries so we make sure to identify these entities + // and put then into the same column. + if (name == 'v0.js / amp.js' || name == 'current-min/v0.js') { + name = 'v0.js'; + } else if ( + name == 'current-min/f.js / current/integration.js' || + name == 'current-min/f.js' + ) { + name = 'f.js'; + } else if ( + name == 'alp.max.js' || + name == 'alp.js / install-alp.js' || + name == 'alp.js / alp.max.js' + ) { + name = 'alp.js'; + } else if (name == 'sw.js / sw.max.js') { + name = 'sw.js'; + } else if (name == 'sw-kill.js / sw-kill.max.js') { + name = 'sw-kill.js'; + } else if (name == 'a4a-host-v0.js / amp-inabox-host.js') { + name = 'amp4ads-host-v0.js / amp-inabox-host.js'; + } else if (name == 'a4a-v0.js / amp-inabox.js') { + name = 'amp4ads-v0.js / amp-inabox.js'; + } - return { - name: `"${name}"`, - size: `"${reversePrettyBytes(columns[minPos])}"`, - }; - }).filter(x => !!x); + return { + name: `"${name}"`, + size: `"${reversePrettyBytes(columns[minPos])}"`, + }; + }) + .filter(x => !!x); } /** @@ -149,20 +159,23 @@ function mergeTables(dateTimes, tables) { }); // Populate the headers array with unique file names for row 1 - Object.keys(obj).sort().forEach(fileName => { - // TODO(erwinm): figure out where this is occurring. - if (fileName.trim() == '""') { - return; - } - tableHeaders[0].push(fileName); - }); + Object.keys(obj) + .sort() + .forEach(fileName => { + // TODO(erwinm): figure out where this is occurring. + if (fileName.trim() == '""') { + return; + } + tableHeaders[0].push(fileName); + }); // Populate column A with all the dates we've seen and then // populate all other columns with their respective file size if any. dateTimes.forEach(dateTime => { // Seed array with empty string values - const row = - Array.apply(null, Array(tableHeaders[0].length)).map(() => '""'); + const row = Array.apply(null, Array(tableHeaders[0].length)).map( + () => '""' + ); rows.push(row); row[0] = dateTime; // Exclude the datetime column @@ -187,7 +200,8 @@ function mergeTables(dateTimes, tables) { */ function reversePrettyBytes(prettyBytes) { const triple = prettyBytes.match( - /(\d+(?:\.\d+)?)\s+(B|kB|MB|GB|TB|PB|EB|ZB|YB)/); + /(\d+(?:\.\d+)?)\s+(B|kB|MB|GB|TB|PB|EB|ZB|YB)/ + ); if (!triple) { throw new Error('No matching bytes data found'); } @@ -216,27 +230,30 @@ function serializeCheckout(logs) { return acc.then(tables => { // We checkout all the known commits for the file and accumulate // all the tables. - return exec(`git checkout ${sha} ${filePath}`).then(() => { - return fs.readFileAsync(`${filePath}`); - }).then(file => { - const quotedDateTime = `"${dateTime}"`; - dateTimes.push(quotedDateTime); - // We convert the read file string into an Table objects - const fields = parseSizeFile(file.toString()).map(field => { - field.dateTime = quotedDateTime; - return field; - }); - tables.push(fields); - return tables; - }).catch(e => { - // Ignore if pathspec error. This can happen if the file was - // deleted in git. - if (/error: pathspec/.test(e.message)) { - tables.push([]); + return exec(`git checkout ${sha} ${filePath}`) + .then(() => { + return fs.readFileAsync(`${filePath}`); + }) + .then(file => { + const quotedDateTime = `"${dateTime}"`; + dateTimes.push(quotedDateTime); + // We convert the read file string into an Table objects + const fields = parseSizeFile(file.toString()).map(field => { + field.dateTime = quotedDateTime; + return field; + }); + tables.push(fields); return tables; - } - log(colors.red(e.message)); - }); + }) + .catch(e => { + // Ignore if pathspec error. This can happen if the file was + // deleted in git. + if (/error: pathspec/.test(e.message)) { + tables.push([]); + return tables; + } + log(colors.red(e.message)); + }); }); }, Promise.resolve(tables)); return promise.then(mergeTables.bind(null, dateTimes)); @@ -244,15 +261,14 @@ function serializeCheckout(logs) { async function csvifySize() { const shaAndDate = '%H %ai'; - return getLog(shaAndDate) - .then(logs => { - // Reverse it from oldest to newest - return serializeCheckout(logs.reverse()).then(rows => { - rows.unshift.apply(rows, tableHeaders); - const tbl = rows.map(row => row.join(',')).join('\n'); - return fs.writeFileAsync('test/size.csv', `${tbl}\n`); - }); - }); + return getLog(shaAndDate).then(logs => { + // Reverse it from oldest to newest + return serializeCheckout(logs.reverse()).then(rows => { + rows.unshift.apply(rows, tableHeaders); + const tbl = rows.map(row => row.join(',')).join('\n'); + return fs.writeFileAsync('test/size.csv', `${tbl}\n`); + }); + }); } module.exports = { diff --git a/build-system/tasks/csvify-size/test.js b/build-system/tasks/csvify-size/test.js index d4cb46e8f252..37d68cb875c2 100644 --- a/build-system/tasks/csvify-size/test.js +++ b/build-system/tasks/csvify-size/test.js @@ -15,7 +15,6 @@ */ 'use strict'; - const m = require('./'); const test = require('ava'); @@ -50,9 +49,7 @@ test('sync - parse table typedef', t => { t.plan(1); const dateTimes = ['"0"', '"1"', '"2"']; const tables = [ - [ - {name: '"v0.js"', size: '"5.5"', dateTime: '"0"'}, - ], + [{name: '"v0.js"', size: '"5.5"', dateTime: '"0"'}], [ {name: '"v0.js"', size: '"8.5"', dateTime: '"1"'}, {name: '"f.js"', size: '"70.11"', dateTime: '"1"'}, diff --git a/build-system/tasks/dep-check.js b/build-system/tasks/dep-check.js index 806694d42d66..921eabc5e894 100644 --- a/build-system/tasks/dep-check.js +++ b/build-system/tasks/dep-check.js @@ -15,7 +15,6 @@ */ 'use strict'; - const babelify = require('babelify'); const BBPromise = require('bluebird'); const browserify = require('browserify'); @@ -105,8 +104,9 @@ Rule.prototype.matchBadDeps = function(moduleName, deps) { return []; } - const isFilenameMatch = this.filesMatching_ - .some(x => minimatch(moduleName, x)); + const isFilenameMatch = this.filesMatching_.some(x => + minimatch(moduleName, x) + ); if (!isFilenameMatch) { return []; } @@ -117,7 +117,6 @@ Rule.prototype.matchBadDeps = function(moduleName, deps) { deps.forEach(dep => { this.mustNotDependOn_.forEach(badDepPattern => { if (minimatch(dep, badDepPattern)) { - // Allow extension files to depend on their own code. const dir = path.dirname(dep); if (dir.startsWith('extensions/')) { @@ -140,8 +139,10 @@ Rule.prototype.matchBadDeps = function(moduleName, deps) { if (inWhitelist) { return; } - mustNotDependErrors.push(`${moduleName} must not depend on ${dep}. ` + - `Rule: ${JSON.stringify(this.config_)}.`); + mustNotDependErrors.push( + `${moduleName} must not depend on ${dep}. ` + + `Rule: ${JSON.stringify(this.config_)}.` + ); } }); }); @@ -159,24 +160,34 @@ const rules = depCheckConfig.rules.map(config => new Rule(config)); * @return {!Promise>} */ function getSrcs() { - return fs.readdirAsync('extensions').then(dirItems => { - // Look for extension entry points - return flatten(dirItems - .map(x => `extensions/${x}`) - .filter(x => fs.statSync(x).isDirectory()) - .map(getEntryModule) - // Concat the core binary and integration binary as entry points. - .concat('src/amp.js', '3p/integration.js')); - }).then(files => { - // Write all the entry modules into a single file so they can be processed - // together. - fs.mkdirpSync('./.amp-build'); - const filename = './.amp-build/gulp-dep-check-collection.js'; - fs.writeFileSync(filename, files.map(file => { - return `import '../${file}';`; - }).join('\n')); - return [filename]; - }); + return fs + .readdirAsync('extensions') + .then(dirItems => { + // Look for extension entry points + return flatten( + dirItems + .map(x => `extensions/${x}`) + .filter(x => fs.statSync(x).isDirectory()) + .map(getEntryModule) + // Concat the core binary and integration binary as entry points. + .concat('src/amp.js', '3p/integration.js') + ); + }) + .then(files => { + // Write all the entry modules into a single file so they can be processed + // together. + fs.mkdirpSync('./.amp-build'); + const filename = './.amp-build/gulp-dep-check-collection.js'; + fs.writeFileSync( + filename, + files + .map(file => { + return `import '../${file}';`; + }) + .join('\n') + ); + return [filename]; + }); } /** @@ -194,29 +205,31 @@ function getGraph(entryModule) { // TODO(erwinm): Try and work this in with `gulp build` so that // we're not running browserify twice on travis. - const bundler = browserify(entryModule, {debug: true}) - .transform(babelify, { - compact: false, - // Transform files in node_modules since deps use ES6 export. - // https://github.com/babel/babelify#why-arent-files-in-node_modules-being-transformed - global: true, - }); + const bundler = browserify(entryModule, {debug: true}).transform(babelify, { + compact: false, + // Transform files in node_modules since deps use ES6 export. + // https://github.com/babel/babelify#why-arent-files-in-node_modules-being-transformed + global: true, + }); - bundler.pipeline.get('deps').push(through.obj(function(row, enc, next) { - module.deps.push({ - name: row.file.replace(absPathRegExp, ''), - deps: row.deps, - }); - this.push(row); - next(); - })); - bundler.bundle() - .pipe(source(entryModule)) - // Unfortunately we need to write the files out. - .pipe(gulp.dest('./.amp-build')) - .on('end', () => { - resolve(module); + bundler.pipeline.get('deps').push( + through.obj(function(row, enc, next) { + module.deps.push({ + name: row.file.replace(absPathRegExp, ''), + deps: row.deps, }); + this.push(row); + next(); + }) + ); + bundler + .bundle() + .pipe(source(entryModule)) + // Unfortunately we need to write the files out. + .pipe(gulp.dest('./.amp-build')) + .on('end', () => { + resolve(module); + }); return promise; } @@ -226,12 +239,13 @@ function getGraph(entryModule) { */ function getEntryModule(extensionFolder) { const extension = path.basename(extensionFolder); - return fs.readdirSync(extensionFolder) - .map(x => `${extensionFolder}/${x}`) - .filter(x => fs.statSync(x).isDirectory()) - .map(x => `${x}/${extension}.js`) - .filter(x => fs.existsSync(x)) - .filter(x => fs.statSync(x).isFile()); + return fs + .readdirSync(extensionFolder) + .map(x => `${extensionFolder}/${x}`) + .filter(x => fs.statSync(x).isDirectory()) + .map(x => `${x}/${extension}.js`) + .filter(x => fs.existsSync(x)) + .filter(x => fs.statSync(x).isFile()); } /** @@ -248,16 +262,15 @@ function flattenGraph(entryPoints) { // the entry points. entryPoints = entryPoints.map(entryPoint => entryPoint.deps); // Now make the graph have unique entries - return flatten(entryPoints) - .reduce((acc, cur) => { - const {name} = cur; - if (!acc[name]) { - acc[name] = Object.keys(cur.deps) - // Get rid of the absolute path for minimatch'ing - .map(x => cur.deps[x].replace(absPathRegExp, '')); - } - return acc; - }, Object.create(null)); + return flatten(entryPoints).reduce((acc, cur) => { + const {name} = cur; + if (!acc[name]) { + acc[name] = Object.keys(cur.deps) + // Get rid of the absolute path for minimatch'ing + .map(x => cur.deps[x].replace(absPathRegExp, '')); + } + return acc; + }, Object.create(null)); } /** @@ -287,16 +300,21 @@ async function depCheck() { if (!isTravisBuild()) { log('Checking dependencies...'); } - return getSrcs().then(entryPoints => { - // This check is for extension folders that actually dont have - // an extension entry point module yet. - entryPoints = entryPoints.filter(x => fs.existsSync(x)); - return BBPromise.all(entryPoints.map(getGraph)); - }).then(flattenGraph).then(runRules).then(errorsFound => { - if (errorsFound) { - process.exit(1); - } - }).then(() => exitCtrlcHandler(handlerProcess)); + return getSrcs() + .then(entryPoints => { + // This check is for extension folders that actually dont have + // an extension entry point module yet. + entryPoints = entryPoints.filter(x => fs.existsSync(x)); + return BBPromise.all(entryPoints.map(getGraph)); + }) + .then(flattenGraph) + .then(runRules) + .then(errorsFound => { + if (errorsFound) { + process.exit(1); + } + }) + .then(() => exitCtrlcHandler(handlerProcess)); } /** diff --git a/build-system/tasks/dev-dashboard-tests.js b/build-system/tasks/dev-dashboard-tests.js index bfe258f3308c..828b26cb1f00 100644 --- a/build-system/tasks/dev-dashboard-tests.js +++ b/build-system/tasks/dev-dashboard-tests.js @@ -20,7 +20,6 @@ const deglob = require('globs-to-files'); const Mocha = require('mocha'); const {isTravisBuild} = require('../travis'); - /** * Run all the dev dashboard tests */ @@ -35,7 +34,9 @@ async function devDashboardTests() { // Create our deffered let resolver; - const deferred = new Promise(resolverIn => {resolver = resolverIn;}); + const deferred = new Promise(resolverIn => { + resolver = resolverIn; + }); // Run the tests. mocha.run(function(failures) { diff --git a/build-system/tasks/dist.js b/build-system/tasks/dist.js index 7023b45c3829..2c76a04296b7 100644 --- a/build-system/tasks/dist.js +++ b/build-system/tasks/dist.js @@ -39,11 +39,17 @@ const { getExtensionsToBuild, parseExtensionFlags, } = require('./extension-helpers'); +const { + closureNailgunPort, + startNailgunServer, + stopNailgunServer, +} = require('./nailgun'); +const { + createModuleCompatibleES5Bundle, +} = require('./create-module-compatible-es5-bundle'); const {cleanupBuildDir} = require('../compile/compile'); -const {closureNailgunPort, startNailgunServer, stopNailgunServer} = require('./nailgun'); const {compileCss, cssEntryPoints} = require('./css'); const {createCtrlcHandler, exitCtrlcHandler} = require('../ctrlcHandler'); -const {createModuleCompatibleES5Bundle} = require('./create-module-compatible-es5-bundle'); const {isTravisBuild} = require('../travis'); const {maybeUpdatePackages} = require('./update-packages'); @@ -68,51 +74,59 @@ async function dist() { } if (argv.single_pass) { if (!isTravisBuild()) { - log(green('Building all AMP extensions in'), cyan('single_pass'), - green('mode.')); + log( + green('Building all AMP extensions in'), + cyan('single_pass'), + green('mode.') + ); } } else { parseExtensionFlags(); } return compileCss(/* watch */ undefined, /* opt_compileAll */ true) - .then(async() => { - await startNailgunServer(closureNailgunPort, /* detached */ false); - }) - .then(() => { + .then(async () => { + await startNailgunServer(closureNailgunPort, /* detached */ false); + }) + .then(() => { + return Promise.all([ + compileAllMinifiedTargets(), + // NOTE: When adding a line here, + // consider whether you need to include polyfills + // and whether you need to init logging (initLogConstructor). + buildAlp({minify: true, watch: false}), + buildExaminer({minify: true, watch: false}), + buildWebWorker({minify: true, watch: false}), + buildExtensions({minify: true, watch: false}), + buildExperiments({minify: true, watch: false}), + buildLoginDone({minify: true, watch: false}), + buildWebPushPublisherFiles({minify: true, watch: false}), + copyCss(), + ]); + }) + .then(() => { + if (isTravisBuild()) { + // New line after all the compilation progress dots on Travis. + console.log('\n'); + } + }) + .then(async () => { + await stopNailgunServer(closureNailgunPort); + }) + .then(() => { + return copyAliasExtensions(); + }) + .then(() => { + if (argv.esm) { return Promise.all([ - compileAllMinifiedTargets(), - // NOTE: When adding a line here, - // consider whether you need to include polyfills - // and whether you need to init logging (initLogConstructor). - buildAlp({minify: true, watch: false}), - buildExaminer({minify: true, watch: false}), - buildWebWorker({minify: true, watch: false}), - buildExtensions({minify: true, watch: false}), - buildExperiments({minify: true, watch: false}), - buildLoginDone({minify: true, watch: false}), - buildWebPushPublisherFiles({minify: true, watch: false}), - copyCss(), + createModuleCompatibleES5Bundle('v0.js'), + createModuleCompatibleES5Bundle('amp4ads-v0.js'), + createModuleCompatibleES5Bundle('shadow-v0.js'), ]); - }).then(() => { - if (isTravisBuild()) { - // New line after all the compilation progress dots on Travis. - console.log('\n'); - } - }).then(async() => { - await stopNailgunServer(closureNailgunPort); - }).then(() => { - return copyAliasExtensions(); - }).then(() => { - if (argv.esm) { - return Promise.all([ - createModuleCompatibleES5Bundle('v0.js'), - createModuleCompatibleES5Bundle('amp4ads-v0.js'), - createModuleCompatibleES5Bundle('shadow-v0.js'), - ]); - } else { - return Promise.resolve(); - } - }).then(() => exitCtrlcHandler(handlerProcess)); + } else { + return Promise.resolve(); + } + }) + .then(() => exitCtrlcHandler(handlerProcess)); } /** @@ -126,11 +140,13 @@ function copyCss() { fs.copySync(`build/css/${outCss}`, `dist/${outCss}`); }); - return toPromise(gulp.src('build/css/amp-*.css', {base: 'build/css/'}) - .pipe(gulp.dest('dist/v0'))) - .then(() => { - endBuildStep('Copied', 'build/css/*.css to dist/*.css', startTime); - }); + return toPromise( + gulp + .src('build/css/amp-*.css', {base: 'build/css/'}) + .pipe(gulp.dest('dist/v0')) + ).then(() => { + endBuildStep('Copied', 'build/css/*.css to dist/*.css', startTime); + }); } /** @@ -145,12 +161,16 @@ function copyAliasExtensions() { const extensionsToBuild = getExtensionsToBuild(); for (const key in extensionAliasFilePath) { - if (extensionsToBuild.length > 0 && - extensionsToBuild.indexOf(extensionAliasFilePath[key]['name']) == -1) { + if ( + extensionsToBuild.length > 0 && + extensionsToBuild.indexOf(extensionAliasFilePath[key]['name']) == -1 + ) { continue; } - fs.copySync('dist/v0/' + extensionAliasFilePath[key]['file'], - 'dist/v0/' + key); + fs.copySync( + 'dist/v0/' + extensionAliasFilePath[key]['file'], + 'dist/v0/' + key + ); } return Promise.resolve(); @@ -174,8 +194,10 @@ function buildWebPushPublisherFiles(options) { function buildWebPushPublisherFilesVersion(version, options) { options = options || {}; const {watch} = options; - const fileNames = - ['amp-web-push-helper-frame', 'amp-web-push-permission-dialog']; + const fileNames = [ + 'amp-web-push-helper-frame', + 'amp-web-push-permission-dialog', + ]; const promises = []; mkdirSync('dist'); @@ -207,35 +229,38 @@ function buildWebPushPublisherFile(version, fileName, watch, options) { const js = fs.readFileSync(basePath + fileName + '.js', 'utf8'); const builtName = fileName + '.js'; const minifiedName = fileName + '.js'; - return toPromise(gulp.src(basePath + '/*.js', {base: '.'}) + return toPromise( + gulp + .src(basePath + '/*.js', {base: '.'}) .pipe(file(builtName, js)) - .pipe(gulp.dest(tempBuildDir))) - .then(function() { - return compileJs('./' + tempBuildDir, builtName, './' + distDir, { - watch, - includePolyfills: true, - minify: options.minify || argv.minify, - minifiedName, - extraGlobs: [ - tempBuildDir + '*.js', - ], - }); - }) - .then(function() { - if (fs.existsSync(distDir + '/' + minifiedName)) { - // Build Helper Frame HTML - let fileContents = - fs.readFileSync(basePath + fileName + '.html', 'utf8'); - fileContents = fileContents.replace( - '', - '' - ); - - fs.writeFileSync('dist/v0/' + fileName + '.html', - fileContents); - } + .pipe(gulp.dest(tempBuildDir)) + ) + .then(function() { + return compileJs('./' + tempBuildDir, builtName, './' + distDir, { + watch, + includePolyfills: true, + minify: options.minify || argv.minify, + minifiedName, + extraGlobs: [tempBuildDir + '*.js'], }); + }) + .then(function() { + if (fs.existsSync(distDir + '/' + minifiedName)) { + // Build Helper Frame HTML + let fileContents = fs.readFileSync( + basePath + fileName + '.html', + 'utf8' + ); + fileContents = fileContents.replace( + '', + '' + ); + + fs.writeFileSync('dist/v0/' + fileName + '.html', fileContents); + } + }); } /** @@ -280,12 +305,8 @@ async function buildLoginDoneVersion(version, options) { const html = fs.readFileSync(htmlPath, 'utf8'); const minJs = `https://${hostname}/v0/amp-login-done-${version}.js`; const minHtml = html - .replace( - `../../../dist/v0/amp-login-done-${version}.max.js`, - minJs) - .replace( - `../../../dist/v0/amp-login-done-${version}.js`, - minJs); + .replace(`../../../dist/v0/amp-login-done-${version}.max.js`, minJs) + .replace(`../../../dist/v0/amp-login-done-${version}.js`, minJs); if (minHtml.indexOf(minJs) == -1) { throw new Error('Failed to correctly set JS in login-done.html'); } @@ -293,30 +314,31 @@ async function buildLoginDoneVersion(version, options) { mkdirSync('dist'); mkdirSync('dist/v0'); - fs.writeFileSync('dist/v0/amp-login-done-' + version + '.html', - minHtml); + fs.writeFileSync('dist/v0/amp-login-done-' + version + '.html', minHtml); // Build JS. const js = fs.readFileSync(jsPath, 'utf8'); const builtName = 'amp-login-done-' + version + '.max.js'; const minifiedName = 'amp-login-done-' + version + '.js'; const latestName = 'amp-login-done-latest.js'; - return toPromise(gulp.src(path + '/*.js', {base: path}) + return toPromise( + gulp + .src(path + '/*.js', {base: path}) .pipe(file(builtName, js)) - .pipe(gulp.dest(buildDir))) - .then(function() { - return compileJs('./' + buildDir, builtName, './dist/v0/', { - watch: false, - includePolyfills: true, - minify: options.minify || argv.minify, - minifiedName, - latestName, - extraGlobs: [ - buildDir + 'amp-login-done-0.1.max.js', - buildDir + 'amp-login-done-dialog.js', - ], - }); - }); + .pipe(gulp.dest(buildDir)) + ).then(function() { + return compileJs('./' + buildDir, builtName, './dist/v0/', { + watch: false, + includePolyfills: true, + minify: options.minify || argv.minify, + minifiedName, + latestName, + extraGlobs: [ + buildDir + 'amp-login-done-0.1.max.js', + buildDir + 'amp-login-done-dialog.js', + ], + }); + }); } module.exports = { @@ -331,15 +353,17 @@ buildExperiments.description = 'Builds experiments.html/js'; buildLoginDone.description = 'Builds login-done.html/js'; dist.description = 'Build production binaries'; dist.flags = { - pseudo_names: ' Compiles with readable names. ' + - 'Great for profiling and debugging production code.', + pseudo_names: + ' Compiles with readable names. ' + + 'Great for profiling and debugging production code.', fortesting: ' Compiles production binaries for local testing', config: ' Sets the runtime\'s AMP_CONFIG to one of "prod" or "canary"', - single_pass: 'Compile AMP\'s primary JS bundles in a single invocation', + single_pass: "Compile AMP's primary JS bundles in a single invocation", extensions: ' Builds only the listed extensions.', extensions_from: ' Builds only the extensions from the listed AMP(s).', noextensions: ' Builds with no extensions.', - single_pass_dest: ' The directory closure compiler will write out to ' + - 'with --single_pass mode. The default directory is `dist`', + single_pass_dest: + ' The directory closure compiler will write out to ' + + 'with --single_pass mode. The default directory is `dist`', full_sourcemaps: ' Includes source code content in sourcemaps', }; diff --git a/build-system/tasks/e2e/amp-driver.js b/build-system/tasks/e2e/amp-driver.js index d52b790f529d..eaa14ec5c852 100644 --- a/build-system/tasks/e2e/amp-driver.js +++ b/build-system/tasks/e2e/amp-driver.js @@ -14,7 +14,6 @@ * limitations under the License. */ - /** @enum {string} */ const AmpdocEnvironment = { SINGLE: 'single', @@ -35,15 +34,18 @@ const EnvironmentBehaviorMap = { [AmpdocEnvironment.VIEWER_DEMO]: { ready(controller) { - return controller.findElement('#AMP_DOC_dynamic[data-loaded]') - .then(frame => controller.switchToFrame(frame)); + return controller + .findElement('#AMP_DOC_dynamic[data-loaded]') + .then(frame => controller.switchToFrame(frame)); }, url(url) { - const defaultCaps = ['a2a','focus-rect','foo','keyboard','swipe']; + const defaultCaps = ['a2a', 'focus-rect', 'foo', 'keyboard', 'swipe']; // TODO(estherkim): somehow allow non-8000 port and domain - return `http://localhost:8000/examples/viewer.html#href=${url}` + - `&caps=${defaultCaps.join(',')}`; + return ( + `http://localhost:8000/examples/viewer.html#href=${url}` + + `&caps=${defaultCaps.join(',')}` + ); }, }, @@ -52,7 +54,8 @@ const EnvironmentBehaviorMap = { // TODO(cvializ): this is a HACK // There should be a better way to detect that the shadowdoc is ready. const shadowHost = await controller.findElement( - '.amp-doc-host[style="visibility: visible;"]'); + '.amp-doc-host[style="visibility: visible;"]' + ); await controller.switchToShadow(shadowHost); }, @@ -82,11 +85,15 @@ class AmpDriver { * @return {!Promise} */ async toggleExperiment(name, toggle) { - await this.controller_.evaluate((name, toggle) => { - (window.AMP = window.AMP || []).push(AMP => { - AMP.toggleExperiment(name, toggle); - }); - }, name, toggle); + await this.controller_.evaluate( + (name, toggle) => { + (window.AMP = window.AMP || []).push(AMP => { + AMP.toggleExperiment(name, toggle); + }); + }, + name, + toggle + ); } /** diff --git a/build-system/tasks/e2e/describes-e2e.js b/build-system/tasks/e2e/describes-e2e.js index d3aac26fe014..2d16e18a26f8 100644 --- a/build-system/tasks/e2e/describes-e2e.js +++ b/build-system/tasks/e2e/describes-e2e.js @@ -18,13 +18,15 @@ require('chromedriver'); // eslint-disable-line no-unused-vars const puppeteer = require('puppeteer'); +const { + SeleniumWebDriverController, +} = require('./selenium-webdriver-controller'); const {AmpDriver, AmpdocEnvironment} = require('./amp-driver'); const {Builder, Capabilities} = require('selenium-webdriver'); const {clearLastExpectError, getLastExpectError} = require('./expect'); const {installRepl, uninstallRepl} = require('./repl'); const {isTravisBuild} = require('../../travis'); const {PuppeteerController} = require('./puppeteer-controller'); -const {SeleniumWebDriverController} = require('./selenium-webdriver-controller'); /** Should have something in the name, otherwise nothing is shown. */ const SUB = ' '; @@ -142,12 +144,18 @@ const endtoend = describeEnv(spec => new EndToEndFixture(spec)); * Maps an environment enum value to a `describes.repeated` variant object. */ const EnvironmentVariantMap = { - [AmpdocEnvironment.SINGLE]: - {name: 'Standalone environment', value: {environment: 'single'}}, - [AmpdocEnvironment.VIEWER_DEMO]: - {name: 'Viewer environment', value: {environment: 'viewer-demo'}}, - [AmpdocEnvironment.SHADOW_DEMO]: - {name: 'Shadow environment', value: {environment: 'shadow-demo'}}, + [AmpdocEnvironment.SINGLE]: { + name: 'Standalone environment', + value: {environment: 'single'}, + }, + [AmpdocEnvironment.VIEWER_DEMO]: { + name: 'Viewer environment', + value: {environment: 'viewer-demo'}, + }, + [AmpdocEnvironment.SHADOW_DEMO]: { + name: 'Shadow environment', + value: {environment: 'shadow-demo'}, + }, }; const defaultEnvironments = [ @@ -162,7 +170,7 @@ const defaultEnvironments = [ * * Example usage: * it.configure().skipViewerDemo().skipShadowDemo().run('Should ...', ...); -*/ + */ class ItConfig { constructor(it, env) { this.it = it; @@ -289,7 +297,7 @@ function describeEnv(factory) { * @param {function(!Object)} fn */ mainFunc.only = function(name, spec, fn) { - return templateFunc(name, spec, fn, describe./*OK*/only); + return templateFunc(name, spec, fn, describe./*OK*/ only); }; mainFunc.skip = function(name, variants, fn) { @@ -300,7 +308,6 @@ function describeEnv(factory) { } class EndToEndFixture { - /** @param {!TestSpec} spec */ constructor(spec) { /** @const */ @@ -319,9 +326,7 @@ class EndToEndFixture { experiments = [], initialRect = DEFAULT_E2E_INITIAL_RECT, } = this.spec; - const { - environment, - } = env; + const {environment} = env; await toggleExperiments(ampDriver, testUrl, experiments); @@ -344,10 +349,7 @@ class EndToEndFixture { * Get the controller object for the configured engine. * @param {!DescribesConfigDef} describesConfig */ -async function getController({ - engine = 'selenium', - headless = false, -}) { +async function getController({engine = 'selenium', headless = false}) { if (engine == 'puppeteer') { const browser = await createPuppeteer({headless}); return new PuppeteerController(browser); diff --git a/build-system/tasks/e2e/driver/query-xpath.js b/build-system/tasks/e2e/driver/query-xpath.js index 9033cd1c4603..670ee5d0e475 100644 --- a/build-system/tasks/e2e/driver/query-xpath.js +++ b/build-system/tasks/e2e/driver/query-xpath.js @@ -57,16 +57,21 @@ function queryXpath(xpathString, context) { // Add an ID to every element in the tree so we can reference them later. // This allows us to correlate nodes from the cloned tree to the // original tree. - for (const {item, index} of - createIndexedInterator(createElementIterator(context))) { + for (const {item, index} of createIndexedInterator( + createElementIterator(context) + )) { setData(item, index); } const fakeDocument = document.implementation.createDocument( - 'http://www.w3.org/1999/xhtml', 'html', null); + 'http://www.w3.org/1999/xhtml', + 'html', + null + ); try { fakeDocument.documentElement.appendChild( - context.cloneNode(/* deep */ true)); + context.cloneNode(/* deep */ true) + ); } catch (e) { // Appending the AMP `CustomElement`s to the new document throws errors // because the implementations expect the AmpDoc to be present and it @@ -77,11 +82,12 @@ function queryXpath(xpathString, context) { // Map the xpath results from the fake tree to the corresponding nodes in // the test document tree. const xpathIterator = createXpathIterator( - evaluate(xpathString, fakeDocument, null, XPathResult.ANY_TYPE)); + evaluate(xpathString, fakeDocument, null, XPathResult.ANY_TYPE) + ); const elements = [...xpathIterator].map(node => { const testId = getData(node); const selector = `[${TEST_ID_ATTRIBUTE}="${testId}"]`; - return context./*OK*/querySelector(selector); + return context./*OK*/ querySelector(selector); }); // Restore the DOM to the original state without the ID in the dataset. @@ -126,8 +132,9 @@ function removeData(node) { function createElementIterator(element) { const document = element.ownerDocument; return createCommonIterator( - document.createNodeIterator(element, NodeFilter.SHOW_ELEMENT), - 'nextNode'); + document.createNodeIterator(element, NodeFilter.SHOW_ELEMENT), + 'nextNode' + ); } /** @@ -148,7 +155,7 @@ function createXpathIterator(xpathResult) { */ function* createCommonIterator(iterator, nextProperty) { let item; - while (Boolean(item = iterator[nextProperty]())) { + while (Boolean((item = iterator[nextProperty]()))) { yield item; } } @@ -168,5 +175,4 @@ function* createIndexedInterator(iter) { } } - window.queryXpath = queryXpath; diff --git a/build-system/tasks/e2e/expect.js b/build-system/tasks/e2e/expect.js index 445697a8ee3a..382396b35514 100644 --- a/build-system/tasks/e2e/expect.js +++ b/build-system/tasks/e2e/expect.js @@ -69,13 +69,25 @@ function installIncludeWrapper(chai, utils) { const overwrite = overwriteAlwaysUseSuper(utils); Assertion.overwriteChainableMethod( - 'include', overwrite, inheritChainingBehavior); + 'include', + overwrite, + inheritChainingBehavior + ); Assertion.overwriteChainableMethod( - 'includes', overwrite, inheritChainingBehavior); + 'includes', + overwrite, + inheritChainingBehavior + ); Assertion.overwriteChainableMethod( - 'contain', overwrite, inheritChainingBehavior); + 'contain', + overwrite, + inheritChainingBehavior + ); Assertion.overwriteChainableMethod( - 'contains', overwrite, inheritChainingBehavior); + 'contains', + overwrite, + inheritChainingBehavior + ); } function installMatchWrapper(chai, utils) { @@ -91,9 +103,15 @@ function installLengthWrapper(chai, utils) { const overwrite = overwriteAlwaysUseSuper(utils); Assertion.overwriteChainableMethod( - 'length', overwrite, inheritChainingBehavior); + 'length', + overwrite, + inheritChainingBehavior + ); Assertion.overwriteChainableMethod( - 'lengthOf', overwrite, inheritChainingBehavior); + 'lengthOf', + overwrite, + inheritChainingBehavior + ); } function installAboveWrapper(chai, utils) { @@ -146,7 +164,6 @@ function installIsNullWrapper(chai, utils) { Assertion.overwriteProperty('null', overwrite); } - function overwriteAlwaysUseSuper(utils) { const {flag} = utils; diff --git a/build-system/tasks/e2e/functional-test-controller.js b/build-system/tasks/e2e/functional-test-controller.js index 4e04470c1cbd..44c71ce91a65 100644 --- a/build-system/tasks/e2e/functional-test-controller.js +++ b/build-system/tasks/e2e/functional-test-controller.js @@ -40,7 +40,7 @@ class ElementHandle { * @package */ getElement() { - return this.element_; + return this.element_; } } @@ -75,9 +75,10 @@ class ControllerPromise { * @param {function(TYPE,function(TYPE): ?TYPE): !Promise=} opt_waitForValue */ constructor(executorOrPromise, opt_waitForValue) { - this.promise_ = typeof executorOrPromise == 'function' ? - new Promise(executorOrPromise) : - executorOrPromise; + this.promise_ = + typeof executorOrPromise == 'function' + ? new Promise(executorOrPromise) + : executorOrPromise; /** * Returns a Promise that resolves when the given expected value fulfills @@ -91,15 +92,17 @@ class ControllerPromise { /** @override */ catch(onRejected) { return new ControllerPromise( - this.promise_.catch(onRejected), - this.waitForValue); + this.promise_.catch(onRejected), + this.waitForValue + ); } - /** @override */ + /** @override */ finally(onFinally) { return new ControllerPromise( - this.promise_.finally(onFinally), - this.waitForValue); + this.promise_.finally(onFinally), + this.waitForValue + ); } /** @override */ @@ -111,13 +114,15 @@ class ControllerPromise { wrappedWait = (condition, opt_mutate) => { opt_mutate = opt_mutate || (x => x); return this.waitForValue(condition, value => - opt_mutate(opt_onFulfilled(value))); + opt_mutate(opt_onFulfilled(value)) + ); }; } return new ControllerPromise( - this.promise_.then(opt_onFulfilled, opt_onRejected), - wrappedWait); + this.promise_.then(opt_onFulfilled, opt_onRejected), + wrappedWait + ); } } @@ -444,13 +449,12 @@ class FunctionalTestController { async dispose() {} } - /** * @typedef {{ * width: number, * height: number * }} WindowRectDef -*/ + */ let WindowRectDef; /** @@ -460,7 +464,7 @@ let WindowRectDef; * width: number, * height: number * }} -*/ + */ let DOMRectDef; /** @enum {string} */ diff --git a/build-system/tasks/e2e/index.js b/build-system/tasks/e2e/index.js index 46c37ec6c1f1..9f3de297cb99 100644 --- a/build-system/tasks/e2e/index.js +++ b/build-system/tasks/e2e/index.js @@ -44,8 +44,9 @@ function buildRuntime_() { function launchWebServer_() { webServerProcess_ = execScriptAsync( - `gulp serve --host ${HOST} --port ${PORT}`, - {stdio: 'ignore'}); + `gulp serve --host ${HOST} --port ${PORT}`, + {stdio: 'ignore'} + ); let resolver; const deferred = new Promise(resolverIn => { @@ -116,8 +117,7 @@ async function e2e() { delete require.cache[file]; mocha.addFile(file); }); - } - else { + } else { config.e2eTestPaths.forEach(path => { glob.sync(path).forEach(file => { delete require.cache[file]; @@ -134,8 +134,7 @@ async function e2e() { process.exitCode = failures ? 1 : 0; await resolver(); }); - } - else { + } else { const filesToWatch = argv.files ? [argv.files] : [config.e2eTestPaths]; const watcher = watch(filesToWatch); log('Watching', cyan(filesToWatch), 'for changes...'); diff --git a/build-system/tasks/e2e/puppeteer-controller.js b/build-system/tasks/e2e/puppeteer-controller.js index c14a765a0042..7bf8894fc729 100644 --- a/build-system/tasks/e2e/puppeteer-controller.js +++ b/build-system/tasks/e2e/puppeteer-controller.js @@ -65,7 +65,10 @@ async function waitFor(page, valueFn, args, condition, opt_mutate) { while (!condition(value)) { const handle = await page.waitForFunction( - valueFn, {timeout: DEFAULT_WAIT_TIMEOUT}, ...args); + valueFn, + {timeout: DEFAULT_WAIT_TIMEOUT}, + ...args + ); value = await handle.jsonValue(); if (opt_mutate) { value = await opt_mutate(value); @@ -141,7 +144,7 @@ class PuppeteerController { * @template T */ getWaitFn_(valueFn, ...args) { - return async(condition, opt_mutate) => { + return async (condition, opt_mutate) => { const frame = await this.getCurrentFrame_(); return waitFor(frame, valueFn, args, condition, opt_mutate); }; @@ -167,9 +170,14 @@ class PuppeteerController { async findElement(selector) { const frame = await this.getCurrentFrame_(); const root = await this.getRoot_(); - const jsHandle = await frame.waitForFunction((root, selector) => { - return root./*OK*/querySelector(selector); - }, {timeout: DEFAULT_WAIT_TIMEOUT}, root, selector); + const jsHandle = await frame.waitForFunction( + (root, selector) => { + return root./*OK*/ querySelector(selector); + }, + {timeout: DEFAULT_WAIT_TIMEOUT}, + root, + selector + ); const elementHandle = jsHandle.asElement(); return new ElementHandle(elementHandle); } @@ -182,10 +190,15 @@ class PuppeteerController { async findElements(selector) { const frame = await this.getCurrentFrame_(); const root = await this.getRoot_(); - const nodeListHandle = await frame.waitForFunction((root, selector) => { - const nodeList = root./*OK*/querySelectorAll(selector); - return nodeList.length > 0 ? nodeList : null; - }, {timeout: DEFAULT_WAIT_TIMEOUT}, root, selector); + const nodeListHandle = await frame.waitForFunction( + (root, selector) => { + const nodeList = root./*OK*/ querySelectorAll(selector); + return nodeList.length > 0 ? nodeList : null; + }, + {timeout: DEFAULT_WAIT_TIMEOUT}, + root, + selector + ); const lengthHandle = await nodeListHandle.getProperty('length'); const length = await lengthHandle.jsonValue(); @@ -209,10 +222,15 @@ class PuppeteerController { const frame = await this.getCurrentFrame_(); const root = await this.getRoot_(); - const jsHandle = await frame.waitForFunction((xpath, root) => { - const results = window.queryXpath(xpath, root); - return results && results[0]; - }, {timeout: DEFAULT_WAIT_TIMEOUT}, xpath, root); + const jsHandle = await frame.waitForFunction( + (xpath, root) => { + const results = window.queryXpath(xpath, root); + return results && results[0]; + }, + {timeout: DEFAULT_WAIT_TIMEOUT}, + xpath, + root + ); return new ElementHandle(jsHandle.asElement()); } @@ -227,9 +245,14 @@ class PuppeteerController { const frame = await this.getCurrentFrame_(); const root = await this.getRoot_(); - const arrayHandle = await frame.waitForFunction((xpath, root) => { - return window.queryXpath(xpath, root); - }, {timeout: DEFAULT_WAIT_TIMEOUT}, xpath, root); + const arrayHandle = await frame.waitForFunction( + (xpath, root) => { + return window.queryXpath(xpath, root); + }, + {timeout: DEFAULT_WAIT_TIMEOUT}, + xpath, + root + ); const lengthHandle = await arrayHandle.getProperty('length'); const length = await lengthHandle.jsonValue(); @@ -289,10 +312,9 @@ class PuppeteerController { */ async type(handle, keys) { const frame = await this.getCurrentFrame_(); - const targetElement = handle ? - handle.getElement() : - await frame.$(':focus'); - + const targetElement = handle + ? handle.getElement() + : await frame.$(':focus'); const key = KeyToPuppeteerMap[keys]; if (key) { @@ -312,8 +334,9 @@ class PuppeteerController { const element = handle.getElement(); const getter = element => element.textContent; return new ControllerPromise( - this.evaluate(getter, element), - this.getWaitFn_(getter, element)); + this.evaluate(getter, element), + this.getWaitFn_(getter, element) + ); } /** @@ -325,11 +348,12 @@ class PuppeteerController { getElementCssValue(handle, styleProperty) { const element = handle.getElement(); const getter = (element, styleProperty) => { - return window/*OK*/['getComputedStyle'](element)[styleProperty]; + return window /*OK*/['getComputedStyle'](element)[styleProperty]; }; return new ControllerPromise( - this.evaluate(getter, element, styleProperty), - this.getWaitFn_(getter, element, styleProperty)); + this.evaluate(getter, element, styleProperty), + this.getWaitFn_(getter, element, styleProperty) + ); } /** @@ -342,8 +366,9 @@ class PuppeteerController { const element = handle.getElement(); const getter = (element, attribute) => element.getAttribute(attribute); return new ControllerPromise( - this.evaluate(getter, element, attribute), - this.getWaitFn_(getter, element, attribute)); + this.evaluate(getter, element, attribute), + this.getWaitFn_(getter, element, attribute) + ); } /** @@ -358,8 +383,9 @@ class PuppeteerController { return element[property]; }; return new ControllerPromise( - this.evaluate(getter, element, property), - this.getWaitFn_(getter, element, property)); + this.evaluate(getter, element, property), + this.getWaitFn_(getter, element, property) + ); } /** @@ -373,7 +399,7 @@ class PuppeteerController { // Extracting the values seems to perform better than returning // the raw ClientRect from the element, in terms of flakiness. // The raw ClientRect also has hundredths of a pixel. We round to int. - const {x, y, width, height} = element./*OK*/getBoundingClientRect(); + const {x, y, width, height} = element./*OK*/ getBoundingClientRect(); return { x: Math.round(x), y: Math.round(y), @@ -382,8 +408,9 @@ class PuppeteerController { }; }; return new ControllerPromise( - this.evaluate(getter, element), - this.getWaitFn_(getter, element)); + this.evaluate(getter, element), + this.getWaitFn_(getter, element) + ); } /** @@ -392,14 +419,10 @@ class PuppeteerController { * @override */ async setWindowRect(rect) { - const { - width, - height, - } = rect; + const {width, height} = rect; await this.resizeWindow_(width, height); } - /** * Resize the window and the viewport. The `page.setViewport` method only * changes the size of the rendered area of the browser, not the window size. @@ -438,7 +461,7 @@ class PuppeteerController { }); const chromePaddingWidth = outerWidth > 0 ? outerWidth - clientWidth : 0; const chromePaddingHeight = - outerHeight > 0 ? outerHeight - clientHeight : 0; + outerHeight > 0 ? outerHeight - clientHeight : 0; await page.setViewport({ width: width + chromePaddingWidth, @@ -447,27 +470,30 @@ class PuppeteerController { }); // Any tab. - const {targetInfos: [{targetId}]} = await browser._connection.send( - 'Target.getTargets'); + const { + targetInfos: [{targetId}], + } = await browser._connection.send('Target.getTargets'); // Tab window. try { const {windowId} = await browser._connection.send( - 'Browser.getWindowForTarget', {targetId}); + 'Browser.getWindowForTarget', + {targetId} + ); // Resize. - await browser._connection.send( - 'Browser.setWindowBounds', { - bounds: { - width: width + chromePaddingWidth, - height: height + chromePaddingHeight, - }, - windowId, - }); + await browser._connection.send('Browser.setWindowBounds', { + bounds: { + width: width + chromePaddingWidth, + height: height + chromePaddingHeight, + }, + windowId, + }); } catch (e) { // Catch if we're in headless. - if (!e.toString().includes( - 'Protocol error (Browser.getWindowForTarget)')) { + if ( + !e.toString().includes('Protocol error (Browser.getWindowForTarget)') + ) { throw e; } } @@ -478,8 +504,9 @@ class PuppeteerController { */ getTitle() { const title = this.getCurrentFrame_().then(frame => frame.title()); - return new ControllerPromise( - title, () => this.getCurrentFrame_().then(frame => frame.title())); + return new ControllerPromise(title, () => + this.getCurrentFrame_().then(frame => frame.title()) + ); } /** @@ -510,9 +537,13 @@ class PuppeteerController { */ async scroll(handle, opt_scrollToOptions) { const element = handle.getElement(); - await this.evaluate((element, opt_scrollToOptions) => { - element./*OK*/scrollTo(opt_scrollToOptions); - }, element, opt_scrollToOptions); + await this.evaluate( + (element, opt_scrollToOptions) => { + element./*OK*/ scrollTo(opt_scrollToOptions); + }, + element, + opt_scrollToOptions + ); } /** @@ -523,9 +554,13 @@ class PuppeteerController { */ async scrollBy(handle, opt_scrollToOptions) { const element = handle.getElement(); - await this.evaluate((element, opt_scrollToOptions) => { - element./*OK*/scrollBy(opt_scrollToOptions); - }, element, opt_scrollToOptions); + await this.evaluate( + (element, opt_scrollToOptions) => { + element./*OK*/ scrollBy(opt_scrollToOptions); + }, + element, + opt_scrollToOptions + ); } /** diff --git a/build-system/tasks/e2e/repl.js b/build-system/tasks/e2e/repl.js index 789b7003175e..9120f2c563eb 100644 --- a/build-system/tasks/e2e/repl.js +++ b/build-system/tasks/e2e/repl.js @@ -59,7 +59,7 @@ function installRepl(global, env) { }); } - console./*OK*/log(READY_MESSAGE); + console./*OK*/ log(READY_MESSAGE); return replPromise; }; @@ -76,7 +76,7 @@ function installRepl(global, env) { delete global.repl.env; delete global.repl.continue; - console./*OK*/log(CONTINUE_MESSAGE); + console./*OK*/ log(CONTINUE_MESSAGE); } } diff --git a/build-system/tasks/e2e/selenium-webdriver-controller.js b/build-system/tasks/e2e/selenium-webdriver-controller.js index 9097c4d32fb0..4b68e4548c7a 100644 --- a/build-system/tasks/e2e/selenium-webdriver-controller.js +++ b/build-system/tasks/e2e/selenium-webdriver-controller.js @@ -15,17 +15,12 @@ */ const fs = require('fs'); -const { - By, - Condition, - Key: SeleniumKey, - error, -} = require('selenium-webdriver'); const { ControllerPromise, ElementHandle, Key, } = require('./functional-test-controller'); +const {By, Condition, Key: SeleniumKey, error} = require('selenium-webdriver'); const {expect} = require('chai'); const {NoSuchElementError} = error; @@ -52,7 +47,7 @@ const KeyToSeleniumMap = { */ function expectCondition(valueFn, condition, opt_mutate) { opt_mutate = opt_mutate || (x => x); - return new Condition('value matches condition', async() => { + return new Condition('value matches condition', async () => { const value = await valueFn(); const mutatedValue = await opt_mutate(value); return condition(mutatedValue); @@ -75,8 +70,9 @@ function waitFor(driver, valueFn, condition, opt_mutate) { // (like "") do not cause driver.wait to continue waiting. return condition(value) ? {value} : null; }; - return driver.wait(expectCondition(valueFn, conditionValue, opt_mutate)) - .then(result => result.value); // Unbox the value. + return driver + .wait(expectCondition(valueFn, conditionValue, opt_mutate)) + .then(result => result.value); // Unbox the value. } /** @implements {FunctionalTestController} */ @@ -124,7 +120,7 @@ class SeleniumWebDriverController { const bySelector = By.css(selector); const label = 'for element to be located ' + selector; - const condition = new Condition(label, async() => { + const condition = new Condition(label, async () => { try { const root = await this.getRoot_(); return await root.findElement(bySelector); @@ -155,7 +151,7 @@ class SeleniumWebDriverController { const bySelector = By.css(selector); const label = 'for at least one element to be located ' + selector; - const condition = new Condition(label, async() => { + const condition = new Condition(label, async () => { try { const root = await this.getRoot_(); const elements = await root.findElements(bySelector); @@ -180,13 +176,20 @@ class SeleniumWebDriverController { await this.maybeInstallXpath_(); const label = 'for element to be located ' + xpath; - const webElement = await this.driver.wait(new Condition(label, async() => { - const root = await this.getRoot_(); - const results = await this.evaluate((xpath, root) => { - return window.queryXpath(xpath, root); - }, xpath, root); - return (results && results[0]); - }), ELEMENT_WAIT_TIMEOUT); + const webElement = await this.driver.wait( + new Condition(label, async () => { + const root = await this.getRoot_(); + const results = await this.evaluate( + (xpath, root) => { + return window.queryXpath(xpath, root); + }, + xpath, + root + ); + return results && results[0]; + }), + ELEMENT_WAIT_TIMEOUT + ); return new ElementHandle(webElement, this); } @@ -198,13 +201,20 @@ class SeleniumWebDriverController { async findElementsXPath(xpath) { await this.maybeInstallXpath_(); const label = 'for at least one element to be located ' + xpath; - const webElements = await this.driver.wait(new Condition(label, async() => { - const root = await this.getRoot_(); - const results = await this.evaluate((xpath, root) => { - return window.queryXpath(xpath, root); - }, xpath, root); - return results; - }), ELEMENT_WAIT_TIMEOUT); + const webElements = await this.driver.wait( + new Condition(label, async () => { + const root = await this.getRoot_(); + const results = await this.evaluate( + (xpath, root) => { + return window.queryXpath(xpath, root); + }, + xpath, + root + ); + return results; + }), + ELEMENT_WAIT_TIMEOUT + ); return webElements.map(webElement => new ElementHandle(webElement, this)); } @@ -232,8 +242,7 @@ class SeleniumWebDriverController { async getActiveElement() { const root = await this.getRoot_(); const getter = root => root.parentNode.activeElement; - const activeElement = - await this.driver.executeScript(getter, root); + const activeElement = await this.driver.executeScript(getter, root); return new ElementHandle(activeElement); } @@ -244,8 +253,7 @@ class SeleniumWebDriverController { async getDocumentElement() { const root = await this.getRoot_(); const getter = root => root.ownerDocument.documentElement; - const documentElement = - await this.driver.executeScript(getter, root); + const documentElement = await this.driver.executeScript(getter, root); return new ElementHandle(documentElement); } @@ -265,10 +273,9 @@ class SeleniumWebDriverController { * @override */ async type(handle, keys) { - const targetElement = handle ? - handle.getElement() : - await this.driver.switchTo().activeElement(); - + const targetElement = handle + ? handle.getElement() + : await this.driver.switchTo().activeElement(); const key = KeyToSeleniumMap[keys]; if (key) { @@ -286,8 +293,9 @@ class SeleniumWebDriverController { getElementText(handle) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.getText(), - this.getWaitFn_(() => webElement.getText())); + webElement.getText(), + this.getWaitFn_(() => webElement.getText()) + ); } /** @@ -310,8 +318,9 @@ class SeleniumWebDriverController { const webElement = handle.getElement(); const getter = (element, attribute) => element.getAttribute(attribute); return new ControllerPromise( - this.evaluate(getter, webElement, attribute), - this.getWaitFn_(() => this.evaluate(getter, webElement, attribute))); + this.evaluate(getter, webElement, attribute), + this.getWaitFn_(() => this.evaluate(getter, webElement, attribute)) + ); } /** @@ -325,9 +334,11 @@ class SeleniumWebDriverController { const getProperty = (element, property) => element[property]; return new ControllerPromise( - this.driver.executeScript(getProperty, webElement, property), - this.getWaitFn_(() => this.driver.executeScript( - getProperty, webElement, property))); + this.driver.executeScript(getProperty, webElement, property), + this.getWaitFn_(() => + this.driver.executeScript(getProperty, webElement, property) + ) + ); } /** @@ -338,8 +349,9 @@ class SeleniumWebDriverController { getElementRect(handle) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.getRect(), - this.getWaitFn_(() => webElement.getRect())); + webElement.getRect(), + this.getWaitFn_(() => webElement.getRect()) + ); } /** @@ -351,8 +363,9 @@ class SeleniumWebDriverController { getElementCssValue(handle, styleProperty) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.getCssValue(styleProperty), - this.getWaitFn_(() => webElement.getCssValue(styleProperty))); + webElement.getCssValue(styleProperty), + this.getWaitFn_(() => webElement.getCssValue(styleProperty)) + ); } /** @@ -363,8 +376,9 @@ class SeleniumWebDriverController { isElementEnabled(handle) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.isEnabled(), - this.getWaitFn_(() => webElement.isEnabled())); + webElement.isEnabled(), + this.getWaitFn_(() => webElement.isEnabled()) + ); } /** @@ -375,8 +389,9 @@ class SeleniumWebDriverController { isElementSelected(handle) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.isSelected(), - this.getWaitFn_(() => webElement.isSelected())); + webElement.isSelected(), + this.getWaitFn_(() => webElement.isSelected()) + ); } /** @@ -386,18 +401,17 @@ class SeleniumWebDriverController { * @override */ async setWindowRect(rect) { - const { - width, - height, - } = rect; - - - await this.driver.manage().window().setRect({ - x: 0, - y: 0, - width, - height, - }); + const {width, height} = rect; + + await this.driver + .manage() + .window() + .setRect({ + x: 0, + y: 0, + width, + height, + }); // Check to make sure we resized the content to the correct size. const htmlElement = this.driver.findElement(By.tagName('html')); @@ -420,10 +434,13 @@ class SeleniumWebDriverController { const horizBorder = width - clientWidth; const vertBorder = height - clientHeight; - await this.driver.manage().window().setRect({ - width: width + horizBorder, - height: height + vertBorder, - }); + await this.driver + .manage() + .window() + .setRect({ + width: width + horizBorder, + height: height + vertBorder, + }); // Verify the size. The browser may refuse to resize smaller than some // size when not running headless. It is better to fail here rather than @@ -439,11 +456,13 @@ class SeleniumWebDriverController { // to fail immediately,.Figure out why, we want the test to fail here // instead of continuing. expect(resultWidth).to.equal( - width, - 'Failed to resize the window to the requested width.'); + width, + 'Failed to resize the window to the requested width.' + ); expect(resultHeight).to.equal( - height, - 'Failed to resize the window to the requested height.'); + height, + 'Failed to resize the window to the requested height.' + ); } /** @@ -455,8 +474,9 @@ class SeleniumWebDriverController { const getTitle = () => document.title; return new ControllerPromise( - this.driver.executeScript(getTitle), - this.getWaitFn_(() => this.driver.executeScript(getTitle))); + this.driver.executeScript(getTitle), + this.getWaitFn_(() => this.driver.executeScript(getTitle)) + ); } /** @@ -478,11 +498,14 @@ class SeleniumWebDriverController { async scroll(handle, opt_scrollToOptions) { const webElement = handle.getElement(); const scrollTo = (element, opt_scrollToOptions) => { - element./*OK*/scrollTo(opt_scrollToOptions); + element./*OK*/ scrollTo(opt_scrollToOptions); }; return await this.driver.executeScript( - scrollTo, webElement, opt_scrollToOptions); + scrollTo, + webElement, + opt_scrollToOptions + ); } /** @@ -494,11 +517,14 @@ class SeleniumWebDriverController { async scrollBy(handle, opt_scrollToOptions) { const webElement = handle.getElement(); const scrollBy = (element, opt_scrollToOptions) => { - element./*OK*/scrollBy(opt_scrollToOptions); + element./*OK*/ scrollBy(opt_scrollToOptions); }; return await this.driver.executeScript( - scrollBy, webElement, opt_scrollToOptions); + scrollBy, + webElement, + opt_scrollToOptions + ); } /** @@ -510,11 +536,14 @@ class SeleniumWebDriverController { async scrollTo(handle, opt_scrollToOptions) { const webElement = handle.getElement(); const scrollTo = (element, opt_scrollToOptions) => { - element./*OK*/scrollTo(opt_scrollToOptions); + element./*OK*/ scrollTo(opt_scrollToOptions); }; return await this.driver.executeScript( - scrollTo, webElement, opt_scrollToOptions); + scrollTo, + webElement, + opt_scrollToOptions + ); } /** @@ -577,7 +606,9 @@ class SeleniumWebDriverController { async switchToShadow(handle) { const shadowHost = handle.getElement(); const shadowRootBody = await this.evaluate( - shadowHost => shadowHost.shadowRoot.body, shadowHost); + shadowHost => shadowHost.shadowRoot.body, + shadowHost + ); this.shadowRoot_ = shadowRootBody; } diff --git a/build-system/tasks/extension-generator/index.js b/build-system/tasks/extension-generator/index.js index 45db600a521f..abacd6a81b0a 100644 --- a/build-system/tasks/extension-generator/index.js +++ b/build-system/tasks/extension-generator/index.js @@ -25,8 +25,12 @@ const year = new Date().getFullYear(); /*eslint "max-len": 0*/ function pascalCase(str) { - return str[0].toUpperCase() + str.slice(1).replace(/-([a-z])/g, - function(g) { return g[1].toUpperCase(); }); + return ( + str[0].toUpperCase() + + str.slice(1).replace(/-([a-z])/g, function(g) { + return g[1].toUpperCase(); + }) + ); } function getValidatorFile(name) { @@ -274,33 +278,45 @@ function getExamplesFile(name) { async function makeExtension() { if (!argv.name) { - log(colors.red( - 'Error! Please pass in the "--name" flag with a value')); + log(colors.red('Error! Please pass in the "--name" flag with a value')); } const {name} = argv; const examplesFile = getExamplesFile(name); fs.mkdirpSync(`extensions/${name}/0.1/test`); - fs.writeFileSync(`extensions/${name}/${name}.md`, - getMarkdownExtensionFile(name)); - fs.writeFileSync(`extensions/${name}/validator-${name}.protoascii`, - getValidatorFile(name)); - fs.writeFileSync(`extensions/${name}/0.1/${name}.js`, - getJsExtensionFile(name)); - fs.writeFileSync(`extensions/${name}/0.1/test/test-${name}.js`, - getJsTestExtensionFile(name)); - fs.writeFileSync(`extensions/${name}/0.1/test/validator-${name}.html`, - examplesFile); - - const examplesFileValidatorOut = examplesFile.trim().split('\n') - .map(line => `| ${line}`) - .join('\n'); - - fs.writeFileSync(`extensions/${name}/0.1/test/validator-${name}.out`, - ['PASS', examplesFileValidatorOut].join('\n')); - - fs.writeFileSync(`examples/${name}.amp.html`, - examplesFile); + fs.writeFileSync( + `extensions/${name}/${name}.md`, + getMarkdownExtensionFile(name) + ); + fs.writeFileSync( + `extensions/${name}/validator-${name}.protoascii`, + getValidatorFile(name) + ); + fs.writeFileSync( + `extensions/${name}/0.1/${name}.js`, + getJsExtensionFile(name) + ); + fs.writeFileSync( + `extensions/${name}/0.1/test/test-${name}.js`, + getJsTestExtensionFile(name) + ); + fs.writeFileSync( + `extensions/${name}/0.1/test/validator-${name}.html`, + examplesFile + ); + + const examplesFileValidatorOut = examplesFile + .trim() + .split('\n') + .map(line => `| ${line}`) + .join('\n'); + + fs.writeFileSync( + `extensions/${name}/0.1/test/validator-${name}.out`, + ['PASS', examplesFileValidatorOut].join('\n') + ); + + fs.writeFileSync(`examples/${name}.amp.html`, examplesFile); } module.exports = { diff --git a/build-system/tasks/extension-helpers.js b/build-system/tasks/extension-helpers.js index b0e61dbee9ea..92b8be9523f2 100644 --- a/build-system/tasks/extension-helpers.js +++ b/build-system/tasks/extension-helpers.js @@ -20,7 +20,12 @@ const log = require('fancy-log'); const minimatch = require('minimatch'); const watch = require('gulp-watch'); const wrappers = require('../compile-wrappers'); -const {aliasBundles, extensionBundles, verifyExtensionBundles, verifyExtensionAliasBundles} = require('../../bundles.config'); +const { + aliasBundles, + extensionBundles, + verifyExtensionBundles, + verifyExtensionAliasBundles, +} = require('../../bundles.config'); const {compileJs, mkdirSync} = require('./helpers'); const {isTravisBuild} = require('../travis'); const {jsifyCssAsync} = require('./jsify-css'); @@ -77,9 +82,9 @@ function declareExtension(name, version, latestVersion, options) { const versions = Array.isArray(version) ? version : [version]; versions.forEach(v => { extensions[`${name}-${v}`] = Object.assign( - {name, version: v, latestVersion}, - defaultOptions, - options + {name, version: v, latestVersion}, + defaultOptions, + options ); }); if (name.startsWith('amp-ad-network-')) { @@ -105,7 +110,11 @@ function maybeInitializeExtensions() { verifyExtensionAliasBundles(); aliasBundles.forEach(c => { declareExtensionVersionAlias( - c.name, c.version, c.latestVersion, c.options); + c.name, + c.version, + c.latestVersion, + c.options + ); }); } } @@ -176,21 +185,26 @@ function getExtensionsToBuild() { */ function parseExtensionFlags() { if (!isTravisBuild()) { - const noExtensionsMessage = green('⤷ Use ') + - cyan('--noextensions ') + - green('to skip building extensions.'); - const extensionsMessage = green('⤷ Use ') + - cyan('--extensions=amp-foo,amp-bar ') + - green('to choose which extensions to build.'); - const minimalSetMessage = green('⤷ Use ') + - cyan('--extensions=minimal_set ') + - green('to build just the extensions needed to load ') + - cyan('article.amp.html') + green('.'); - const extensionsFromMessage = green('⤷ Use ') + - cyan('--extensions_from=examples/foo.amp.html ') + - green('to build extensions from example docs.'); + const noExtensionsMessage = + green('⤷ Use ') + + cyan('--noextensions ') + + green('to skip building extensions.'); + const extensionsMessage = + green('⤷ Use ') + + cyan('--extensions=amp-foo,amp-bar ') + + green('to choose which extensions to build.'); + const minimalSetMessage = + green('⤷ Use ') + + cyan('--extensions=minimal_set ') + + green('to build just the extensions needed to load ') + + cyan('article.amp.html') + + green('.'); + const extensionsFromMessage = + green('⤷ Use ') + + cyan('--extensions_from=examples/foo.amp.html ') + + green('to build extensions from example docs.'); if (argv.extensions) { - if (typeof (argv.extensions) !== 'string') { + if (typeof argv.extensions !== 'string') { log(red('ERROR:'), 'Missing list of extensions.'); log(noExtensionsMessage); log(extensionsMessage); @@ -202,8 +216,10 @@ function parseExtensionFlags() { } if (argv.extensions || argv.extensions_from) { - log(green('Building extension(s):'), - cyan(getExtensionsToBuild().join(', '))); + log( + green('Building extension(s):'), + cyan(getExtensionsToBuild().join(', ')) + ); } else if (argv.noextensions) { log(green('Not building any AMP extensions.')); } else { @@ -261,7 +277,7 @@ function getExtensionsFromArg(examples) { */ function dedupe(arr) { const map = Object.create(null); - arr.forEach(item => map[item] = true); + arr.forEach(item => (map[item] = true)); return Object.keys(map); } @@ -277,26 +293,29 @@ function buildExtensions(options) { return Promise.resolve(); } - const extensionsToBuild = options.compileAll ? - [] : getExtensionsToBuild(); + const extensionsToBuild = options.compileAll ? [] : getExtensionsToBuild(); const results = []; for (const key in extensions) { - if (extensionsToBuild.length > 0 && - extensionsToBuild.indexOf(extensions[key].name) == -1) { + if ( + extensionsToBuild.length > 0 && + extensionsToBuild.indexOf(extensions[key].name) == -1 + ) { continue; } const e = extensions[key]; let o = Object.assign({}, options); o = Object.assign(o, e); - results.push(buildExtension( + results.push( + buildExtension( e.name, e.version, e.latestVersion, e.hasCss, o, e.extraGlobs - )); + ) + ); } return Promise.all(results); } @@ -322,7 +341,13 @@ function buildExtensions(options) { * @return {!Promise} */ function buildExtension( - name, version, latestVersion, hasCss, options, opt_extraGlobs) { + name, + version, + latestVersion, + hasCss, + options, + opt_extraGlobs +) { options = options || {}; options.extraGlobs = opt_extraGlobs; if (options.compileOnlyCss && !hasCss) { @@ -390,14 +415,19 @@ function buildExtensionCss(path, name, version, options) { fs.writeFileSync(cssName, css, 'utf-8'); } const promises = []; - const mainCssBinary = jsifyCssAsync(path + '/' + name + '.css') - .then(writeCssBinaries.bind(null, `${name}-${version}.css`)); + const mainCssBinary = jsifyCssAsync(path + '/' + name + '.css').then( + writeCssBinaries.bind(null, `${name}-${version}.css`) + ); if (Array.isArray(options.cssBinaries)) { - promises.push.apply(promises, options.cssBinaries.map(function(name) { - return jsifyCssAsync(`${path}/${name}.css`) - .then(css => writeCssBinaries(`${name}-${version}.css`, css)); - })); + promises.push.apply( + promises, + options.cssBinaries.map(function(name) { + return jsifyCssAsync(`${path}/${name}.css`).then(css => + writeCssBinaries(`${name}-${version}.css`, css) + ); + }) + ); } promises.push(mainCssBinary); return Promise.all(promises); @@ -417,18 +447,24 @@ function buildExtensionCss(path, name, version, options) { */ function buildExtensionJs(path, name, version, latestVersion, options) { const filename = options.filename || name + '.js'; - return compileJs(path + '/', filename, './dist/v0', Object.assign(options, { - toName: `${name}-${version}.max.js`, - minifiedName: `${name}-${version}.js`, - latestName: version === latestVersion ? `${name}-latest.js` : '', - // Wrapper that either registers the extension or schedules it for - // execution after the main binary comes back. - // The `function` is wrapped in `()` to avoid lazy parsing it, - // since it will be immediately executed anyway. - // See https://github.com/ampproject/amphtml/issues/3977 - wrapper: options.noWrapper ? '' - : wrappers.extension(name, options.loadPriority), - })).then(() => { + return compileJs( + path + '/', + filename, + './dist/v0', + Object.assign(options, { + toName: `${name}-${version}.max.js`, + minifiedName: `${name}-${version}.js`, + latestName: version === latestVersion ? `${name}-latest.js` : '', + // Wrapper that either registers the extension or schedules it for + // execution after the main binary comes back. + // The `function` is wrapped in `()` to avoid lazy parsing it, + // since it will be immediately executed anyway. + // See https://github.com/ampproject/amphtml/issues/3977 + wrapper: options.noWrapper + ? '' + : wrappers.extension(name, options.loadPriority), + }) + ).then(() => { // Copy @ampproject/worker-dom/dist/worker.safe.js to the dist/ folder. if (name === 'amp-script') { // TODO(choumx): Compile this when worker-dom externs are available. diff --git a/build-system/tasks/firebase.js b/build-system/tasks/firebase.js index 015848f61c1d..f98b6fcea8b6 100644 --- a/build-system/tasks/firebase.js +++ b/build-system/tasks/firebase.js @@ -21,7 +21,6 @@ const path = require('path'); const {build} = require('./build'); const {dist} = require('./dist'); - async function walk(dest) { const filelist = []; const files = await fs.readdir(dest); @@ -29,9 +28,9 @@ async function walk(dest) { for (let i = 0; i < files.length; i++) { const file = `${dest}/${files[i]}`; - fs.statSync(file).isDirectory() ? - Array.prototype.push.apply(filelist, await walk(file)) : - filelist.push(file); + fs.statSync(file).isDirectory() + ? Array.prototype.push.apply(filelist, await walk(file)) + : filelist.push(file); } return filelist; @@ -41,8 +40,9 @@ async function copyAndReplaceUrls(src, dest) { await fs.copy(src, dest, {overwrite: true}); // Recursively gets all the files within the directory and its children. const files = await walk(dest); - const promises = files.filter(fileName => path.extname(fileName) == '.html') - .map(file => replaceUrls(file)); + const promises = files + .filter(fileName => path.extname(fileName) == '.html') + .map(file => replaceUrls(file)); await Promise.all(promises); } @@ -50,8 +50,9 @@ async function modifyThirdPartyUrl() { const filePath = 'firebase/dist/amp.js'; const data = await fs.readFile('firebase/dist/amp.js', 'utf8'); const result = data.replace( - 'self.AMP_CONFIG={', - 'self.AMP_CONFIG={"thirdPartyUrl":location.origin,'); + 'self.AMP_CONFIG={', + 'self.AMP_CONFIG={"thirdPartyUrl":location.origin,' + ); await fs.writeFile(filePath, result, 'utf8'); } @@ -67,8 +68,9 @@ async function firebase() { if (argv.file) { log(colors.green(`Processing file: ${argv.file}.`)); log(colors.green('Writing file to firebase.index.html.')); - await fs.copyFile(/*src*/ argv.file, 'firebase/index.html', - {overwrite: true}); + await fs.copyFile(/*src*/ argv.file, 'firebase/index.html', { + overwrite: true, + }); await replaceUrls('firebase/index.html'); } else { await Promise.all([ @@ -83,18 +85,28 @@ async function firebase() { ]); await Promise.all([ modifyThirdPartyUrl(), - fs.copyFile('firebase/dist/ww.max.js', 'firebase/dist/ww.js', - {overwrite: true}), + fs.copyFile('firebase/dist/ww.max.js', 'firebase/dist/ww.js', { + overwrite: true, + }), ]); } async function replaceUrls(filePath) { const data = await fs.readFile(filePath, 'utf8'); - let result = data.replace(/https:\/\/cdn\.ampproject\.org\/v0\.js/g, '/dist/amp.js'); + let result = data.replace( + /https:\/\/cdn\.ampproject\.org\/v0\.js/g, + '/dist/amp.js' + ); if (argv.min) { - result = result.replace(/https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, '/dist/v0/$1.js'); + result = result.replace( + /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, + '/dist/v0/$1.js' + ); } else { - result = result.replace(/https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, '/dist/v0/$1.max.js'); + result = result.replace( + /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, + '/dist/v0/$1.max.js' + ); } await fs.writeFile(filePath, result, 'utf8'); } diff --git a/build-system/tasks/get-zindex/index.js b/build-system/tasks/get-zindex/index.js index 8d47d7c7d14a..32abd0e42f2f 100644 --- a/build-system/tasks/get-zindex/index.js +++ b/build-system/tasks/get-zindex/index.js @@ -15,7 +15,6 @@ */ 'use strict'; - const fs = require('fs'); const gulp = require('gulp'); const PluginError = require('plugin-error'); @@ -23,17 +22,13 @@ const postcss = require('postcss'); const table = require('text-table'); const through = require('through2'); -const tableHeaders = [ - ['selector', 'z-index', 'file'], - ['---', '---', '---'], -]; +const tableHeaders = [['selector', 'z-index', 'file'], ['---', '---', '---']]; const tableOptions = { align: ['l', 'l', 'l'], hsep: ' | ', }; - /** * @param {!Object} acc accumulator object for selectors * @param {!Rules} css post css rules object @@ -74,11 +69,12 @@ function onFileThrough(file, enc, cb) { const selectors = Object.create(null); postcss([zIndexCollector.bind(null, selectors)]) - .process(file.contents.toString(), { - from: file.relative, - }).then(() => { - cb(null, {name: file.relative, selectors}); - }); + .process(file.contents.toString(), { + from: file.relative, + }) + .then(() => { + cb(null, {name: file.relative, selectors}); + }); } /** @@ -88,14 +84,18 @@ function onFileThrough(file, enc, cb) { */ function createTable(filesData) { const rows = []; - Object.keys(filesData).sort().forEach(fileName => { - const selectors = filesData[fileName]; - Object.keys(selectors).sort().forEach(selectorName => { - const zIndex = selectors[selectorName]; - const row = [selectorName, zIndex, fileName]; - rows.push(row); + Object.keys(filesData) + .sort() + .forEach(fileName => { + const selectors = filesData[fileName]; + Object.keys(selectors) + .sort() + .forEach(selectorName => { + const zIndex = selectors[selectorName]; + const row = [selectorName, zIndex, fileName]; + rows.push(row); + }); }); - }); rows.sort((a, b) => { const aZIndex = parseInt(a[1], 10); const bZIndex = parseInt(b[1], 10); @@ -104,7 +104,6 @@ function createTable(filesData) { return rows; } - /** * @param {string} glob * @return {!Stream} @@ -120,16 +119,16 @@ function getZindex(cb) { const filesData = Object.create(null); // Don't return the stream here since we do a `writeFileSync` getZindexStream('{css,src,extensions}/**/*.css') - .on('data', chunk => { - filesData[chunk.name] = chunk.selectors; - }) - .on('end', () => { - const rows = createTable(filesData); - rows.unshift.apply(rows, tableHeaders); - const tbl = table(rows, tableOptions); - fs.writeFileSync('css/Z_INDEX.md', tbl); - cb(); - }); + .on('data', chunk => { + filesData[chunk.name] = chunk.selectors; + }) + .on('end', () => { + const rows = createTable(filesData); + rows.unshift.apply(rows, tableHeaders); + const tbl = table(rows, tableOptions); + fs.writeFileSync('css/Z_INDEX.md', tbl); + cb(); + }); } module.exports = { @@ -139,4 +138,4 @@ module.exports = { }; getZindex.description = - 'Runs through all css files of project to gather z-index values'; + 'Runs through all css files of project to gather z-index values'; diff --git a/build-system/tasks/get-zindex/test.js b/build-system/tasks/get-zindex/test.js index a009a4d116ea..18453c35fa17 100644 --- a/build-system/tasks/get-zindex/test.js +++ b/build-system/tasks/get-zindex/test.js @@ -15,7 +15,6 @@ */ 'use strict'; - const m = require('./'); const test = require('ava'); @@ -34,13 +33,13 @@ test.cb('collects selectors', t => { const data = Object.create(null); const testFiles = `${__dirname}/*.css`; m.getZindexStream(testFiles) - .on('data', chunk => { - data[chunk.name] = chunk.selectors; - }) - .on('end', () => { - t.deepEqual(data, result); - t.end(); - }); + .on('data', chunk => { + data[chunk.name] = chunk.selectors; + }) + .on('end', () => { + t.deepEqual(data, result); + t.end(); + }); }); test('sync - create array of arrays with z index order', t => { diff --git a/build-system/tasks/helpers.js b/build-system/tasks/helpers.js index c17a9a1d08da..80c460b52b5c 100644 --- a/build-system/tasks/helpers.js +++ b/build-system/tasks/helpers.js @@ -39,7 +39,7 @@ const {closureCompile} = require('../compile/compile'); const {isTravisBuild} = require('../travis'); const {thirdPartyFrames} = require('../config'); const {transpileTs} = require('../typescript'); -const {VERSION: internalRuntimeVersion} = require('../internal-version') ; +const {VERSION: internalRuntimeVersion} = require('../internal-version'); const {green, red, cyan} = colors; const argv = require('minimist')(process.argv.slice(2)); @@ -57,9 +57,7 @@ const EXTENSION_BUNDLE_MAP = { 'third_party/d3-geo-projection/d3-geo-projection.js', 'third_party/vega/vega.js', ], - 'amp-inputmask.js': [ - 'third_party/inputmask/bundle.js', - ], + 'amp-inputmask.js': ['third_party/inputmask/bundle.js'], }; const UNMINIFIED_TARGETS = [ @@ -108,157 +106,178 @@ function compileAllUnminifiedTargets(watch) { */ function compile(watch, shouldMinify) { const promises = [ - compileJs('./3p/', 'integration.js', - './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), { - minifiedName: 'f.js', - watch, - minify: shouldMinify, - externs: ['./ads/ads.extern.js'], - include3pDirectories: true, - includePolyfills: true, - }), - compileJs('./3p/', 'ampcontext-lib.js', - './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), { - minifiedName: 'ampcontext-v0.js', - watch, - minify: shouldMinify, - externs: ['./ads/ads.extern.js'], - include3pDirectories: true, - includePolyfills: false, - }), - compileJs('./3p/', 'iframe-transport-client-lib.js', - './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), { - minifiedName: 'iframe-transport-client-v0.js', - watch, - minify: shouldMinify, - externs: ['./ads/ads.extern.js'], - include3pDirectories: true, - includePolyfills: false, - }), - compileJs('./3p/', 'recaptcha.js', - './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), { - minifiedName: 'recaptcha.js', - watch, - minify: shouldMinify, - externs: [], - include3pDirectories: true, - includePolyfills: true, - }), - compileJs('./extensions/amp-viewer-integration/0.1/examples/', - 'amp-viewer-host.js', './dist/v0/examples', { - toName: 'amp-viewer-host.max.js', - minifiedName: 'amp-viewer-host.js', - incudePolyfills: true, - watch, - extraGlobs: ['extensions/amp-viewer-integration/**/*.js'], - compilationLevel: 'WHITESPACE_ONLY', - minify: false, - }), + compileJs( + './3p/', + 'integration.js', + './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), + { + minifiedName: 'f.js', + watch, + minify: shouldMinify, + externs: ['./ads/ads.extern.js'], + include3pDirectories: true, + includePolyfills: true, + } + ), + compileJs( + './3p/', + 'ampcontext-lib.js', + './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), + { + minifiedName: 'ampcontext-v0.js', + watch, + minify: shouldMinify, + externs: ['./ads/ads.extern.js'], + include3pDirectories: true, + includePolyfills: false, + } + ), + compileJs( + './3p/', + 'iframe-transport-client-lib.js', + './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), + { + minifiedName: 'iframe-transport-client-v0.js', + watch, + minify: shouldMinify, + externs: ['./ads/ads.extern.js'], + include3pDirectories: true, + includePolyfills: false, + } + ), + compileJs( + './3p/', + 'recaptcha.js', + './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), + { + minifiedName: 'recaptcha.js', + watch, + minify: shouldMinify, + externs: [], + include3pDirectories: true, + includePolyfills: true, + } + ), + compileJs( + './extensions/amp-viewer-integration/0.1/examples/', + 'amp-viewer-host.js', + './dist/v0/examples', + { + toName: 'amp-viewer-host.max.js', + minifiedName: 'amp-viewer-host.js', + incudePolyfills: true, + watch, + extraGlobs: ['extensions/amp-viewer-integration/**/*.js'], + compilationLevel: 'WHITESPACE_ONLY', + minify: false, + } + ), ]; if (!argv.single_pass && (!watch || argv.with_shadow)) { promises.push( - compileJs('./src/', 'amp-shadow.js', './dist', { - minifiedName: 'shadow-v0.js', - includePolyfills: true, - watch, - minify: shouldMinify, - }) + compileJs('./src/', 'amp-shadow.js', './dist', { + minifiedName: 'shadow-v0.js', + includePolyfills: true, + watch, + minify: shouldMinify, + }) ); } if (!watch || argv.with_video_iframe_integration) { promises.push( - compileJs('./src/', 'video-iframe-integration.js', './dist', { - minifiedName: 'video-iframe-integration-v0.js', - includePolyfills: false, - watch, - minify: shouldMinify, - })); + compileJs('./src/', 'video-iframe-integration.js', './dist', { + minifiedName: 'video-iframe-integration-v0.js', + includePolyfills: false, + watch, + minify: shouldMinify, + }) + ); } if (!watch || argv.with_inabox) { if (!argv.single_pass) { promises.push( - // Entry point for inabox runtime. - compileJs('./src/inabox/', 'amp-inabox.js', './dist', { - toName: 'amp-inabox.js', - minifiedName: 'amp4ads-v0.js', - includePolyfills: true, - extraGlobs: ['src/inabox/*.js', '3p/iframe-messaging-client.js'], - watch, - minify: shouldMinify, - })); - } - promises.push( - // inabox-host - compileJs('./ads/inabox/', 'inabox-host.js', './dist', { - toName: 'amp-inabox-host.js', - minifiedName: 'amp4ads-host-v0.js', - includePolyfills: false, + // Entry point for inabox runtime. + compileJs('./src/inabox/', 'amp-inabox.js', './dist', { + toName: 'amp-inabox.js', + minifiedName: 'amp4ads-v0.js', + includePolyfills: true, + extraGlobs: ['src/inabox/*.js', '3p/iframe-messaging-client.js'], watch, minify: shouldMinify, }) + ); + } + promises.push( + // inabox-host + compileJs('./ads/inabox/', 'inabox-host.js', './dist', { + toName: 'amp-inabox-host.js', + minifiedName: 'amp4ads-host-v0.js', + includePolyfills: false, + watch, + minify: shouldMinify, + }) ); } if (argv.with_inabox_lite) { promises.push( - // Entry point for inabox runtime. - compileJs('./src/inabox/', 'amp-inabox-lite.js', './dist', { - toName: 'amp-inabox-lite.js', - minifiedName: 'amp4ads-lite-v0.js', - includePolyfills: true, - extraGlobs: ['src/inabox/*.js', '3p/iframe-messaging-client.js'], - watch, - minify: shouldMinify, - })); + // Entry point for inabox runtime. + compileJs('./src/inabox/', 'amp-inabox-lite.js', './dist', { + toName: 'amp-inabox-lite.js', + minifiedName: 'amp4ads-lite-v0.js', + includePolyfills: true, + extraGlobs: ['src/inabox/*.js', '3p/iframe-messaging-client.js'], + watch, + minify: shouldMinify, + }) + ); } thirdPartyFrames.forEach(frameObject => { promises.push( - thirdPartyBootstrap( - frameObject.max, frameObject.min, shouldMinify) + thirdPartyBootstrap(frameObject.max, frameObject.min, shouldMinify) ); }); if (watch) { thirdPartyFrames.forEach(frameObject => { gulpWatch(frameObject.max, function() { - thirdPartyBootstrap( - frameObject.max, frameObject.min, shouldMinify); + thirdPartyBootstrap(frameObject.max, frameObject.min, shouldMinify); }); }); } return Promise.all(promises) - .then(() => { + .then(() => { + return compileJs('./src/', 'amp.js', './dist', { + toName: 'amp.js', + minifiedName: 'v0.js', + includePolyfills: true, + watch, + minify: shouldMinify, + wrapper: wrappers.mainBinary, + singlePassCompilation: argv.single_pass, + esmPassCompilation: argv.esm, + }); + }) + .then(() => { + if (!argv.single_pass) { return compileJs('./src/', 'amp.js', './dist', { - toName: 'amp.js', - minifiedName: 'v0.js', + toName: 'amp-esm.js', + minifiedName: 'v0-esm.js', includePolyfills: true, + includeOnlyESMLevelPolyfills: true, watch, minify: shouldMinify, wrapper: wrappers.mainBinary, - singlePassCompilation: argv.single_pass, - esmPassCompilation: argv.esm, }); - }) - .then(() => { - if (!argv.single_pass) { - return compileJs('./src/', 'amp.js', './dist', { - toName: 'amp-esm.js', - minifiedName: 'v0-esm.js', - includePolyfills: true, - includeOnlyESMLevelPolyfills: true, - watch, - minify: shouldMinify, - wrapper: wrappers.mainBinary, - }); - } else { - return Promise.resolve(); - } - }); + } else { + return Promise.resolve(); + } + }); } /** @@ -279,10 +298,14 @@ function appendToCompiledFile(srcFilename, destFilePath) { const firstLineBreak = file.indexOf('\n'); const wrapperOpen = file.substr(0, firstLineBreak + 1); const reactDates = fs.readFileSync( - 'third_party/react-dates/bundle.js', 'utf8'); + 'third_party/react-dates/bundle.js', + 'utf8' + ); // Inject the bundle inside the standard AMP wrapper (after the first line). const newSource = [ - wrapperOpen, reactDates, file.substr(firstLineBreak + 1), + wrapperOpen, + reactDates, + file.substr(firstLineBreak + 1), ].join('\n'); fs.writeFileSync(destFilePath, newSource, 'utf8'); } @@ -301,44 +324,44 @@ function compileMinifiedJs(srcDir, srcFilename, destDir, options) { const entryPoint = path.join(srcDir, srcFilename); const {minifiedName} = options; return closureCompile(entryPoint, destDir, minifiedName, options) - .then(function() { - const destPath = path.join(destDir, minifiedName); - appendToCompiledFile(srcFilename, destPath); - fs.writeFileSync( - path.join(destDir, 'version.txt'), internalRuntimeVersion); - if (options.latestName) { - fs.copySync( - destPath, - path.join(destDir, options.latestName)); - } - }) - .then(() => { - let name = minifiedName; - if (options.latestName) { - name += ` → ${options.latestName}`; - } - if (options.singlePassCompilation) { - altMainBundles.forEach(bundle => { - name += `, ${bundle.name}.js`; - }); - name += ', and all extensions'; - } - endBuildStep('Minified', name, startTime); - }) - .then(() => { - if (argv.fortesting && MINIFIED_TARGETS.includes(minifiedName)) { - return enableLocalTesting(`${destDir}/${minifiedName}`); - } - }) - .then(() => { - if (argv.fortesting && options.singlePassCompilation) { - const promises = []; - altMainBundles.forEach(bundle => { - promises.push(enableLocalTesting(`dist/${bundle.name}.js`)); - }); - return Promise.all(promises); - } - }); + .then(function() { + const destPath = path.join(destDir, minifiedName); + appendToCompiledFile(srcFilename, destPath); + fs.writeFileSync( + path.join(destDir, 'version.txt'), + internalRuntimeVersion + ); + if (options.latestName) { + fs.copySync(destPath, path.join(destDir, options.latestName)); + } + }) + .then(() => { + let name = minifiedName; + if (options.latestName) { + name += ` → ${options.latestName}`; + } + if (options.singlePassCompilation) { + altMainBundles.forEach(bundle => { + name += `, ${bundle.name}.js`; + }); + name += ', and all extensions'; + } + endBuildStep('Minified', name, startTime); + }) + .then(() => { + if (argv.fortesting && MINIFIED_TARGETS.includes(minifiedName)) { + return enableLocalTesting(`${destDir}/${minifiedName}`); + } + }) + .then(() => { + if (argv.fortesting && options.singlePassCompilation) { + const promises = []; + altMainBundles.forEach(bundle => { + promises.push(enableLocalTesting(`dist/${bundle.name}.js`)); + }); + return Promise.all(promises); + } + }); } /** @@ -370,17 +393,16 @@ function handleBundleError(err, failOnError, srcFilename, startTime) { * @param {?Object} options */ function finishBundle(srcFilename, destDir, destFilename, options) { - appendToCompiledFile(srcFilename, - path.join(destDir, destFilename)); + appendToCompiledFile(srcFilename, path.join(destDir, destFilename)); if (options.latestName) { // "amp-foo-latest.js" -> "amp-foo-latest.max.js" - const latestMaxName = - options.latestName.split('.js')[0] + '.max.js'; + const latestMaxName = options.latestName.split('.js')[0] + '.max.js'; // Copy amp-foo-0.1.js to amp-foo-latest.max.js. fs.copySync( - path.join(destDir, options.toName), - path.join(destDir, latestMaxName)); + path.join(destDir, options.toName), + path.join(destDir, latestMaxName) + ); } } @@ -407,15 +429,37 @@ function compileUnminifiedJs(srcDir, srcFilename, destDir, options) { const devWrapper = wrapper.replace('<%= contents %>', '$1'); const lazybuild = lazypipe() - .pipe(source, srcFilename) - .pipe(buffer) - .pipe(sourcemaps.init.bind(sourcemaps), {loadMaps: true}) - .pipe(regexpSourcemaps, /\$internalRuntimeVersion\$/g, internalRuntimeVersion, 'runtime-version') - .pipe(regexpSourcemaps, /([^]+)/, devWrapper, 'wrapper'); + .pipe( + source, + srcFilename + ) + .pipe(buffer) + .pipe( + sourcemaps.init.bind(sourcemaps), + {loadMaps: true} + ) + .pipe( + regexpSourcemaps, + /\$internalRuntimeVersion\$/g, + internalRuntimeVersion, + 'runtime-version' + ) + .pipe( + regexpSourcemaps, + /([^]+)/, + devWrapper, + 'wrapper' + ); const lazywrite = lazypipe() - .pipe(sourcemaps.write.bind(sourcemaps), './') - .pipe(gulp.dest.bind(gulp), destDir); + .pipe( + sourcemaps.write.bind(sourcemaps), + './' + ) + .pipe( + gulp.dest.bind(gulp), + destDir + ); const destFilename = options.toName || srcFilename; /** @@ -425,28 +469,31 @@ function compileUnminifiedJs(srcDir, srcFilename, destDir, options) { function rebundle(failOnError) { const startTime = Date.now(); return toPromise( - bundler.bundle() - .on('error', err => handleBundleError( - err, failOnError, srcFilename, startTime)) - .pipe(lazybuild()) - .pipe(rename(destFilename)) - .pipe(lazywrite()) - .on('end', () => finishBundle( - srcFilename, destDir, destFilename, options))) - .then(() => { - let name = destFilename; - if (options.latestName) { - const latestMaxName = - options.latestName.split('.js')[0] + '.max.js'; - name = `${name} → ${latestMaxName}`; - } - endBuildStep('Compiled', name, startTime); - }) - .then(() => { - if (UNMINIFIED_TARGETS.includes(destFilename)) { - return enableLocalTesting(`${destDir}/${destFilename}`); - } - }); + bundler + .bundle() + .on('error', err => + handleBundleError(err, failOnError, srcFilename, startTime) + ) + .pipe(lazybuild()) + .pipe(rename(destFilename)) + .pipe(lazywrite()) + .on('end', () => + finishBundle(srcFilename, destDir, destFilename, options) + ) + ) + .then(() => { + let name = destFilename; + if (options.latestName) { + const latestMaxName = options.latestName.split('.js')[0] + '.max.js'; + name = `${name} → ${latestMaxName}`; + } + endBuildStep('Compiled', name, startTime); + }) + .then(() => { + if (UNMINIFIED_TARGETS.includes(destFilename)) { + return enableLocalTesting(`${destDir}/${destFilename}`); + } + }); } if (options.watch) { @@ -536,12 +583,20 @@ function endBuildStep(stepName, targetName, startTime) { */ function printConfigHelp(command) { if (!isTravisBuild()) { - log(green('Building version'), cyan(internalRuntimeVersion), - green('of the runtime for local testing with the'), - cyan((argv.config === 'canary') ? 'canary' : 'prod'), - green('AMP config.')); - log(green('⤷ Use'), cyan('--config={canary|prod}'), green('with your'), - cyan(command), green('command to specify which config to apply.')); + log( + green('Building version'), + cyan(internalRuntimeVersion), + green('of the runtime for local testing with the'), + cyan(argv.config === 'canary' ? 'canary' : 'prod'), + green('AMP config.') + ); + log( + green('⤷ Use'), + cyan('--config={canary|prod}'), + green('with your'), + cyan(command), + green('command to specify which config to apply.') + ); } } @@ -550,11 +605,18 @@ function printConfigHelp(command) { */ function printNobuildHelp() { if (!isTravisBuild()) { - for (const task of NOBUILD_HELP_TASKS) { // eslint-disable-line amphtml-internal/no-for-of-statement + for (const task of NOBUILD_HELP_TASKS) { + // eslint-disable-line amphtml-internal/no-for-of-statement if (argv._.includes(task)) { - log(green('To skip building during future'), cyan(task), - green('runs, use'), cyan('--nobuild'), green('with your'), - cyan(`gulp ${task}`), green('command.')); + log( + green('To skip building during future'), + cyan(task), + green('runs, use'), + cyan('--nobuild'), + green('with your'), + cyan(`gulp ${task}`), + green('command.') + ); return; } } @@ -567,15 +629,20 @@ function printNobuildHelp() { * @param {string} targetFile File to which the config is to be written. */ async function enableLocalTesting(targetFile) { - const config = (argv.config === 'canary') ? 'canary' : 'prod'; + const config = argv.config === 'canary' ? 'canary' : 'prod'; const baseConfigFile = - 'build-system/global-configs/' + config + '-config.json'; + 'build-system/global-configs/' + config + '-config.json'; return removeConfig(targetFile).then(() => { return applyConfig( - config, targetFile, baseConfigFile, - /* opt_localDev */ true, /* opt_localBranch */ true, - /* opt_branch */ false, /* opt_fortesting */ !!argv.fortesting); + config, + targetFile, + baseConfigFile, + /* opt_localDev */ true, + /* opt_localBranch */ true, + /* opt_branch */ false, + /* opt_fortesting */ !!argv.fortesting + ); }); } @@ -586,9 +653,11 @@ async function enableLocalTesting(targetFile) { * @return {string} The concatenated contents of the given files. */ function concatFilesToString(files) { - return files.map(function(filePath) { - return fs.readFileSync(filePath, 'utf8'); - }).join(MODULE_SEPARATOR); + return files + .map(function(filePath) { + return fs.readFileSync(filePath, 'utf8'); + }) + .join(MODULE_SEPARATOR); } /** @@ -603,11 +672,11 @@ function concatFilesToString(files) { function thirdPartyBootstrap(input, outputName, shouldMinify) { const startTime = Date.now(); if (!shouldMinify) { - return toPromise(gulp.src(input) - .pipe(gulp.dest('dist.3p/current'))) - .then(() => { - endBuildStep('Processed', input, startTime); - }); + return toPromise(gulp.src(input).pipe(gulp.dest('dist.3p/current'))).then( + () => { + endBuildStep('Processed', input, startTime); + } + ); } // By default we use an absolute URL, that is independent of the @@ -618,9 +687,11 @@ function thirdPartyBootstrap(input, outputName, shouldMinify) { ? './f.js' : `https://${hostname3p}/${internalRuntimeVersion}/f.js`; // Convert default relative URL to absolute min URL. - const html = fs.readFileSync(input, 'utf8') - .replace(/\.\/integration\.js/g, integrationJs); - return toPromise(file(outputName, html, {src: true}) + const html = fs + .readFileSync(input, 'utf8') + .replace(/\.\/integration\.js/g, integrationJs); + return toPromise( + file(outputName, html, {src: true}) .pipe(gulp.dest('dist.3p/' + internalRuntimeVersion)) .on('end', function() { const aliasToLatestBuild = 'dist.3p/current-min'; @@ -628,13 +699,14 @@ function thirdPartyBootstrap(input, outputName, shouldMinify) { fs.unlinkSync(aliasToLatestBuild); } fs.symlinkSync( - './' + internalRuntimeVersion, - aliasToLatestBuild, - 'dir'); - })) - .then(() => { - endBuildStep('Processed', input, startTime); - }); + './' + internalRuntimeVersion, + aliasToLatestBuild, + 'dir' + ); + }) + ).then(() => { + endBuildStep('Processed', input, startTime); + }); } /** @@ -666,28 +738,37 @@ async function buildExperiments(options) { // Build HTML. const html = fs.readFileSync(htmlPath, 'utf8'); - const minHtml = html.replace('/dist.tools/experiments/experiments.js', - `https://${hostname}/v0/experiments.js`); - gulp.src(htmlPath) - .pipe(file('experiments.cdn.html', minHtml)) - .pipe(gulp.dest('dist.tools/experiments/')); + const minHtml = html.replace( + '/dist.tools/experiments/experiments.js', + `https://${hostname}/v0/experiments.js` + ); + gulp + .src(htmlPath) + .pipe(file('experiments.cdn.html', minHtml)) + .pipe(gulp.dest('dist.tools/experiments/')); // Build JS. const js = fs.readFileSync(jsPath, 'utf8'); const builtName = 'experiments.max.js'; const minifiedName = 'experiments.js'; - return toPromise(gulp.src(path + '/*.js') + return toPromise( + gulp + .src(path + '/*.js') .pipe(file(builtName, js)) - .pipe(gulp.dest('build/experiments/'))) - .then(function() { - return compileJs( - './build/experiments/', builtName, './dist.tools/experiments/', { - watch: false, - minify: options.minify || argv.minify, - includePolyfills: true, - minifiedName, - }); - }); + .pipe(gulp.dest('build/experiments/')) + ).then(function() { + return compileJs( + './build/experiments/', + builtName, + './dist.tools/experiments/', + { + watch: false, + minify: options.minify || argv.minify, + includePolyfills: true, + minifiedName, + } + ); + }); } /** diff --git a/build-system/tasks/jsify-css.js b/build-system/tasks/jsify-css.js index 4cfe6ec7f1e7..ddcf5ced1ae4 100644 --- a/build-system/tasks/jsify-css.js +++ b/build-system/tasks/jsify-css.js @@ -15,7 +15,6 @@ */ 'use strict'; - const autoprefixer = require('autoprefixer'); const colors = require('ansi-colors'); const cssnano = require('cssnano'); @@ -65,8 +64,11 @@ function transformCss(filename, opt_cssnano) { opt_cssnano = opt_cssnano || Object.create(null); // See http://cssnano.co/optimisations/ for full list. // We try and turn off any optimization that is marked unsafe. - const cssnanoOptions = Object.assign(Object.create(null), - cssNanoDefaultOptions, opt_cssnano); + const cssnanoOptions = Object.assign( + Object.create(null), + cssNanoDefaultOptions, + opt_cssnano + ); const cssnanoTransformer = cssnano({preset: ['default', cssnanoOptions]}); const css = fs.readFileSync(filename, 'utf8'); diff --git a/build-system/tasks/json-check.js b/build-system/tasks/json-check.js index 229e1a84a214..7bbf2d11c7ec 100644 --- a/build-system/tasks/json-check.js +++ b/build-system/tasks/json-check.js @@ -27,29 +27,35 @@ const expectedCaches = ['cloudflare', 'google']; * Fail if caches.json is missing some expected caches. */ async function cachesJson() { - return gulp.src(['caches.json']) - .pipe(through2.obj(function(file) { - let obj; - try { - obj = JSON.parse(file.contents.toString()); - } catch (e) { - log(colors.yellow('Could not parse caches.json. ' - + 'This is most likely a fatal error that ' - + 'will be found by checkValidJson')); - return; - } - const foundCaches = []; - for (const foundCache of obj.caches) { - foundCaches.push(foundCache.id); + return gulp.src(['caches.json']).pipe( + through2.obj(function(file) { + let obj; + try { + obj = JSON.parse(file.contents.toString()); + } catch (e) { + log( + colors.yellow( + 'Could not parse caches.json. ' + + 'This is most likely a fatal error that ' + + 'will be found by checkValidJson' + ) + ); + return; + } + const foundCaches = []; + for (const foundCache of obj.caches) { + foundCaches.push(foundCache.id); + } + for (const cache of expectedCaches) { + if (!foundCaches.includes(cache)) { + log( + colors.red('Missing expected cache "' + cache + '" in caches.json') + ); + process.exitCode = 1; } - for (const cache of expectedCaches) { - if (!foundCaches.includes(cache)) { - log(colors.red('Missing expected cache "' - + cache + '" in caches.json')); - process.exitCode = 1; - } - } - })); + } + }) + ); } /** @@ -57,21 +63,25 @@ async function cachesJson() { */ async function jsonSyntax() { let hasError = false; - return gulp.src(jsonGlobs) - .pipe(through2.obj(function(file) { + return gulp + .src(jsonGlobs) + .pipe( + through2.obj(function(file) { try { JSON.parse(file.contents.toString()); } catch (e) { - log(colors.red('Invalid JSON in ' - + file.relative + ': ' + e.message)); + log( + colors.red('Invalid JSON in ' + file.relative + ': ' + e.message) + ); hasError = true; } - })) - .on('end', function() { - if (hasError) { - process.exit(1); - } - }); + }) + ) + .on('end', function() { + if (hasError) { + process.exit(1); + } + }); } module.exports = { diff --git a/build-system/tasks/lint.js b/build-system/tasks/lint.js index d0922945a229..af0e4675e2fb 100644 --- a/build-system/tasks/lint.js +++ b/build-system/tasks/lint.js @@ -15,7 +15,6 @@ */ 'use strict'; - const argv = require('minimist')(process.argv.slice(2)); const colors = require('ansi-colors'); const config = require('../config'); @@ -31,7 +30,7 @@ const {gitDiffNameOnlyMaster} = require('../git'); const {isTravisBuild} = require('../travis'); const {maybeUpdatePackages} = require('./update-packages'); -const isWatching = (argv.watch || argv.w) || false; +const isWatching = argv.watch || argv.w || false; const options = { fix: false, quiet: argv.quiet || false, @@ -48,7 +47,10 @@ const rootDir = path.dirname(path.dirname(__dirname)); function initializeStream(globs, streamOptions) { let stream = gulp.src(globs, streamOptions); if (isWatching) { - const watcher = lazypipe().pipe(watch, globs); + const watcher = lazypipe().pipe( + watch, + globs + ); stream = stream.pipe(watcher()); } return stream; @@ -79,44 +81,73 @@ function runLinter(filePath, stream, options) { log(colors.green('Starting linter...')); } const fixedFiles = {}; - return stream.pipe(eslint(options)) - .pipe(eslint.formatEach('stylish', function(msg) { + return stream + .pipe(eslint(options)) + .pipe( + eslint.formatEach('stylish', function(msg) { logOnSameLine(msg.trim() + '\n'); - })) - .pipe(eslintIfFixed(filePath)) - .pipe(eslint.result(function(result) { + }) + ) + .pipe(eslintIfFixed(filePath)) + .pipe( + eslint.result(function(result) { if (!isTravisBuild()) { logOnSameLine(colors.green('Linted: ') + result.filePath); } if (options.fix && result.fixed) { const relativePath = path.relative(rootDir, result.filePath); - const status = result.errorCount == 0 ? - colors.green('Fixed: ') : colors.yellow('Partially fixed: '); + const status = + result.errorCount == 0 + ? colors.green('Fixed: ') + : colors.yellow('Partially fixed: '); logOnSameLine(status + colors.cyan(relativePath)); fixedFiles[relativePath] = status; } - })) - .pipe(eslint.results(function(results) { + }) + ) + .pipe( + eslint.results(function(results) { if (results.errorCount == 0 && results.warningCount == 0) { if (!isTravisBuild()) { - logOnSameLine(colors.green('SUCCESS: ') + - 'No linter warnings or errors.'); + logOnSameLine( + colors.green('SUCCESS: ') + 'No linter warnings or errors.' + ); } } else { - const prefix = results.errorCount == 0 ? - colors.yellow('WARNING: ') : colors.red('ERROR: '); - logOnSameLine(prefix + 'Found ' + - results.errorCount + ' error(s) and ' + - results.warningCount + ' warning(s).'); + const prefix = + results.errorCount == 0 + ? colors.yellow('WARNING: ') + : colors.red('ERROR: '); + logOnSameLine( + prefix + + 'Found ' + + results.errorCount + + ' error(s) and ' + + results.warningCount + + ' warning(s).' + ); if (!options.fix) { - log(colors.yellow('NOTE 1:'), - 'You may be able to automatically fix some of these warnings ' + + log( + colors.yellow('NOTE 1:'), + 'You may be able to automatically fix some of these warnings ' + '/ errors by running', - colors.cyan('gulp lint --local-changes --fix'), - 'from your local branch.'); - log(colors.yellow('NOTE 2:'), - 'Since this is a destructive operation (that edits your files', - 'in-place), make sure you commit before running the command.'); + colors.cyan('gulp lint --local-changes --fix'), + 'from your local branch.' + ); + log( + colors.yellow('NOTE 2:'), + 'Since this is a destructive operation (that edits your files', + 'in-place), make sure you commit before running the command.' + ); + log( + colors.yellow('NOTE 3:'), + 'If you see any', + colors.cyan('prettier/prettier'), + 'errors, read', + colors.cyan( + 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-e2e.md#code-quality-and-style' + ) + ); } } if (options.fix && Object.keys(fixedFiles).length > 0) { @@ -125,8 +156,9 @@ function runLinter(filePath, stream, options) { log(fixedFiles[file] + colors.cyan(file)); }); } - })) - .pipe(eslint.failAfterError()); + }) + ) + .pipe(eslint.failAfterError()); } /** @@ -147,10 +179,14 @@ function jsFilesChanged() { * @return {boolean} */ function eslintRulesChanged() { - return gitDiffNameOnlyMaster().filter(function(file) { - return path.basename(file).includes('.eslintrc') || - path.dirname(file) === 'build-system/eslint-rules'; - }).length > 0; + return ( + gitDiffNameOnlyMaster().filter(function(file) { + return ( + path.basename(file).includes('.eslintrc') || + path.dirname(file) === 'build-system/eslint-rules' + ); + }).length > 0 + ); } /** @@ -159,8 +195,9 @@ function eslintRulesChanged() { * @param {!Array} files */ function setFilesToLint(files) { - config.lintGlobs = - config.lintGlobs.filter(e => e !== '**/*.js').concat(files); + config.lintGlobs = config.lintGlobs + .filter(e => e !== '**/*.js') + .concat(files); if (!isTravisBuild()) { log(colors.green('INFO: ') + 'Running lint on the following files:'); files.forEach(file => { @@ -180,8 +217,10 @@ function lint() { } if (argv.files) { setFilesToLint(argv.files.split(',')); - } else if (!eslintRulesChanged() && - (process.env.LOCAL_PR_CHECK || argv['local-changes'])) { + } else if ( + !eslintRulesChanged() && + (process.env.LOCAL_PR_CHECK || argv['local-changes']) + ) { const jsFiles = jsFilesChanged(); if (jsFiles.length == 0) { log(colors.green('INFO: ') + 'No JS files in this PR'); @@ -202,7 +241,6 @@ lint.description = 'Validates against Google Closure Linter'; lint.flags = { 'watch': ' Watches for changes in files, validates against the linter', 'fix': ' Fixes simple lint errors (spacing etc)', - 'local-changes': - ' Lints just the changes commited to the local branch', + 'local-changes': ' Lints just the changes commited to the local branch', 'quiet': ' Suppress warnings from outputting', }; diff --git a/build-system/tasks/mocha-ci-reporter.js b/build-system/tasks/mocha-ci-reporter.js index bc6e8ce122c3..fd5d65cd6453 100644 --- a/build-system/tasks/mocha-ci-reporter.js +++ b/build-system/tasks/mocha-ci-reporter.js @@ -48,8 +48,9 @@ function ciReporter(runner) { const {failures, stats} = self; Base.list(failures); process.stdout.write( - `Executed ${stats.failures + stats.passes} of ${stats.tests} ` + - `(Skipped ${stats.pending}) `); + `Executed ${stats.failures + stats.passes} of ${stats.tests} ` + + `(Skipped ${stats.pending}) ` + ); if (stats.failures == 0) { process.stdout.write(Base.color('green', 'SUCCESS \n')); } else { diff --git a/build-system/tasks/nailgun.js b/build-system/tasks/nailgun.js index 32045b40d794..a52f017e2622 100644 --- a/build-system/tasks/nailgun.js +++ b/build-system/tasks/nailgun.js @@ -24,10 +24,12 @@ const {isTravisBuild} = require('../travis'); // Used to start and stop the Closure nailgun server let nailgunRunnerReplacer; -const nailgunRunner = - require.resolve('../../third_party/nailgun/nailgun-runner'); -const nailgunServer = - require.resolve('../../third_party/nailgun/nailgun-server.jar'); +const nailgunRunner = require.resolve( + '../../third_party/nailgun/nailgun-runner' +); +const nailgunServer = require.resolve( + '../../third_party/nailgun/nailgun-server.jar' +); const customRunner = require.resolve('../runner/dist/runner.jar'); const DEFAULT_NAILGUN_PORT = '2113'; const CLOSURE_NAILGUN_PORT = '2114'; @@ -39,17 +41,28 @@ const NAILGUN_STARTUP_TIMEOUT_MS = 5 * 1000; function maybeReplaceDefaultCompiler() { if (process.platform == 'darwin') { return require('require-hijack') - .replace('google-closure-compiler-osx').with(nailgunRunner); + .replace('google-closure-compiler-osx') + .with(nailgunRunner); return true; } else if (process.platform == 'linux') { return require('require-hijack') - .replace('google-closure-compiler-linux').with(nailgunRunner); + .replace('google-closure-compiler-linux') + .with(nailgunRunner); } else { - log(yellow('WARNING:'), 'Cannot run', cyan('nailgun-server.jar'), - 'on', cyan(process.platform)); - log(yellow('WARNING:'), - 'Closure compiler will be significantly slower than on', - cyan('macos'), 'or', cyan('linux')); + log( + yellow('WARNING:'), + 'Cannot run', + cyan('nailgun-server.jar'), + 'on', + cyan(process.platform) + ); + log( + yellow('WARNING:'), + 'Closure compiler will be significantly slower than on', + cyan('macos'), + 'or', + cyan('linux') + ); return null; } } @@ -67,14 +80,13 @@ async function startNailgunServer(port, detached) { // Start up the nailgun server after cleaning up old instances (if any) const startNailgunServerCmd = - 'java -XX:+TieredCompilation -server -cp ' + - `${nailgunServer}:${customRunner} ` + - `com.facebook.nailgun.NGServer ${port}`; - const stopNailgunServerCmd = - `${nailgunRunner} --nailgun-port ${port} ng-stop`; + 'java -XX:+TieredCompilation -server -cp ' + + `${nailgunServer}:${customRunner} ` + + `com.facebook.nailgun.NGServer ${port}`; + const stopNailgunServerCmd = `${nailgunRunner} --nailgun-port ${port} ng-stop`; const getVersionCmd = - `${nailgunRunner} --nailgun-port ${port} ` + - 'org.ampproject.AmpCommandLineRunner -- --version'; + `${nailgunRunner} --nailgun-port ${port} ` + + 'org.ampproject.AmpCommandLineRunner -- --version'; exec(stopNailgunServerCmd, {stdio: 'pipe'}); const nailgunServerProcess = execScriptAsync(startNailgunServerCmd, { stdio: detached ? 'ignore' : 'pipe', @@ -99,8 +111,13 @@ async function startNailgunServer(port, detached) { await sleep(1000); } } - log(red('ERROR:'), 'Could not start', - cyan('nailgun-server.jar'), 'on port', cyan(port) + '...'); + log( + red('ERROR:'), + 'Could not start', + cyan('nailgun-server.jar'), + 'on port', + cyan(port) + '...' + ); process.exit(1); } @@ -114,16 +131,19 @@ async function stopNailgunServer(port) { nailgunRunnerReplacer.restore(); } if (process.platform == 'darwin' || process.platform == 'linux') { - const stopNailgunServerCmd = - `${nailgunRunner} --nailgun-port ${port} ng-stop`; + const stopNailgunServerCmd = `${nailgunRunner} --nailgun-port ${port} ng-stop`; if (exec(stopNailgunServerCmd, {stdio: 'pipe'}).status == 0) { if (!isTravisBuild()) { - log('Stopped', cyan('nailgun-server.jar'), 'on port', - cyan(port)); + log('Stopped', cyan('nailgun-server.jar'), 'on port', cyan(port)); } } else { - log(yellow('WARNING:'), 'Could not find a running instance of', - cyan('nailgun-server.jar'), 'on port', cyan(port)); + log( + yellow('WARNING:'), + 'Could not find a running instance of', + cyan('nailgun-server.jar'), + 'on port', + cyan(port) + ); } } } diff --git a/build-system/tasks/pr-check.js b/build-system/tasks/pr-check.js index be7350e7627c..b2c940526e1e 100644 --- a/build-system/tasks/pr-check.js +++ b/build-system/tasks/pr-check.js @@ -23,8 +23,8 @@ const { const {determineBuildTargets} = require('../pr-check/build-targets'); const FILENAME = 'pr-check.js'; -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); /** * This file runs tests against the local workspace to mimic the CI build as @@ -51,13 +51,11 @@ async function prCheck() { timedExecOrDie('gulp check-types'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('UNIT_TEST')) { + if (buildTargets.has('RUNTIME') || buildTargets.has('UNIT_TEST')) { timedExecOrDie('gulp test --unit --local-changes --headless'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('INTEGRATION_TEST')) { + if (buildTargets.has('RUNTIME') || buildTargets.has('INTEGRATION_TEST')) { if (!argv.nobuild) { timedExecOrDie('gulp clean'); timedExecOrDie('gulp dist --fortesting'); @@ -65,13 +63,11 @@ async function prCheck() { timedExecOrDie('gulp test --nobuild --integration --headless'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('VALIDATOR')) { + if (buildTargets.has('RUNTIME') || buildTargets.has('VALIDATOR')) { timedExecOrDie('gulp validator'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('VALIDATOR_WEBUI')) { + if (buildTargets.has('RUNTIME') || buildTargets.has('VALIDATOR_WEBUI')) { timedExecOrDie('gulp validator-webui'); } } @@ -81,7 +77,7 @@ module.exports = { }; prCheck.description = - 'Runs a subset of the Travis CI checks against local changes.'; + 'Runs a subset of the Travis CI checks against local changes.'; prCheck.flags = { 'nobuild': ' Skips building the runtime via `gulp dist`.', }; diff --git a/build-system/tasks/prepend-global/index.js b/build-system/tasks/prepend-global/index.js index 3e06a0a2dca8..6fce1dd937ad 100644 --- a/build-system/tasks/prepend-global/index.js +++ b/build-system/tasks/prepend-global/index.js @@ -48,7 +48,8 @@ function sanityCheck(str) { const numMatches = numConfigs(str); if (numMatches != 1) { throw new Error( - 'Found ' + numMatches + ' AMP_CONFIG(s) before write. Aborting!'); + 'Found ' + numMatches + ' AMP_CONFIG(s) before write. Aborting!' + ); } } @@ -64,15 +65,14 @@ function checkoutBranchConfigs(filename, opt_localBranch, opt_branch) { } const branch = opt_branch || 'origin/master'; // One bad path here will fail the whole operation. - return exec(`git checkout ${branch} ${filename}`) - .catch(function(e) { - // This means the files don't exist in master. Assume that it exists - // in the current branch. - if (/did not match any file/.test(e.message)) { - return; - } - throw e; - }); + return exec(`git checkout ${branch} ${filename}`).catch(function(e) { + // This means the files don't exist in master. Assume that it exists + // in the current branch. + if (/did not match any file/.test(e.message)) { + return; + } + throw e; + }); } /** @@ -81,8 +81,10 @@ function checkoutBranchConfigs(filename, opt_localBranch, opt_branch) { * @return {string} */ function prependConfig(configString, fileString) { - return `self.AMP_CONFIG||(self.AMP_CONFIG=${configString});` + - `/*AMP_CONFIG*/${fileString}`; + return ( + `self.AMP_CONFIG||(self.AMP_CONFIG=${configString});` + + `/*AMP_CONFIG*/${fileString}` + ); } /** @@ -123,45 +125,54 @@ function valueOrDefault(value, defaultValue) { * @return {!Promise} */ function applyConfig( - config, target, filename, opt_localDev, opt_localBranch, opt_branch, - opt_fortesting) { + config, + target, + filename, + opt_localDev, + opt_localBranch, + opt_branch, + opt_fortesting +) { return checkoutBranchConfigs(filename, opt_localBranch, opt_branch) - .then(() => { - return Promise.all([ - fs.readFileAsync(filename), - fs.readFileAsync(target), - ]); - }) - .then(files => { - let configJson; - try { - configJson = JSON.parse(files[0].toString()); - } catch (e) { - log(red(`Error parsing config file: ${filename}`)); - throw e; - } - if (opt_localDev) { - configJson = enableLocalDev(config, target, configJson); - } - if (opt_fortesting) { - configJson = Object.assign({test: true}, configJson); - } - const targetString = files[1].toString(); - const configString = JSON.stringify(configJson); - return prependConfig(configString, targetString); - }) - .then(fileString => { - sanityCheck(fileString); - return writeTarget(target, fileString, argv.dryrun); - }) - .then(() => { - if (!isTravisBuild()) { - const details = '(' + cyan(config) + - (opt_localDev ? ', ' + cyan('localDev') : '') + - (opt_fortesting ? ', ' + cyan('test') : '') + ')'; - log('Applied AMP config', details, 'to', cyan(path.basename(target))); - } - }); + .then(() => { + return Promise.all([ + fs.readFileAsync(filename), + fs.readFileAsync(target), + ]); + }) + .then(files => { + let configJson; + try { + configJson = JSON.parse(files[0].toString()); + } catch (e) { + log(red(`Error parsing config file: ${filename}`)); + throw e; + } + if (opt_localDev) { + configJson = enableLocalDev(config, target, configJson); + } + if (opt_fortesting) { + configJson = Object.assign({test: true}, configJson); + } + const targetString = files[1].toString(); + const configString = JSON.stringify(configJson); + return prependConfig(configString, targetString); + }) + .then(fileString => { + sanityCheck(fileString); + return writeTarget(target, fileString, argv.dryrun); + }) + .then(() => { + if (!isTravisBuild()) { + const details = + '(' + + cyan(config) + + (opt_localDev ? ', ' + cyan('localDev') : '') + + (opt_fortesting ? ', ' + cyan('test') : '') + + ')'; + log('Applied AMP config', details, 'to', cyan(path.basename(target))); + } + }); } /** @@ -174,10 +185,10 @@ function enableLocalDev(config, target, configJson) { let LOCAL_DEV_AMP_CONFIG = {localDev: true}; const TESTING_HOST = process.env.AMP_TESTING_HOST; if (typeof TESTING_HOST == 'string') { - const TESTING_HOST_FULL_URL = TESTING_HOST.match(/^https?:\/\//) ? - TESTING_HOST : 'http://' + TESTING_HOST; - const TESTING_HOST_NO_PROTOCOL = - TESTING_HOST.replace(/^https?:\/\//, ''); + const TESTING_HOST_FULL_URL = TESTING_HOST.match(/^https?:\/\//) + ? TESTING_HOST + : 'http://' + TESTING_HOST; + const TESTING_HOST_NO_PROTOCOL = TESTING_HOST.replace(/^https?:\/\//, ''); LOCAL_DEV_AMP_CONFIG = Object.assign(LOCAL_DEV_AMP_CONFIG, { thirdPartyUrl: TESTING_HOST_FULL_URL, @@ -185,8 +196,14 @@ function enableLocalDev(config, target, configJson) { thirdPartyFrameRegex: TESTING_HOST_NO_PROTOCOL, }); if (!isTravisBuild()) { - log('Set', cyan('TESTING_HOST'), 'to', cyan(TESTING_HOST), - 'in', cyan(target)); + log( + 'Set', + cyan('TESTING_HOST'), + 'to', + cyan(TESTING_HOST), + 'in', + cyan(target) + ); } } return Object.assign(LOCAL_DEV_AMP_CONFIG, configJson); @@ -197,22 +214,20 @@ function enableLocalDev(config, target, configJson) { * @return {!Promise} */ function removeConfig(target) { - return fs.readFileAsync(target) - .then(file => { - let contents = file.toString(); - if (numConfigs(contents) == 0) { - return Promise.resolve(); - } - sanityCheck(contents); - const config = - /self\.AMP_CONFIG\|\|\(self\.AMP_CONFIG=.*?\/\*AMP_CONFIG\*\//; - contents = contents.replace(config, ''); - return writeTarget(target, contents, argv.dryrun).then(() => { - if (!isTravisBuild()) { - log('Removed existing config from', cyan(target)); - } - }); - }); + return fs.readFileAsync(target).then(file => { + let contents = file.toString(); + if (numConfigs(contents) == 0) { + return Promise.resolve(); + } + sanityCheck(contents); + const config = /self\.AMP_CONFIG\|\|\(self\.AMP_CONFIG=.*?\/\*AMP_CONFIG\*\//; + contents = contents.replace(config, ''); + return writeTarget(target, contents, argv.dryrun).then(() => { + if (!isTravisBuild()) { + log('Removed existing config from', cyan(target)); + } + }); + }); } async function prependGlobal() { @@ -238,16 +253,26 @@ async function prependGlobal() { // Prod by default. const config = argv.canary ? 'canary' : 'prod'; if (argv.canary) { - filename = valueOrDefault(argv.canary, - 'build-system/global-configs/canary-config.json'); + filename = valueOrDefault( + argv.canary, + 'build-system/global-configs/canary-config.json' + ); } else { - filename = valueOrDefault(argv.prod, - 'build-system/global-configs/prod-config.json'); + filename = valueOrDefault( + argv.prod, + 'build-system/global-configs/prod-config.json' + ); } return removeConfig(target).then(() => { return applyConfig( - config, target, filename, - argv.local_dev, argv.local_branch, argv.branch, argv.fortesting); + config, + target, + filename, + argv.local_dev, + argv.local_branch, + argv.branch, + argv.fortesting + ); }); } @@ -266,16 +291,20 @@ module.exports = { prependGlobal.description = 'Prepends a json config to a target file'; prependGlobal.flags = { 'target': ' The file to prepend the json config to.', - 'canary': ' Prepend the default canary config. ' + - 'Takes in an optional value for a custom canary config source.', - 'prod': ' Prepend the default prod config. ' + - 'Takes in an optional value for a custom prod config source.', + 'canary': + ' Prepend the default canary config. ' + + 'Takes in an optional value for a custom canary config source.', + 'prod': + ' Prepend the default prod config. ' + + 'Takes in an optional value for a custom prod config source.', 'local_dev': ' Enables runtime to be used for local development.', - 'branch': ' Switch to a git branch to get config source from. ' + - 'Uses master by default.', - 'local_branch': ' Don\'t switch branches and use the config from the ' + - 'local branch.', + 'branch': + ' Switch to a git branch to get config source from. ' + + 'Uses master by default.', + 'local_branch': + " Don't switch branches and use the config from the local branch.", 'fortesting': ' Force the config to return true for getMode().test', - 'remove': ' Removes previously prepended json config from the target ' + - 'file (if present).', + 'remove': + ' Removes previously prepended json config from the target ' + + 'file (if present).', }; diff --git a/build-system/tasks/prepend-global/test.js b/build-system/tasks/prepend-global/test.js index 60b049f6831d..3d75765c47d2 100644 --- a/build-system/tasks/prepend-global/test.js +++ b/build-system/tasks/prepend-global/test.js @@ -15,16 +15,17 @@ */ 'use strict'; - const m = require('./'); const test = require('ava'); - test('sync - prepends global config', t => { t.plan(1); const res = m.prependConfig('{"hello":"world"}', 'var x = 1 + 1;'); - t.is(res, 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"});' + - '/*AMP_CONFIG*/var x = 1 + 1;'); + t.is( + res, + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"});' + + '/*AMP_CONFIG*/var x = 1 + 1;' + ); }); test('sync - valueOrDefault', t => { @@ -37,15 +38,17 @@ test('sync - valueOrDefault', t => { test('sync - sanityCheck', t => { t.plan(3); - const badStr = 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + - '/*AMP_CONFIG*/' + - 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + - '/*AMP_CONFIG*/' + - 'var x = 1 + 1;'; + const badStr = + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + + '/*AMP_CONFIG*/' + + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + + '/*AMP_CONFIG*/' + + 'var x = 1 + 1;'; const badStr2 = 'var x = 1 + 1;'; - const goodStr = 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + - '/*AMP_CONFIG*/' + - 'var x = 1 + 1;'; + const goodStr = + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + + '/*AMP_CONFIG*/' + + 'var x = 1 + 1;'; t.false(m.numConfigs(badStr) == 1); t.true(m.numConfigs(goodStr) == 1); t.false(m.numConfigs(badStr2) == 1); diff --git a/build-system/tasks/presubmit-checks.js b/build-system/tasks/presubmit-checks.js index aa46c5bcff05..3b52bb022ae0 100644 --- a/build-system/tasks/presubmit-checks.js +++ b/build-system/tasks/presubmit-checks.js @@ -25,22 +25,25 @@ const through2 = require('through2'); const dedicatedCopyrightNoteSources = /(\.js|\.css|\.go)$/; const requiresReviewPrivacy = - 'Usage of this API requires dedicated review due to ' + - 'being privacy sensitive. Please file an issue asking for permission' + - ' to use if you have not yet done so.'; + 'Usage of this API requires dedicated review due to ' + + 'being privacy sensitive. Please file an issue asking for permission' + + ' to use if you have not yet done so.'; -const privateServiceFactory = 'This service should only be installed in ' + - 'the whitelisted files. Other modules should use a public function ' + - 'typically called serviceNameFor.'; +const privateServiceFactory = + 'This service should only be installed in ' + + 'the whitelisted files. Other modules should use a public function ' + + 'typically called serviceNameFor.'; const shouldNeverBeUsed = - 'Usage of this API is not allowed - only for internal purposes.'; + 'Usage of this API is not allowed - only for internal purposes.'; -const backwardCompat = 'This method must not be called. It is only retained ' + - 'for backward compatibility during rollout.'; +const backwardCompat = + 'This method must not be called. It is only retained ' + + 'for backward compatibility during rollout.'; -const realiasGetMode = 'Do not re-alias getMode or its return so it can be ' + - 'DCE\'d. Use explicitly like "getMode().localDev" instead.'; +const realiasGetMode = + 'Do not re-alias getMode or its return so it can be ' + + 'DCE\'d. Use explicitly like "getMode().localDev" instead.'; // Terms that must not appear in our source files. const forbiddenTerms = { @@ -72,11 +75,12 @@ const forbiddenTerms = { 'dev\\(\\)\\.assert\\(': 'Use the devAssert function instead.', '[^.]user\\(\\)\\.assert\\(': 'Use the userAssert function instead.', 'it\\.only': '', - 'Math\.random[^;()]*=': 'Use Sinon to stub!!!', + 'Math.random[^;()]*=': 'Use Sinon to stub!!!', 'gulp-util': { - message: '`gulp-util` will be deprecated soon. See ' + - 'https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5 ' + - 'for a list of alternatives.', + message: + '`gulp-util` will be deprecated soon. See ' + + 'https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5 ' + + 'for a list of alternatives.', }, 'document-register-element.node': { message: 'Use `document-register-element.patched` instead', @@ -92,12 +96,14 @@ const forbiddenTerms = { message: 'Use a sandbox instead to avoid repeated `#restore` calls', }, 'sandbox\\.(spy|stub|mock)\\([^,\\s]*[iI]?frame[^,\\s]*,': { - message: 'Do NOT stub on a cross domain iframe! #5359\n' + - ' If this is same domain, mark /*OK*/.\n' + - ' If this is cross domain, overwrite the method directly.', + message: + 'Do NOT stub on a cross domain iframe! #5359\n' + + ' If this is same domain, mark /*OK*/.\n' + + ' If this is cross domain, overwrite the method directly.', }, 'console\\.\\w+\\(': { - message: 'If you run against this, use console/*OK*/.[log|error] to ' + + message: + 'If you run against this, use console/*OK*/.[log|error] to ' + 'whitelist a legit case.', whitelist: [ 'build-system/app.js', @@ -129,10 +135,7 @@ const forbiddenTerms = { // as a variable. '\\bgetMode\\([^)]*\\)(?!\\.)': { message: realiasGetMode, - whitelist: [ - 'src/mode.js', - 'dist.3p/current/integration.js', - ], + whitelist: ['src/mode.js', 'dist.3p/current/integration.js'], }, 'import[^}]*\\bgetMode as': { message: realiasGetMode, @@ -147,23 +150,22 @@ const forbiddenTerms = { ], }, '(?:var|let|const) +IS_DEV +=': { - message: 'IS_DEV local var only allowed in mode.js and ' + - 'dist.3p/current/integration.js', - whitelist: [ - 'src/mode.js', + message: + 'IS_DEV local var only allowed in mode.js and ' + 'dist.3p/current/integration.js', - ], + whitelist: ['src/mode.js', 'dist.3p/current/integration.js'], }, '\\.prefetch\\(': { message: 'Do not use preconnect.prefetch, use preconnect.preload instead.', }, 'iframePing': { - message: 'This is only available in vendor config for ' + - 'temporary workarounds.', + message: + 'This is only available in vendor config for temporary workarounds.', whitelist: [ 'build-system/routes/analytics.js', 'extensions/amp-analytics/0.1/config.js', 'extensions/amp-analytics/0.1/requests.js', + 'extensions/amp-analytics/0.1/vendors.js', ], }, // Service factories that should only be installed once. @@ -194,23 +196,15 @@ const forbiddenTerms = { }, 'cidServiceForDocForTesting': { message: privateServiceFactory, - whitelist: [ - 'src/service/cid-impl.js', - ], + whitelist: ['src/service/cid-impl.js'], }, 'installCryptoService': { message: privateServiceFactory, - whitelist: [ - 'src/service/crypto-impl.js', - 'src/runtime.js', - ], + whitelist: ['src/service/crypto-impl.js', 'src/runtime.js'], }, 'installDocumentStateService': { message: privateServiceFactory, - whitelist: [ - 'src/service/document-state.js', - 'src/runtime.js', - ], + whitelist: ['src/service/document-state.js', 'src/runtime.js'], }, 'installDocService': { message: privateServiceFactory, @@ -244,10 +238,7 @@ const forbiddenTerms = { }, 'installTemplatesService': { message: privateServiceFactory, - whitelist: [ - 'src/runtime.js', - 'src/service/template-impl.js', - ], + whitelist: ['src/runtime.js', 'src/service/template-impl.js'], }, 'installUrlReplacementsServiceForDoc': { message: privateServiceFactory, @@ -268,17 +259,11 @@ const forbiddenTerms = { }, 'setViewerVisibilityState': { message: privateServiceFactory, - whitelist: [ - 'src/runtime.js', - 'src/service/viewer-impl.js', - ], + whitelist: ['src/runtime.js', 'src/service/viewer-impl.js'], }, 'installViewportServiceForDoc': { message: privateServiceFactory, - whitelist: [ - 'src/runtime.js', - 'src/service/viewport/viewport-impl.js', - ], + whitelist: ['src/runtime.js', 'src/service/viewport/viewport-impl.js'], }, 'installVsyncService': { message: privateServiceFactory, @@ -300,10 +285,7 @@ const forbiddenTerms = { }, 'installXhrService': { message: privateServiceFactory, - whitelist: [ - 'src/runtime.js', - 'src/service/xhr-impl.js', - ], + whitelist: ['src/runtime.js', 'src/service/xhr-impl.js'], }, 'installPositionObserverServiceForDoc': { message: privateServiceFactory, @@ -404,10 +386,7 @@ const forbiddenTerms = { }, 'getBaseCid': { message: requiresReviewPrivacy, - whitelist: [ - 'src/service/cid-impl.js', - 'src/service/viewer-impl.js', - ], + whitelist: ['src/service/cid-impl.js', 'src/service/viewer-impl.js'], }, 'isTrustedViewer': { message: requiresReviewPrivacy, @@ -425,9 +404,7 @@ const forbiddenTerms = { }, 'eval\\(': { message: shouldNeverBeUsed, - whitelist: [ - 'extension/amp-bind/0.1/test/test-bind-expr.js', - ], + whitelist: ['extension/amp-bind/0.1/test/test-bind-expr.js'], }, 'storageForDoc': { message: requiresReviewPrivacy, @@ -508,8 +485,9 @@ const forbiddenTerms = { ], }, 'internalListenImplementation': { - message: 'Use `listen()` in either `event-helper` or `3p-frame-messaging`' + - ', depending on your use case.', + message: + 'Use `listen()` in either `event-helper` or `3p-frame-messaging`' + + ', depending on your use case.', whitelist: [ 'src/3p-frame-messaging.js', 'src/event-helper.js', @@ -519,22 +497,21 @@ const forbiddenTerms = { }, 'setTimeout.*throw': { message: 'Use dev.error or user.error instead.', - whitelist: [ - 'src/log.js', - ], + whitelist: ['src/log.js'], }, - '(dev|user)\\(\\)\\.(fine|info|warn|error)\\((?!\\s*([A-Z0-9-]+|[\'"`][A-Z0-9-]+[\'"`]))[^,)\n]*': { // eslint-disable-line max-len - message: 'Logging message require explicitly `TAG`, or an all uppercase' + - ' string as the first parameter', + '(dev|user)\\(\\)\\.(fine|info|warn|error)\\((?!\\s*([A-Z0-9-]+|[\'"`][A-Z0-9-]+[\'"`]))[^,)\n]*': { + // eslint-disable-line max-len + message: + 'Logging message require explicitly `TAG`, or an all uppercase' + + ' string as the first parameter', }, '\\.schedulePass\\(': { message: 'schedulePass is heavy, think twice before using it', - whitelist: [ - 'src/service/resources-impl.js', - ], + whitelist: ['src/service/resources-impl.js'], }, '\\.requireLayout\\(': { - message: 'requireLayout is restricted b/c it affects non-contained elements', // eslint-disable-line max-len + message: + 'requireLayout is restricted b/c it affects non-contained elements', // eslint-disable-line max-len whitelist: [ 'extensions/amp-animation/0.1/web-animations.js', 'extensions/amp-lightbox-gallery/0.1/amp-lightbox-gallery.js', @@ -551,14 +528,11 @@ const forbiddenTerms = { }, '(win|Win)(dow)?(\\(\\))?\\.open\\W': { message: 'Use dom.openWindowDialog', - whitelist: [ - 'src/dom.js', - ], + whitelist: ['src/dom.js'], }, '\\.getWin\\(': { message: backwardCompat, - whitelist: [ - ], + whitelist: [], }, '/\\*\\* @type \\{\\!Element\\} \\*/': { message: 'Use assertElement instead of casting to !Element.', @@ -566,6 +540,7 @@ const forbiddenTerms = { 'src/log.js', // Has actual implementation of assertElement. 'dist.3p/current/integration.js', // Includes the previous. 'src/polyfills/custom-elements.js', + 'ads/google/imaVideo.js', // Required until #22277 is fixed. ], }, 'startupChunk\\(': { @@ -578,8 +553,9 @@ const forbiddenTerms = { ], }, 'AMP_CONFIG': { - message: 'Do not access AMP_CONFIG directly. Use isExperimentOn() ' + - 'and getMode() to access config', + message: + 'Do not access AMP_CONFIG directly. Use isExperimentOn() ' + + 'and getMode() to access config', whitelist: [ 'build-system/amp.extern.js', 'build-system/app.js', @@ -600,21 +576,19 @@ const forbiddenTerms = { ], }, 'data:image/svg(?!\\+xml;charset=utf-8,)[^,]*,': { - message: 'SVG data images must use charset=utf-8: ' + - '"data:image/svg+xml;charset=utf-8,..."', + message: + 'SVG data images must use charset=utf-8: ' + + '"data:image/svg+xml;charset=utf-8,..."', }, 'new CustomEvent\\(': { message: 'Use createCustomEvent() helper instead.', - whitelist: [ - 'src/event-helper.js', - ], + whitelist: ['src/event-helper.js'], }, 'new FormData\\(': { - message: 'Use createFormDataWrapper() instead and call ' + - 'formDataWrapper.getFormData() to get the native FormData object.', - whitelist: [ - 'src/form-data-wrapper.js', - ], + message: + 'Use createFormDataWrapper() instead and call ' + + 'formDataWrapper.getFormData() to get the native FormData object.', + whitelist: ['src/form-data-wrapper.js'], }, '([eE]xit|[eE]nter|[cC]ancel|[rR]equest)Full[Ss]creen\\(': { message: 'Use fullscreenEnter() and fullscreenExit() from dom.js instead.', @@ -629,26 +603,25 @@ const forbiddenTerms = { '\\.defer\\(\\)': { message: 'Promise.defer() is deprecated and should not be used.', }, - '(dev|user)\\(\\)\\.assert(Element|String|Number)?\\(\\s*([A-Z][A-Z0-9-]*,)': { // eslint-disable-line max-len + '(dev|user)\\(\\)\\.assert(Element|String|Number)?\\(\\s*([A-Z][A-Z0-9-]*,)': { + // eslint-disable-line max-len message: 'TAG is not an argument to assert(). Will cause false positives.', }, 'eslint no-unused-vars': { message: 'Use a line-level "no-unused-vars" rule instead.', - whitelist: [ - 'extensions/amp-access/0.1/iframe-api/access-controller.js', - ], + whitelist: ['extensions/amp-access/0.1/iframe-api/access-controller.js'], }, 'this\\.skip\\(\\)': { - message: 'Use of `this.skip()` is forbidden in test files. Use ' + - '`this.skipTest()` from within a `before()` block instead. See #17245.', + message: + 'Use of `this.skip()` is forbidden in test files. Use ' + + '`this.skipTest()` from within a `before()` block instead. See #17245.', checkInTestFolder: true, - whitelist: [ - 'test/_init_tests.js', - ], + whitelist: ['test/_init_tests.js'], }, '[^\\.]makeBodyVisible\\(': { - message: 'This is a protected function. If you are calling this to show ' + - 'body after an error please use `makeBodyVisibleRecovery`', + message: + 'This is a protected function. If you are calling this to show ' + + 'body after an error please use `makeBodyVisibleRecovery`', whitelist: [ 'src/amp.js', 'src/amp-shadow.js', @@ -659,8 +632,9 @@ const forbiddenTerms = { }, }; -const ThreePTermsMessage = 'The 3p bootstrap iframe has no polyfills loaded' + - ' and can thus not use most modern web APIs.'; +const ThreePTermsMessage = + 'The 3p bootstrap iframe has no polyfills loaded' + + ' and can thus not use most modern web APIs.'; const forbidden3pTerms = { // We need to forbid promise usage because we don't have our own polyfill @@ -670,19 +644,20 @@ const forbidden3pTerms = { '\\.then\\((?!callNext)': ThreePTermsMessage, }; -const bannedTermsHelpString = 'Please review viewport service for helper ' + - 'methods or mark with `/*OK*/` or `/*REVIEW*/` and consult the AMP team. ' + - 'Most of the forbidden property/method access banned on the ' + - '`forbiddenTermsSrcInclusive` object can be found in ' + - '[What forces layout / reflow gist by Paul Irish]' + - '(https://gist.github.com/paulirish/5d52fb081b3570c81e3a). ' + - 'These properties/methods when read/used require the browser ' + - 'to have the up-to-date value to return which might possibly be an ' + - 'expensive computation and could also be triggered multiple times ' + - 'if we are not careful. Please mark the call with ' + - '`object./*OK*/property` if you explicitly need to read or update the ' + - 'forbidden property/method or mark it with `object./*REVIEW*/property` ' + - 'if you are unsure and so that it stands out in code reviews.'; +const bannedTermsHelpString = + 'Please review viewport service for helper ' + + 'methods or mark with `/*OK*/` or `/*REVIEW*/` and consult the AMP team. ' + + 'Most of the forbidden property/method access banned on the ' + + '`forbiddenTermsSrcInclusive` object can be found in ' + + '[What forces layout / reflow gist by Paul Irish]' + + '(https://gist.github.com/paulirish/5d52fb081b3570c81e3a). ' + + 'These properties/methods when read/used require the browser ' + + 'to have the up-to-date value to return which might possibly be an ' + + 'expensive computation and could also be triggered multiple times ' + + 'if we are not careful. Please mark the call with ' + + '`object./*OK*/property` if you explicitly need to read or update the ' + + 'forbidden property/method or mark it with `object./*REVIEW*/property` ' + + 'if you are unsure and so that it stands out in code reviews.'; const forbiddenTermsSrcInclusive = { '\\.innerHTML(?!_)': bannedTermsHelpString, @@ -736,16 +711,15 @@ const forbiddenTermsSrcInclusive = { ], }, 'getComputedStyle\\(': { - message: 'Due to various bugs in Firefox, you must use the computedStyle ' + - 'helper in style.js.', - whitelist: [ - 'src/style.js', - 'dist.3p/current/integration.js', - ], + message: + 'Due to various bugs in Firefox, you must use the computedStyle ' + + 'helper in style.js.', + whitelist: ['src/style.js', 'dist.3p/current/integration.js'], }, 'decodeURIComponent\\(': { - message: 'decodeURIComponent throws for malformed URL components. Please ' + - 'use tryDecodeUriComponent from src/url.js', + message: + 'decodeURIComponent throws for malformed URL components. Please ' + + 'use tryDecodeUriComponent from src/url.js', whitelist: [ '3p/integration.js', 'dist.3p/current/integration.js', @@ -760,8 +734,9 @@ const forbiddenTermsSrcInclusive = { ], }, 'Text(Encoder|Decoder)\\(': { - message: 'TextEncoder/TextDecoder is not supported in all browsers.' + - ' Please use UTF8 utilities from src/bytes.js', + message: + 'TextEncoder/TextDecoder is not supported in all browsers.' + + ' Please use UTF8 utilities from src/bytes.js', whitelist: [ 'ads/google/a4a/line-delimited-response-handler.js', 'examples/pwa/pwa.js', @@ -813,8 +788,9 @@ const forbiddenTermsSrcInclusive = { ], }, 'reject\\(\\)': { - message: 'Always supply a reason in rejections. ' + - 'error.cancellation() may be applicable.', + message: + 'Always supply a reason in rejections. ' + + 'error.cancellation() may be applicable.', whitelist: [ 'extensions/amp-access/0.1/access-expr-impl.js', 'extensions/amp-animation/0.1/parsers/css-expr-impl.js', @@ -842,9 +818,7 @@ const forbiddenTermsSrcInclusive = { }, '\\.getTime\\(\\)': { message: 'Unless you do weird date math (whitelist), use Date.now().', - whitelist: [ - 'extensions/amp-timeago/0.1/amp-timeago.js', - ], + whitelist: ['extensions/amp-timeago/0.1/amp-timeago.js'], }, '\\.expandStringSync\\(': { message: requiresReviewPrivacy, @@ -885,8 +859,9 @@ const forbiddenTermsSrcInclusive = { ], }, '(cdn|3p)\\.ampproject\\.': { - message: 'The CDN domain should typically not be hardcoded in source ' + - 'code. Use a property of urls from src/config.js instead.', + message: + 'The CDN domain should typically not be hardcoded in source ' + + 'code. Use a property of urls from src/config.js instead.', whitelist: [ 'ads/_a4a-config.js', 'build-system/app.js', @@ -915,29 +890,21 @@ const forbiddenTermsSrcInclusive = { }, '\\.indexOf\\([\'"][^)]+\\)\\s*===?\\s*0\\b': { message: 'use startsWith helper in src/string.js', - whitelist: [ - 'dist.3p/current/integration.js', - 'build-system/app.js', - ], + whitelist: ['dist.3p/current/integration.js', 'build-system/app.js'], }, '\\.indexOf\\(.*===?.*\\.length': 'use endsWith helper in src/string.js', '/url-parse-query-string': { message: 'Import parseQueryString from `src/url.js`', - whitelist: [ - 'src/url.js', - 'src/mode.js', - 'dist.3p/current/integration.js', - ], + whitelist: ['src/url.js', 'src/mode.js', 'dist.3p/current/integration.js'], }, '\\.trim(Left|Right)\\(\\)': { message: 'Unsupported on IE; use trim() or a helper instead.', - whitelist: [ - 'validator/engine/validator.js', - ], + whitelist: ['validator/engine/validator.js'], }, - 'process\\.env(\\.TRAVIS|\\[\\\'TRAVIS)': { - message: 'Do not directly use process.env.TRAVIS. Instead, add a ' + - 'function to build-system/travis.js', + "process\\.env(\\.TRAVIS|\\[\\'TRAVIS)": { + message: + 'Do not directly use process.env.TRAVIS. Instead, add a ' + + 'function to build-system/travis.js', whitelist: [ 'build-system/check-package-manager.js', 'build-system/travis.js', @@ -948,15 +915,11 @@ const forbiddenTermsSrcInclusive = { // Terms that must appear in a source file. const requiredTerms = { - 'Copyright 20(15|16|17|18|19) The AMP HTML Authors\\.': - dedicatedCopyrightNoteSources, - 'Licensed under the Apache License, Version 2\\.0': - dedicatedCopyrightNoteSources, - 'http\\://www\\.apache\\.org/licenses/LICENSE-2\\.0': - dedicatedCopyrightNoteSources, + 'Copyright 20(15|16|17|18|19) The AMP HTML Authors\\.': dedicatedCopyrightNoteSources, + 'Licensed under the Apache License, Version 2\\.0': dedicatedCopyrightNoteSources, + 'http\\://www\\.apache\\.org/licenses/LICENSE-2\\.0': dedicatedCopyrightNoteSources, }; - /** * Check if root of path is test/ or file is in a folder named test. * @param {string} path @@ -974,8 +937,10 @@ function isInTestFolder(path) { */ function isInBuildSystemFixtureFolder(filePath) { const folder = path.dirname(filePath); - return folder.startsWith('build-system/babel-plugins') && - folder.includes('test/fixtures'); + return ( + folder.startsWith('build-system/babel-plugins') && + folder.includes('test/fixtures') + ); } /** @@ -1013,61 +978,75 @@ function stripComments(contents) { function matchTerms(file, terms) { const contents = stripComments(file.contents.toString()); const {relative} = file; - return Object.keys(terms).map(function(term) { - let fix; - const {whitelist, checkInTestFolder} = terms[term]; - // NOTE: we could do a glob test instead of exact check in the future - // if needed but that might be too permissive. - if (isInBuildSystemFixtureFolder(relative) || (Array.isArray(whitelist) && - (whitelist.indexOf(relative) != -1 || - (isInTestFolder(relative) && !checkInTestFolder)))) { - return false; - } - // we can't optimize building the `RegExp` objects early unless we build - // another mapping of term -> regexp object to be able to get back to the - // original term to get the possible fix value. This is ok as the - // presubmit doesn't have to be blazing fast and this is most likely - // negligible. - const regex = new RegExp(term, 'gm'); - let index = 0; - let line = 1; - let column = 0; - let match; - let hasTerm = false; + return Object.keys(terms) + .map(function(term) { + let fix; + const {whitelist, checkInTestFolder} = terms[term]; + // NOTE: we could do a glob test instead of exact check in the future + // if needed but that might be too permissive. + if ( + isInBuildSystemFixtureFolder(relative) || + (Array.isArray(whitelist) && + (whitelist.indexOf(relative) != -1 || + (isInTestFolder(relative) && !checkInTestFolder))) + ) { + return false; + } + // we can't optimize building the `RegExp` objects early unless we build + // another mapping of term -> regexp object to be able to get back to the + // original term to get the possible fix value. This is ok as the + // presubmit doesn't have to be blazing fast and this is most likely + // negligible. + const regex = new RegExp(term, 'gm'); + let index = 0; + let line = 1; + let column = 0; + let match; + let hasTerm = false; - while ((match = regex.exec(contents))) { - hasTerm = true; - for (index; index < match.index; index++) { - if (contents[index] === '\n') { - line++; - column = 1; - } else { - column++; + while ((match = regex.exec(contents))) { + hasTerm = true; + for (index; index < match.index; index++) { + if (contents[index] === '\n') { + line++; + column = 1; + } else { + column++; + } } - } - log(colors.red('Found forbidden: "' + match[0] + - '" in ' + relative + ':' + line + ':' + column)); - if (typeof terms[term] === 'string') { - fix = terms[term]; - } else { - fix = terms[term].message; - } + log( + colors.red( + 'Found forbidden: "' + + match[0] + + '" in ' + + relative + + ':' + + line + + ':' + + column + ) + ); + if (typeof terms[term] === 'string') { + fix = terms[term]; + } else { + fix = terms[term].message; + } - // log the possible fix information if provided for the term. - if (fix) { - log(colors.blue(fix)); + // log the possible fix information if provided for the term. + if (fix) { + log(colors.blue(fix)); + } + log(colors.blue('==========')); } - log(colors.blue('==========')); - } - return hasTerm; - }).some(function(hasAnyTerm) { - return hasAnyTerm; - }); + return hasTerm; + }) + .some(function(hasAnyTerm) { + return hasAnyTerm; + }); } - /** * Test if a file's contents match any of the * forbidden terms @@ -1085,15 +1064,18 @@ function hasAnyTerms(file) { hasTerms = matchTerms(file, forbiddenTerms); - const isTestFile = /^test-/.test(basename) || /^_init_tests/.test(basename) - || /_test\.js$/.test(basename); + const isTestFile = + /^test-/.test(basename) || + /^_init_tests/.test(basename) || + /_test\.js$/.test(basename); if (!isTestFile) { hasSrcInclusiveTerms = matchTerms(file, forbiddenTermsSrcInclusive); } - const is3pFile = /\/(3p|ads)\//.test(pathname) || - basename == '3p.js' || - basename == 'style.js'; + const is3pFile = + /\/(3p|ads)\//.test(pathname) || + basename == '3p.js' || + basename == 'style.js'; // Yet another reason to move ads/google/a4a somewhere else const isA4A = /\/a4a\//.test(pathname); const isRecaptcha = basename == 'recaptcha.js'; @@ -1114,23 +1096,28 @@ function hasAnyTerms(file) { */ function isMissingTerms(file) { const contents = file.contents.toString(); - return Object.keys(requiredTerms).map(function(term) { - const filter = requiredTerms[term]; - if (!filter.test(file.path)) { - return false; - } + return Object.keys(requiredTerms) + .map(function(term) { + const filter = requiredTerms[term]; + if (!filter.test(file.path)) { + return false; + } - const matches = contents.match(new RegExp(term)); - if (!matches) { - log(colors.red('Did not find required: "' + term + - '" in ' + file.relative)); - log(colors.blue('==========')); - return true; - } - return false; - }).some(function(hasMissingTerm) { - return hasMissingTerm; - }); + const matches = contents.match(new RegExp(term)); + if (!matches) { + log( + colors.red( + 'Did not find required: "' + term + '" in ' + file.relative + ) + ); + log(colors.blue('==========')); + return true; + } + return false; + }) + .some(function(hasMissingTerm) { + return hasMissingTerm; + }); } /** @@ -1140,26 +1127,35 @@ function isMissingTerms(file) { function presubmit() { let forbiddenFound = false; let missingRequirements = false; - return gulp.src(srcGlobs) - .pipe(through2.obj(function(file, enc, cb) { + return gulp + .src(srcGlobs) + .pipe( + through2.obj(function(file, enc, cb) { forbiddenFound = hasAnyTerms(file) || forbiddenFound; missingRequirements = isMissingTerms(file) || missingRequirements; cb(); - })) - .on('end', function() { - if (forbiddenFound) { - log(colors.blue( - 'Please remove these usages or consult with the AMP team.')); - } - if (missingRequirements) { - log(colors.blue( - 'Adding these terms (e.g. by adding a required LICENSE ' + - 'to the file)')); - } - if (forbiddenFound || missingRequirements) { - process.exitCode = 1; - } - }); + }) + ) + .on('end', function() { + if (forbiddenFound) { + log( + colors.blue( + 'Please remove these usages or consult with the AMP team.' + ) + ); + } + if (missingRequirements) { + log( + colors.blue( + 'Adding these terms (e.g. by adding a required LICENSE ' + + 'to the file)' + ) + ); + } + if (forbiddenFound || missingRequirements) { + process.exitCode = 1; + } + }); } module.exports = { @@ -1167,4 +1163,4 @@ module.exports = { }; presubmit.description = - 'Run validation against files to check for forbidden and required terms'; + 'Run validation against files to check for forbidden and required terms'; diff --git a/build-system/tasks/process-3p-github-pr.js b/build-system/tasks/process-3p-github-pr.js index 6f913fa82a6d..cf47bb62b5e7 100644 --- a/build-system/tasks/process-3p-github-pr.js +++ b/build-system/tasks/process-3p-github-pr.js @@ -78,16 +78,17 @@ const ANALYZE_OUTCOME = { AD: 1, // Ad integration PR, ping the ad onduty person }; -const AD_COMMENT = 'Dear contributor! Thank you for the pull request. ' + - 'It looks like this PR is trying to add support to an ad network. \n \n' + - 'If this is your first time adding support for ' + - 'a new third-party ad service, please make sure your follow our ' + - '[developer guideline](https://github.com/ampproject/amphtml/blob/master/' + - 'ads/README.md#developer-guidelines-for-a-pull-request). \n \n' + - 'If you have not implemented it, we also highly recommend implementing ' + - 'the [renderStart API](https://github.com/ampproject/amphtml/blob/master/' + - 'ads/README.md#available-apis) to provide better user experience. ' + - 'Please let us know if there is any question. \n \n'; +const AD_COMMENT = + 'Dear contributor! Thank you for the pull request. ' + + 'It looks like this PR is trying to add support to an ad network. \n \n' + + 'If this is your first time adding support for ' + + 'a new third-party ad service, please make sure your follow our ' + + '[developer guideline](https://github.com/ampproject/amphtml/blob/master/' + + 'ads/README.md#developer-guidelines-for-a-pull-request). \n \n' + + 'If you have not implemented it, we also highly recommend implementing ' + + 'the [renderStart API](https://github.com/ampproject/amphtml/blob/master/' + + 'ads/README.md#available-apis) to provide better user experience. ' + + 'Please let us know if there is any question. \n \n'; const defaultOption = { headers: { @@ -118,12 +119,15 @@ function calculateReviewer() { */ function process3pGithubPr() { if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('You have not set the ' + - 'GITHUB_ACCESS_TOKEN env var.')); - log(colors.green('See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.')); + log(colors.red('You have not set the GITHUB_ACCESS_TOKEN env var.')); + log( + colors.green( + 'See https://help.github.com/articles/' + + 'creating-an-access-token-for-command-line-use/ ' + + 'for instructions on how to create a github access token. We only ' + + 'need `public_repo` scope.' + ) + ); return; } @@ -135,17 +139,18 @@ function process3pGithubPr() { arrayPromises.push(getIssues(batch)); } return BBPromise.all(arrayPromises) - .then(requests => [].concat.apply([], requests)) - .then(issues => { - const allIssues = issues; - const allTasks = []; - allIssues.forEach(function(issue) { - allTasks.push(handleIssue(issue)); - }); - return Promise.all(allTasks); - }).then(() => { - log(colors.blue('auto triaging succeed!')); + .then(requests => [].concat.apply([], requests)) + .then(issues => { + const allIssues = issues; + const allTasks = []; + allIssues.forEach(function(issue) { + allTasks.push(handleIssue(issue)); }); + return Promise.all(allTasks); + }) + .then(() => { + log(colors.blue('auto triaging succeed!')); + }); } function handleIssue(issue) { @@ -185,8 +190,9 @@ function getIssues(opt_page) { function getPullRequestFiles(pr) { const options = extend({}, defaultOption); const {number} = pr; - options.url = 'https://api.github.com/repos/ampproject/amphtml/pulls/' - + `${number}/files`; + options.url = + 'https://api.github.com/repos/ampproject/amphtml/pulls/' + + `${number}/files`; return request(options).then(res => { const files = JSON.parse(res.body); if (!Array.isArray(files)) { @@ -260,14 +266,16 @@ function isQualifiedPR(issue) { function replyToPR(pr, outcome) { let promise = Promise.resolve(); if (outcome == ANALYZE_OUTCOME.AD) { - promise = promise.then(() => { - // We should be good with rate limit given the number of - // 3p integration PRs today. - const comment = AD_COMMENT + `Thank you! Ping @${reviewer} for review`; - return applyComment(pr, comment); - }).then(() => { - return assignIssue(pr, [reviewer]); - }); + promise = promise + .then(() => { + // We should be good with rate limit given the number of + // 3p integration PRs today. + const comment = AD_COMMENT + `Thank you! Ping @${reviewer} for review`; + return applyComment(pr, comment); + }) + .then(() => { + return assignIssue(pr, [reviewer]); + }); } return promise; } @@ -279,17 +287,20 @@ function replyToPR(pr, outcome) { */ function applyComment(issue, comment) { const {number} = issue; - const options = extend({ - url: 'https://api.github.com/repos/ampproject/amphtml/issues/' - + `${number}/comments`, - method: 'POST', - body: JSON.stringify({ - 'body': comment, - }), - }, defaultOption); + const options = extend( + { + url: + 'https://api.github.com/repos/ampproject/amphtml/issues/' + + `${number}/comments`, + method: 'POST', + body: JSON.stringify({ + 'body': comment, + }), + }, + defaultOption + ); if (isDryrun) { - log(colors.blue(`apply comment to PR #${number}, ` + - `comment is ${comment}`)); + log(colors.blue(`apply comment to PR #${number}, comment is ${comment}`)); return Promise.resolve(); } return request(options); @@ -302,17 +313,20 @@ function applyComment(issue, comment) { */ function assignIssue(issue, assignees) { const {number} = issue; - const options = extend({ - url: 'https://api.github.com/repos/ampproject/amphtml/issues/' - + `${number}/assignees`, - method: 'POST', - body: JSON.stringify({ - 'assignees': assignees, - }), - }, defaultOption); + const options = extend( + { + url: + 'https://api.github.com/repos/ampproject/amphtml/issues/' + + `${number}/assignees`, + method: 'POST', + body: JSON.stringify({ + 'assignees': assignees, + }), + }, + defaultOption + ); if (isDryrun) { - log(colors.blue(`assign PR #${number}, ` + - `to ${assignees}`)); + log(colors.blue(`assign PR #${number}, to ${assignees}`)); return Promise.resolve(); } return request(options); @@ -320,5 +334,5 @@ function assignIssue(issue, assignees) { process3pGithubPr.description = 'Automatically triage 3P integration PRs'; process3pGithubPr.flags = { - dryrun: ' Generate process but don\'t push it out', + dryrun: " Generate process but don't push it out", }; diff --git a/build-system/tasks/process-github-issues.js b/build-system/tasks/process-github-issues.js index bb348d33f5b3..91694a42a4a6 100644 --- a/build-system/tasks/process-github-issues.js +++ b/build-system/tasks/process-github-issues.js @@ -72,12 +72,15 @@ const NUM_BATCHES = 14; // We start processing the issues by checking token first function processGithubIssues() { if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('You have not set the ' + - 'GITHUB_ACCESS_TOKEN env var.')); - log(colors.green('See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.')); + log(colors.red('You have not set the GITHUB_ACCESS_TOKEN env var.')); + log( + colors.green( + 'See https://help.github.com/articles/' + + 'creating-an-access-token-for-command-line-use/ ' + + 'for instructions on how to create a github access token. We only ' + + 'need `public_repo` scope.' + ) + ); return; } return updateGitHubIssues().then(function() { @@ -118,166 +121,212 @@ function updateGitHubIssues() { arrayPromises.push(getIssues(batch)); } return BBPromise.all(arrayPromises) - .then(requests => [].concat.apply([], requests)) - .then(issues => { - const allIssues = issues; - allIssues.forEach(function(issue) { - const { - labels, - milestone, - assignee, - 'pull_request': pullRequest, - 'updated_at': issueLastUpdate, - } = issue; - let issueType; - let milestoneTitle; - let milestoneState; - let hasPriority = false; - let hasCategory = false; - let issueNewMilestone = MILESTONE_PENDING_TRIAGE; - let assigneeName = ''; - let biweeklyUpdate = true; - let quartelyUpdate = true; - // if an issue is a pull request, we'll skip it - if (pullRequest) { - if (isDryrun) { - log(colors.red(issue.number + ' is a pull request')); - } - return; - } - if (getLastUpdate(issueLastUpdate) > QUARTERLY_DAYS) { - quartelyUpdate = false; - biweeklyUpdate = false; - } else if (getLastUpdate(issueLastUpdate) > BIWEEKLY_DAYS) { - biweeklyUpdate = false; - } - // Get the assignee - if (assignee) { - assigneeName = '@' + assignee.login; - } - // Get the title and state of the milestone - if (milestone) { - milestoneTitle = milestone.title; - milestoneState = milestone.state; - issueNewMilestone = milestone.number; + .then(requests => [].concat.apply([], requests)) + .then(issues => { + const allIssues = issues; + allIssues.forEach(function(issue) { + const { + labels, + milestone, + assignee, + 'pull_request': pullRequest, + 'updated_at': issueLastUpdate, + } = issue; + let issueType; + let milestoneTitle; + let milestoneState; + let hasPriority = false; + let hasCategory = false; + let issueNewMilestone = MILESTONE_PENDING_TRIAGE; + let assigneeName = ''; + let biweeklyUpdate = true; + let quartelyUpdate = true; + // if an issue is a pull request, we'll skip it + if (pullRequest) { + if (isDryrun) { + log(colors.red(issue.number + ' is a pull request')); } - // promise starts - promise = promise.then(function() { - log('Update ' + issue.number); - const updates = []; - // Get the labels we want to check - labels.forEach(function(label) { - if (label) { - // Check if the issues has type - if (label.name.startsWith('Type') || - label.name.startsWith('Related')) { - issueType = label.name; - } - // Check if the issues has Priority - if (label.name.startsWith('P0') || - label.name.startsWith('P1') || - label.name.startsWith('P2') || - label.name.startsWith('P3')) { - hasPriority = true; - if (label.name.startsWith('P0') || - label.name.startsWith('P1')) { - if (biweeklyUpdate == false) { - biweeklyUpdate = true; - updates.push(applyComment(issue, 'This is a high priority' - + ' issue but it hasn\'t been updated in awhile. ' + - assigneeName + ' Do you have any updates?')); - } - } else if (label.name.startsWith('P2') && - quartelyUpdate == false) { - quartelyUpdate = true; - updates.push(applyComment(issue, 'This issue hasn\'t been ' - + ' updated in awhile. ' + - assigneeName + ' Do you have any updates?')); + return; + } + if (getLastUpdate(issueLastUpdate) > QUARTERLY_DAYS) { + quartelyUpdate = false; + biweeklyUpdate = false; + } else if (getLastUpdate(issueLastUpdate) > BIWEEKLY_DAYS) { + biweeklyUpdate = false; + } + // Get the assignee + if (assignee) { + assigneeName = '@' + assignee.login; + } + // Get the title and state of the milestone + if (milestone) { + milestoneTitle = milestone.title; + milestoneState = milestone.state; + issueNewMilestone = milestone.number; + } + // promise starts + promise = promise.then(function() { + log('Update ' + issue.number); + const updates = []; + // Get the labels we want to check + labels.forEach(function(label) { + if (label) { + // Check if the issues has type + if ( + label.name.startsWith('Type') || + label.name.startsWith('Related') + ) { + issueType = label.name; + } + // Check if the issues has Priority + if ( + label.name.startsWith('P0') || + label.name.startsWith('P1') || + label.name.startsWith('P2') || + label.name.startsWith('P3') + ) { + hasPriority = true; + if ( + label.name.startsWith('P0') || + label.name.startsWith('P1') + ) { + if (biweeklyUpdate == false) { + biweeklyUpdate = true; + updates.push( + applyComment( + issue, + 'This is a high priority' + + " issue but it hasn't been updated in awhile. " + + assigneeName + + ' Do you have any updates?' + ) + ); } - } - if (label.name.startsWith('Category') || - label.name.startsWith('Related to') || - label.name.startsWith('GFI') || - label.name.startsWith('good first issue')) { - hasCategory = true; + } else if ( + label.name.startsWith('P2') && + quartelyUpdate == false + ) { + quartelyUpdate = true; + updates.push( + applyComment( + issue, + "This issue hasn't been " + + ' updated in awhile. ' + + assigneeName + + ' Do you have any updates?' + ) + ); } } - }); - // Milestone task: move issues from closed milestone - if (milestone) { - if (milestoneState === 'closed') { - issueNewMilestone = MILESTONE_BACKLOG_BUGS; - updates.push(applyMilestone(issue, issueNewMilestone)); + if ( + label.name.startsWith('Category') || + label.name.startsWith('Related to') || + label.name.startsWith('GFI') || + label.name.startsWith('good first issue') + ) { + hasCategory = true; } } - if (issueNewMilestone === MILESTONE_PENDING_TRIAGE) { - if (quartelyUpdate == false) { - quartelyUpdate = true; - updates.push(applyComment(issue, 'This issue seems to be in ' + + }); + // Milestone task: move issues from closed milestone + if (milestone) { + if (milestoneState === 'closed') { + issueNewMilestone = MILESTONE_BACKLOG_BUGS; + updates.push(applyMilestone(issue, issueNewMilestone)); + } + } + if (issueNewMilestone === MILESTONE_PENDING_TRIAGE) { + if (quartelyUpdate == false) { + quartelyUpdate = true; + updates.push( + applyComment( + issue, + 'This issue seems to be in ' + ' Pending Triage for awhile. ' + - assigneeName + ' Please triage this to ' + - 'an appropriate milestone.')); - } + assigneeName + + ' Please triage this to ' + + 'an appropriate milestone.' + ) + ); } - // if issueType is not null, add correct milestones - if (issueType != null) { - if (issueNewMilestone === MILESTONE_PENDING_TRIAGE || - milestone == null) { - if (issueType === 'Type: Feature Request') { - issueNewMilestone = MILESTONE_NEW_FRS; - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if (issueType === 'Related to: Documentation' || - issueType === 'Type: Design Review' || - issueType === 'Type: Status Update') { - issueNewMilestone = MILESTONE_DOCS_UPDATES; - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if (issueType === 'Type: Bug' || - issueType === 'Related to: Flaky Tests') { - issueNewMilestone = MILESTONE_BACKLOG_BUGS; - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if (milestone == null) { - updates.push(applyMilestone(issue, issueNewMilestone)); - } + } + // if issueType is not null, add correct milestones + if (issueType != null) { + if ( + issueNewMilestone === MILESTONE_PENDING_TRIAGE || + milestone == null + ) { + if (issueType === 'Type: Feature Request') { + issueNewMilestone = MILESTONE_NEW_FRS; + updates.push(applyMilestone(issue, issueNewMilestone)); + } else if ( + issueType === 'Related to: Documentation' || + issueType === 'Type: Design Review' || + issueType === 'Type: Status Update' + ) { + issueNewMilestone = MILESTONE_DOCS_UPDATES; + updates.push(applyMilestone(issue, issueNewMilestone)); + } else if ( + issueType === 'Type: Bug' || + issueType === 'Related to: Flaky Tests' + ) { + issueNewMilestone = MILESTONE_BACKLOG_BUGS; + updates.push(applyMilestone(issue, issueNewMilestone)); + } else if (milestone == null) { + updates.push(applyMilestone(issue, issueNewMilestone)); } - } else if (milestone == null) { - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if (issueNewMilestone === MILESTONE_PRIORITIZED_FRS || - issueNewMilestone === MILESTONE_NEW_FRS) { - updates.push(applyLabel(issue, 'Type: Feature Request')); - } else if (issueNewMilestone === MILESTONE_BACKLOG_BUGS || - milestoneTitle.startsWith('Sprint')) { - updates.push(applyLabel(issue, 'Type: Bug')); } - // Apply default priority if no priority - if (hasPriority == false && - issueNewMilestone != MILESTONE_NEW_FRS && - issueNewMilestone !== MILESTONE_3P_IMPLEMENTATION && - issueNewMilestone !== MILESTONE_PENDING_TRIAGE && - milestone != null) { - updates.push(applyLabel(issue, 'P2: Soon')); - } - // Add comment with missing Category - if (hasCategory == false) { - if (issueNewMilestone === MILESTONE_PENDING_TRIAGE || - issueNewMilestone === MILESTONE_DOCS_UPDATES || - issueNewMilestone == null || - issueNewMilestone === MILESTONE_GREAT_ISSUES) { - if (isDryrun) { - log(colors.green('No comment needed ' - + ' for #' + issue.number)); - } - } else { - updates.push(applyComment(issue, - 'This issue doesn\'t have a category' - + ' which makes it harder for us to keep track of it. ' + - assigneeName + ' Please add an appropriate category.')); + } else if (milestone == null) { + updates.push(applyMilestone(issue, issueNewMilestone)); + } else if ( + issueNewMilestone === MILESTONE_PRIORITIZED_FRS || + issueNewMilestone === MILESTONE_NEW_FRS + ) { + updates.push(applyLabel(issue, 'Type: Feature Request')); + } else if ( + issueNewMilestone === MILESTONE_BACKLOG_BUGS || + milestoneTitle.startsWith('Sprint') + ) { + updates.push(applyLabel(issue, 'Type: Bug')); + } + // Apply default priority if no priority + if ( + hasPriority == false && + issueNewMilestone != MILESTONE_NEW_FRS && + issueNewMilestone !== MILESTONE_3P_IMPLEMENTATION && + issueNewMilestone !== MILESTONE_PENDING_TRIAGE && + milestone != null + ) { + updates.push(applyLabel(issue, 'P2: Soon')); + } + // Add comment with missing Category + if (hasCategory == false) { + if ( + issueNewMilestone === MILESTONE_PENDING_TRIAGE || + issueNewMilestone === MILESTONE_DOCS_UPDATES || + issueNewMilestone == null || + issueNewMilestone === MILESTONE_GREAT_ISSUES + ) { + if (isDryrun) { + log(colors.green('No comment needed for #' + issue.number)); } + } else { + updates.push( + applyComment( + issue, + "This issue doesn't have a category" + + ' which makes it harder for us to keep track of it. ' + + assigneeName + + ' Please add an appropriate category.' + ) + ); } - return Promise.all(updates); - }); + } + return Promise.all(updates); }); - return promise; }); + return promise; + }); } /** @@ -295,12 +344,19 @@ function applyMilestone(issue, milestoneNumber) { issue.milestone = milestoneNumber; if (isDryrun) { - log(colors.green('Milestone applied ' + milestoneNumber + - ' for #' + issue.number)); + log( + colors.green( + 'Milestone applied ' + milestoneNumber + ' for #' + issue.number + ) + ); return; } else { - return createGithubRequest('/issues/' + issue.number,'PATCH', - issue.milestone, 'milestone'); + return createGithubRequest( + '/issues/' + issue.number, + 'PATCH', + issue.milestone, + 'milestone' + ); } } @@ -317,14 +373,16 @@ function applyLabel(issue, label) { 'access_token': GITHUB_ACCESS_TOKEN, }; if (isDryrun) { - log(colors.green('Label applied ' + - label + ' for #' + issue.number)); + log(colors.green('Label applied ' + label + ' for #' + issue.number)); return; } else { - return createGithubRequest('/issues/' + issue.number + '/labels','POST', - [label], 'label'); + return createGithubRequest( + '/issues/' + issue.number + '/labels', + 'POST', + [label], + 'label' + ); } - } /** @@ -344,13 +402,23 @@ function applyComment(issue, comment) { return promise.then(function() { if (isDryrun) { log(colors.blue('waited 2 minutes to avoid gh rate limits')); - log(colors.green('Comment applied after ' + - 'waiting 2 minutes to avoid github rate limits: ' + comment + - ' for #' + issue.number)); + log( + colors.green( + 'Comment applied after ' + + 'waiting 2 minutes to avoid github rate limits: ' + + comment + + ' for #' + + issue.number + ) + ); return; } else { - createGithubRequest('/issues/' + issue.number + '/comments','POST', - comment, 'comment'); + createGithubRequest( + '/issues/' + issue.number + '/comments', + 'POST', + comment, + 'comment' + ); } }); } @@ -359,10 +427,11 @@ function getLastUpdate(issueLastUpdate) { const t = new Date(); const splits = issueLastUpdate.split('-', 3); const exactDay = splits[2].split('T', 1); - const firstDate = Date.UTC(splits[0],splits[1],exactDay[0]); - const secondDate = Date.UTC(t.getFullYear(),t.getMonth() + 1,t.getDate()); - const diff = Math.abs((firstDate.valueOf() - - secondDate.valueOf()) / (24 * 60 * 60 * 1000)); + const firstDate = Date.UTC(splits[0], splits[1], exactDay[0]); + const secondDate = Date.UTC(t.getFullYear(), t.getMonth() + 1, t.getDate()); + const diff = Math.abs( + (firstDate.valueOf() - secondDate.valueOf()) / (24 * 60 * 60 * 1000) + ); return diff; } @@ -406,8 +475,9 @@ module.exports = { processGithubIssues, }; -processGithubIssues.description = 'Automatically updates the labels ' + - 'and milestones of all open issues at github.com/ampproject/amphtml.'; +processGithubIssues.description = + 'Automatically updates the labels ' + + 'and milestones of all open issues at github.com/ampproject/amphtml.'; processGithubIssues.flags = { - dryrun: ' Generate process but don\'t push it out', + dryrun: " Generate process but don't push it out", }; diff --git a/build-system/tasks/release-tagging.js b/build-system/tasks/release-tagging.js index d1a61d9e926f..521be59151e9 100644 --- a/build-system/tasks/release-tagging.js +++ b/build-system/tasks/release-tagging.js @@ -27,14 +27,13 @@ const {GITHUB_ACCESS_TOKEN} = process.env; const gitExec = BBPromise.promisify(git.exec); const isDryrun = argv.dryrun; -const verbose = (argv.verbose || argv.v); +const verbose = argv.verbose || argv.v; const LABELS = { 'canary': 'PR use: In Canary', 'prod': 'PR use: In Production', }; - /** * @param {string} type Either of "canary" or "prod". * @param {string} dir Working dir. @@ -47,19 +46,21 @@ function releaseTagFor(type, dir) { // Fetch tag. let tag; - promise = promise.then(function() { - return githubRequest('/releases'); - }).then(res => { - const array = JSON.parse(res.body); - for (let i = 0; i < array.length; i++) { - const release = array[i]; - const releaseType = release.prerelease ? 'canary' : 'prod'; - if (releaseType == type) { - tag = release.tag_name; - break; + promise = promise + .then(function() { + return githubRequest('/releases'); + }) + .then(res => { + const array = JSON.parse(res.body); + for (let i = 0; i < array.length; i++) { + const release = array[i]; + const releaseType = release.prerelease ? 'canary' : 'prod'; + if (releaseType == type) { + tag = release.tag_name; + break; + } } - } - }); + }); // Checkout tag. promise = promise.then(function() { @@ -72,29 +73,31 @@ function releaseTagFor(type, dir) { // Log. const pullRequests = []; - promise = promise.then(function() { - const date = new Date(); - date.setDate(date.getDate() - 15); - const dateIso = date.toISOString().split('T')[0]; - return gitExec({ - cwd: ampDir, - args: 'log --pretty=oneline --since=' + dateIso, - }); - }).then(function(output) { - const lines = output.split('\n'); - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - const paren = line.lastIndexOf('('); - line = paren != -1 ? line.substring(paren) : ''; - if (!line) { - continue; - } - const match = line.match(/\(\#(\d+)\)/); - if (match && match[1]) { - pullRequests.push(match[1]); + promise = promise + .then(function() { + const date = new Date(); + date.setDate(date.getDate() - 15); + const dateIso = date.toISOString().split('T')[0]; + return gitExec({ + cwd: ampDir, + args: 'log --pretty=oneline --since=' + dateIso, + }); + }) + .then(function(output) { + const lines = output.split('\n'); + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + const paren = line.lastIndexOf('('); + line = paren != -1 ? line.substring(paren) : ''; + if (!line) { + continue; + } + const match = line.match(/\(\#(\d+)\)/); + if (match && match[1]) { + pullRequests.push(match[1]); + } } - } - }); + }); // Update. const label = LABELS[type]; @@ -124,13 +127,11 @@ function applyLabel(pullRequest, label) { if (isDryrun) { return Promise.resolve(); } - return githubRequest( - '/issues/' + pullRequest + '/labels', - 'POST', - [label]).then(function() { + return githubRequest('/issues/' + pullRequest + '/labels', 'POST', [ + label, + ]).then(function() { if (verbose) { - log(colors.green( - 'Label applied ' + label + ' for #' + pullRequest)); + log(colors.green('Label applied ' + label + ' for #' + pullRequest)); } }); } diff --git a/build-system/tasks/runtime-test/helpers.js b/build-system/tasks/runtime-test/helpers.js index 9617e821944d..1b16d6f9e177 100644 --- a/build-system/tasks/runtime-test/helpers.js +++ b/build-system/tasks/runtime-test/helpers.js @@ -40,8 +40,11 @@ const LARGE_REFACTOR_THRESHOLD = 50; function extractCssJsFileMap() { //TODO(estherkim): consolidate arg validation logic if (!fs.existsSync(extensionsCssMapPath)) { - log(red('ERROR:'), 'Could not find the file', - cyan(extensionsCssMapPath) + '.'); + log( + red('ERROR:'), + 'Could not find the file', + cyan(extensionsCssMapPath) + '.' + ); log('Make sure', cyan('gulp css'), 'was run prior to this.'); process.exit(); } @@ -53,8 +56,9 @@ function extractCssJsFileMap() { // Adds an entry that maps a CSS file to a JS file function addCssJsEntry(cssData, cssBinaryName, cssJsFileMap) { - const cssFilePath = `extensions/${cssData['name']}/${cssData['version']}/` + - `${cssBinaryName}.css`; + const cssFilePath = + `extensions/${cssData['name']}/${cssData['version']}/` + + `${cssBinaryName}.css`; const jsFilePath = `build/${cssBinaryName}-${cssData['version']}.css.js`; cssJsFileMap[cssFilePath] = jsFilePath; } @@ -94,8 +98,11 @@ function getAdTypes() { // Add all other ad types const files = fs.readdirSync('./ads/'); for (let i = 0; i < files.length; i++) { - if (path.extname(files[i]) == '.js' - && files[i][0] != '_' && files[i] != 'ads.extern.js') { + if ( + path.extname(files[i]) == '.js' && + files[i][0] != '_' && + files[i] != 'ads.extern.js' + ) { const adType = path.basename(files[i], '.js'); const expanded = namingExceptions[adType]; if (expanded) { @@ -180,18 +187,22 @@ function unitTestsToRun(unitTestPaths) { function shouldRunTest(testFile, srcFiles) { const filesImported = getImports(testFile); - return filesImported.filter(function(file) { - return srcFiles.includes(file); - }).length > 0; + return ( + filesImported.filter(function(file) { + return srcFiles.includes(file); + }).length > 0 + ); } // Retrieves the set of unit tests that should be run // for a set of source files. function getTestsFor(srcFiles) { const allUnitTests = deglob.sync(unitTestPaths); - return allUnitTests.filter(testFile => { - return shouldRunTest(testFile, srcFiles); - }).map(fullPath => path.relative(ROOT_DIR, fullPath)); + return allUnitTests + .filter(testFile => { + return shouldRunTest(testFile, srcFiles); + }) + .map(fullPath => path.relative(ROOT_DIR, fullPath)); } filesChanged.forEach(file => { diff --git a/build-system/tasks/runtime-test/index.js b/build-system/tasks/runtime-test/index.js index b5e358ffac79..9e3f46a7f764 100644 --- a/build-system/tasks/runtime-test/index.js +++ b/build-system/tasks/runtime-test/index.js @@ -45,7 +45,6 @@ const {css} = require('../css'); const {getStdout} = require('../../exec'); const {isTravisBuild} = require('../../travis'); - const {green, yellow, cyan, red} = colors; const batchSize = 4; // Number of Sauce Lab browsers @@ -70,13 +69,15 @@ function getConfig() { return Object.assign({}, karmaDefault, {browsers: ['Edge']}); } if (argv.ie) { - return Object.assign({}, karmaDefault, {browsers: ['IE'], + return Object.assign({}, karmaDefault, { + browsers: ['IE'], customLaunchers: { IeNoAddOns: { base: 'IE', flags: ['-extoff'], }, - }}); + }, + }); } if (argv.chrome_canary && !argv.chrome_flags) { return Object.assign({}, karmaDefault, {browsers: ['ChromeCanary']}); @@ -89,7 +90,8 @@ function getConfig() { const config = Object.assign({}, karmaDefault, { browsers: ['Chrome_flags'], customLaunchers: { - Chrome_flags: { // eslint-disable-line google-camelcase/google-camelcase + Chrome_flags: { + // eslint-disable-line google-camelcase/google-camelcase base: chromeBase, flags: formattedFlagList, }, @@ -98,8 +100,9 @@ function getConfig() { return config; } if (argv.headless) { - return Object.assign({}, karmaDefault, - {browsers: ['Chrome_no_extensions_headless']}); + return Object.assign({}, karmaDefault, { + browsers: ['Chrome_no_extensions_headless'], + }); } if (argv.saucelabs || argv.saucelabs_lite) { if (!process.env.SAUCE_USERNAME) { @@ -110,26 +113,27 @@ function getConfig() { } // Browser names are defined in `karma.conf.js`. - saucelabsBrowsers = argv.saucelabs ? - // With --saucelabs, integration tests are run on this set of browsers. - [ - 'SL_Chrome', - 'SL_Firefox', - 'SL_Edge_17', - 'SL_Safari_12', - 'SL_IE_11', - // TODO(amp-infra): Evaluate and add more platforms here. - //'SL_Chrome_Android_7', - //'SL_iOS_11', - //'SL_iOS_12', - 'SL_Chrome_Beta', - 'SL_Firefox_Beta', - ] : [ - // With --saucelabs_lite, a subset of the unit tests are run. - // Only browsers that support chai-as-promised may be included below. - // TODO(rsimha): Add more browsers to this list. #6039. - 'SL_Safari_12', - ]; + saucelabsBrowsers = argv.saucelabs + ? // With --saucelabs, integration tests are run on this set of browsers. + [ + 'SL_Chrome', + 'SL_Firefox', + 'SL_Edge_17', + 'SL_Safari_12', + 'SL_IE_11', + // TODO(amp-infra): Evaluate and add more platforms here. + //'SL_Chrome_Android_7', + //'SL_iOS_11', + //'SL_iOS_12', + 'SL_Chrome_Beta', + 'SL_Firefox_Beta', + ] + : [ + // With --saucelabs_lite, a subset of the unit tests are run. + // Only browsers that support chai-as-promised may be included below. + // TODO(rsimha): Add more browsers to this list. #6039. + 'SL_Safari_12', + ]; return Object.assign({}, karmaDefault, { reporters: ['super-dots', 'saucelabs', 'karmaSimpleReporter'], @@ -152,47 +156,74 @@ function printArgvMessages() { saucelabs: 'Running integration tests on Sauce Labs browsers.', saucelabs_lite: 'Running tests on a subset of Sauce Labs browsers.', // eslint-disable-line google-camelcase/google-camelcase nobuild: 'Skipping build.', - watch: 'Enabling watch mode. Editing and saving a file will cause the' + - ' tests for that file to be re-run in the same browser instance.', + watch: + 'Enabling watch mode. Editing and saving a file will cause the' + + ' tests for that file to be re-run in the same browser instance.', verbose: 'Enabling verbose mode. Expect lots of output!', testnames: 'Listing the names of all tests being run.', files: 'Running tests in the file(s): ' + cyan(argv.files), - integration: 'Running only the integration tests. Prerequisite: ' + - cyan('gulp build'), + integration: + 'Running only the integration tests. Prerequisite: ' + cyan('gulp build'), unit: 'Running only the unit tests. Prerequisite: ' + cyan('gulp css'), a4a: 'Running only A4A tests.', compiled: 'Running tests against minified code.', - grep: 'Only running tests that match the pattern "' + - cyan(argv.grep) + '".', + grep: + 'Only running tests that match the pattern "' + cyan(argv.grep) + '".', coverage: 'Running tests in code coverage mode.', headless: 'Running tests in a headless Chrome window.', - 'local-changes': 'Running unit tests directly affected by the files' + - ' changed in the local branch.', + 'local-changes': + 'Running unit tests directly affected by the files' + + ' changed in the local branch.', }; if (argv.chrome_flags) { - log(green('Launching'), cyan(chromeBase), green('with flags'), - cyan(formattedFlagList)); + log( + green('Launching'), + cyan(chromeBase), + green('with flags'), + cyan(formattedFlagList) + ); } if (!isTravisBuild()) { - log(green('Run'), cyan('gulp help'), - green('to see a list of all test flags.')); - log(green('⤷ Use'), cyan('--nohelp'), - green('to silence these messages.')); - if (!argv.unit && !argv.integration && !argv.files && !argv.a4a && - !argv['local-changes']) { + log( + green('Run'), + cyan('gulp help'), + green('to see a list of all test flags.') + ); + log(green('⤷ Use'), cyan('--nohelp'), green('to silence these messages.')); + if ( + !argv.unit && + !argv.integration && + !argv.files && + !argv.a4a && + !argv['local-changes'] + ) { log(green('Running all tests.')); - log(green('⤷ Use'), cyan('--unit'), green('or'), cyan('--integration'), - green('to run just the unit tests or integration tests.')); - log(green('⤷ Use'), cyan('--local-changes'), - green('to run unit tests from files commited to the local branch.')); + log( + green('⤷ Use'), + cyan('--unit'), + green('or'), + cyan('--integration'), + green('to run just the unit tests or integration tests.') + ); + log( + green('⤷ Use'), + cyan('--local-changes'), + green('to run unit tests from files commited to the local branch.') + ); } if (!argv.testnames && !argv.files && !argv['local-changes']) { - log(green('⤷ Use'), cyan('--testnames'), - green('to see the names of all tests being run.')); + log( + green('⤷ Use'), + cyan('--testnames'), + green('to see the names of all tests being run.') + ); } if (!argv.headless) { - log(green('⤷ Use'), cyan('--headless'), - green('to run tests in a headless Chrome window.')); + log( + green('⤷ Use'), + cyan('--headless'), + green('to run tests in a headless Chrome window.') + ); } if (!argv.compiled) { log(green('Running tests against unminified code.')); @@ -211,12 +242,15 @@ function printArgvMessages() { */ async function runTests() { if (!argv.integration && process.env.AMPSAUCE_REPO) { - console./* OK*/info('Deactivated for ampsauce repo'); + console./* OK*/ info('Deactivated for ampsauce repo'); } if (argv.saucelabs && !argv.integration) { - log(red('ERROR:'), 'Only integration tests may be run on the full set of', - 'Sauce Labs browsers'); + log( + red('ERROR:'), + 'Only integration tests may be run on the full set of', + 'Sauce Labs browsers' + ); log('Use', cyan('--saucelabs'), 'with', cyan('--integration')); process.exit(); } @@ -240,8 +274,10 @@ async function runTests() { transform: [['babelify', {global: true}]], configure: function(bundle) { bundle.on('prebundle', function() { - log(green('Transforming tests with'), - cyan('browserify') + green('...')); + log( + green('Transforming tests with'), + cyan('browserify') + green('...') + ); }); bundle.on('transform', function(tr) { if (tr instanceof babelify) { @@ -264,14 +300,18 @@ async function runTests() { } } else if (argv['local-changes']) { if (isLargeRefactor()) { - log(green('INFO:'), - 'Skipping tests on local changes because this is a large refactor.'); + log( + green('INFO:'), + 'Skipping tests on local changes because this is a large refactor.' + ); return reportTestSkipped(); } const testsToRun = unitTestsToRun(config.unitTestPaths); if (testsToRun.length == 0) { - log(green('INFO:'), - 'No unit tests were directly affected by local changes.'); + log( + green('INFO:'), + 'No unit tests were directly affected by local changes.' + ); return reportTestSkipped(); } else { log(green('INFO:'), 'Running the following unit tests:'); @@ -283,14 +323,20 @@ async function runTests() { c.files = c.files.concat(config.commonUnitTestPaths, testsToRun); } else if (argv.integration) { c.files = c.files.concat( - config.commonIntegrationTestPaths, config.integrationTestPaths); + config.commonIntegrationTestPaths, + config.integrationTestPaths + ); } else if (argv.unit) { if (argv.saucelabs_lite) { c.files = c.files.concat( - config.commonUnitTestPaths, config.unitTestOnSaucePaths); + config.commonUnitTestPaths, + config.unitTestOnSaucePaths + ); } else { c.files = c.files.concat( - config.commonUnitTestPaths, config.unitTestPaths); + config.commonUnitTestPaths, + config.unitTestPaths + ); } } else if (argv.a4a) { c.files = c.files.concat(config.a4aTestPaths); @@ -301,14 +347,14 @@ async function runTests() { // c.client is available in test browser via window.parent.karma.config c.client.amp = { useCompiledJs: !!argv.compiled, - saucelabs: (!!argv.saucelabs) || (!!argv.saucelabs_lite), + saucelabs: !!argv.saucelabs || !!argv.saucelabs_lite, singlePass: !!argv.single_pass, adTypes: getAdTypes(), mochaTimeout: c.client.mocha.timeout, propertiesObfuscated: !!argv.single_pass, testServerPort: c.client.testServerPort, - testOnIe: !!argv.ie || - (!!argv.saucelabs && saucelabsBrowsers.includes('SL_IE_11')), + testOnIe: + !!argv.ie || (!!argv.saucelabs && saucelabsBrowsers.includes('SL_IE_11')), }; if (argv.compiled) { @@ -325,19 +371,25 @@ async function runTests() { if (argv.coverage) { c.browserify.transform = [ - ['babelify', { - plugins: [ - ['babel-plugin-istanbul', { - exclude: [ - './ads/**/*.js', - './third_party/**/*.js', - './test/**/*.js', - './extensions/**/test/**/*.js', - './testing/**/*.js', + [ + 'babelify', + { + plugins: [ + [ + 'babel-plugin-istanbul', + { + exclude: [ + './ads/**/*.js', + './third_party/**/*.js', + './test/**/*.js', + './extensions/**/test/**/*.js', + './testing/**/*.js', + ], + }, ], - }], - ], - }], + ], + }, + ], ]; c.plugins.push('karma-coverage-istanbul-reporter'); c.reporters = c.reporters.concat(['coverage-istanbul']); @@ -347,17 +399,27 @@ async function runTests() { }; } - const server = gulp.src(process.cwd(), {base: '.'}).pipe(webserver({ - port: karmaDefault.client.testServerPort, - host: 'localhost', - directoryListing: true, - middleware: [app], - }).on('kill', function() { - log(yellow('Shutting down test responses server on ' - + `localhost:${karmaDefault.client.testServerPort}`)); - })); - log(yellow('Started test responses server on ' - + `localhost:${karmaDefault.client.testServerPort}`)); + const server = gulp.src(process.cwd(), {base: '.'}).pipe( + webserver({ + port: karmaDefault.client.testServerPort, + host: 'localhost', + directoryListing: true, + middleware: [app], + }).on('kill', function() { + log( + yellow( + 'Shutting down test responses server on ' + + `localhost:${karmaDefault.client.testServerPort}` + ) + ); + }) + ); + log( + yellow( + 'Started test responses server on ' + + `localhost:${karmaDefault.client.testServerPort}` + ) + ); // Listen for Ctrl + C to cancel testing const handlerProcess = createCtrlcHandler('test'); @@ -387,8 +449,9 @@ async function runTests() { if (processExitCode != 0) { log( - red('ERROR:'), - yellow(`Karma test failed with exit code ${processExitCode}`)); + red('ERROR:'), + yellow(`Karma test failed with exit code ${processExitCode}`) + ); process.exitCode = processExitCode; } @@ -406,30 +469,42 @@ async function runTests() { const browsers = {stable: [], beta: []}; for (const browserId of saucelabsBrowsers) { browsers[ - browserId.toLowerCase().endsWith('_beta') - ? 'beta' : 'stable'] - .push(browserId); + browserId.toLowerCase().endsWith('_beta') ? 'beta' : 'stable' + ].push(browserId); } if (browsers.stable.length) { const allBatchesExitCodes = await runTestInBatchesWithBrowsers( - 'stable', browsers.stable); + 'stable', + browsers.stable + ); if (allBatchesExitCodes) { - log(yellow('Some tests have failed on'), cyan('stable'), - yellow('browsers, so skipping running them on'), cyan('beta'), - yellow('browsers.')); + log( + yellow('Some tests have failed on'), + cyan('stable'), + yellow('browsers, so skipping running them on'), + cyan('beta'), + yellow('browsers.') + ); return allBatchesExitCodes; } } if (browsers.beta.length) { const allBatchesExitCodes = await runTestInBatchesWithBrowsers( - 'beta', browsers.beta); + 'beta', + browsers.beta + ); if (allBatchesExitCodes) { - log(yellow('Some tests have failed on'), cyan('beta'), - yellow('browsers.')); - log(yellow('This is not currently a fatal error, but will become an'), - yellow('error once the beta browsers are released as next stable'), - yellow('version!')); + log( + yellow('Some tests have failed on'), + cyan('beta'), + yellow('browsers.') + ); + log( + yellow('This is not currently a fatal error, but will become an'), + yellow('error once the beta browsers are released as next stable'), + yellow('version!') + ); } } @@ -450,13 +525,22 @@ async function runTests() { let endIndex = batchSize; const batchExitCodes = []; - log(green('Running tests on'), cyan(browsers.length), - green('Sauce Labs'), cyan(batchName), green('browser(s)...')); + log( + green('Running tests on'), + cyan(browsers.length), + green('Sauce Labs'), + cyan(batchName), + green('browser(s)...') + ); while (startIndex < endIndex) { const configBatch = Object.assign({}, c); configBatch.browsers = browsers.slice(startIndex, endIndex); - log(green('Batch'), cyan(`#${batch}`) + green(': Running tests on'), - cyan(configBatch.browsers.length), green('Sauce Labs browser(s)...')); + log( + green('Batch'), + cyan(`#${batch}`) + green(': Running tests on'), + cyan(configBatch.browsers.length), + green('Sauce Labs browser(s)...') + ); batchExitCodes.push(await createKarmaServer(configBatch)); startIndex = batch * batchSize; batch++; @@ -473,79 +557,101 @@ async function runTests() { */ function createKarmaServer(configBatch) { let resolver; - const deferred = new Promise(resolverIn => {resolver = resolverIn;}); + const deferred = new Promise(resolverIn => { + resolver = resolverIn; + }); new Karma(configBatch, function(exitCode) { if (argv.coverage) { if (isTravisBuild()) { const codecovCmd = - './node_modules/.bin/codecov --file=test/coverage/lcov.info'; + './node_modules/.bin/codecov --file=test/coverage/lcov.info'; let flags = ''; if (argv.unit) { flags = ' --flags=unit_tests'; } else if (argv.integration) { flags = ' --flags=integration_tests'; } - log(green('INFO:'), 'Uploading code coverage report to', - cyan('https://codecov.io/gh/ampproject/amphtml'), 'by running', - cyan(codecovCmd + flags) + '...'); + log( + green('INFO:'), + 'Uploading code coverage report to', + cyan('https://codecov.io/gh/ampproject/amphtml'), + 'by running', + cyan(codecovCmd + flags) + '...' + ); const output = getStdout(codecovCmd + flags); const viewReportPrefix = 'View report at: '; const viewReport = output.match(`${viewReportPrefix}.*`); if (viewReport && viewReport.length > 0) { - log(green('INFO:'), viewReportPrefix + - cyan(viewReport[0].replace(viewReportPrefix, ''))); + log( + green('INFO:'), + viewReportPrefix + + cyan(viewReport[0].replace(viewReportPrefix, '')) + ); } else { - log(yellow('WARNING:'), - 'Code coverage report upload may have failed:\n', - yellow(output)); + log( + yellow('WARNING:'), + 'Code coverage report upload may have failed:\n', + yellow(output) + ); } } else { const coverageReportUrl = - 'file://' + path.resolve('test/coverage/index.html'); - log(green('INFO:'), 'Generated code coverage report at', - cyan(coverageReportUrl)); + 'file://' + path.resolve('test/coverage/index.html'); + log( + green('INFO:'), + 'Generated code coverage report at', + cyan(coverageReportUrl) + ); opn(coverageReportUrl, {wait: false}); } } resolver(exitCode); - }).on('run_start', function() { - if (!argv.saucelabs && !argv.saucelabs_lite) { - log(green('Running tests locally...')); - } - reportTestStarted(); - }).on('browsers_ready', function() { - console./*OK*/log('\n'); - log(green('Done. Running tests...')); - }).on('browser_complete', function(browser) { - const result = browser.lastResult; - // Prevent cases where Karma detects zero tests and still passes. #16851. - if (result.total == 0) { - log(red('ERROR: Zero tests detected by Karma. Something went wrong.')); - reportTestErrored().finally(() => { - if (!argv.watch) { - process.exit(1); - } - }); - } - // Print a summary for each browser as soon as tests complete. - let message = `${browser.name}: Executed ` + + }) + .on('run_start', function() { + if (!argv.saucelabs && !argv.saucelabs_lite) { + log(green('Running tests locally...')); + } + reportTestStarted(); + }) + .on('browsers_ready', function() { + console./*OK*/ log('\n'); + log(green('Done. Running tests...')); + }) + .on('browser_complete', function(browser) { + const result = browser.lastResult; + // Prevent cases where Karma detects zero tests and still passes. #16851. + if (result.total == 0) { + log( + red('ERROR: Zero tests detected by Karma. Something went wrong.') + ); + reportTestErrored().finally(() => { + if (!argv.watch) { + process.exit(1); + } + }); + } + // Print a summary for each browser as soon as tests complete. + let message = + `${browser.name}: Executed ` + `${result.success + result.failed} of ${result.total} ` + `(Skipped ${result.skipped}) `; - if (result.failed === 0) { - message += green('SUCCESS'); - } else { - message += red(result.failed + ' FAILED'); - } - message += '\n'; - console./*OK*/log('\n'); - log(message); - }).on('run_complete', (browsers, results) => { - if (results.error) { - reportTestErrored(); - } else { - reportTestFinished(results.success, results.failed); - } - }).start(); + if (result.failed === 0) { + message += green('SUCCESS'); + } else { + message += red(result.failed + ' FAILED'); + } + message += '\n'; + console./*OK*/ log('\n'); + log(message); + }) + .on('run_complete', (browsers, results) => { + if (results.error) { + reportTestErrored(); + } else { + reportTestFinished(results.success, results.failed); + } + }) + .start(); return deferred; } } @@ -579,25 +685,26 @@ test.flags = { 'testnames': ' Lists the name of each test being run', 'watch': ' Watches for changes in files, runs corresponding test(s)', 'saucelabs': ' Runs integration tests on saucelabs (requires setup)', - 'saucelabs_lite': ' Runs tests on a subset of saucelabs browsers ' + - '(requires setup)', + 'saucelabs_lite': + ' Runs tests on a subset of saucelabs browsers (requires setup)', 'safari': ' Runs tests on Safari', 'firefox': ' Runs tests on Firefox', 'edge': ' Runs tests on Edge', 'ie': ' Runs tests on IE', 'chrome_canary': 'Runs tests on Chrome Canary', - 'chrome_flags': - 'Uses the given flags to launch Chrome', + 'chrome_flags': 'Uses the given flags to launch Chrome', 'unit': ' Run only unit tests.', 'integration': ' Run only integration tests.', - 'compiled': ' Changes integration tests to use production JS ' + - 'binaries for execution', + 'compiled': + ' Changes integration tests to use production JS ' + + 'binaries for execution', 'grep': ' Runs tests that match the pattern', 'files': ' Runs tests for specific files', 'nohelp': ' Silence help messages that are printed prior to test run', 'a4a': ' Runs all A4A tests', 'coverage': ' Run tests in code coverage mode', 'headless': ' Run tests in a headless Chrome window', - 'local-changes': ' Run unit tests directly affected by the files ' + - 'changed in the local branch', + 'local-changes': + ' Run unit tests directly affected by the files ' + + 'changed in the local branch', }; diff --git a/build-system/tasks/runtime-test/status-report.js b/build-system/tasks/runtime-test/status-report.js index 4fb2d9772e32..b5ff7b4de40a 100644 --- a/build-system/tasks/runtime-test/status-report.js +++ b/build-system/tasks/runtime-test/status-report.js @@ -71,19 +71,33 @@ function postReport(type, action) { if (type !== null && isTravisPullRequestBuild()) { const commitHash = gitCommitHash(); const postUrl = `${reportBaseUrl}/${commitHash}/${type}/${action}`; - return requestPromise.post(postUrl) - .then(body => { - log(green('INFO:'), 'reported', cyan(`${type}/${action}`), - 'to the test-status GitHub App'); - if (body.length > 0) { - log(green('INFO:'), 'response from test-status was', - cyan(body.substr(0, 100))); - } - }).catch(error => { - log(yellow('WARNING:'), 'failed to report', cyan(`${type}/${action}`), - 'to the test-status GitHub App:\n', error.message.substr(0, 100)); - return; - }); + return requestPromise + .post(postUrl) + .then(body => { + log( + green('INFO:'), + 'reported', + cyan(`${type}/${action}`), + 'to the test-status GitHub App' + ); + if (body.length > 0) { + log( + green('INFO:'), + 'response from test-status was', + cyan(body.substr(0, 100)) + ); + } + }) + .catch(error => { + log( + yellow('WARNING:'), + 'failed to report', + cyan(`${type}/${action}`), + 'to the test-status GitHub App:\n', + error.message.substr(0, 100) + ); + return; + }); } return Promise.resolve(); } @@ -108,7 +122,8 @@ async function reportAllExpectedTests(buildTargets) { for (const [type, subTypes] of TEST_TYPE_SUBTYPES) { const testTypeBuildTargets = TEST_TYPE_BUILD_TARGETS.get(type); const action = testTypeBuildTargets.some(target => buildTargets.has(target)) - ? 'queued' : 'skipped'; + ? 'queued' + : 'skipped'; for (const subType of subTypes) { await postReport(`${type}/${subType}`, action); } diff --git a/build-system/tasks/serve.js b/build-system/tasks/serve.js index 5caec476b015..dd1dc098ac4b 100644 --- a/build-system/tasks/serve.js +++ b/build-system/tasks/serve.js @@ -99,7 +99,6 @@ serve.flags = { 'port': ' Specifies alternative port (default: 8000)', 'https': ' Use HTTPS server (default: false)', 'quiet': ' Do not log HTTP requests (default: false)', - 'cache': ' Make local resources cacheable by the browser ' + - '(default: false)', + 'cache': ' Make local resources cacheable by the browser (default: false)', 'inspect': ' Run nodemon in `inspect` mode', }; diff --git a/build-system/tasks/size.js b/build-system/tasks/size.js index dce0d947e7f6..58bc68bb42d0 100644 --- a/build-system/tasks/size.js +++ b/build-system/tasks/size.js @@ -24,7 +24,6 @@ const prettyBytes = require('pretty-bytes'); const table = require('text-table'); const through = require('through2'); - const tempFolderName = '__size-temp'; const MIN_FILE_SIZE_POS = 0; @@ -97,11 +96,19 @@ function normalizeRows(rows) { // normalize integration.js normalizeRow(rows, 'current-min/f.js', 'current/integration.js', true); - normalizeRow(rows, 'current-min/ampcontext-v0.js', - 'current/ampcontext-lib.js', true); + normalizeRow( + rows, + 'current-min/ampcontext-v0.js', + 'current/ampcontext-lib.js', + true + ); - normalizeRow(rows, 'current-min/iframe-transport-client-v0.js', - 'current/iframe-transport-client-lib.js', true); + normalizeRow( + rows, + 'current-min/iframe-transport-client-v0.js', + 'current/iframe-transport-client-lib.js', + true + ); // normalize alp.js normalizeRow(rows, 'alp.js', 'alp.max.js', true); @@ -142,13 +149,17 @@ function normalizeRows(rows) { */ function normalizeExtension(rows, filename) { const isMax = /\.max\.js$/.test(filename); - const counterpartName = filename.replace(/(v0\/.*?)(\.max)?(\.js)$/, - function(full, grp1, grp2, grp3) { - if (isMax) { - return grp1 + grp3; - } - return full; - }); + const counterpartName = filename.replace(/(v0\/.*?)(\.max)?(\.js)$/, function( + full, + grp1, + grp2, + grp3 + ) { + if (isMax) { + return grp1 + grp3; + } + return full; + }); if (isMax) { normalizeRow(rows, counterpartName, filename, false); @@ -198,7 +209,8 @@ function onFileThroughEnd(rows, cb) { rows = normalizeRows(rows); rows.unshift.apply(rows, tableHeaders); const tbl = table(rows, tableOptions); - console/* OK*/.log(tbl); + console /* OK*/ + .log(tbl); fs.writeFileSync('test/size.txt', tbl); cb(); } @@ -211,8 +223,8 @@ function onFileThroughEnd(rows, cb) { function sizer() { const rows = []; return through.obj( - onFileThrough.bind(null, rows), - onFileThroughEnd.bind(null, rows) + onFileThrough.bind(null, rows), + onFileThroughEnd.bind(null, rows) ); } @@ -222,16 +234,17 @@ function sizer() { * output from the process. */ function size() { - gulp.src([ - 'dist/**/*.js', - '!dist/**/*-latest.js', - '!dist/**/*check-types.js', - '!dist/**/amp-viewer-host.max.js', - 'dist.3p/{current,current-min}/**/*.js', - ]) - .pipe(sizer()) - .pipe(gulp.dest(tempFolderName)) - .on('end', del.bind(null, [tempFolderName])); + gulp + .src([ + 'dist/**/*.js', + '!dist/**/*-latest.js', + '!dist/**/*check-types.js', + '!dist/**/amp-viewer-host.max.js', + 'dist.3p/{current,current-min}/**/*.js', + ]) + .pipe(sizer()) + .pipe(gulp.dest(tempFolderName)) + .on('end', del.bind(null, [tempFolderName])); } module.exports = { diff --git a/build-system/tasks/todos.js b/build-system/tasks/todos.js index 3965363e3c2b..2a2896b8503a 100644 --- a/build-system/tasks/todos.js +++ b/build-system/tasks/todos.js @@ -28,7 +28,6 @@ const {GITHUB_ACCESS_TOKEN} = process.env; /** @type {!Object>} */ const issueCache = Object.create(null); - /** * Test if a file's contents contains closed TODOs. * @@ -56,17 +55,18 @@ function findClosedTodosInFile(file) { if (promises.length == 0) { return Promise.resolve(0); } - return Promise.all(promises).then(results => { - return results.reduce(function(acc, v) { - return acc + v; - }, 0); - }).catch(function(error) { - log(colors.red('Failed in', file.path, error, error.stack)); - return 0; - }); + return Promise.all(promises) + .then(results => { + return results.reduce(function(acc, v) { + return acc + v; + }, 0); + }) + .catch(function(error) { + log(colors.red('Failed in', file.path, error, error.stack)); + return 0; + }); } - /** * @param {!File} file file is a vinyl file object * @param {string} issueId @@ -77,18 +77,18 @@ function reportClosedIssue(file, issueId, todo) { if (issueCache[issueId] !== undefined) { return issueCache[issueId]; } - return issueCache[issueId] = githubRequest('/issues/' + issueId) - .then(response => { - const issue = JSON.parse(response.body); - const value = issue.state == 'closed' ? 1 : 0; - if (value) { - log(colors.red(todo, 'in', file.path)); - } - return value; - }); + return (issueCache[issueId] = githubRequest('/issues/' + issueId).then( + response => { + const issue = JSON.parse(response.body); + const value = issue.state == 'closed' ? 1 : 0; + if (value) { + log(colors.red(todo, 'in', file.path)); + } + return value; + } + )); } - /** * @param {string} path * @param {string=} opt_method @@ -116,25 +116,27 @@ function githubRequest(path, opt_method, opt_data) { return request(options); } - /** * todos:find-closed task. */ function todosFindClosed() { let foundCount = 0; - return gulp.src(srcGlobs) - .pipe(through2.obj(function(file, enc, cb) { + return gulp + .src(srcGlobs) + .pipe( + through2.obj(function(file, enc, cb) { findClosedTodosInFile(file).then(function(count) { foundCount += count; cb(); }); - })) - .on('end', function() { - if (foundCount > 0) { - log(colors.red('Found closed TODOs: ', foundCount)); - process.exit(1); - } - }); + }) + ) + .on('end', function() { + if (foundCount > 0) { + log(colors.red('Found closed TODOs: ', foundCount)); + process.exit(1); + } + }); } module.exports = { diff --git a/build-system/tasks/update-packages.js b/build-system/tasks/update-packages.js index de22c636ced2..3d96d68f5f1f 100644 --- a/build-system/tasks/update-packages.js +++ b/build-system/tasks/update-packages.js @@ -31,8 +31,7 @@ const yarnExecutable = 'npx yarn'; * @param {string} file Contents to write */ function writeIfUpdated(patchedName, file) { - if (!fs.existsSync(patchedName) || - fs.readFileSync(patchedName) != file) { + if (!fs.existsSync(patchedName) || fs.readFileSync(patchedName) != file) { fs.writeFileSync(patchedName, file); if (!isTravisBuild()) { log(colors.green('Patched'), colors.cyan(patchedName)); @@ -65,11 +64,11 @@ function replaceInFile(filePath, newFilePath, ...args) { */ function patchWebAnimations() { // Copies web-animations-js into a new file that has an export. - const patchedName = 'node_modules/web-animations-js/' + - 'web-animations.install.js'; - let file = fs.readFileSync( - 'node_modules/web-animations-js/' + - 'web-animations.min.js').toString(); + const patchedName = + 'node_modules/web-animations-js/web-animations.install.js'; + let file = fs + .readFileSync('node_modules/web-animations-js/web-animations.min.js') + .toString(); // Replace |requestAnimationFrame| with |window|. file = file.replace(/requestAnimationFrame/g, function(a, b) { if (file.charAt(b - 1) == '.') { @@ -88,11 +87,12 @@ function patchWebAnimations() { file = file.replace(/this\._isFinished\s*=\s*\!0,/, ''); // Wrap the contents inside the install function. - file = 'export function installWebAnimations(window) {\n' + - 'var document = window.document;\n' + - file + - '\n' + - '}\n'; + file = + 'export function installWebAnimations(window) {\n' + + 'var document = window.document;\n' + + file + + '\n' + + '}\n'; writeIfUpdated(patchedName, file); } @@ -107,14 +107,15 @@ function patchRegisterElement() { // compilation: https://github.com/google/closure-compiler/issues/1831 const dir = 'node_modules/document-register-element/build/'; replaceInFile( - dir + 'document-register-element.node.js', - dir + 'document-register-element.patched.js', - // Elimate the immediate side effect. - 'installCustomElements(global);', - '', - // Replace CJS export with ES6 export. - 'module.exports = installCustomElements;', - 'export {installCustomElements};'); + dir + 'document-register-element.node.js', + dir + 'document-register-element.patched.js', + // Elimate the immediate side effect. + 'installCustomElements(global);', + '', + // Replace CJS export with ES6 export. + 'module.exports = installCustomElements;', + 'export {installCustomElements};' + ); } /** @@ -124,8 +125,9 @@ function patchRegisterElement() { function patchWorkerDom() { const dir = 'node_modules/@ampproject/worker-dom/dist/'; fs.copyFileSync( - dir + 'unminified.index.safe.mjs', - dir + 'unminified.index.safe.mjs.patched.js'); + dir + 'unminified.index.safe.mjs', + dir + 'unminified.index.safe.mjs.patched.js' + ); } /** @@ -149,8 +151,10 @@ function transformEs6Packages() { const updatedPackageJson = JSON.stringify(packageJson, null, 2); fs.writeFileSync(packageJsonFile, updatedPackageJson, 'utf8'); if (!isTravisBuild()) { - log(colors.green('Enabled ES6 transforms for runtime dependency'), - colors.cyan(es6Package)); + log( + colors.green('Enabled ES6 transforms for runtime dependency'), + colors.cyan(es6Package) + ); } } }); @@ -177,9 +181,13 @@ function installCustomEslintRules() { function runYarnCheck() { const integrityCmd = yarnExecutable + ' check --integrity'; if (getStderr(integrityCmd).trim() != '') { - log(colors.yellow('WARNING:'), 'The packages in', - colors.cyan('node_modules'), 'do not match', - colors.cyan('package.json.')); + log( + colors.yellow('WARNING:'), + 'The packages in', + colors.cyan('node_modules'), + 'do not match', + colors.cyan('package.json.') + ); const verifyTreeCmd = yarnExecutable + ' check --verify-tree'; exec(verifyTreeCmd); log('Running', colors.cyan('yarn'), 'to update packages...'); @@ -190,8 +198,11 @@ function runYarnCheck() { */ execOrDie(`${yarnExecutable} install --production=false`); // Stop execution when Ctrl + C is detected. } else { - log(colors.green('All packages in'), - colors.cyan('node_modules'), colors.green('are up to date.')); + log( + colors.green('All packages in'), + colors.cyan('node_modules'), + colors.green('are up to date.') + ); } } @@ -227,4 +238,4 @@ module.exports = { }; updatePackages.description = - 'Runs yarn if node_modules is out of date, and applies custom patches'; + 'Runs yarn if node_modules is out of date, and applies custom patches'; diff --git a/build-system/tasks/visual-diff/helpers.js b/build-system/tasks/visual-diff/helpers.js index 09e698619e56..881794b8fa61 100644 --- a/build-system/tasks/visual-diff/helpers.js +++ b/build-system/tasks/visual-diff/helpers.js @@ -23,7 +23,7 @@ const {isTravisBuild} = require('../../travis'); const CSS_SELECTOR_RETRY_MS = 200; const CSS_SELECTOR_RETRY_ATTEMPTS = 50; const CSS_SELECTOR_TIMEOUT_MS = - CSS_SELECTOR_RETRY_MS * CSS_SELECTOR_RETRY_ATTEMPTS; + CSS_SELECTOR_RETRY_MS * CSS_SELECTOR_RETRY_ATTEMPTS; const HTML_ESCAPE_CHARS = { '&': '&', @@ -89,15 +89,23 @@ function log(mode, ...messages) { * @throws {Error} an encountered error. */ async function verifySelectorsInvisible(page, testName, selectors) { - log('verbose', 'Waiting for invisibility of all:', - colors.cyan(selectors.join(', '))); + log( + 'verbose', + 'Waiting for invisibility of all:', + colors.cyan(selectors.join(', ')) + ); try { - await Promise.all(selectors.map( - selector => waitForElementVisibility(page, selector, {hidden: true}))); + await Promise.all( + selectors.map(selector => + waitForElementVisibility(page, selector, {hidden: true}) + ) + ); } catch (e) { - throw new Error(`${colors.cyan(testName)} | An element with the CSS ` + + throw new Error( + `${colors.cyan(testName)} | An element with the CSS ` + `selector ${colors.cyan(e.message)} is still visible after ` + - `${CSS_SELECTOR_TIMEOUT_MS} ms`); + `${CSS_SELECTOR_TIMEOUT_MS} ms` + ); } } @@ -111,25 +119,39 @@ async function verifySelectorsInvisible(page, testName, selectors) { * @throws {Error} an encountered error. */ async function verifySelectorsVisible(page, testName, selectors) { - log('verbose', 'Waiting for existence of all:', - colors.cyan(selectors.join(', '))); + log( + 'verbose', + 'Waiting for existence of all:', + colors.cyan(selectors.join(', ')) + ); try { await Promise.all( - selectors.map(selector => waitForSelectorExistence(page, selector))); + selectors.map(selector => waitForSelectorExistence(page, selector)) + ); } catch (e) { - throw new Error(`${colors.cyan(testName)} | The CSS selector ` + - `${colors.cyan(e.message)} does not match any elements in the page`); + throw new Error( + `${colors.cyan(testName)} | The CSS selector ` + + `${colors.cyan(e.message)} does not match any elements in the page` + ); } - log('verbose', 'Waiting for visibility of all:', - colors.cyan(selectors.join(', '))); + log( + 'verbose', + 'Waiting for visibility of all:', + colors.cyan(selectors.join(', ')) + ); try { - await Promise.all(selectors.map( - selector => waitForElementVisibility(page, selector, {visible: true}))); + await Promise.all( + selectors.map(selector => + waitForElementVisibility(page, selector, {visible: true}) + ) + ); } catch (e) { - throw new Error(`${colors.cyan(testName)} | An element with the CSS ` + + throw new Error( + `${colors.cyan(testName)} | An element with the CSS ` + `selector ${colors.cyan(e.message)} is still invisible after ` + - `${CSS_SELECTOR_TIMEOUT_MS} ms`); + `${CSS_SELECTOR_TIMEOUT_MS} ms` + ); } } @@ -142,10 +164,15 @@ async function verifySelectorsVisible(page, testName, selectors) { */ async function waitForLoaderDots(page, testName) { const allLoaderDotsGone = await waitForElementVisibility( - page, '.i-amphtml-loader-dot', {hidden: true}); + page, + '.i-amphtml-loader-dot', + {hidden: true} + ); if (!allLoaderDotsGone) { - throw new Error(`${colors.cyan(testName)} still has the AMP loader dot ` + - `after ${CSS_SELECTOR_TIMEOUT_MS} ms`); + throw new Error( + `${colors.cyan(testName)} still has the AMP loader dot ` + + `after ${CSS_SELECTOR_TIMEOUT_MS} ms` + ); } } @@ -165,8 +192,11 @@ async function waitForElementVisibility(page, selector, options) { const waitForVisible = Boolean(options['visible']); const waitForHidden = Boolean(options['hidden']); if (waitForVisible == waitForHidden) { - log('fatal', 'waitForElementVisibility must be called with exactly one of', - "'visible' or 'hidden' set to true."); + log( + 'fatal', + 'waitForElementVisibility must be called with exactly one of', + "'visible' or 'hidden' set to true." + ); } let attempt = 0; @@ -175,24 +205,36 @@ async function waitForElementVisibility(page, selector, options) { for (const elementHandle of await page.$$(selector)) { const boundingBox = await elementHandle.boundingBox(); - const elementIsVisible = boundingBox != null && boundingBox.height > 0 && - boundingBox.width > 0; + const elementIsVisible = + boundingBox != null && boundingBox.height > 0 && boundingBox.width > 0; elementsAreVisible.push(elementIsVisible); } if (elementsAreVisible.length) { - log('verbose', 'Found', colors.cyan(elementsAreVisible.length), - 'element(s) matching the CSS selector', colors.cyan(selector)); - log('verbose', 'Expecting all element visibilities to be', - colors.cyan(waitForVisible), '; they are', - colors.cyan(elementsAreVisible)); + log( + 'verbose', + 'Found', + colors.cyan(elementsAreVisible.length), + 'element(s) matching the CSS selector', + colors.cyan(selector) + ); + log( + 'verbose', + 'Expecting all element visibilities to be', + colors.cyan(waitForVisible), + '; they are', + colors.cyan(elementsAreVisible) + ); } else { log('verbose', 'No', colors.cyan(selector), 'matches found'); } // Since we assert that waitForVisible == !waitForHidden, there is no need // to check equality to both waitForVisible and waitForHidden. - if (elementsAreVisible.every( - elementIsVisible => elementIsVisible == waitForVisible)) { + if ( + elementsAreVisible.every( + elementIsVisible => elementIsVisible == waitForVisible + ) + ) { return true; } diff --git a/build-system/tasks/visual-diff/index.js b/build-system/tasks/visual-diff/index.js index 4dd9c68e22ac..d08fa8fad4ca 100644 --- a/build-system/tasks/visual-diff/index.js +++ b/build-system/tasks/visual-diff/index.js @@ -56,21 +56,30 @@ const WEBSERVER_TIMEOUT_RETRIES = 10; const NAVIGATE_TIMEOUT_MS = 30000; const MAX_PARALLEL_TABS = 5; const WAIT_FOR_TABS_MS = 1000; -const BUILD_STATUS_URL = 'https://amphtml-percy-status-checker.appspot.com/status'; +const BUILD_STATUS_URL = + 'https://amphtml-percy-status-checker.appspot.com/status'; const ROOT_DIR = path.resolve(__dirname, '../../../'); // JavaScript snippets that execute inside the page. const WRAP_IN_IFRAME_SNIPPET = fs.readFileSync( - path.resolve(__dirname, 'snippets/iframe-wrapper.js'), 'utf8'); + path.resolve(__dirname, 'snippets/iframe-wrapper.js'), + 'utf8' +); const REMOVE_AMP_SCRIPTS_SNIPPET = fs.readFileSync( - path.resolve(__dirname, 'snippets/remove-amp-scripts.js'), 'utf8'); + path.resolve(__dirname, 'snippets/remove-amp-scripts.js'), + 'utf8' +); const FREEZE_FORM_VALUE_SNIPPET = fs.readFileSync( - path.resolve(__dirname, 'snippets/freeze-form-values.js'), 'utf8'); + path.resolve(__dirname, 'snippets/freeze-form-values.js'), + 'utf8' +); // HTML snippet to create an error page snapshot. const SNAPSHOT_ERROR_SNIPPET = fs.readFileSync( - path.resolve(__dirname, 'snippets/snapshot-error.html'), 'utf8'); + path.resolve(__dirname, 'snippets/snapshot-error.html'), + 'utf8' +); let browser_; let webServerProcess_; @@ -92,8 +101,7 @@ function maybeOverridePercyEnvironmentVariables() { * as baselines for future builds. */ function setPercyBranch() { - if (!process.env['PERCY_BRANCH'] && - (!argv.master || !isTravisBuild())) { + if (!process.env['PERCY_BRANCH'] && (!argv.master || !isTravisBuild())) { const userName = gitCommitterEmail(); const branchName = gitBranchName(); process.env['PERCY_BRANCH'] = userName + '-' + branchName; @@ -127,18 +135,22 @@ function setPercyTargetCommit() { */ async function launchWebServer() { webServerProcess_ = execScriptAsync( - `gulp serve --host ${HOST} --port ${PORT} ${process.env.WEBSERVER_QUIET}`, - { - stdio: argv.webserver_debug ? - ['ignore', process.stdout, process.stderr] : - 'ignore', - }); + `gulp serve --host ${HOST} --port ${PORT} ${process.env.WEBSERVER_QUIET}`, + { + stdio: argv.webserver_debug + ? ['ignore', process.stdout, process.stderr] + : 'ignore', + } + ); webServerProcess_.on('close', code => { code = code || 0; if (code != 0) { - log('fatal', colors.cyan("'serve'"), - `errored with code ${code}. Cannot continue with visual diff tests`); + log( + 'fatal', + colors.cyan("'serve'"), + `errored with code ${code}. Cannot continue with visual diff tests` + ); } }); @@ -151,9 +163,11 @@ async function launchWebServer() { host: HOST, port: PORT, retries: WEBSERVER_TIMEOUT_RETRIES, // retry timeout defaults to 1 sec - }).on('connected', () => { - return resolver(webServerProcess_); - }).on('timeout', rejecter); + }) + .on('connected', () => { + return resolver(webServerProcess_); + }) + .on('timeout', rejecter); return deferred; } @@ -219,22 +233,34 @@ async function newPage(browser, viewport = null) { page.on('request', interceptedRequest => { const requestUrl = new URL(interceptedRequest.url()); const mockedFilepath = path.join( - path.dirname(__filename), 'network-mocks', requestUrl.hostname, - encodeURIComponent( - `${requestUrl.pathname.substr(1)}${requestUrl.search}`) - .replace(/%2F/g, '/')); - - if (requestUrl.hostname == HOST || - requestUrl.hostname.endsWith(`.${HOST}`)) { + path.dirname(__filename), + 'network-mocks', + requestUrl.hostname, + encodeURIComponent( + `${requestUrl.pathname.substr(1)}${requestUrl.search}` + ).replace(/%2F/g, '/') + ); + + if ( + requestUrl.hostname == HOST || + requestUrl.hostname.endsWith(`.${HOST}`) + ) { return interceptedRequest.continue(); } else if (fs.existsSync(mockedFilepath)) { - log('verbose', 'Mocked network request for', - colors.yellow(requestUrl.href), 'with file', - colors.cyan(mockedFilepath)); + log( + 'verbose', + 'Mocked network request for', + colors.yellow(requestUrl.href), + 'with file', + colors.cyan(mockedFilepath) + ); return interceptedRequest.respond(fs.readFileSync(mockedFilepath)); } else { - log('verbose', 'Blocked external network request for', - colors.yellow(requestUrl.href)); + log( + 'verbose', + 'Blocked external network request for', + colors.yellow(requestUrl.href) + ); return interceptedRequest.abort('blockedbyclient'); } }); @@ -253,8 +279,13 @@ async function resetPage(page, viewport = null) { const width = viewport ? viewport.width : VIEWPORT_WIDTH; const height = viewport ? viewport.height : VIEWPORT_HEIGHT; - log('verbose', 'Resetting tab to', colors.yellow('about:blank'), 'with size', - colors.yellow(`${width}×${height}`)); + log( + 'verbose', + 'Resetting tab to', + colors.yellow('about:blank'), + 'with size', + colors.yellow(`${width}×${height}`) + ); await page.goto('about:blank'); await page.setViewport({width, height}); @@ -284,11 +315,21 @@ function addTestError(testErrors, name, message, error, consoleMessages) { * @param {!JsonObject} testError object as created by addTestError. */ function logTestError(testError) { - log('error', 'Error in test', colors.yellow(testError.name), '\n ', - testError.message, '\n ', testError.error); + log( + 'error', + 'Error in test', + colors.yellow(testError.name), + '\n ', + testError.message, + '\n ', + testError.error + ); if (testError.consoleMessages.length > 0) { - log('error', colors.cyan(testError.consoleMessages.length), - 'Console messages in the browser so far:'); + log( + 'error', + colors.cyan(testError.consoleMessages.length), + 'Console messages in the browser so far:' + ); for (const message of testError.consoleMessages) { log('error', colors.cyan(`[console.${message.type()}]`), message.text()); } @@ -311,8 +352,11 @@ async function runVisualTests(assetGlobs, webpages) { fs.writeFileSync('PERCY_BUILD_ID', buildId); log('info', 'Started Percy build', colors.cyan(buildId)); if (process.env['PERCY_TARGET_COMMIT']) { - log('info', 'The Percy build is baselined on top of commit', - colors.cyan(shortSha(process.env['PERCY_TARGET_COMMIT']))); + log( + 'info', + 'The Percy build is baselined on top of commit', + colors.cyan(shortSha(process.env['PERCY_TARGET_COMMIT'])) + ); } try { @@ -328,8 +372,12 @@ async function runVisualTests(assetGlobs, webpages) { if (status.state == 'failed') { log('fatal', 'Build', colors.cyan(buildId), 'failed!'); } else { - log('info', 'Build', colors.cyan(buildId), - 'is now being processed by Percy.'); + log( + 'info', + 'Build', + colors.cyan(buildId), + 'is now being processed by Percy.' + ); } } @@ -367,13 +415,22 @@ async function generateSnapshots(percy, webpages) { const numUnfilteredPages = webpages.length; webpages = webpages.filter(webpage => !webpage.flaky); if (numUnfilteredPages != webpages.length) { - log('info', 'Skipping', colors.cyan(numUnfilteredPages - webpages.length), - 'flaky pages'); + log( + 'info', + 'Skipping', + colors.cyan(numUnfilteredPages - webpages.length), + 'flaky pages' + ); } if (argv.grep) { webpages = webpages.filter(webpage => argv.grep.test(webpage.name)); - log('info', colors.cyan(`--grep ${argv.grep}`), 'matched', - colors.cyan(webpages.length), 'pages'); + log( + 'info', + colors.cyan(`--grep ${argv.grep}`), + 'matched', + colors.cyan(webpages.length), + 'pages' + ); } // Expand all the interactive tests. Every test should have a base test with @@ -381,34 +438,51 @@ async function generateSnapshots(percy, webpages) { // load those tests here. for (const webpage of webpages) { webpage.tests_ = { - '': async() => {}, + '': async () => {}, }; if (webpage.interactive_tests) { try { - Object.assign(webpage.tests_, - require(path.resolve(ROOT_DIR, webpage.interactive_tests))); + Object.assign( + webpage.tests_, + require(path.resolve(ROOT_DIR, webpage.interactive_tests)) + ); } catch (error) { - log('fatal', 'Failed to load interactive test', - colors.cyan(webpage.interactive_tests), 'for test', - colors.cyan(webpage.name), '\nError:', error); + log( + 'fatal', + 'Failed to load interactive test', + colors.cyan(webpage.interactive_tests), + 'for test', + colors.cyan(webpage.name), + '\nError:', + error + ); } } } const totalTests = webpages.reduce( - (numTests, webpage) => numTests + Object.keys(webpage.tests_).length, 0); + (numTests, webpage) => numTests + Object.keys(webpage.tests_).length, + 0 + ); if (!totalTests) { log('fatal', 'No pages left to test!'); } else { - log('info', 'Executing', colors.cyan(totalTests), 'visual diff tests on', - colors.cyan(webpages.length), 'pages'); + log( + 'info', + 'Executing', + colors.cyan(totalTests), + 'visual diff tests on', + colors.cyan(webpages.length), + 'pages' + ); } const browser = await launchBrowser(); if (argv.master) { const page = await newPage(browser); await page.goto( - `http://${HOST}:${PORT}/examples/visual-tests/blank-page/blank.html`); + `http://${HOST}:${PORT}/examples/visual-tests/blank-page/blank.html` + ); await percy.snapshot('Blank page', page, SNAPSHOT_SINGLE_BUILD_OPTIONS); } @@ -471,18 +545,26 @@ async function snapshotWebpages(percy, browser, webpages) { // to wait until there are no more network requests. This method is flaky // since Puppeteer doesn't always understand Chrome's network activity, so // ignore timeouts again. - const pagePromise = (async() => { + const pagePromise = (async () => { const responseWatcher = new Promise((resolve, reject) => { const responseTimeout = setTimeout(() => { - reject(new puppeteer.TimeoutError( + reject( + new puppeteer.TimeoutError( `Response was not received in test ${testName} for page ` + - `${webpage.url} after ${NAVIGATE_TIMEOUT_MS}ms`)); + `${webpage.url} after ${NAVIGATE_TIMEOUT_MS}ms` + ) + ); }, NAVIGATE_TIMEOUT_MS); page.once('response', async response => { - log('verbose', 'Response for url', colors.yellow(response.url()), - 'with status', colors.cyan(response.status()), - colors.cyan(response.statusText())); + log( + 'verbose', + 'Response for url', + colors.yellow(response.url()), + 'with status', + colors.cyan(response.status()), + colors.cyan(response.statusText()) + ); clearTimeout(responseTimeout); resolve(); }); @@ -494,97 +576,123 @@ async function snapshotWebpages(percy, browser, webpages) { page.goto(fullUrl, {waitUntil: 'networkidle2'}), ]); })() - .then(() => { - log('verbose', 'Page navigation of test', colors.yellow(name), - 'is done, verifying page'); - }) - .catch(navigationError => { - hasWarnings = true; - addTestError(testErrors, name, - 'The browser test runner failed to complete the navigation ' + - 'to the test page', navigationError, consoleMessages); - if (!isTravisBuild()) { - log('warning', 'Continuing to verify page regardless...'); - } - }) - .then(async() => { - // Perform visibility checks: wait for all AMP built-in loader dots - // to disappear (i.e., all visible components are finished being - // layed out and external resources such as images are loaded and - // displayed), then, depending on the test configurations, wait for - // invisibility/visibility of specific elements that match the - // configured CSS selectors. - await waitForLoaderDots(page, name); - if (webpage.loading_incomplete_selectors) { - await verifySelectorsInvisible( - page, name, webpage.loading_incomplete_selectors); - } - if (webpage.loading_complete_selectors) { - await verifySelectorsVisible( - page, name, webpage.loading_complete_selectors); - } - - // Based on test configuration, wait for a specific amount of time. - if (webpage.loading_complete_delay_ms) { - log('verbose', 'Waiting', - colors.cyan(`${webpage.loading_complete_delay_ms}ms`), - 'for loading to complete'); - await sleep(webpage.loading_complete_delay_ms); - } - - // Run any other custom code located in the test's interactive_tests - // file. If there is no interactive test, this defaults to an empty - // function. - await testFunction(page, name); - - // Execute post-scripts that clean up the page's HTML and send - // prepare it for snapshotting on Percy. See comments inside the - // snippet files for description of each. - await page.evaluate(REMOVE_AMP_SCRIPTS_SNIPPET); - await page.evaluate(FREEZE_FORM_VALUE_SNIPPET); - - // Create a default set of snapshot options for Percy and modify - // them based on the test's configuration. - const snapshotOptions = Object.assign({}, DEFAULT_SNAPSHOT_OPTIONS); - if (webpage.enable_percy_javascript) { - snapshotOptions.enableJavaScript = true; - } - - if (viewport) { - snapshotOptions.widths = [viewport.width]; - log('verbose', 'Wrapping viewport-constrained page in an iframe'); - await page.evaluate(WRAP_IN_IFRAME_SNIPPET - .replace(/__WIDTH__/g, viewport.width) - .replace(/__HEIGHT__/g, viewport.height)); - } - - // Finally, send the snapshot to percy. - await percy.snapshot(name, page, snapshotOptions); - log('travis', hasWarnings ? colors.yellow('●') : colors.cyan('●')); - }) - .catch(async testError => { - log('travis', colors.red('●')); - addTestError(testErrors, name, 'Unknown failure in test page', - testError, consoleMessages); - - let htmlSnapshot; - try { - htmlSnapshot = await page.content(); - } catch (e) { - htmlSnapshot = e.message; - } - await page.setContent( - SNAPSHOT_ERROR_SNIPPET - .replace('__TEST_NAME__', name) - .replace('__TEST_ERROR__', testError) - .replace('__HTML_SNAPSHOT__', escapeHtml(htmlSnapshot))); - await percy.snapshot(name, page, SNAPSHOT_SINGLE_BUILD_OPTIONS); - }) - .finally(async() => { - log('verbose', 'Finished test', colors.yellow(name)); - page.removeListener('console', consoleLogger); - availablePages.push(page); - }); + .then(() => { + log( + 'verbose', + 'Page navigation of test', + colors.yellow(name), + 'is done, verifying page' + ); + }) + .catch(navigationError => { + hasWarnings = true; + addTestError( + testErrors, + name, + 'The browser test runner failed to complete the navigation ' + + 'to the test page', + navigationError, + consoleMessages + ); + if (!isTravisBuild()) { + log('warning', 'Continuing to verify page regardless...'); + } + }) + .then(async () => { + // Perform visibility checks: wait for all AMP built-in loader dots + // to disappear (i.e., all visible components are finished being + // layed out and external resources such as images are loaded and + // displayed), then, depending on the test configurations, wait for + // invisibility/visibility of specific elements that match the + // configured CSS selectors. + await waitForLoaderDots(page, name); + if (webpage.loading_incomplete_selectors) { + await verifySelectorsInvisible( + page, + name, + webpage.loading_incomplete_selectors + ); + } + if (webpage.loading_complete_selectors) { + await verifySelectorsVisible( + page, + name, + webpage.loading_complete_selectors + ); + } + + // Based on test configuration, wait for a specific amount of time. + if (webpage.loading_complete_delay_ms) { + log( + 'verbose', + 'Waiting', + colors.cyan(`${webpage.loading_complete_delay_ms}ms`), + 'for loading to complete' + ); + await sleep(webpage.loading_complete_delay_ms); + } + + // Run any other custom code located in the test's interactive_tests + // file. If there is no interactive test, this defaults to an empty + // function. + await testFunction(page, name); + + // Execute post-scripts that clean up the page's HTML and send + // prepare it for snapshotting on Percy. See comments inside the + // snippet files for description of each. + await page.evaluate(REMOVE_AMP_SCRIPTS_SNIPPET); + await page.evaluate(FREEZE_FORM_VALUE_SNIPPET); + + // Create a default set of snapshot options for Percy and modify + // them based on the test's configuration. + const snapshotOptions = Object.assign({}, DEFAULT_SNAPSHOT_OPTIONS); + if (webpage.enable_percy_javascript) { + snapshotOptions.enableJavaScript = true; + } + + if (viewport) { + snapshotOptions.widths = [viewport.width]; + log('verbose', 'Wrapping viewport-constrained page in an iframe'); + await page.evaluate( + WRAP_IN_IFRAME_SNIPPET.replace( + /__WIDTH__/g, + viewport.width + ).replace(/__HEIGHT__/g, viewport.height) + ); + } + + // Finally, send the snapshot to percy. + await percy.snapshot(name, page, snapshotOptions); + log('travis', hasWarnings ? colors.yellow('●') : colors.cyan('●')); + }) + .catch(async testError => { + log('travis', colors.red('●')); + addTestError( + testErrors, + name, + 'Unknown failure in test page', + testError, + consoleMessages + ); + + let htmlSnapshot; + try { + htmlSnapshot = await page.content(); + } catch (e) { + htmlSnapshot = e.message; + } + await page.setContent( + SNAPSHOT_ERROR_SNIPPET.replace('__TEST_NAME__', name) + .replace('__TEST_ERROR__', testError) + .replace('__HTML_SNAPSHOT__', escapeHtml(htmlSnapshot)) + ); + await percy.snapshot(name, page, SNAPSHOT_SINGLE_BUILD_OPTIONS); + }) + .finally(async () => { + log('verbose', 'Finished test', colors.yellow(name)); + page.removeListener('console', consoleLogger); + availablePages.push(page); + }); pagePromises.push(pagePromise); } } @@ -593,11 +701,14 @@ async function snapshotWebpages(percy, browser, webpages) { log('travis', '\n'); if (isTravisBuild() && testErrors.length > 0) { testErrors.sort((a, b) => a.name.localeCompare(b.name)); - log('info', colors.yellow('Tests warnings and errors:'), - 'expand this section'); - console./*OK*/log('travis_fold:start:visual_tests\n'); + log( + 'info', + colors.yellow('Tests warnings and errors:'), + 'expand this section' + ); + console./*OK*/ log('travis_fold:start:visual_tests\n'); testErrors.forEach(logTestError); - console./*OK*/log('travis_fold:end:visual_tests'); + console./*OK*/ log('travis_fold:end:visual_tests'); return false; } return true; @@ -634,13 +745,15 @@ async function createEmptyBuild() { const percy = new Percy({ loaders: [ new PercyAssetsLoader( - [path.resolve(__dirname, blankAssetsDir)], ROOT_DIR), + [path.resolve(__dirname, blankAssetsDir)], + ROOT_DIR + ), ], }); await percy.startBuild(); - await page.goto( - `http://${HOST}:${PORT}/examples/visual-tests/blank-page/blank.html`) - .then(() => {}, () => {}); + await page + .goto(`http://${HOST}:${PORT}/examples/visual-tests/blank-page/blank.html`) + .then(() => {}, () => {}); await percy.snapshot('Blank page', page, SNAPSHOT_SINGLE_BUILD_OPTIONS); await percy.finalizeBuild(); } @@ -671,10 +784,18 @@ async function visualDiff() { * Runs the AMP visual diff tests. */ async function performVisualTests() { - if (!argv.percy_disabled && - (!process.env.PERCY_PROJECT || !process.env.PERCY_TOKEN)) { - log('fatal', 'Could not find', colors.cyan('PERCY_PROJECT'), 'and', - colors.cyan('PERCY_TOKEN'), 'environment variables'); + if ( + !argv.percy_disabled && + (!process.env.PERCY_PROJECT || !process.env.PERCY_TOKEN) + ) { + log( + 'fatal', + 'Could not find', + colors.cyan('PERCY_PROJECT'), + 'and', + colors.cyan('PERCY_TOKEN'), + 'environment variables' + ); } setDebuggingLevel(); @@ -690,22 +811,32 @@ async function performVisualTests() { } else { // Load and parse the config. Use JSON5 due to JSON comments in file. const visualTestsConfig = JSON5.parse( - fs.readFileSync( - path.resolve(__dirname, '../../../test/visual-diff/visual-tests'), - 'utf8')); + fs.readFileSync( + path.resolve(__dirname, '../../../test/visual-diff/visual-tests'), + 'utf8' + ) + ); await runVisualTests( - visualTestsConfig.asset_globs, visualTestsConfig.webpages); + visualTestsConfig.asset_globs, + visualTestsConfig.webpages + ); } } async function ensureOrBuildAmpRuntimeInTestMode_() { if (argv.nobuild) { const isInTestMode = /AMP_CONFIG=\{(?:.+,)?"test":true\b/.test( - fs.readFileSync('dist/amp.js', 'utf8')); + fs.readFileSync('dist/amp.js', 'utf8') + ); if (!isInTestMode) { - log('fatal', 'The AMP runtime was not built in test mode. Run', - colors.cyan('gulp build --fortesting'), 'or remove the', - colors.cyan('--nobuild'), 'option from this command'); + log( + 'fatal', + 'The AMP runtime was not built in test mode. Run', + colors.cyan('gulp build --fortesting'), + 'or remove the', + colors.cyan('--nobuild'), + 'option from this command' + ); } } else { execOrDie('gulp build --fortesting'); @@ -715,8 +846,9 @@ async function ensureOrBuildAmpRuntimeInTestMode_() { function installPercy_() { if (!argv.noyarn) { log('info', 'Running', colors.cyan('yarn'), 'to install Percy...'); - execOrDie('npx yarn --cwd build-system/tasks/visual-diff', - {'stdio': 'ignore'}); + execOrDie('npx yarn --cwd build-system/tasks/visual-diff', { + 'stdio': 'ignore', + }); } puppeteer = require('puppeteer'); diff --git a/build-system/tasks/visual-diff/percy-assets-loader.js b/build-system/tasks/visual-diff/percy-assets-loader.js index fd3beefee1fa..cb1bae83e2c2 100644 --- a/build-system/tasks/visual-diff/percy-assets-loader.js +++ b/build-system/tasks/visual-diff/percy-assets-loader.js @@ -65,14 +65,13 @@ class PercyAssetsLoader { assetFile = assetFile.replace(/\\/g, '/'); } - const content = fs.readFileSync(assetFile); resources.push( - percyClient.makeResource({ - resourceUrl: encodeURI(`/${assetFile}`), - content, - mimetype: mime.lookup(assetFile), - }), + percyClient.makeResource({ + resourceUrl: encodeURI(`/${assetFile}`), + content, + mimetype: mime.lookup(assetFile), + }) ); } } diff --git a/build-system/test-server.js b/build-system/test-server.js index c97626c999e8..a94907c160c2 100644 --- a/build-system/test-server.js +++ b/build-system/test-server.js @@ -30,10 +30,14 @@ function setCorsHeaders(req, res, next) { res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.query.__amp_source_origin) { - res.setHeader('Access-Control-Expose-Headers', - 'AMP-Access-Control-Allow-Source-Origin'); - res.setHeader('AMP-Access-Control-Allow-Source-Origin', - req.query.__amp_source_origin); + res.setHeader( + 'Access-Control-Expose-Headers', + 'AMP-Access-Control-Allow-Source-Origin' + ); + res.setHeader( + 'AMP-Access-Control-Allow-Source-Origin', + req.query.__amp_source_origin + ); } next(); } @@ -50,7 +54,6 @@ app.use('/redirect-to', function(req, res) { res.redirect(302, req.query.url); }); - app.use('/status/404', function(req, res) { res.status(404).end(); }); @@ -62,7 +65,7 @@ app.use('/status/500', function(req, res) { app.use('/cookies/set', function(req, res) { delete req.query.__amp_source_origin; for (const name in req.query) { - res./*OK*/cookie(name, req.query[name]); + res./*OK*/ cookie(name, req.query[name]); } res.json({ cookies: req.cookies || {}, @@ -88,11 +91,7 @@ app.use('/form/post/success', function(req, res) { delete req.query.__amp_source_origin; res.json({ name: 'John Miller', - interests: [ - {title: 'Football'}, - {title: 'Basketball'}, - {title: 'Writing'}, - ], + interests: [{title: 'Football'}, {title: 'Basketball'}, {title: 'Writing'}], }); }); @@ -118,5 +117,4 @@ app.use('/form/verify-error', function(req, res) { }); }); - exports.app = app; diff --git a/build-system/travis.js b/build-system/travis.js index b8d023338440..50ef25ac49df 100644 --- a/build-system/travis.js +++ b/build-system/travis.js @@ -37,8 +37,9 @@ exports.isTravisBuild = function() { * @return {boolean} */ exports.isTravisPullRequestBuild = function() { - return exports.isTravisBuild() && - process.env.TRAVIS_EVENT_TYPE === 'pull_request'; + return ( + exports.isTravisBuild() && process.env.TRAVIS_EVENT_TYPE === 'pull_request' + ); }; /** @@ -46,8 +47,7 @@ exports.isTravisPullRequestBuild = function() { * @return {boolean} */ exports.isTravisPushBuild = function() { - return exports.isTravisBuild() && - process.env.TRAVIS_EVENT_TYPE === 'push'; + return exports.isTravisBuild() && process.env.TRAVIS_EVENT_TYPE === 'push'; }; /** @@ -56,8 +56,11 @@ exports.isTravisPushBuild = function() { */ exports.travisBuildNumber = function() { if (!exports.isTravisBuild()) { - log(red('ERROR:'), 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_BUILD_NUMBER') + '.'); + log( + red('ERROR:'), + 'This is not a Travis build. Cannot get', + cyan('process.env.TRAVIS_BUILD_NUMBER') + '.' + ); } return process.env.TRAVIS_BUILD_NUMBER; }; @@ -68,8 +71,11 @@ exports.travisBuildNumber = function() { */ exports.travisJobNumber = function() { if (!exports.isTravisBuild()) { - log(red('ERROR:'), 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_JOB_NUMBER') + '.'); + log( + red('ERROR:'), + 'This is not a Travis build. Cannot get', + cyan('process.env.TRAVIS_JOB_NUMBER') + '.' + ); } return process.env.TRAVIS_JOB_NUMBER; }; @@ -80,8 +86,11 @@ exports.travisJobNumber = function() { */ exports.travisRepoSlug = function() { if (!exports.isTravisBuild()) { - log(red('ERROR:'), 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_REPO_SLUG') + '.'); + log( + red('ERROR:'), + 'This is not a Travis build. Cannot get', + cyan('process.env.TRAVIS_REPO_SLUG') + '.' + ); } return process.env.TRAVIS_REPO_SLUG; }; @@ -92,8 +101,11 @@ exports.travisRepoSlug = function() { */ exports.travisPullRequestSha = function() { if (!exports.isTravisPullRequestBuild()) { - log(red('ERROR:'), 'This is not a Travis PR build. Cannot get', - cyan('process.env.TRAVIS_PULL_REQUEST_SHA') + '.'); + log( + red('ERROR:'), + 'This is not a Travis PR build. Cannot get', + cyan('process.env.TRAVIS_PULL_REQUEST_SHA') + '.' + ); } return process.env.TRAVIS_PULL_REQUEST_SHA; }; @@ -104,8 +116,11 @@ exports.travisPullRequestSha = function() { */ exports.travisPullRequestBranch = function() { if (!exports.isTravisPullRequestBuild()) { - log(red('ERROR:'), 'This is not a Travis PR build. Cannot get', - cyan('process.env.TRAVIS_PULL_REQUEST_BRANCH') + '.'); + log( + red('ERROR:'), + 'This is not a Travis PR build. Cannot get', + cyan('process.env.TRAVIS_PULL_REQUEST_BRANCH') + '.' + ); } return process.env.TRAVIS_PULL_REQUEST_BRANCH; }; diff --git a/build-system/typescript.js b/build-system/typescript.js index 00fd81948685..52a26bc7ba9f 100644 --- a/build-system/typescript.js +++ b/build-system/typescript.js @@ -34,10 +34,13 @@ const {endBuildStep} = require('./tasks/helpers'); exports.transpileTs = function(srcDir, srcFilename) { const startTime = Date.now(); const tsEntry = path.join(srcDir, srcFilename).replace(/\.js$/, '.ts'); - const tsConfig = ts.convertCompilerOptionsFromJson({ - 'module': 'ES6', - 'target': 'ES6', - }, srcDir); + const tsConfig = ts.convertCompilerOptionsFromJson( + { + 'module': 'ES6', + 'target': 'ES6', + }, + srcDir + ); const tsOptions = tsConfig.options; if (tsConfig.errors.length) { log(colors.red('TSickle:'), tsickle.formatDiagnostics(tsConfig.errors)); @@ -64,17 +67,24 @@ exports.transpileTs = function(srcDir, srcFilename) { shouldSkipTsickleProcessing: () => false, transformTypesToClosure: true, }; - return tsickle.emitWithTsickle( - program, transformerHost, compilerHost, tsOptions, undefined, + return tsickle + .emitWithTsickle( + program, + transformerHost, + compilerHost, + tsOptions, + undefined, (filePath, contents) => { fs.writeFileSync(filePath, contents, {encoding: 'utf-8'}); - }) - .then(emitResult => { - const diagnostics = - ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); - if (diagnostics.length) { - log(colors.red('TSickle:'), tsickle.formatDiagnostics(diagnostics)); - } - endBuildStep('Transpiled', srcFilename, startTime); - }); + } + ) + .then(emitResult => { + const diagnostics = ts + .getPreEmitDiagnostics(program) + .concat(emitResult.diagnostics); + if (diagnostics.length) { + log(colors.red('TSickle:'), tsickle.formatDiagnostics(diagnostics)); + } + endBuildStep('Transpiled', srcFilename, startTime); + }); }; diff --git a/builtins/amp-img.js b/builtins/amp-img.js index 79f9430eaf57..513f3b21059a 100644 --- a/builtins/amp-img.js +++ b/builtins/amp-img.js @@ -27,11 +27,19 @@ import {registerElement} from '../src/service/custom-element-registry'; * Attributes to propagate to internal image when changed externally. * @type {!Array} */ -const ATTRIBUTES_TO_PROPAGATE = ['alt', 'title', 'referrerpolicy', 'aria-label', - 'aria-describedby', 'aria-labelledby','srcset', 'src', 'sizes']; +const ATTRIBUTES_TO_PROPAGATE = [ + 'alt', + 'title', + 'referrerpolicy', + 'aria-label', + 'aria-describedby', + 'aria-labelledby', + 'srcset', + 'src', + 'sizes', +]; export class AmpImg extends BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -62,9 +70,13 @@ export class AmpImg extends BaseElement { mutatedAttributesCallback(mutations) { if (this.img_) { const attrs = ATTRIBUTES_TO_PROPAGATE.filter( - value => mutations[value] !== undefined); + value => mutations[value] !== undefined + ); this.propagateAttributes( - attrs, this.img_, /* opt_removeMissingAttrs */ true); + attrs, + this.img_, + /* opt_removeMissingAttrs */ true + ); guaranteeSrcForSrcsetUnsupportedBrowsers(this.img_); } } @@ -136,9 +148,11 @@ export class AmpImg extends BaseElement { if (this.element.getAttribute('role') == 'img') { this.element.removeAttribute('role'); this.user().error( - 'AMP-IMG', 'Setting role=img on amp-img elements breaks ' + - 'screen readers please just set alt or ARIA attributes, they will ' + - 'be correctly propagated for the underlying element.'); + 'AMP-IMG', + 'Setting role=img on amp-img elements breaks ' + + 'screen readers please just set alt or ARIA attributes, they will ' + + 'be correctly propagated for the underlying element.' + ); } this.propagateAttributes(ATTRIBUTES_TO_PROPAGATE, this.img_); @@ -182,7 +196,7 @@ export class AmpImg extends BaseElement { let defaultSize = width + 'px'; if (this.getLayout() !== Layout.FIXED) { - const ratio = Math.round(width * 100 / viewportWidth); + const ratio = Math.round((width * 100) / viewportWidth); defaultSize = Math.max(ratio, 100) + 'vw'; } @@ -240,12 +254,14 @@ export class AmpImg extends BaseElement { return true; } - /** @override **/ + /** @override */ firstLayoutCompleted() { const placeholder = this.getPlaceholder(); - if (placeholder && + if ( + placeholder && placeholder.classList.contains('i-amphtml-blurry-placeholder') && - isExperimentOn(this.win, 'blurry-placeholder')) { + isExperimentOn(this.win, 'blurry-placeholder') + ) { setImportantStyles(placeholder, {'opacity': 0}); } else { this.togglePlaceholder(false); @@ -256,8 +272,10 @@ export class AmpImg extends BaseElement { * @private */ hideFallbackImg_() { - if (!this.allowImgLoadFallback_ - && this.img_.classList.contains('i-amphtml-ghost')) { + if ( + !this.allowImgLoadFallback_ && + this.img_.classList.contains('i-amphtml-ghost') + ) { this.getVsync().mutate(() => { this.img_.classList.remove('i-amphtml-ghost'); this.toggleFallback(false); diff --git a/builtins/amp-layout.js b/builtins/amp-layout.js index 9bdd354970a8..337930672590 100644 --- a/builtins/amp-layout.js +++ b/builtins/amp-layout.js @@ -19,11 +19,9 @@ import {Layout, isLayoutSizeDefined} from '../src/layout'; import {registerElement} from '../src/service/custom-element-registry'; class AmpLayout extends BaseElement { - /** @override */ isLayoutSupported(layout) { - return layout == Layout.CONTAINER || - isLayoutSizeDefined(layout); + return layout == Layout.CONTAINER || isLayoutSizeDefined(layout); } /** @override */ @@ -52,5 +50,3 @@ class AmpLayout extends BaseElement { export function installLayout(win) { registerElement(win, 'amp-layout', AmpLayout); } - - diff --git a/builtins/amp-pixel.js b/builtins/amp-pixel.js index e7e4dcd1bfa6..655d3c110232 100644 --- a/builtins/amp-pixel.js +++ b/builtins/amp-pixel.js @@ -22,12 +22,10 @@ import {registerElement} from '../src/service/custom-element-registry'; const TAG = 'amp-pixel'; - /** * A simple analytics instrument. Fires as an impression signal. */ export class AmpPixel extends BaseElement { - /** @override */ constructor(element) { super(element); @@ -53,12 +51,16 @@ export class AmpPixel extends BaseElement { // Safari doesn't support referrerPolicy yet. We're using an // iframe based trick to remove referrer, which apparently can // only do "no-referrer". - userAssert(this.referrerPolicy_ == 'no-referrer', - `${TAG}: invalid "referrerpolicy" value "${this.referrerPolicy_}".` - + ' Only "no-referrer" is supported'); + userAssert( + this.referrerPolicy_ == 'no-referrer', + `${TAG}: invalid "referrerpolicy" value "${this.referrerPolicy_}".` + + ' Only "no-referrer" is supported' + ); } - if (this.element.hasAttribute('i-amphtml-ssr') && - this.element.querySelector('img')) { + if ( + this.element.hasAttribute('i-amphtml-ssr') && + this.element.querySelector('img') + ) { dev().info(TAG, 'inabox img already present'); return; } @@ -79,19 +81,21 @@ export class AmpPixel extends BaseElement { } // Delay(1) provides a rudimentary "idle" signal. // TODO(dvoytenko): use an improved idle signal when available. - this.triggerPromise_ = Services.timerFor(this.win).promise(1).then(() => { - const src = this.element.getAttribute('src'); - if (!src) { - return; - } - return Services.urlReplacementsForDoc(this.element) + this.triggerPromise_ = Services.timerFor(this.win) + .promise(1) + .then(() => { + const src = this.element.getAttribute('src'); + if (!src) { + return; + } + return Services.urlReplacementsForDoc(this.element) .expandUrlAsync(this.assertSource_(src)) .then(src => { const pixel = createPixel(this.win, src, this.referrerPolicy_); dev().info(TAG, 'pixel triggered: ', src); return pixel; }); - }); + }); } /** @@ -101,9 +105,11 @@ export class AmpPixel extends BaseElement { */ assertSource_(src) { userAssert( - /^(https\:\/\/|\/\/)/i.test(src), - 'The src attribute must start with ' + - '"https://" or "//". Invalid value: ' + src); + /^(https\:\/\/|\/\/)/i.test(src), + 'The src attribute must start with ' + + '"https://" or "//". Invalid value: ' + + src + ); return /** @type {string} */ (src); } } diff --git a/bundles.config.js b/bundles.config.js index 5eccad5dfe9e..1796ffa93c3a 100644 --- a/bundles.config.js +++ b/bundles.config.js @@ -22,11 +22,11 @@ const log = require('fancy-log'); /** * @enum {string} */ -const TYPES = exports.TYPES = { +const TYPES = (exports.TYPES = { AD: '_base_ad', MEDIA: '_base_media', MISC: '_base_misc', -}; +}); exports.extensionBundles = [ { @@ -460,9 +460,7 @@ exports.extensionBundles = [ version: '0.1', latestVersion: '0.1', type: TYPES.MISC, - postPrepend: [ - 'third_party/inputmask/bundle.js', - ], + postPrepend: ['third_party/inputmask/bundle.js'], }, { name: 'amp-instagram', @@ -642,8 +640,7 @@ exports.extensionBundles = [ name: 'amp-story', version: '0.1', latestVersion: '1.0', - options: - { + options: { hasCss: true, cssBinaries: [ 'amp-story-bookend', @@ -663,8 +660,7 @@ exports.extensionBundles = [ name: 'amp-story', version: '1.0', latestVersion: '1.0', - options: - { + options: { hasCss: true, cssBinaries: [ 'amp-story-bookend', @@ -688,9 +684,7 @@ exports.extensionBundles = [ latestVersion: '0.1', options: { hasCss: true, - cssBinaries: [ - 'amp-story-auto-ads-attribution', - ], + cssBinaries: ['amp-story-auto-ads-attribution'], }, type: TYPES.MISC, }, @@ -732,9 +726,7 @@ exports.extensionBundles = [ latestVersion: '0.1', options: {hasCss: true}, type: TYPES.MISC, - postPrepend: [ - 'third_party/react-dates/bundle.js', - ], + postPrepend: ['third_party/react-dates/bundle.js'], }, { name: 'amp-image-viewer', @@ -848,8 +840,7 @@ exports.extensionBundles = [ name: 'amp-viewer-integration', version: '0.1', latestVersion: '0.1', - options: - { + options: { // The viewer integration code needs to run asap, so that viewers // can influence document state asap. Otherwise the document may take // a long time to learn that it should start process other extensions @@ -961,9 +952,13 @@ exports.altMainBundles = [ */ function verifyBundle_(condition, field, message, name, found) { if (!condition) { - log(colors.red('ERROR:'), - colors.cyan(field), message, colors.cyan(name), - '\n' + found); + log( + colors.red('ERROR:'), + colors.cyan(field), + message, + colors.cyan(name), + '\n' + found + ); process.exit(1); } } @@ -972,29 +967,53 @@ exports.verifyExtensionBundles = function() { exports.extensionBundles.forEach(bundle => { const bundleString = JSON.stringify(bundle, null, 2); verifyBundle_( - 'name' in bundle, - 'name', 'is missing from', '', bundleString); + 'name' in bundle, + 'name', + 'is missing from', + '', + bundleString + ); verifyBundle_( - 'version' in bundle, - 'version', 'is missing from', bundle.name, bundleString); + 'version' in bundle, + 'version', + 'is missing from', + bundle.name, + bundleString + ); verifyBundle_( - 'latestVersion' in bundle, - 'latestVersion', 'is missing from', bundle.name, bundleString); + 'latestVersion' in bundle, + 'latestVersion', + 'is missing from', + bundle.name, + bundleString + ); const duplicates = exports.extensionBundles.filter( - duplicate => duplicate.name === bundle.name); + duplicate => duplicate.name === bundle.name + ); verifyBundle_( - duplicates.every( - duplicate => duplicate.latestVersion === bundle.latestVersion), - 'latestVersion', 'is not the same for all versions of', bundle.name, - JSON.stringify(duplicates, null, 2)); + duplicates.every( + duplicate => duplicate.latestVersion === bundle.latestVersion + ), + 'latestVersion', + 'is not the same for all versions of', + bundle.name, + JSON.stringify(duplicates, null, 2) + ); verifyBundle_( - 'type' in bundle, - 'type', 'is missing from', bundle.name, bundleString); + 'type' in bundle, + 'type', + 'is missing from', + bundle.name, + bundleString + ); const validTypes = Object.keys(TYPES).map(x => TYPES[x]); verifyBundle_( - validTypes.some(validType => validType === bundle.type), - 'type', `is not one of ${validTypes.join(',')} in`, bundle.name, - bundleString); + validTypes.some(validType => validType === bundle.type), + 'type', + `is not one of ${validTypes.join(',')} in`, + bundle.name, + bundleString + ); }); }; @@ -1002,13 +1021,25 @@ exports.verifyExtensionAliasBundles = function() { exports.aliasBundles.forEach(bundle => { const bundleString = JSON.stringify(bundle, null, 2); verifyBundle_( - 'name' in bundle, - 'name', 'is missing from', '', bundleString); + 'name' in bundle, + 'name', + 'is missing from', + '', + bundleString + ); verifyBundle_( - 'version' in bundle, - 'version', 'is missing from', bundle.name, bundleString); + 'version' in bundle, + 'version', + 'is missing from', + bundle.name, + bundleString + ); verifyBundle_( - 'latestVersion' in bundle, - 'latestVersion', 'is missing from', bundle.name, bundleString); + 'latestVersion' in bundle, + 'latestVersion', + 'is missing from', + bundle.name, + bundleString + ); }); }; diff --git a/contributing/getting-started-e2e.md b/contributing/getting-started-e2e.md index ce9f95aef59c..800fae56694b 100644 --- a/contributing/getting-started-e2e.md +++ b/contributing/getting-started-e2e.md @@ -35,6 +35,7 @@ If you do not yet have a specific code contribution project in mind as you go th - [Create a Git branch](#create-a-git-branch) - [Pull the latest changes from the amphtml repository](#pull-the-latest-changes-from-the-amphtml-repository) - [Edit files and commit them](#edit-files-and-commit-them) + * [Code quality and style](#code-quality-and-style) - [Testing your changes](#testing-your-changes) * [Running tests locally](#running-tests-locally) * [Running all the Travis CI checks locally](#running-all-the-travis-ci-checks-locally) @@ -293,9 +294,9 @@ The common workflow for making changes to files in Git is: * edit some files using your favorite editor -* if your code requires a new dependency, run `yarn add --dev --exact [packagename]`, which will automatically update `package.json` and `yarn.lock` +* if your code requires a new dependency, run `yarn add --exact [--dev] `, which will automatically update `package.json` and `yarn.lock` -* if you manually edited `package.json`, run `yarn install` to install the dependency and generate an updated `yarn.lock` file +* if you manually edited `package.json`, run `yarn` to install the dependency and generate an updated `yarn.lock` file * tell Git that you care about these changes by _staging_ them using the `git add` command @@ -343,13 +344,24 @@ git commit -a -m "" Note that you *can* add changes into an existing commit but that opens up some additional Git concepts that you don't really need to make a contribution. +## Code quality and style + +The AMP Project uses [Eslint](https://eslint.org/) to ensure code quality and [Prettier](https://prettier.io/) to standardize code style. For easy development, here are two recommendations: +- Use a code editor with Eslint support to make sure that your code satisfies all of AMP's quality and style rules. [Here](https://eslint.org/docs/user-guide/integrations#editors) is a list of editors with Eslint extension support. +- Set your editor to automatically fix Eslint errors in your code on save. + +For example, if you use [Visual Studio Code](https://code.visualstudio.com/), you can install its [Eslint plugin](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint), and enable the `eslint.autoFixOnSave` setting. + +Alternatively, you can manually fix lint errors in your code by running: +``` +gulp lint --local-changes --fix +``` + # Testing your changes Before sending your code changes for review, please make sure that all of the affected tests are passing. You may be expected to add/modify some tests as well. -{% call callout('Note', type='note') %} -Automatically run most checks on `git push` by enabling our pre-push hook. Run `./build-system/enable-git-pre-push.sh` -{% endcall %} +Note: You can automatically run critical checks before `git push` by enabling our pre-push hook. To do so, run `./build-system/enable-git-pre-push.sh`. ## Running tests locally diff --git a/contributing/getting-started-quick.md b/contributing/getting-started-quick.md index 59893dae6e51..d0a0afa0f52a 100644 --- a/contributing/getting-started-quick.md +++ b/contributing/getting-started-quick.md @@ -107,12 +107,13 @@ git checkout -b master ## Create commits to contain your changes -1. Edit files in your favorite editor -2. If your code requires a new dependency, run `yarn add --dev --exact [packagename]`, which automatically updates `package.json` and `yarn.lock` -3. If you manually edited `package.json`, run `yarn` to install the dependency and generate an updated `yarn.lock` file -4. Add each file you change: `git add ` -5. Create a commit: `git commit -m ""` -6. To avoid having to run `git add` on each file, you can use `git commit -a -m ""` instead. +1. Edit files in your favorite editor +2. Make sure your changes satisfy AMP's [code quality and style rules](getting-started-e2e.md#code-quality-and-style) +3. If your code requires a new dependency, run `yarn add --dev --exact [packagename]`, which automatically updates `package.json` and `yarn.lock` +4. If you manually edited `package.json`, run `yarn` to install the dependency and generate an updated `yarn.lock` file +5. Add each file you change: `git add ` +6. Create a commit: `git commit -m ""` +7. To avoid having to run `git add` on each file, you can use `git commit -a -m ""` instead. ## Pull the latest changes diff --git a/extensions/amp-3d-gltf/0.1/amp-3d-gltf.js b/extensions/amp-3d-gltf/0.1/amp-3d-gltf.js index cc92dbd7cd66..74cedb47e95f 100644 --- a/extensions/amp-3d-gltf/0.1/amp-3d-gltf.js +++ b/extensions/amp-3d-gltf/0.1/amp-3d-gltf.js @@ -27,13 +27,12 @@ const TAG = 'amp-3d-gltf'; const isWebGLSupported = () => { const canvas = document.createElement('canvas'); - const gl = canvas.getContext('webgl') - || canvas.getContext('experimental-webgl'); + const gl = + canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); return gl && gl instanceof WebGLRenderingContext; }; export class Amp3dGltf extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -60,9 +59,18 @@ export class Amp3dGltf extends AMP.BaseElement { */ preconnectCallback(opt_onLayout) { preloadBootstrap(this.win, this.preconnect); - this.preconnect.url('https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.js', opt_onLayout); - this.preconnect.url('https://cdn.jsdelivr.net/npm/three@0.91/examples/js/loaders/GLTFLoader.js', opt_onLayout); - this.preconnect.url('https://cdn.jsdelivr.net/npm/three@0.91/examples/js/controls/OrbitControls.js', opt_onLayout); + this.preconnect.url( + 'https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.js', + opt_onLayout + ); + this.preconnect.url( + 'https://cdn.jsdelivr.net/npm/three@0.91/examples/js/loaders/GLTFLoader.js', + opt_onLayout + ); + this.preconnect.url( + 'https://cdn.jsdelivr.net/npm/three@0.91/examples/js/controls/OrbitControls.js', + opt_onLayout + ); } /** @override */ @@ -92,9 +100,7 @@ export class Amp3dGltf extends AMP.BaseElement { const string = x => x; const number = x => parseFloat(x); - const src = assertHttpsUrl( - getOption('src', string, ''), - this.element); + const src = assertHttpsUrl(getOption('src', string, ''), this.element); const useAlpha = getOption('alpha', bool, false); @@ -106,21 +112,27 @@ export class Amp3dGltf extends AMP.BaseElement { }, 'rendererSettings': { 'clearAlpha': useAlpha ? 0 : 1, - 'clearColor': - getOption('clearColor', string, '#fff'), - 'maxPixelRatio': - getOption('maxPixelRatio', number, devicePixelRatio || 1), + 'clearColor': getOption('clearColor', string, '#fff'), + 'maxPixelRatio': getOption( + 'maxPixelRatio', + number, + devicePixelRatio || 1 + ), }, 'controls': { 'enableZoom': getOption('enableZoom', bool, true), 'autoRotate': getOption('autoRotate', bool, false), }, }); - this.registerAction('setModelRotation', invocation => { - this.sendCommandWhenReady_('setModelRotation', invocation.args) - .catch(e => dev() - .error('AMP-3D-GLTF', 'setModelRotation failed: %s', e)); - }, ActionTrust.LOW); + this.registerAction( + 'setModelRotation', + invocation => { + this.sendCommandWhenReady_('setModelRotation', invocation.args).catch( + e => dev().error('AMP-3D-GLTF', 'setModelRotation failed: %s', e) + ); + }, + ActionTrust.LOW + ); } /** @override */ @@ -130,9 +142,7 @@ export class Amp3dGltf extends AMP.BaseElement { return Promise.resolve(); } - const iframe = getIframe( - this.win, this.element, '3d-gltf', this.context_ - ); + const iframe = getIframe(this.win, this.element, '3d-gltf', this.context_); this.applyFillContent(iframe, true); this.iframe_ = iframe; @@ -149,11 +159,8 @@ export class Amp3dGltf extends AMP.BaseElement { return; } - const listenIframe = (evName, cb) => listenFor( - dev().assertElement(this.iframe_), - evName, - cb, - true); + const listenIframe = (evName, cb) => + listenFor(dev().assertElement(this.iframe_), evName, cb, true); const disposers = [ listenIframe('ready', this.willBeReady_.resolve), @@ -192,12 +199,7 @@ export class Amp3dGltf extends AMP.BaseElement { * @private */ postMessage_(type, message) { - postMessage( - dev().assertElement(this.iframe_), - type, - message, - '*', - true); + postMessage(dev().assertElement(this.iframe_), type, message, '*', true); } /** @@ -225,8 +227,9 @@ export class Amp3dGltf extends AMP.BaseElement { onLayoutMeasure() { const box = this.getLayoutBox(); this.sendCommandWhenReady_( - 'setSize', - dict({'width': box.width, 'height': box.height})); + 'setSize', + dict({'width': box.width, 'height': box.height}) + ); } /** @override */ diff --git a/extensions/amp-3d-gltf/0.1/test/test-amp-3d-gltf.js b/extensions/amp-3d-gltf/0.1/test/test-amp-3d-gltf.js index 280a30ded811..1fd1d7755308 100644 --- a/extensions/amp-3d-gltf/0.1/test/test-amp-3d-gltf.js +++ b/extensions/amp-3d-gltf/0.1/test/test-amp-3d-gltf.js @@ -17,96 +17,99 @@ import '../amp-3d-gltf'; import {createIframeWithMessageStub} from '../../../../testing/iframe'; -describes.realWin('amp-3d-gltf', { - amp: { - extensions: ['amp-3d-gltf'], +describes.realWin( + 'amp-3d-gltf', + { + amp: { + extensions: ['amp-3d-gltf'], + }, + allowExternalResources: true, }, - allowExternalResources: true, -}, env => { - let win; - let doc; - let iframe; - let testIndex = 0; - let sendFakeMessage = () => {}; + env => { + let win; + let doc; + let iframe; + let testIndex = 0; + let sendFakeMessage = () => {}; - beforeEach(() => { - win = env.win; - doc = win.document; - testIndex++; - const sentinel = 'amp3ptest' + testIndex; - iframe = createIframeWithMessageStub(win); - iframe.setAttribute('data-amp-3p-sentinel', sentinel); - iframe.name = 'test_nomaster'; + beforeEach(() => { + win = env.win; + doc = win.document; + testIndex++; + const sentinel = 'amp3ptest' + testIndex; + iframe = createIframeWithMessageStub(win); + iframe.setAttribute('data-amp-3p-sentinel', sentinel); + iframe.name = 'test_nomaster'; - sendFakeMessage = type => { - return new Promise(resolve => { - iframe.postMessageToParent({sentinel, type}); - setTimeout(resolve, 100); - }); - }; - }); + sendFakeMessage = type => { + return new Promise(resolve => { + iframe.postMessageToParent({sentinel, type}); + setTimeout(resolve, 100); + }); + }; + }); - const createElement = () => { - const amp3dGltfEl = doc.createElement('amp-3d-gltf'); - amp3dGltfEl.setAttribute('src', 'https://fake.com/fake.gltf'); - amp3dGltfEl.setAttribute('layout', 'fixed'); - amp3dGltfEl.setAttribute('width', '320'); - amp3dGltfEl.setAttribute('height', '240'); + const createElement = () => { + const amp3dGltfEl = doc.createElement('amp-3d-gltf'); + amp3dGltfEl.setAttribute('src', 'https://fake.com/fake.gltf'); + amp3dGltfEl.setAttribute('layout', 'fixed'); + amp3dGltfEl.setAttribute('width', '320'); + amp3dGltfEl.setAttribute('height', '240'); - doc.body.appendChild(amp3dGltfEl); + doc.body.appendChild(amp3dGltfEl); - return amp3dGltfEl.build() - .then(() => { - const amp3dGltf = amp3dGltfEl.implementation_; - sandbox.stub(amp3dGltf, 'iframe_') - .get(() => iframe) - .set(() => {}); + return amp3dGltfEl.build().then(() => { + const amp3dGltf = amp3dGltfEl.implementation_; + sandbox + .stub(amp3dGltf, 'iframe_') + .get(() => iframe) + .set(() => {}); - const willLayout = amp3dGltfEl.layoutCallback(); + const willLayout = amp3dGltfEl.layoutCallback(); - return sendFakeMessage('ready') - .then(() => sendFakeMessage('loaded')) - .then(() => willLayout) - .then(() => amp3dGltf); - }); - }; + return sendFakeMessage('ready') + .then(() => sendFakeMessage('loaded')) + .then(() => willLayout) + .then(() => amp3dGltf); + }); + }; - // TODO (#16080): this test keeps timing out for some reason. - // Unskip when we figure out root cause. - it.skip('renders iframe', () => { - return createElement() - .then(() => { - expect(!!doc.body.querySelector('amp-3d-gltf > iframe')).to.be.true; - }); - }); + // TODO (#16080): this test keeps timing out for some reason. + // Unskip when we figure out root cause. + it.skip('renders iframe', () => { + return createElement().then(() => { + expect(!!doc.body.querySelector('amp-3d-gltf > iframe')).to.be.true; + }); + }); - // TODO (#16080): this test times out on Travis. Re-enable when fixed. - it.skip('sends toggleAmpViewport(false) when exiting viewport', () => { - return createElement() - .then(amp3dGltf => { - const postMessageSpy = sandbox.spy(amp3dGltf, 'postMessage_'); - return amp3dGltf.viewportCallback(false).then(() => { - expect(postMessageSpy.calledOnce).to.be.true; - expect(postMessageSpy.firstCall.args[0]).to.equal('action'); - expect(postMessageSpy.firstCall.args[1].action) - .to.equal('toggleAmpViewport'); - expect(postMessageSpy.firstCall.args[1].args).to.be.false; - }); + // TODO (#16080): this test times out on Travis. Re-enable when fixed. + it.skip('sends toggleAmpViewport(false) when exiting viewport', () => { + return createElement().then(amp3dGltf => { + const postMessageSpy = sandbox.spy(amp3dGltf, 'postMessage_'); + return amp3dGltf.viewportCallback(false).then(() => { + expect(postMessageSpy.calledOnce).to.be.true; + expect(postMessageSpy.firstCall.args[0]).to.equal('action'); + expect(postMessageSpy.firstCall.args[1].action).to.equal( + 'toggleAmpViewport' + ); + expect(postMessageSpy.firstCall.args[1].args).to.be.false; }); - }); + }); + }); - // TODO (#16080): this test times out on Travis. Re-enable when fixed. - it.skip('sends toggleAmpViewport(true) when entering viewport', () => { - return createElement() - .then(amp3dGltf => { - const postMessageSpy = sandbox.spy(amp3dGltf, 'postMessage_'); - return amp3dGltf.viewportCallback(true).then(() => { - expect(postMessageSpy.calledOnce).to.be.true; - expect(postMessageSpy.firstCall.args[0]).to.equal('action'); - expect(postMessageSpy.firstCall.args[1].action) - .to.equal('toggleAmpViewport'); - expect(postMessageSpy.firstCall.args[1].args).to.be.true; - }); + // TODO (#16080): this test times out on Travis. Re-enable when fixed. + it.skip('sends toggleAmpViewport(true) when entering viewport', () => { + return createElement().then(amp3dGltf => { + const postMessageSpy = sandbox.spy(amp3dGltf, 'postMessage_'); + return amp3dGltf.viewportCallback(true).then(() => { + expect(postMessageSpy.calledOnce).to.be.true; + expect(postMessageSpy.firstCall.args[0]).to.equal('action'); + expect(postMessageSpy.firstCall.args[1].action).to.equal( + 'toggleAmpViewport' + ); + expect(postMessageSpy.firstCall.args[1].args).to.be.true; }); - }); -}); + }); + }); + } +); diff --git a/extensions/amp-3q-player/0.1/amp-3q-player.js b/extensions/amp-3q-player/0.1/amp-3q-player.js index d015f243a03e..b393c87459a6 100644 --- a/extensions/amp-3q-player/0.1/amp-3q-player.js +++ b/extensions/amp-3q-player/0.1/amp-3q-player.js @@ -30,18 +30,13 @@ import { removeElement, } from '../../../src/dom'; import {getData, listen} from '../../../src/event-helper'; -import { - installVideoManagerForDoc, -} from '../../../src/service/video-manager-impl'; +import {installVideoManagerForDoc} from '../../../src/service/video-manager-impl'; import {isLayoutSizeDefined} from '../../../src/layout'; - const TAG = 'amp-3q-player'; - /** @implements {../../../src/video-interface.VideoInterface} */ class Amp3QPlayer extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -74,9 +69,10 @@ class Amp3QPlayer extends AMP.BaseElement { const {element: el} = this; this.dataId = userAssert( - el.getAttribute('data-id'), - 'The data-id attribute is required for %s', - el); + el.getAttribute('data-id'), + 'The data-id attribute is required for %s', + el + ); const deferred = new Deferred(); this.playerReadyPromise_ = deferred.promise; @@ -88,18 +84,21 @@ class Amp3QPlayer extends AMP.BaseElement { /** @override */ layoutCallback() { - const iframe = createFrameFor(this, - 'https://playout.3qsdn.com/' - + encodeURIComponent(dev().assertString(this.dataId)) - // Autoplay is handled by VideoManager - + '?autoplay=false&=true'); + const iframe = createFrameFor( + this, + 'https://playout.3qsdn.com/' + + encodeURIComponent(dev().assertString(this.dataId)) + + // Autoplay is handled by VideoManager + '?autoplay=false&=true' + ); this.iframe_ = iframe; this.unlistenMessage_ = listen( - this.win, - 'message', - this.sdnBridge_.bind(this)); + this.win, + 'message', + this.sdnBridge_.bind(this) + ); return this.loadPromise(this.iframe_).then(() => this.playerReadyPromise_); } @@ -178,7 +177,7 @@ class Amp3QPlayer extends AMP.BaseElement { sdnPostMessage_(message) { this.playerReadyPromise_.then(() => { if (this.iframe_ && this.iframe_.contentWindow) { - this.iframe_.contentWindow./*OK*/postMessage(message, '*'); + this.iframe_.contentWindow./*OK*/ postMessage(message, '*'); } }); } @@ -291,7 +290,6 @@ class Amp3QPlayer extends AMP.BaseElement { } } - AMP.extension(TAG, '0.1', AMP => { AMP.registerElement(TAG, Amp3QPlayer); }); diff --git a/extensions/amp-3q-player/0.1/test/test-amp-3q-player.js b/extensions/amp-3q-player/0.1/test/test-amp-3q-player.js index 19b4dce85f6a..9f4f76f58fda 100644 --- a/extensions/amp-3q-player/0.1/test/test-amp-3q-player.js +++ b/extensions/amp-3q-player/0.1/test/test-amp-3q-player.js @@ -19,85 +19,101 @@ import {Services} from '../../../../src/services'; import {VideoEvents} from '../../../../src/video-interface'; import {listenOncePromise} from '../../../../src/event-helper'; - -describes.realWin('amp-3q-player', { - amp: { - extensions: ['amp-3q-player'], +describes.realWin( + 'amp-3q-player', + { + amp: { + extensions: ['amp-3q-player'], + }, }, -}, function(env) { - let win; - let doc; - let timer; + function(env) { + let win; + let doc; + let timer; - beforeEach(() => { - win = env.win; - doc = win.document; - timer = Services.timerFor(win); - }); + beforeEach(() => { + win = env.win; + doc = win.document; + timer = Services.timerFor(win); + }); - function get3QElement(playoutId) { - const player = doc.createElement('amp-3q-player'); - if (playoutId) { - player.setAttribute('data-id', playoutId); + function get3QElement(playoutId) { + const player = doc.createElement('amp-3q-player'); + if (playoutId) { + player.setAttribute('data-id', playoutId); + } + doc.body.appendChild(player); + return player + .build() + .then(() => { + player.layoutCallback(); + const iframe = player.querySelector('iframe'); + player.implementation_.sdnBridge_({ + source: iframe.contentWindow, + data: JSON.stringify({data: 'ready'}), + }); + }) + .then(() => { + return player; + }); } - doc.body.appendChild(player); - return player.build().then(() => { - player.layoutCallback(); - const iframe = player.querySelector('iframe'); - player.implementation_.sdnBridge_({ - source: iframe.contentWindow, - data: JSON.stringify({data: 'ready'}), - }); - }).then(() => { - return player; - }); - } - it('renders', () => { - return get3QElement( - 'c8dbe7f4-7f7f-11e6-a407-0cc47a188158').then(player => { - const iframe = player.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.src).to.equal('https://playout.3qsdn.com/c8dbe7f4-7f7f-11e6-a407-0cc47a188158?autoplay=false&=true'); + it('renders', () => { + return get3QElement('c8dbe7f4-7f7f-11e6-a407-0cc47a188158').then( + player => { + const iframe = player.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.src).to.equal( + 'https://playout.3qsdn.com/c8dbe7f4-7f7f-11e6-a407-0cc47a188158?autoplay=false&=true' + ); + } + ); }); - }); - it('requires data-id', () => { - return allowConsoleError(() => { - return get3QElement('').should.eventually.be.rejectedWith( - /The data-id attribute is required/); + it('requires data-id', () => { + return allowConsoleError(() => { + return get3QElement('').should.eventually.be.rejectedWith( + /The data-id attribute is required/ + ); + }); }); - }); - - it('should forward events from amp-3q-player to the amp element', () => { - return get3QElement( - 'c8dbe7f4-7f7f-11e6-a407-0cc47a188158').then(player => { - const iframe = player.querySelector('iframe'); + it('should forward events from amp-3q-player to the amp element', () => { + return get3QElement('c8dbe7f4-7f7f-11e6-a407-0cc47a188158').then( + player => { + const iframe = player.querySelector('iframe'); - return Promise.resolve().then(() => { - const p = listenOncePromise(player, VideoEvents.MUTED); - sendFakeMessage(player, iframe, 'muted'); - return p; - }).then(() => { - const p = listenOncePromise(player, VideoEvents.PLAYING); - sendFakeMessage(player, iframe, 'playing'); - return p; - }).then(() => { - const p = listenOncePromise(player, VideoEvents.PAUSE); - sendFakeMessage(player, iframe, 'paused'); - return p; - }).then(() => { - const p = listenOncePromise(player, VideoEvents.UNMUTED); - sendFakeMessage(player, iframe, 'unmuted'); - const successTimeout = timer.promise(10); - return Promise.race([p, successTimeout]); - }); + return Promise.resolve() + .then(() => { + const p = listenOncePromise(player, VideoEvents.MUTED); + sendFakeMessage(player, iframe, 'muted'); + return p; + }) + .then(() => { + const p = listenOncePromise(player, VideoEvents.PLAYING); + sendFakeMessage(player, iframe, 'playing'); + return p; + }) + .then(() => { + const p = listenOncePromise(player, VideoEvents.PAUSE); + sendFakeMessage(player, iframe, 'paused'); + return p; + }) + .then(() => { + const p = listenOncePromise(player, VideoEvents.UNMUTED); + sendFakeMessage(player, iframe, 'unmuted'); + const successTimeout = timer.promise(10); + return Promise.race([p, successTimeout]); + }); + } + ); }); - }); - function sendFakeMessage(player, iframe, command) { - player.implementation_.sdnBridge_( - {source: iframe.contentWindow, data: JSON.stringify({data: command})}); + function sendFakeMessage(player, iframe, command) { + player.implementation_.sdnBridge_({ + source: iframe.contentWindow, + data: JSON.stringify({data: command}), + }); + } } -}); +); diff --git a/extensions/amp-a4a/0.1/a4a-variable-source.js b/extensions/amp-a4a/0.1/a4a-variable-source.js index ecc3043333b7..6fa81cddc050 100644 --- a/extensions/amp-a4a/0.1/a4a-variable-source.js +++ b/extensions/amp-a4a/0.1/a4a-variable-source.js @@ -23,7 +23,6 @@ import { } from '../../../src/service/variable-source'; import {user, userAssert} from '../../../src/log'; - const WHITELISTED_VARIABLES = [ 'AMPDOC_HOST', 'AMPDOC_HOSTNAME', @@ -102,19 +101,27 @@ export class A4AVariableSource extends VariableSource { } this.set('NAV_TIMING', (startAttribute, endAttribute) => { - userAssert(startAttribute, 'The first argument to NAV_TIMING, the' + - ' start attribute name, is required'); + userAssert( + startAttribute, + 'The first argument to NAV_TIMING, the' + + ' start attribute name, is required' + ); return getTimingDataSync( - this.win_, - /**@type {string}*/(startAttribute), - /**@type {string}*/(endAttribute)); + this.win_, + /**@type {string}*/ (startAttribute), + /**@type {string}*/ (endAttribute) + ); }).setAsync('NAV_TIMING', (startAttribute, endAttribute) => { - userAssert(startAttribute, 'The first argument to NAV_TIMING, the' + - ' start attribute name, is required'); + userAssert( + startAttribute, + 'The first argument to NAV_TIMING, the' + + ' start attribute name, is required' + ); return getTimingDataAsync( - this.win_, - /**@type {string}*/(startAttribute), - /**@type {string}*/(endAttribute)); + this.win_, + /**@type {string}*/ (startAttribute), + /**@type {string}*/ (endAttribute) + ); }); this.set('NAV_TYPE', () => { @@ -125,8 +132,10 @@ export class A4AVariableSource extends VariableSource { return getNavigationData(this.win_, 'redirectCount'); }); - this.set('HTML_ATTR', - /** @type {function(...*)} */(this.htmlAttributeBinding_.bind(this))); + this.set( + 'HTML_ATTR', + /** @type {function(...*)} */ (this.htmlAttributeBinding_.bind(this)) + ); this.set('CLIENT_ID', () => null); } @@ -179,20 +188,27 @@ export class A4AVariableSource extends VariableSource { return '[]'; } if (elements.length > HTML_ATTR_MAX_ELEMENTS_TO_TRAVERSE) { - user().error(TAG, 'CSS selector may match at most ' + - `${HTML_ATTR_MAX_ELEMENTS_TO_TRAVERSE} elements.`); + user().error( + TAG, + 'CSS selector may match at most ' + + `${HTML_ATTR_MAX_ELEMENTS_TO_TRAVERSE} elements.` + ); return '[]'; } const result = []; - for (let i = 0; i < elements.length && - result.length < HTML_ATTR_MAX_ELEMENTS_TO_RETURN; ++i) { + for ( + let i = 0; + i < elements.length && result.length < HTML_ATTR_MAX_ELEMENTS_TO_RETURN; + ++i + ) { const currentResult = {}; let foundAtLeastOneAttr = false; for (let j = 0; j < attributeNames.length; ++j) { const attributeName = attributeNames[j]; if (elements[i].hasAttribute(attributeName)) { - currentResult[attributeName] = - elements[i].getAttribute(attributeName); + currentResult[attributeName] = elements[i].getAttribute( + attributeName + ); foundAtLeastOneAttr = true; } } diff --git a/extensions/amp-a4a/0.1/amp-a4a.js b/extensions/amp-a4a/0.1/amp-a4a.js index d4a6cc1b2390..d33cf7e40528 100644 --- a/extensions/amp-a4a/0.1/amp-a4a.js +++ b/extensions/amp-a4a/0.1/amp-a4a.js @@ -30,10 +30,7 @@ import { generateSentinel, getDefaultBootstrapBaseUrl, } from '../../../src/3p-frame'; -import { - assertHttpsUrl, - tryDecodeUriComponent, -} from '../../../src/url'; +import {assertHttpsUrl, tryDecodeUriComponent} from '../../../src/url'; import {cancellation, isCancellation} from '../../../src/error'; import {createElementWithAttributes} from '../../../src/dom'; import { @@ -59,9 +56,7 @@ import { installFriendlyIframeEmbed, setFriendlyIframeEmbedVisible, } from '../../../src/friendly-iframe-embed'; -import { - installUrlReplacementsForEmbed, -} from '../../../src/service/url-replacements-impl'; +import {installUrlReplacementsForEmbed} from '../../../src/service/url-replacements-impl'; import {isAdPositionAllowed} from '../../../src/ad-helper'; import {isArray, isEnumValue, isObject} from '../../../src/types'; import {isExperimentOn} from '../../../src/experiments'; @@ -76,7 +71,8 @@ import {utf8Decode} from '../../../src/utils/bytes'; const METADATA_STRINGS = [ ''); if (metadataEnd < 0) { // Couldn't find a metadata blob. - dev().warn(TAG, this.element.getAttribute('type'), - 'Could not locate closing script tag for amp meta data in: %s', - creative); + dev().warn( + TAG, + this.element.getAttribute('type'), + 'Could not locate closing script tag for amp meta data in: %s', + creative + ); return null; } try { const metaDataObj = parseJson( - creative.slice(metadataStart + metadataString.length, metadataEnd)); + creative.slice(metadataStart + metadataString.length, metadataEnd) + ); const ampRuntimeUtf16CharOffsets = metaDataObj['ampRuntimeUtf16CharOffsets']; - if (!isArray(ampRuntimeUtf16CharOffsets) || - ampRuntimeUtf16CharOffsets.length != 2 || - typeof ampRuntimeUtf16CharOffsets[0] !== 'number' || - typeof ampRuntimeUtf16CharOffsets[1] !== 'number') { + if ( + !isArray(ampRuntimeUtf16CharOffsets) || + ampRuntimeUtf16CharOffsets.length != 2 || + typeof ampRuntimeUtf16CharOffsets[0] !== 'number' || + typeof ampRuntimeUtf16CharOffsets[1] !== 'number' + ) { throw new Error('Invalid runtime offsets'); } const metaData = {}; @@ -1626,7 +1751,9 @@ export class AmpA4A extends AMP.BaseElement { metaDataObj['customElementExtensions']; if (!isArray(metaData.customElementExtensions)) { throw new Error( - 'Invalid extensions', metaData.customElementExtensions); + 'Invalid extensions', + metaData.customElementExtensions + ); } } else { metaData.customElementExtensions = []; @@ -1642,9 +1769,12 @@ export class AmpA4A extends AMP.BaseElement { const urls = Services.urlForDoc(this.element); metaData.customStylesheets.forEach(stylesheet => { - if (!isObject(stylesheet) || !stylesheet['href'] || - typeof stylesheet['href'] !== 'string' || - !urls.isSecure(stylesheet['href'])) { + if ( + !isObject(stylesheet) || + !stylesheet['href'] || + typeof stylesheet['href'] !== 'string' || + !urls.isSecure(stylesheet['href']) + ) { throw new Error(errorMsg); } }); @@ -1669,8 +1799,11 @@ export class AmpA4A extends AMP.BaseElement { return metaData; } catch (err) { dev().warn( - TAG, this.element.getAttribute('type'), 'Invalid amp metadata: %s', - creative.slice(metadataStart + metadataString.length, metadataEnd)); + TAG, + this.element.getAttribute('type'), + 'Invalid amp metadata: %s', + creative.slice(metadataStart + metadataString.length, metadataEnd) + ); if (this.isSinglePageStoryAd) { throw err; } @@ -1682,8 +1815,10 @@ export class AmpA4A extends AMP.BaseElement { * @return {string} full url to safeframe implementation. */ getSafeframePath() { - return 'https://tpc.googlesyndication.com/safeframe/' + - `${this.safeframeVersion}/html/container.html`; + return ( + 'https://tpc.googlesyndication.com/safeframe/' + + `${this.safeframeVersion}/html/container.html` + ); } /** @@ -1697,11 +1832,13 @@ export class AmpA4A extends AMP.BaseElement { // No config exists that will listen to this event. return; } - const analyticsEvent = - devAssert(LIFECYCLE_STAGE_TO_ANALYTICS_TRIGGER[lifecycleStage]); + const analyticsEvent = devAssert( + LIFECYCLE_STAGE_TO_ANALYTICS_TRIGGER[lifecycleStage] + ); const analyticsVars = /** @type {!JsonObject} */ (Object.assign( - dict({'time': Math.round(this.getNow_())}), - this.getA4aAnalyticsVars(analyticsEvent))); + dict({'time': Math.round(this.getNow_())}), + this.getA4aAnalyticsVars(analyticsEvent) + )); triggerAnalyticsEvent(this.element, analyticsEvent, analyticsVars); } @@ -1724,7 +1861,9 @@ export class AmpA4A extends AMP.BaseElement { * added to this A4A element and no A4A triggers will be fired. * @return {?JsonObject} */ - getA4aAnalyticsConfig() { return null; } + getA4aAnalyticsConfig() { + return null; + } /** * Attempts to execute Real Time Config, if the ad network has enabled it. @@ -1737,19 +1876,20 @@ export class AmpA4A extends AMP.BaseElement { tryExecuteRealTimeConfig_(consentState, consentString) { if (!!AMP.RealTimeConfigManager) { try { - return new AMP.RealTimeConfigManager(this) - .maybeExecuteRealTimeConfig( - this.getCustomRealTimeConfigMacros_(), - consentState, - consentString - ); + return new AMP.RealTimeConfigManager(this).maybeExecuteRealTimeConfig( + this.getCustomRealTimeConfigMacros_(), + consentState, + consentString + ); } catch (err) { user().error(TAG, 'Could not perform Real Time Config.', err); } } else if (this.element.getAttribute('rtc-config')) { - user().error(TAG, 'RTC not supported for ad network ' + - `${this.element.getAttribute('type')}`); - + user().error( + TAG, + 'RTC not supported for ad network ' + + `${this.element.getAttribute('type')}` + ); } } @@ -1779,13 +1919,16 @@ export class AmpA4A extends AMP.BaseElement { if (headerValue) { if (!isEnumValue(XORIGIN_MODE, headerValue)) { dev().error( - 'AMP-A4A', `cross-origin render mode header ${headerValue}`); + 'AMP-A4A', + `cross-origin render mode header ${headerValue}` + ); } else { return headerValue; } } - return Services.platformFor(this.win).isIos() ? - XORIGIN_MODE.NAMEFRAME : null; + return Services.platformFor(this.win).isIos() + ? XORIGIN_MODE.NAMEFRAME + : null; } /** @@ -1805,7 +1948,6 @@ export class AmpA4A extends AMP.BaseElement { return this.isVerifiedAmpCreative_; } - /** * Adds single pass experiment IDs if the javascript binary has * "singlePassType" mode. @@ -1813,11 +1955,15 @@ export class AmpA4A extends AMP.BaseElement { maybeAddSinglePassExperiment() { const type = getMode().singlePassType; if (type === 'sp') { - addExperimentIdToElement(SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS, - this.element); + addExperimentIdToElement( + SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS, + this.element + ); } else if (type === 'mp') { - addExperimentIdToElement(SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS, - this.element); + addExperimentIdToElement( + SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS, + this.element + ); } } } @@ -1835,8 +1981,10 @@ export function assignAdUrlToError(error, adUrl) { if (adQueryIdx == -1) { return; } - (error.args || (error.args = {}))['au'] = - adUrl.substring(adQueryIdx + 1, adQueryIdx + 251); + (error.args || (error.args = {}))['au'] = adUrl.substring( + adQueryIdx + 1, + adQueryIdx + 251 + ); } /** @@ -1853,6 +2001,8 @@ export function assignAdUrlToError(error, adUrl) { */ export function signatureVerifierFor(win) { const propertyName = 'AMP_FAST_FETCH_SIGNATURE_VERIFIER_'; - return win[propertyName] || - (win[propertyName] = new SignatureVerifier(win, signingServerURLs)); + return ( + win[propertyName] || + (win[propertyName] = new SignatureVerifier(win, signingServerURLs)) + ); } diff --git a/extensions/amp-a4a/0.1/amp-ad-network-base.js b/extensions/amp-a4a/0.1/amp-ad-network-base.js index 19c2d2b8c418..f5cbcadfab9a 100644 --- a/extensions/amp-a4a/0.1/amp-ad-network-base.js +++ b/extensions/amp-a4a/0.1/amp-ad-network-base.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - FailureType, - RecoveryModeType, -} from './amp-ad-type-defs'; +import {FailureType, RecoveryModeType} from './amp-ad-type-defs'; import {Services} from '../../../src/services'; import {dev, devAssert} from '../../../src/log'; import {isLayoutSizeDefined} from '../../../src/layout'; @@ -30,7 +27,6 @@ const TAG = 'amp-ad-network-base'; * @abstract */ export class AmpAdNetworkBase extends AMP.BaseElement { - /** * Creates an instance of AmpAdNetworkBase. * @param {!AmpElement} element @@ -77,12 +73,14 @@ export class AmpAdNetworkBase extends AMP.BaseElement { /** @override */ layoutCallback() { - devAssert(this.adResponsePromise_, - 'layoutCallback invoked before XHR request!'); + devAssert( + this.adResponsePromise_, + 'layoutCallback invoked before XHR request!' + ); return this.adResponsePromise_ - .then(response => this.invokeValidator_(response)) - .then(validatorResult => this.invokeRenderer_(validatorResult)) - .catch(error => this.handleFailure_(error.type, error.msg)); + .then(response => this.invokeValidator_(response)) + .then(validatorResult => this.invokeRenderer_(validatorResult)) + .catch(error => this.handleFailure_(error.type, error.msg)); } /** @@ -92,8 +90,10 @@ export class AmpAdNetworkBase extends AMP.BaseElement { */ onFailure(failure, recovery) { if (this.recoveryModes_[failure]) { - dev().warn(TAG, - `Recovery mode for failure type ${failure} already registered!`); + dev().warn( + TAG, + `Recovery mode for failure type ${failure} already registered!` + ); } this.recoveryModes_[failure] = recovery; } @@ -148,10 +148,12 @@ export class AmpAdNetworkBase extends AMP.BaseElement { * @private */ sendRequest_() { - Services.viewerForDoc(this.getAmpDoc()).whenFirstVisible().then(() => { - const url = this.getRequestUrl(); - this.adResponsePromise_ = sendXhrRequest(this.win, url); - }); + Services.viewerForDoc(this.getAmpDoc()) + .whenFirstVisible() + .then(() => { + const url = this.getRequestUrl(); + this.adResponsePromise_ = sendXhrRequest(this.win, url); + }); } /** @@ -165,14 +167,14 @@ export class AmpAdNetworkBase extends AMP.BaseElement { return Promise.reject(this.handleFailure_(FailureType.INVALID_RESPONSE)); } return response.arrayBuffer().then(unvalidatedBytes => { - const validatorType = response.headers.get('AMP-Ad-Response-Type') - || 'default'; - devAssert(this.validators_[validatorType], - 'Validator never registered!'); - return this.validators_[validatorType].validate( - this.context_, unvalidatedBytes, response.headers) - .catch(err => - Promise.reject({type: FailureType.VALIDATOR_ERROR, msg: err})); + const validatorType = + response.headers.get('AMP-Ad-Response-Type') || 'default'; + devAssert(this.validators_[validatorType], 'Validator never registered!'); + return this.validators_[validatorType] + .validate(this.context_, unvalidatedBytes, response.headers) + .catch(err => + Promise.reject({type: FailureType.VALIDATOR_ERROR, msg: err}) + ); }); } @@ -184,10 +186,11 @@ export class AmpAdNetworkBase extends AMP.BaseElement { invokeRenderer_(validatorOutput) { const renderer = this.renderers_[validatorOutput.type]; devAssert(renderer, 'Renderer for AMP creatives never registered!'); - return renderer.render( - this.context_, this.element, validatorOutput.creativeData) - .catch(err => - Promise.reject({type: FailureType.RENDERER_ERROR, msg: err})); + return renderer + .render(this.context_, this.element, validatorOutput.creativeData) + .catch(err => + Promise.reject({type: FailureType.RENDERER_ERROR, msg: err}) + ); } /** diff --git a/extensions/amp-a4a/0.1/amp-ad-template-helper.js b/extensions/amp-a4a/0.1/amp-ad-template-helper.js index 99fcd9b4b0b2..94426f796ecd 100644 --- a/extensions/amp-a4a/0.1/amp-ad-template-helper.js +++ b/extensions/amp-a4a/0.1/amp-ad-template-helper.js @@ -35,7 +35,6 @@ const TEMPLATE_CORS_CONFIG = { }; export class AmpAdTemplateHelper { - /** * @param {!Window} win */ @@ -54,19 +53,20 @@ export class AmpAdTemplateHelper { * @return {!Promise} */ fetch(templateUrl) { - const proxyUrl = getMode(this.win_).localDev && !isNaN(templateUrl) - ? `http://ads.localhost:${this.win_.location.port}` + + const proxyUrl = + getMode(this.win_).localDev && !isNaN(templateUrl) + ? `http://ads.localhost:${this.win_.location.port}` + `/a4a_template/adzerk/${templateUrl}` - : this.getTemplateProxyUrl_(templateUrl); + : this.getTemplateProxyUrl_(templateUrl); let templatePromise = this.cache_.get(proxyUrl); if (!templatePromise) { templatePromise = Services.xhrFor(this.win_) - .fetchText(proxyUrl, TEMPLATE_CORS_CONFIG) - .then(response => response.text()); + .fetchText(proxyUrl, TEMPLATE_CORS_CONFIG) + .then(response => response.text()); this.cache_.put(proxyUrl, templatePromise); } devAssert(templatePromise); - return /** @type{!Promise} */ (templatePromise); + return /** @type {!Promise} */ (templatePromise); } /** @@ -75,8 +75,10 @@ export class AmpAdTemplateHelper { * @return {!Promise} Promise which resolves after rendering completes. */ render(templateValues, element) { - return Services.templatesFor(this.win_) - .findAndRenderTemplate(element, templateValues); + return Services.templatesFor(this.win_).findAndRenderTemplate( + element, + templateValues + ); } /** @@ -84,8 +86,9 @@ export class AmpAdTemplateHelper { * @param {!Array|!JsonObject} analyticsValue */ insertAnalytics(element, analyticsValue) { - analyticsValue = /**@type {!Array}*/ - (isArray(analyticsValue) ? analyticsValue : [analyticsValue]); + analyticsValue = /**@type {!Array}*/ (isArray(analyticsValue) + ? analyticsValue + : [analyticsValue]); for (let i = 0; i < analyticsValue.length; i++) { const config = analyticsValue[i]; const analyticsEle = element.ownerDocument.createElement('amp-analytics'); @@ -97,10 +100,12 @@ export class AmpAdTemplateHelper { } if (config['inline']) { const scriptElem = createElementWithAttributes( - element.ownerDocument, - 'script', dict({ - 'type': 'application/json', - })); + element.ownerDocument, + 'script', + dict({ + 'type': 'application/json', + }) + ); scriptElem.textContent = JSON.stringify(config['inline']); analyticsEle.appendChild(scriptElem); } @@ -116,8 +121,14 @@ export class AmpAdTemplateHelper { getTemplateProxyUrl_(url) { const cdnUrlSuffix = urls.cdn.slice(8); const loc = parseUrlDeprecated(url); - return loc.origin.indexOf(cdnUrlSuffix) > 0 ? url : - 'https://' + loc.hostname.replace(/-/g, '--').replace(/\./g, '-') + - '.' + cdnUrlSuffix + '/ad/s/' + loc.hostname + loc.pathname; + return loc.origin.indexOf(cdnUrlSuffix) > 0 + ? url + : 'https://' + + loc.hostname.replace(/-/g, '--').replace(/\./g, '-') + + '.' + + cdnUrlSuffix + + '/ad/s/' + + loc.hostname + + loc.pathname; } } diff --git a/extensions/amp-a4a/0.1/amp-ad-utils.js b/extensions/amp-a4a/0.1/amp-ad-utils.js index d3d0eae87352..052eaf0f327c 100644 --- a/extensions/amp-a4a/0.1/amp-ad-utils.js +++ b/extensions/amp-a4a/0.1/amp-ad-utils.js @@ -39,7 +39,8 @@ export function sendXhrRequest(win, url) { const METADATA_STRINGS = [ ''); if (metadataEnd < 0) { // Couldn't find a metadata blob. - dev().warn(TAG, - 'Could not locate closing script tag for amp meta data in: %s', - creative); + dev().warn( + TAG, + 'Could not locate closing script tag for amp meta data in: %s', + creative + ); return null; } try { const metaDataObj = parseJson( - creative.slice(metadataStart + metadataString.length, metadataEnd)); - let ampRuntimeUtf16CharOffsets = - metaDataObj['ampRuntimeUtf16CharOffsets']; + creative.slice(metadataStart + metadataString.length, metadataEnd) + ); + let ampRuntimeUtf16CharOffsets = metaDataObj['ampRuntimeUtf16CharOffsets']; if (!isArray(ampRuntimeUtf16CharOffsets)) { const headStart = creative.indexOf(''); const headEnd = creative.indexOf(' '); const headSubstring = creative.slice( - headStart, headEnd + ' '.length); + headStart, + headEnd + ' '.length + ); const scriptStart = headSubstring.indexOf(''); if (scriptStart < 0 || scriptEnd < 0) { @@ -96,18 +103,18 @@ export function getAmpAdMetadata(creative) { headStart + scriptStart, headStart + scriptEnd + ''.length, ]; - } else if (ampRuntimeUtf16CharOffsets.length != 2 || - typeof ampRuntimeUtf16CharOffsets[0] !== 'number' || - typeof ampRuntimeUtf16CharOffsets[1] !== 'number') { + } else if ( + ampRuntimeUtf16CharOffsets.length != 2 || + typeof ampRuntimeUtf16CharOffsets[0] !== 'number' || + typeof ampRuntimeUtf16CharOffsets[1] !== 'number' + ) { throw new Error('Invalid runtime offsets'); } const metaData = {}; if (metaDataObj['customElementExtensions']) { - metaData.customElementExtensions = - metaDataObj['customElementExtensions']; + metaData.customElementExtensions = metaDataObj['customElementExtensions']; if (!isArray(metaData.customElementExtensions)) { - throw new Error( - 'Invalid extensions', metaData.customElementExtensions); + throw new Error('Invalid extensions', metaData.customElementExtensions); } } else { metaData.customElementExtensions = []; @@ -121,9 +128,12 @@ export function getAmpAdMetadata(creative) { throw new Error(errorMsg); } metaData.customStylesheets.forEach(stylesheet => { - if (!isObject(stylesheet) || !stylesheet['href'] || - typeof stylesheet['href'] !== 'string' || - !isSecureUrlDeprecated(stylesheet['href'])) { + if ( + !isObject(stylesheet) || + !stylesheet['href'] || + typeof stylesheet['href'] !== 'string' || + !isSecureUrlDeprecated(stylesheet['href']) + ) { throw new Error(errorMsg); } }); @@ -141,8 +151,10 @@ export function getAmpAdMetadata(creative) { return metaData; } catch (err) { dev().warn( - TAG, 'Invalid amp metadata: %s', - creative.slice(metadataStart + metadataString.length, metadataEnd)); + TAG, + 'Invalid amp metadata: %s', + creative.slice(metadataStart + metadataString.length, metadataEnd) + ); return null; } } diff --git a/extensions/amp-a4a/0.1/callout-vendors.js b/extensions/amp-a4a/0.1/callout-vendors.js index d95d8c4776d9..f3345641f312 100644 --- a/extensions/amp-a4a/0.1/callout-vendors.js +++ b/extensions/amp-a4a/0.1/callout-vendors.js @@ -37,36 +37,41 @@ let RtcVendorDef; /** @const {!Object} */ export const RTC_VENDORS = { -//////////////////////////////////////////////////////////////////// -// // -// !!! IMPORTANT NOTE !!! // -// // -// If you are adding a new vendor config object to this object, // -// make sure to also update the RTC documentation in these two // -// files under "supported vendors". // -// https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/rtc-documentation.md -// https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/rtc-publisher-implementation-guide.md -//////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////// + // // + // !!! IMPORTANT NOTE !!! // + // // + // If you are adding a new vendor config object to this object, // + // make sure to also update the RTC documentation in these two // + // files under "supported vendors". // + // https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/rtc-documentation.md + // https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/rtc-publisher-implementation-guide.md + //////////////////////////////////////////////////////////////////// // Add vendors here medianet: { - url: 'https://amprtc.media.net/rtb/getrtc?cid=CID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&tgt=TGT&curl=CANONICAL_URL&to=TIMEOUT&purl=HREF', + url: + 'https://amprtc.media.net/rtb/getrtc?cid=CID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&tgt=TGT&curl=CANONICAL_URL&to=TIMEOUT&purl=HREF', macros: ['CID'], - errorReportingUrl: 'https://qsearch-a.akamaihd.net/log?logid=kfk&evtid=projectevents&project=amprtc_error&error=ERROR_TYPE&rd=HREF', + errorReportingUrl: + 'https://qsearch-a.akamaihd.net/log?logid=kfk&evtid=projectevents&project=amprtc_error&error=ERROR_TYPE&rd=HREF', disableKeyAppend: true, }, prebidappnexus: { - url: 'https://prebid.adnxs.com/pbs/v1/openrtb2/amp?tag_id=PLACEMENT_ID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&targeting=TGT&curl=CANONICAL_URL&timeout=TIMEOUT&adcid=ADCID&purl=HREF', + url: + 'https://prebid.adnxs.com/pbs/v1/openrtb2/amp?tag_id=PLACEMENT_ID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&targeting=TGT&curl=CANONICAL_URL&timeout=TIMEOUT&adcid=ADCID&purl=HREF', macros: ['PLACEMENT_ID'], disableKeyAppend: true, }, prebidrubicon: { - url: 'https://prebid-server.rubiconproject.com/openrtb2/amp?tag_id=REQUEST_ID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&targeting=TGT&curl=CANONICAL_URL&timeout=TIMEOUT&adc=ADCID&purl=HREF', + url: + 'https://prebid-server.rubiconproject.com/openrtb2/amp?tag_id=REQUEST_ID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&targeting=TGT&curl=CANONICAL_URL&timeout=TIMEOUT&adc=ADCID&purl=HREF', macros: ['REQUEST_ID'], disableKeyAppend: true, }, indexexchange: { - url: 'https://amp.casalemedia.com/amprtc?v=1&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&s=SITE_ID&p=CANONICAL_URL', + url: + 'https://amp.casalemedia.com/amprtc?v=1&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&s=SITE_ID&p=CANONICAL_URL', macros: ['SITE_ID'], disableKeyAppend: true, }, @@ -76,34 +81,40 @@ export const RTC_VENDORS = { disableKeyAppend: true, }, yieldbot: { - url: 'https://i.yldbt.com/m/YB_PSN/v1/amp/init?curl=CANONICAL_URL&sn=YB_SLOT&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&aup=ATTR(data-slot)&pvi=PAGEVIEWID&tgt=TGT&adcid=ADCID&href=HREF', + url: + 'https://i.yldbt.com/m/YB_PSN/v1/amp/init?curl=CANONICAL_URL&sn=YB_SLOT&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&aup=ATTR(data-slot)&pvi=PAGEVIEWID&tgt=TGT&adcid=ADCID&href=HREF', macros: ['YB_PSN', 'YB_SLOT'], disableKeyAppend: true, }, salesforcedmp: { - url: 'https://cdn.krxd.net/userdata/v2/amp/ORGANIZATION_ID?segments_key=SEGMENTS_KEY&kuid_key=USER_KEY', + url: + 'https://cdn.krxd.net/userdata/v2/amp/ORGANIZATION_ID?segments_key=SEGMENTS_KEY&kuid_key=USER_KEY', macros: ['ORGANIZATION_ID', 'SEGMENTS_KEY', 'USER_KEY'], disableKeyAppend: true, }, purch: { - url: 'https://ads.servebom.com/tmntag.js?v=1.2&fmt=amp&o={%22p%22%3APLACEMENT_ID}&div_id=DIV_ID', + url: + 'https://ads.servebom.com/tmntag.js?v=1.2&fmt=amp&o={%22p%22%3APLACEMENT_ID}&div_id=DIV_ID', macros: ['PLACEMENT_ID', 'DIV_ID'], disableKeyAppend: true, }, aps: { - url: 'https://aax.amazon-adsystem.com/e/dtb/bid?src=PUB_ID&pubid=PUB_UUID&=1&u=CANONICAL_URL&slots=%5B%7B%22sd%22%3A%22ATTR(data-slot)%22%2C%22s%22%3A%5B%22ATTR(width)xATTR(height)%22%5D%2C%22ms%22%3A%22ATTR(data-multi-size)%22%7D%5D&pj=PARAMS', + url: + 'https://aax.amazon-adsystem.com/e/dtb/bid?src=PUB_ID&pubid=PUB_UUID&=1&u=CANONICAL_URL&slots=%5B%7B%22sd%22%3A%22ATTR(data-slot)%22%2C%22s%22%3A%5B%22ATTR(width)xATTR(height)%22%5D%2C%22ms%22%3A%22ATTR(data-multi-size)%22%7D%5D&pj=PARAMS', macros: ['PUB_ID', 'PARAMS', 'PUB_UUID'], disableKeyAppend: true, }, openwrap: { // PubMatic OpenWrap - url: 'https://ow.pubmatic.com/amp?v=1&w=ATTR(width)&h=ATTR(height)&ms=ATTR(data-multi-size)&auId=ATTR(data-slot)&purl=HREF&pubId=PUB_ID&profId=PROFILE_ID', + url: + 'https://ow.pubmatic.com/amp?v=1&w=ATTR(width)&h=ATTR(height)&ms=ATTR(data-multi-size)&auId=ATTR(data-slot)&purl=HREF&pubId=PUB_ID&profId=PROFILE_ID', macros: ['PUB_ID', 'PROFILE_ID'], errorReportingUrl: 'https://ow.pubmatic.com/amp_error?e=ERROR_TYPE&h=HREF', disableKeyAppend: true, }, criteo: { - url: 'https://bidder.criteo.com/amp/rtc?zid=ZONE_ID&nid=NETWORK_ID&psubid=PUBLISHER_SUB_ID&lir=LINE_ITEM_RANGES&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&timeout=TIMEOUT&curl=CANONICAL_URL&href=HREF', + url: + 'https://bidder.criteo.com/amp/rtc?zid=ZONE_ID&nid=NETWORK_ID&psubid=PUBLISHER_SUB_ID&lir=LINE_ITEM_RANGES&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&timeout=TIMEOUT&curl=CANONICAL_URL&href=HREF', macros: ['ZONE_ID', 'NETWORK_ID', 'PUBLISHER_SUB_ID', 'LINE_ITEM_RANGES'], disableKeyAppend: true, }, @@ -112,7 +123,8 @@ export const RTC_VENDORS = { macros: ['NVG_ACC'], }, sonobi: { - url: 'https://apex.go.sonobi.com/trinity.json?key_maker=%7B%22_DIVIDER_ATTR(data-slot)%7C1%22%3A%22PLACEMENT_ID_DIVIDER_ATTR(width)xATTR(height)%2CATTR(data-multi-size)%22%7D&ref=CANONICAL_URL&lib_name=amp&lib_v=0.1&pv=PAGEVIEWID&=1', + url: + 'https://apex.go.sonobi.com/trinity.json?key_maker=%7B%22_DIVIDER_ATTR(data-slot)%7C1%22%3A%22PLACEMENT_ID_DIVIDER_ATTR(width)xATTR(height)%2CATTR(data-multi-size)%22%7D&ref=CANONICAL_URL&lib_name=amp&lib_v=0.1&pv=PAGEVIEWID&=1', disableKeyAppend: true, macros: ['PLACEMENT_ID', '_DIVIDER_'], }, @@ -120,12 +132,14 @@ export const RTC_VENDORS = { // DO NOT MODIFY: Setup for tests if (getMode().localDev || getMode().test) { - RTC_VENDORS['fakevendor'] = /** @type {RtcVendorDef} */({ - url: 'https://localhost:8000/examples/rtcE1.json?slot_id=SLOT_ID&page_id=PAGE_ID&foo_id=FOO_ID', + RTC_VENDORS['fakevendor'] = /** @type {RtcVendorDef} */ ({ + url: + 'https://localhost:8000/examples/rtcE1.json?slot_id=SLOT_ID&page_id=PAGE_ID&foo_id=FOO_ID', macros: ['SLOT_ID', 'PAGE_ID', 'FOO_ID'], }); - RTC_VENDORS['fakevendor2'] = /** @type {RtcVendorDef} */({ - url: 'https://localhost:8000/examples/rtcE1.json?slot_id=SLOT_ID&page_id=PAGE_ID&foo_id=FOO_ID', + RTC_VENDORS['fakevendor2'] = /** @type {RtcVendorDef} */ ({ + url: + 'https://localhost:8000/examples/rtcE1.json?slot_id=SLOT_ID&page_id=PAGE_ID&foo_id=FOO_ID', errorReportingUrl: 'https://localhost:8000/examples/ERROR_TYPE', disableKeyAppend: true, }); diff --git a/extensions/amp-a4a/0.1/cryptographic-validator.js b/extensions/amp-a4a/0.1/cryptographic-validator.js index 22911f7ff96c..f33470bf623b 100644 --- a/extensions/amp-a4a/0.1/cryptographic-validator.js +++ b/extensions/amp-a4a/0.1/cryptographic-validator.js @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - AdResponseType, - Validator, - ValidatorResult, -} from './amp-ad-type-defs'; +import {AdResponseType, Validator, ValidatorResult} from './amp-ad-type-defs'; import {SignatureVerifier, VerificationStatus} from './signature-verifier'; import {getAmpAdMetadata} from './amp-ad-utils'; import {signingServerURLs} from '../../../ads/_a4a-config'; @@ -26,7 +22,7 @@ import {user} from '../../../src/log'; import {utf8Decode} from '../../../src/utils/bytes'; export const SIGNATURE_VERIFIER_PROPERTY_NAME = - 'AMP_FAST_FETCH_SIGNATURE_VERIFIER_'; + 'AMP_FAST_FETCH_SIGNATURE_VERIFIER_'; const TAG = 'amp-ad-cryptographic-validator'; @@ -34,9 +30,13 @@ export class CryptographicValidator extends Validator { /** @param {!Window} win */ getSignatureVerifier_(win) { // TODO(levitzky) extract this into a service registered to ampdoc. - return win[SIGNATURE_VERIFIER_PROPERTY_NAME] || - (win[SIGNATURE_VERIFIER_PROPERTY_NAME] = - new SignatureVerifier(win, signingServerURLs)); + return ( + win[SIGNATURE_VERIFIER_PROPERTY_NAME] || + (win[SIGNATURE_VERIFIER_PROPERTY_NAME] = new SignatureVerifier( + win, + signingServerURLs + )) + ); } /** @@ -49,8 +49,10 @@ export class CryptographicValidator extends Validator { creativeMetadata: getAmpAdMetadata(utf8Decode(bytes)), }; return /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ - type: verificationSucceeded && !!creativeData.creativeMetadata ? - ValidatorResult.AMP : ValidatorResult.NON_AMP, + type: + verificationSucceeded && !!creativeData.creativeMetadata + ? ValidatorResult.AMP + : ValidatorResult.NON_AMP, adResponseType: AdResponseType.CRYPTO, creativeData, }); @@ -59,23 +61,28 @@ export class CryptographicValidator extends Validator { /** @override */ validate(context, unvalidatedBytes, headers) { return this.getSignatureVerifier_(context.win) - .verify(unvalidatedBytes, headers, /* lifecycleCallback */ - (unusedEventName, unusedExtraVariables) => {}) - .then(status => { - switch (status) { - case VerificationStatus.OK: - return this.createOutput_(true, unvalidatedBytes); - case VerificationStatus.UNVERIFIED: - // TODO(levitzky) Preferential render without crypto in some - // instances. - case VerificationStatus.CRYPTO_UNAVAILABLE: - // TODO(@taymonbeal, #9274): differentiate between these - case VerificationStatus.ERROR_KEY_NOT_FOUND: - case VerificationStatus.ERROR_SIGNATURE_MISMATCH: - user().error(TAG, - `Signature verification failed with status ${status}.`); - return this.createOutput_(false, unvalidatedBytes); - } - }); + .verify( + unvalidatedBytes, + headers /* lifecycleCallback */, + (unusedEventName, unusedExtraVariables) => {} + ) + .then(status => { + switch (status) { + case VerificationStatus.OK: + return this.createOutput_(true, unvalidatedBytes); + case VerificationStatus.UNVERIFIED: + // TODO(levitzky) Preferential render without crypto in some + // instances. + case VerificationStatus.CRYPTO_UNAVAILABLE: + // TODO(@taymonbeal, #9274): differentiate between these + case VerificationStatus.ERROR_KEY_NOT_FOUND: + case VerificationStatus.ERROR_SIGNATURE_MISMATCH: + user().error( + TAG, + `Signature verification failed with status ${status}.` + ); + return this.createOutput_(false, unvalidatedBytes); + } + }); } } diff --git a/extensions/amp-a4a/0.1/friendly-frame-renderer.js b/extensions/amp-a4a/0.1/friendly-frame-renderer.js index 02cb9eea527e..c6fc637b93c9 100644 --- a/extensions/amp-a4a/0.1/friendly-frame-renderer.js +++ b/extensions/amp-a4a/0.1/friendly-frame-renderer.js @@ -29,7 +29,6 @@ export let CreativeData; * Render a validated AMP creative directly in the parent page. */ export class FriendlyFrameRenderer extends Renderer { - /** * Constructs a FriendlyFrameRenderer instance. The instance values here are * used by TemplateRenderer, which inherits from FriendlyFrameRenderer. @@ -40,7 +39,6 @@ export class FriendlyFrameRenderer extends Renderer { /** @override */ render(context, element, creativeData) { - creativeData = /** @type {CreativeData} */ (creativeData); const {size, adUrl} = context; @@ -50,6 +48,10 @@ export class FriendlyFrameRenderer extends Renderer { devAssert(adUrl, 'missing ad request url'); return renderCreativeIntoFriendlyFrame( - adUrl, size, element, creativeMetadata); + adUrl, + size, + element, + creativeMetadata + ); } } diff --git a/extensions/amp-a4a/0.1/friendly-frame-util.js b/extensions/amp-a4a/0.1/friendly-frame-util.js index 5eecd0b11923..a21c10bd9531 100644 --- a/extensions/amp-a4a/0.1/friendly-frame-util.js +++ b/extensions/amp-a4a/0.1/friendly-frame-util.js @@ -21,9 +21,7 @@ import { installFriendlyIframeEmbed, setFriendlyIframeEmbedVisible, } from '../../../src/friendly-iframe-embed'; -import { - installUrlReplacementsForEmbed, -} from '../../../src/service/url-replacements-impl'; +import {installUrlReplacementsForEmbed} from '../../../src/service/url-replacements-impl'; import {setStyle} from '../../../src/style'; /** @@ -39,22 +37,26 @@ import {setStyle} from '../../../src/style'; * @return {!Promise} The iframe into which the creative was rendered. */ export function renderCreativeIntoFriendlyFrame( - adUrl, size, element, creativeMetadata) { + adUrl, + size, + element, + creativeMetadata +) { // Create and setup friendly iframe. - const iframe = /** @type {!HTMLIFrameElement} */( - createElementWithAttributes( - /** @type {!Document} */(element.ownerDocument), - 'iframe', - dict({ - // NOTE: It is possible for either width or height to be 'auto', - // a non-numeric value. - 'height': size.height, - 'width': size.width, - 'frameborder': '0', - 'allowfullscreen': '', - 'allowtransparency': '', - 'scrolling': 'no', - }))); + const iframe = /** @type {!HTMLIFrameElement} */ (createElementWithAttributes( + /** @type {!Document} */ (element.ownerDocument), + 'iframe', + dict({ + // NOTE: It is possible for either width or height to be 'auto', + // a non-numeric value. + 'height': size.height, + 'width': size.width, + 'frameborder': '0', + 'allowfullscreen': '', + 'allowtransparency': '', + 'scrolling': 'no', + }) + )); // TODO(glevitzky): Ensure that applyFillContent or equivalent is called. const fontsArray = []; @@ -68,23 +70,29 @@ export function renderCreativeIntoFriendlyFrame( } return installFriendlyIframeEmbed( - iframe, element, { - host: element, - url: /** @type {string} */ (adUrl), - html: creativeMetadata.minifiedCreative, - extensionIds: creativeMetadata.customElementExtensions || [], - fonts: fontsArray, - }, embedWin => { - installUrlReplacementsForEmbed(element.getAmpDoc(), embedWin, - new A4AVariableSource(element.getAmpDoc(), embedWin)); - }) - .then(friendlyIframeEmbed => { - setFriendlyIframeEmbedVisible( - friendlyIframeEmbed, element.isInViewport()); - // Ensure visibility hidden has been removed (set by boilerplate). - const frameDoc = friendlyIframeEmbed.iframe.contentDocument || - friendlyIframeEmbed.win.document; - setStyle(frameDoc.body, 'visibility', 'visible'); - return iframe; - }); + iframe, + element, + { + host: element, + url: /** @type {string} */ (adUrl), + html: creativeMetadata.minifiedCreative, + extensionIds: creativeMetadata.customElementExtensions || [], + fonts: fontsArray, + }, + embedWin => { + installUrlReplacementsForEmbed( + element.getAmpDoc(), + embedWin, + new A4AVariableSource(element.getAmpDoc(), embedWin) + ); + } + ).then(friendlyIframeEmbed => { + setFriendlyIframeEmbedVisible(friendlyIframeEmbed, element.isInViewport()); + // Ensure visibility hidden has been removed (set by boilerplate). + const frameDoc = + friendlyIframeEmbed.iframe.contentDocument || + friendlyIframeEmbed.win.document; + setStyle(frameDoc.body, 'visibility', 'visible'); + return iframe; + }); } diff --git a/extensions/amp-a4a/0.1/name-frame-renderer.js b/extensions/amp-a4a/0.1/name-frame-renderer.js index 6f0650c224a1..9dd3c3f59ceb 100644 --- a/extensions/amp-a4a/0.1/name-frame-renderer.js +++ b/extensions/amp-a4a/0.1/name-frame-renderer.js @@ -27,27 +27,28 @@ import {utf8Decode} from '../../../src/utils/bytes'; export class NameFrameRenderer extends Renderer { /** @override */ render(context, element, crossDomainData) { - crossDomainData = /** @type {!./amp-ad-type-defs.CrossDomainDataDef} */ ( - crossDomainData); + crossDomainData = /** @type {!./amp-ad-type-defs.CrossDomainDataDef} */ (crossDomainData); if (!crossDomainData.creative && !crossDomainData.rawCreativeBytes) { // No creative, nothing to do. return Promise.resolve(); } - const creative = crossDomainData.creative || - // rawCreativeBytes must exist; if we're here, then `creative` must not - // exist, but the if-statement above guarantees that at least one of - // `creative` || `rawCreativeBytes` exists. - utf8Decode(/** @type {!ArrayBuffer} */ ( - crossDomainData.rawCreativeBytes)); - const srcPath = - getDefaultBootstrapBaseUrl(context.win, 'nameframe'); + const creative = + crossDomainData.creative || + // rawCreativeBytes must exist; if we're here, then `creative` must not + // exist, but the if-statement above guarantees that at least one of + // `creative` || `rawCreativeBytes` exists. + utf8Decode( + /** @type {!ArrayBuffer} */ (crossDomainData.rawCreativeBytes) + ); + const srcPath = getDefaultBootstrapBaseUrl(context.win, 'nameframe'); const contextMetadata = getContextMetadata( - context.win, - element, - context.sentinel, - crossDomainData.additionalContextMetadata); + context.win, + element, + context.sentinel, + crossDomainData.additionalContextMetadata + ); contextMetadata['creative'] = creative; const attributes = dict({ 'src': srcPath, @@ -65,8 +66,10 @@ export class NameFrameRenderer extends Renderer { attributes['data-amp-3p-sentinel'] = crossDomainData.sentinel; } const iframe = createElementWithAttributes( - /** @type {!Document} */ (element.ownerDocument), 'iframe', - /** @type {!JsonObject} */ (attributes)); + /** @type {!Document} */ (element.ownerDocument), + 'iframe', + /** @type {!JsonObject} */ (attributes) + ); // TODO(glevitzky): Ensure that applyFillContent or equivalent is called. element.appendChild(iframe); return Promise.resolve(); diff --git a/extensions/amp-a4a/0.1/real-time-config-manager.js b/extensions/amp-a4a/0.1/real-time-config-manager.js index 60baa79940ac..83fc4c82c8da 100644 --- a/extensions/amp-a4a/0.1/real-time-config-manager.js +++ b/extensions/amp-a4a/0.1/real-time-config-manager.js @@ -32,8 +32,8 @@ const MAX_RTC_CALLOUTS = 5; const MAX_URL_LENGTH = 16384; /** @type {boolean} */ -const ERROR_REPORTING_ENABLED = getMode(window).localDev || - getMode(window).test || Math.random() < 0.01; +const ERROR_REPORTING_ENABLED = + getMode(window).localDev || getMode(window).test || Math.random() < 0.01; /** @typedef {{ urls: (undefined|Array| @@ -116,14 +116,14 @@ export class RealTimeConfigManager { * @return {!Promise} * @private */ - buildErrorResponse_( - error, callout, errorReportingUrl, opt_rtcTime) { + buildErrorResponse_(error, callout, errorReportingUrl, opt_rtcTime) { dev().warn(TAG, `RTC callout to ${callout} caused ${error}`); if (errorReportingUrl) { this.sendErrorMessage(error, errorReportingUrl); } - return Promise.resolve(/**@type {rtcResponseDef} */( - {error, callout, rtcTime: opt_rtcTime || 0})); + return Promise.resolve( + /**@type {rtcResponseDef} */ ({error, callout, rtcTime: opt_rtcTime || 0}) + ); } /** @@ -203,18 +203,26 @@ export class RealTimeConfigManager { if (isArray(sendRegardlessOfConsentState)) { for (let i = 0; i < sendRegardlessOfConsentState.length; i++) { - if (this.consentState_ == - CONSENT_POLICY_STATE[sendRegardlessOfConsentState[i]]) { + if ( + this.consentState_ == + CONSENT_POLICY_STATE[sendRegardlessOfConsentState[i]] + ) { return true; } else if (!CONSENT_POLICY_STATE[sendRegardlessOfConsentState[i]]) { - dev().warn(TAG, 'Invalid RTC consent state given: ' + - `${sendRegardlessOfConsentState[i]}`); + dev().warn( + TAG, + 'Invalid RTC consent state given: ' + + `${sendRegardlessOfConsentState[i]}` + ); } } return false; } - user().warn(TAG, 'Invalid value for sendRegardlessOfConsentState:' + - `${sendRegardlessOfConsentState}`); + user().warn( + TAG, + 'Invalid value for sendRegardlessOfConsentState:' + + `${sendRegardlessOfConsentState}` + ); return !!optIsGloballyValid; } @@ -233,23 +241,29 @@ export class RealTimeConfigManager { * custom URL. */ modifyRtcConfigForConsentStateSettings() { - if (this.consentState_ == undefined || - this.consentState_ == CONSENT_POLICY_STATE.SUFFICIENT || - this.consentState_ == CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED) { + if ( + this.consentState_ == undefined || + this.consentState_ == CONSENT_POLICY_STATE.SUFFICIENT || + this.consentState_ == CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED + ) { return; } const isGloballyValid = this.isValidCalloutForConsentState(this.rtcConfig_); - this.rtcConfig_.urls = (this.rtcConfig_.urls || []).filter( - url => this.isValidCalloutForConsentState(url, isGloballyValid)); + this.rtcConfig_.urls = (this.rtcConfig_.urls || []).filter(url => + this.isValidCalloutForConsentState(url, isGloballyValid) + ); Object.keys(this.rtcConfig_.vendors || {}).forEach(vendor => { - if (!this.isValidCalloutForConsentState( - this.rtcConfig_.vendors[vendor], isGloballyValid)) { + if ( + !this.isValidCalloutForConsentState( + this.rtcConfig_.vendors[vendor], + isGloballyValid + ) + ) { delete this.rtcConfig_.vendors[vendor]; } }); - } /** @@ -281,9 +295,7 @@ export class RealTimeConfigManager { } else { dev().warn(TAG, `Invalid url: ${urlObj}`); } - this.inflateAndSendRtc_(url, - customMacros, - errorReportingUrl); + this.inflateAndSendRtc_(url, customMacros, errorReportingUrl); }); } @@ -297,36 +309,44 @@ export class RealTimeConfigManager { Object.keys(this.rtcConfig_.vendors || []).forEach(vendor => { const vendorObject = RTC_VENDORS[vendor.toLowerCase()]; const url = vendorObject ? vendorObject.url : ''; - const errorReportingUrl = vendorObject && vendorObject.errorReportingUrl ? - vendorObject.errorReportingUrl : ''; + const errorReportingUrl = + vendorObject && vendorObject.errorReportingUrl + ? vendorObject.errorReportingUrl + : ''; if (!url) { return this.promiseArray_.push( - this.buildErrorResponse_( - RTC_ERROR_ENUM.UNKNOWN_VENDOR, vendor, errorReportingUrl)); + this.buildErrorResponse_( + RTC_ERROR_ENUM.UNKNOWN_VENDOR, + vendor, + errorReportingUrl + ) + ); } // There are two valid configurations of the vendor object. // It can either be an object of macros mapping string to string, // or it can be an object with sub-objects, one of which can be // 'macros'. This is for backwards compatability. - const vendorMacros = - isObject(this.rtcConfig_.vendors[vendor]['macros']) ? - this.rtcConfig_.vendors[vendor]['macros'] : - this.rtcConfig_.vendors[vendor]; + const vendorMacros = isObject(this.rtcConfig_.vendors[vendor]['macros']) + ? this.rtcConfig_.vendors[vendor]['macros'] + : this.rtcConfig_.vendors[vendor]; const validVendorMacros = {}; Object.keys(vendorMacros).forEach(macro => { if (!(vendorObject.macros && vendorObject.macros.includes(macro))) { user().error(TAG, `Unknown macro: ${macro} for vendor: ${vendor}`); } else { const value = vendorMacros[macro]; - validVendorMacros[macro] = isObject(value) || isArray(value) ? - JSON.stringify(value) : value; + validVendorMacros[macro] = + isObject(value) || isArray(value) ? JSON.stringify(value) : value; } }); // The ad network defined macros override vendor defined/pub specifed. const macros = Object.assign(validVendorMacros, customMacros); - this.inflateAndSendRtc_(url, - macros, errorReportingUrl, - vendor.toLowerCase()); + this.inflateAndSendRtc_( + url, + macros, + errorReportingUrl, + vendor.toLowerCase() + ); }); } @@ -337,12 +357,12 @@ export class RealTimeConfigManager { * @param {string=} opt_vendor * @private */ - inflateAndSendRtc_(url, - macros, errorReportingUrl, opt_vendor) { + inflateAndSendRtc_(url, macros, errorReportingUrl, opt_vendor) { let {timeoutMillis} = this.rtcConfig_; const callout = opt_vendor || this.getCalloutParam_(url); const checkStillCurrent = this.a4aElement_.verifyStillCurrent.bind( - this.a4aElement_)(); + this.a4aElement_ + )(); /** * The time that it takes to substitute the macros into the URL can vary * depending on what the url requires to be substituted, i.e. a long @@ -353,45 +373,66 @@ export class RealTimeConfigManager { const send = url => { if (Object.keys(this.seenUrls_).length == MAX_RTC_CALLOUTS) { return this.buildErrorResponse_( - RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED, - callout, errorReportingUrl); + RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED, + callout, + errorReportingUrl + ); } - if (!Services.urlForDoc( - this.a4aElement_.element).isSecure(url)) { - return this.buildErrorResponse_(RTC_ERROR_ENUM.INSECURE_URL, - callout, errorReportingUrl); + if (!Services.urlForDoc(this.a4aElement_.element).isSecure(url)) { + return this.buildErrorResponse_( + RTC_ERROR_ENUM.INSECURE_URL, + callout, + errorReportingUrl + ); } if (this.seenUrls_[url]) { - return this.buildErrorResponse_(RTC_ERROR_ENUM.DUPLICATE_URL, - callout, errorReportingUrl); + return this.buildErrorResponse_( + RTC_ERROR_ENUM.DUPLICATE_URL, + callout, + errorReportingUrl + ); } this.seenUrls_[url] = true; if (url.length > MAX_URL_LENGTH) { url = this.truncUrl_(url); } return this.sendRtcCallout_( - url, timeoutMillis, callout, checkStillCurrent, - errorReportingUrl); + url, + timeoutMillis, + callout, + checkStillCurrent, + errorReportingUrl + ); }; const whitelist = {}; - Object.keys(macros).forEach(key => whitelist[key] = true); + Object.keys(macros).forEach(key => (whitelist[key] = true)); const urlReplacementStartTime = Date.now(); - this.promiseArray_.push(Services.timerFor(this.win_).timeoutPromise( - timeoutMillis, - Services.urlReplacementsForDoc(this.a4aElement_.element).expandUrlAsync( - url, macros, whitelist)).then(url => { - checkStillCurrent(); - timeoutMillis -= (urlReplacementStartTime - Date.now()); - return send(url); - }).catch(error => { - return isCancellation(error) ? undefined : - this.buildErrorResponse_(RTC_ERROR_ENUM.MACRO_EXPAND_TIMEOUT, - callout, errorReportingUrl); - })); + this.promiseArray_.push( + Services.timerFor(this.win_) + .timeoutPromise( + timeoutMillis, + Services.urlReplacementsForDoc( + this.a4aElement_.element + ).expandUrlAsync(url, macros, whitelist) + ) + .then(url => { + checkStillCurrent(); + timeoutMillis -= urlReplacementStartTime - Date.now(); + return send(url); + }) + .catch(error => { + return isCancellation(error) + ? undefined + : this.buildErrorResponse_( + RTC_ERROR_ENUM.MACRO_EXPAND_TIMEOUT, + callout, + errorReportingUrl + ); + }) + ); } - /** * @param {string} url * @return {string} @@ -410,45 +451,66 @@ export class RealTimeConfigManager { * @return {!Promise} * @private */ - sendRtcCallout_(url, timeoutMillis, callout, checkStillCurrent, - errorReportingUrl) { + sendRtcCallout_( + url, + timeoutMillis, + callout, + checkStillCurrent, + errorReportingUrl + ) { /** * Note: Timeout is enforced by timerFor, not the value of * rtcTime. There are situations where rtcTime could thus * end up being greater than timeoutMillis. */ - return Services.timerFor(this.win_).timeoutPromise( + return Services.timerFor(this.win_) + .timeoutPromise( timeoutMillis, - Services.xhrFor(this.win_).fetchJson( + Services.xhrFor(this.win_) + .fetchJson( // NOTE(bradfrizzell): we could include ampCors:false allowing // the request to be cached across sites but for now assume that // is not a required feature. - url, {credentials: 'include'}).then(res => { - checkStillCurrent(); - return res.text().then(text => { + url, + {credentials: 'include'} + ) + .then(res => { checkStillCurrent(); - const rtcTime = Date.now() - this.rtcStartTime_; - // An empty text response is allowed, not an error. - if (!text) { - return {rtcTime, callout}; - } - const response = tryParseJson(text); - return response ? {response, rtcTime, callout} : - this.buildErrorResponse_( - RTC_ERROR_ENUM.MALFORMED_JSON_RESPONSE, callout, - errorReportingUrl, rtcTime); - }); - })).catch(error => { - return isCancellation(error) ? undefined : - this.buildErrorResponse_( - // The relevant error message for timeout looks like it is - // just 'message' but is in fact 'messageXXX' where the - // X's are hidden special characters. That's why we use - // match here. - (/^timeout/.test(error.message)) ? - RTC_ERROR_ENUM.TIMEOUT : RTC_ERROR_ENUM.NETWORK_FAILURE, - callout, errorReportingUrl, Date.now() - this.rtcStartTime_); - }); + return res.text().then(text => { + checkStillCurrent(); + const rtcTime = Date.now() - this.rtcStartTime_; + // An empty text response is allowed, not an error. + if (!text) { + return {rtcTime, callout}; + } + const response = tryParseJson(text); + return response + ? {response, rtcTime, callout} + : this.buildErrorResponse_( + RTC_ERROR_ENUM.MALFORMED_JSON_RESPONSE, + callout, + errorReportingUrl, + rtcTime + ); + }); + }) + ) + .catch(error => { + return isCancellation(error) + ? undefined + : this.buildErrorResponse_( + // The relevant error message for timeout looks like it is + // just 'message' but is in fact 'messageXXX' where the + // X's are hidden special characters. That's why we use + // match here. + /^timeout/.test(error.message) + ? RTC_ERROR_ENUM.TIMEOUT + : RTC_ERROR_ENUM.NETWORK_FAILURE, + callout, + errorReportingUrl, + Date.now() - this.rtcStartTime_ + ); + }); } /** @@ -477,8 +539,10 @@ export class RealTimeConfigManager { let timeout; try { - userAssert(rtcConfig['vendors'] || rtcConfig['urls'], - 'RTC Config must specify vendors or urls'); + userAssert( + rtcConfig['vendors'] || rtcConfig['urls'], + 'RTC Config must specify vendors or urls' + ); Object.keys(rtcConfig).forEach(key => { switch (key) { case 'vendors': @@ -490,12 +554,18 @@ export class RealTimeConfigManager { case 'timeoutMillis': timeout = parseInt(rtcConfig[key], 10); if (isNaN(timeout)) { - user().warn(TAG, 'Invalid RTC timeout is NaN, ' + - `using default timeout ${defaultTimeoutMillis}ms`); + user().warn( + TAG, + 'Invalid RTC timeout is NaN, ' + + `using default timeout ${defaultTimeoutMillis}ms` + ); timeout = undefined; } else if (timeout >= defaultTimeoutMillis || timeout < 0) { - user().warn(TAG, `Invalid RTC timeout: ${timeout}ms, ` + - `using default timeout ${defaultTimeoutMillis}ms`); + user().warn( + TAG, + `Invalid RTC timeout: ${timeout}ms, ` + + `using default timeout ${defaultTimeoutMillis}ms` + ); timeout = undefined; } break; @@ -504,14 +574,18 @@ export class RealTimeConfigManager { break; } }); - if (!Object.keys(rtcConfig['vendors'] || {}).length - && !(rtcConfig['urls'] || []).length) { + if ( + !Object.keys(rtcConfig['vendors'] || {}).length && + !(rtcConfig['urls'] || []).length + ) { return false; } const validateErrorReportingUrl = urlObj => { const errorUrl = urlObj['errorReportingUrl']; - if (errorUrl && !Services.urlForDoc( - this.a4aElement_.element).isSecure(errorUrl)) { + if ( + errorUrl && + !Services.urlForDoc(this.a4aElement_.element).isSecure(errorUrl) + ) { dev().warn(TAG, `Insecure RTC errorReportingUrl: ${errorUrl}`); urlObj['errorReportingUrl'] = undefined; } @@ -526,9 +600,9 @@ export class RealTimeConfigManager { // This error would be due to the asserts above. return false; } - rtcConfig['timeoutMillis'] = timeout !== undefined ? - timeout : defaultTimeoutMillis; - this.rtcConfig_ = /** @type {RtcConfigDef} */(rtcConfig); + rtcConfig['timeoutMillis'] = + timeout !== undefined ? timeout : defaultTimeoutMillis; + this.rtcConfig_ = /** @type {RtcConfigDef} */ (rtcConfig); return true; } } diff --git a/extensions/amp-a4a/0.1/refresh-intersection-observer-wrapper.js b/extensions/amp-a4a/0.1/refresh-intersection-observer-wrapper.js index c54be4249003..b9fc94259a1e 100644 --- a/extensions/amp-a4a/0.1/refresh-intersection-observer-wrapper.js +++ b/extensions/amp-a4a/0.1/refresh-intersection-observer-wrapper.js @@ -14,9 +14,7 @@ * limitations under the License. */ -import { - IntersectionObserverPolyfill, -} from '../../../src/intersection-observer-polyfill'; +import {IntersectionObserverPolyfill} from '../../../src/intersection-observer-polyfill'; import {devAssert} from '../../../src/log'; export class RefreshIntersectionObserverWrapper { @@ -28,12 +26,13 @@ export class RefreshIntersectionObserverWrapper { * @param {Object} config */ constructor(callback, baseElement, config) { - /** * @private @const {!IntersectionObserverPolyfill} */ this.intersectionObserver_ = new IntersectionObserverPolyfill( - callback, config); + callback, + config + ); /** * Stores elements and their original viewportCallback functions so that diff --git a/extensions/amp-a4a/0.1/refresh-manager.js b/extensions/amp-a4a/0.1/refresh-manager.js index 9fbba86b94c8..b19cb02c388b 100644 --- a/extensions/amp-a4a/0.1/refresh-manager.js +++ b/extensions/amp-a4a/0.1/refresh-manager.js @@ -14,9 +14,7 @@ * limitations under the License. */ -import { - RefreshIntersectionObserverWrapper, -} from './refresh-intersection-observer-wrapper'; +import {RefreshIntersectionObserverWrapper} from './refresh-intersection-observer-wrapper'; import {Services} from '../../../src/services'; import {devAssert, user, userAssert} from '../../../src/log'; import {dict} from '../../../src/utils/object'; @@ -56,18 +54,21 @@ export function getPublisherSpecifiedRefreshInterval(element, win) { return checkAndSanitizeRefreshInterval(refreshInterval); } let metaTag; - const metaTagContent = ((metaTag = win.document - .getElementsByName(METATAG_NAME)) - && metaTag[0] - && metaTag[0].getAttribute('content')); + const metaTagContent = + (metaTag = win.document.getElementsByName(METATAG_NAME)) && + metaTag[0] && + metaTag[0].getAttribute('content'); if (!metaTagContent) { return null; } const networkIntervalPairs = metaTagContent.split(','); for (let i = 0; i < networkIntervalPairs.length; i++) { const pair = networkIntervalPairs[i].split('='); - userAssert(pair.length == 2, 'refresh metadata config must be of ' + - 'the form `network_type=refresh_interval`'); + userAssert( + pair.length == 2, + 'refresh metadata config must be of ' + + 'the form `network_type=refresh_interval`' + ); if (pair[0].toLowerCase() == element.getAttribute('type').toLowerCase()) { return checkAndSanitizeRefreshInterval(pair[1]); } @@ -85,11 +86,12 @@ export function getPublisherSpecifiedRefreshInterval(element, win) { */ function checkAndSanitizeRefreshInterval(refreshInterval) { const refreshIntervalNum = Number(refreshInterval); - if (isNaN(refreshIntervalNum) || - refreshIntervalNum < MIN_REFRESH_INTERVAL) { - user().warn(TAG, - 'invalid refresh interval, must be a number no less than ' + - `${MIN_REFRESH_INTERVAL}: ${refreshInterval}`); + if (isNaN(refreshIntervalNum) || refreshIntervalNum < MIN_REFRESH_INTERVAL) { + user().warn( + TAG, + 'invalid refresh interval, must be a number no less than ' + + `${MIN_REFRESH_INTERVAL}: ${refreshInterval}` + ); return null; } return refreshIntervalNum * 1000; @@ -163,27 +165,30 @@ let refreshManagerIdCounter = 0; * @return {?RefreshManager} */ export function getRefreshManager(a4a, opt_predicate) { - const refreshInterval = - getPublisherSpecifiedRefreshInterval(a4a.element, a4a.win); + const refreshInterval = getPublisherSpecifiedRefreshInterval( + a4a.element, + a4a.win + ); if (!refreshInterval || (opt_predicate && !opt_predicate())) { return null; } - return new RefreshManager(a4a, dict({ - 'visiblePercentageMin': 50, - 'continuousTimeMin': 1, - }), refreshInterval); + return new RefreshManager( + a4a, + dict({ + 'visiblePercentageMin': 50, + 'continuousTimeMin': 1, + }), + refreshInterval + ); } - export class RefreshManager { - /** * @param {!./amp-a4a.AmpA4A} a4a The AmpA4A instance to be refreshed. * @param {!JsonObject} config * @param {number} refreshInterval */ constructor(a4a, config, refreshInterval) { - /** @private {string} */ this.state_ = RefreshLifecycleState.INITIAL; @@ -228,13 +233,18 @@ export class RefreshManager { * @return {(!IntersectionObserver|!RefreshIntersectionObserverWrapper)} */ getIntersectionObserverWithThreshold_(threshold) { - const thresholdString = String(threshold); - return observers[thresholdString] || - (observers[thresholdString] = 'IntersectionObserver' in this.win_ + return ( + observers[thresholdString] || + (observers[thresholdString] = + 'IntersectionObserver' in this.win_ ? new this.win_['IntersectionObserver'](this.ioCallback_, {threshold}) : new RefreshIntersectionObserverWrapper( - this.ioCallback_, this.a4a_, {threshold})); + this.ioCallback_, + this.a4a_, + {threshold} + )) + ); } /** @@ -260,21 +270,27 @@ export class RefreshManager { // threshold. If this timer runs out without interruption, then all // viewability conditions have been met, and we can begin the refresh // timer. - if (entry.intersectionRatio >= - refreshManager.config_['visiblePercentageMin']) { + if ( + entry.intersectionRatio >= + refreshManager.config_['visiblePercentageMin'] + ) { refreshManager.state_ = RefreshLifecycleState.VIEW_PENDING; refreshManager.visibilityTimeoutId_ = refreshManager.timer_.delay( - () => { - refreshManager.state_ = RefreshLifecycleState.REFRESH_PENDING; - refreshManager.startRefreshTimer_(); - }, refreshManager.config_['continuousTimeMin']); + () => { + refreshManager.state_ = RefreshLifecycleState.REFRESH_PENDING; + refreshManager.startRefreshTimer_(); + }, + refreshManager.config_['continuousTimeMin'] + ); } break; case RefreshLifecycleState.VIEW_PENDING: // If the element goes off screen before the minimum on screen time // duration elapses, place it back into INITIAL state. - if (entry.intersectionRatio < - refreshManager.config_['visiblePercentageMin']) { + if ( + entry.intersectionRatio < + refreshManager.config_['visiblePercentageMin'] + ) { refreshManager.timer_.cancel(refreshManager.visibilityTimeoutId_); refreshManager.visibilityTimeoutId_ = null; refreshManager.state_ = RefreshLifecycleState.INITIAL; @@ -295,13 +311,13 @@ export class RefreshManager { switch (this.state_) { case RefreshLifecycleState.INITIAL: this.getIntersectionObserverWithThreshold_( - this.config_['visiblePercentageMin']).observe(this.element_); + this.config_['visiblePercentageMin'] + ).observe(this.element_); break; case RefreshLifecycleState.REFRESH_PENDING: case RefreshLifecycleState.VIEW_PENDING: default: break; - } } @@ -329,9 +345,11 @@ export class RefreshManager { * @return {!JsonObject} */ convertAndSanitizeConfiguration_(config) { - devAssert(config['visiblePercentageMin'] >= 0 && + devAssert( + config['visiblePercentageMin'] >= 0 && config['visiblePercentageMin'] <= 100, - 'visiblePercentageMin for refresh must be in the range [0, 100]'); + 'visiblePercentageMin for refresh must be in the range [0, 100]' + ); // Convert seconds to milliseconds. config['continuousTimeMin'] *= 1000; config['visiblePercentageMin'] /= 100; @@ -343,7 +361,7 @@ export class RefreshManager { */ unobserve() { this.getIntersectionObserverWithThreshold_( - this.config_['visiblePercentageMin']).unobserve(this.element_); + this.config_['visiblePercentageMin'] + ).unobserve(this.element_); } } - diff --git a/extensions/amp-a4a/0.1/signature-verifier.js b/extensions/amp-a4a/0.1/signature-verifier.js index a9d65690e5e7..6f62e143ec31 100644 --- a/extensions/amp-a4a/0.1/signature-verifier.js +++ b/extensions/amp-a4a/0.1/signature-verifier.js @@ -29,7 +29,6 @@ export const AMP_SIGNATURE_HEADER = 'AMP-Fast-Fetch-Signature'; * @enum {number} */ export const VerificationStatus = { - /** The ad was successfully verified as AMP. */ OK: 0, @@ -58,7 +57,6 @@ export const VerificationStatus = { * i.e. is not SSL. */ CRYPTO_UNAVAILABLE: 4, - }; /** @@ -75,7 +73,6 @@ export const VerificationStatus = { * introduced as an experiment. */ export class SignatureVerifier { - /** * @param {!Window} win * @param {!Object} signingServerURLs a map from the name of @@ -134,8 +131,10 @@ export class SignatureVerifier { * * @private @const {function(): number} */ - this.getNow_ = (win.performance && win.performance.now) ? - win.performance.now.bind(win.performance) : Date.now; + this.getNow_ = + win.performance && win.performance.now + ? win.performance.now.bind(win.performance) + : Date.now; } /** @@ -168,8 +167,7 @@ export class SignatureVerifier { * @return {!Promise} */ verify(creative, headers) { - const signatureFormat = - /^([A-Za-z0-9._-]+):([A-Za-z0-9._-]+):([A-Za-z0-9+/]{341}[AQgw]==)$/; + const signatureFormat = /^([A-Za-z0-9._-]+):([A-Za-z0-9._-]+):([A-Za-z0-9+/]{341}[AQgw]==)$/; if (!headers.has(AMP_SIGNATURE_HEADER)) { return Promise.resolve(VerificationStatus.UNVERIFIED); } @@ -178,11 +176,17 @@ export class SignatureVerifier { if (!match) { // TODO(@taymonbeal, #9274): replace this with real error reporting user().error( - 'AMP-A4A', `Invalid signature header: ${headerValue.split(':')[0]}`); + 'AMP-A4A', + `Invalid signature header: ${headerValue.split(':')[0]}` + ); return Promise.resolve(VerificationStatus.ERROR_SIGNATURE_MISMATCH); } return this.verifyCreativeAndSignature( - match[1], match[2], base64DecodeToBytes(match[3]), creative); + match[1], + match[2], + base64DecodeToBytes(match[3]), + creative + ); } /** @@ -207,15 +211,21 @@ export class SignatureVerifier { * @visibleForTesting */ verifyCreativeAndSignature( - signingServiceName, keypairId, signature, creative) { + signingServiceName, + keypairId, + signature, + creative + ) { if (!this.signers_) { // Web Cryptography isn't available. return Promise.resolve(VerificationStatus.CRYPTO_UNAVAILABLE); } const signer = this.signers_[signingServiceName]; devAssert( - signer, 'Keyset for service %s not loaded before verification', - signingServiceName); + signer, + 'Keyset for service %s not loaded before verification', + signingServiceName + ); return signer.promise.then(success => { if (!success) { // The public keyset couldn't be fetched and imported. Probably a @@ -226,19 +236,25 @@ export class SignatureVerifier { if (keyPromise === undefined) { // We don't have this key, but maybe the cache is stale; try // cachebusting. - signer.promise = - this.fetchAndAddKeys_(signer.keys, signingServiceName, keypairId) - .then(success => { - if (signer.keys[keypairId] === undefined) { - // We still don't have this key; make sure we never try - // again. - signer.keys[keypairId] = null; - } - return success; - }); + signer.promise = this.fetchAndAddKeys_( + signer.keys, + signingServiceName, + keypairId + ).then(success => { + if (signer.keys[keypairId] === undefined) { + // We still don't have this key; make sure we never try + // again. + signer.keys[keypairId] = null; + } + return success; + }); // This "recursive" call can recurse at most once. return this.verifyCreativeAndSignature( - signingServiceName, keypairId, signature, creative); + signingServiceName, + keypairId, + signature, + creative + ); } else if (keyPromise === null) { // We don't have this key and we already tried cachebusting. return VerificationStatus.ERROR_KEY_NOT_FOUND; @@ -251,19 +267,21 @@ export class SignatureVerifier { } const crypto = Services.cryptoFor(this.win_); return crypto.verifyPkcs(key, signature, creative).then( - result => result ? VerificationStatus.OK : - VerificationStatus.ERROR_SIGNATURE_MISMATCH, - err => { - // Web Cryptography rejected the verification attempt. This - // hopefully won't happen in the wild, but browsers can be weird - // about this, so we need to guard against the possibility. - // Phone home to the AMP Project so that we can understand why - // this occurred. - const message = err && err.message; - dev().error( - 'AMP-A4A', `Failed to verify signature: ${message}`); - return VerificationStatus.UNVERIFIED; - }); + result => + result + ? VerificationStatus.OK + : VerificationStatus.ERROR_SIGNATURE_MISMATCH, + err => { + // Web Cryptography rejected the verification attempt. This + // hopefully won't happen in the wild, but browsers can be weird + // about this, so we need to guard against the possibility. + // Phone home to the AMP Project so that we can understand why + // this occurred. + const message = err && err.message; + dev().error('AMP-A4A', `Failed to verify signature: ${message}`); + return VerificationStatus.UNVERIFIED; + } + ); }); } }); @@ -292,89 +310,97 @@ export class SignatureVerifier { } // TODO(@taymonbeal, #11088): consider a timeout on this fetch return Services.xhrFor(this.win_) - .fetchJson(url, { - mode: 'cors', - method: 'GET', - // This should be cached across publisher domains, so don't append - // __amp_source_origin to the URL. - ampCors: false, - credentials: 'omit', - }).then( - response => { - // These are assertions on signing service behavior required by - // the spec. However, nothing terrible happens if they aren't met - // and there's no meaningful error recovery to be done if they - // fail, so we don't need to do them at runtime in production. - // They're included in dev mode as a debugging aid. - devAssert( - response.status === 200, - 'Fast Fetch keyset spec requires status code 200'); - devAssert( - response.headers.get('Content-Type') == - 'application/jwk-set+json', - 'Fast Fetch keyset spec requires Content-Type: ' + - 'application/jwk-set+json'); - return response.json().then( - jsonResponse => { - const jwkSet = /** @type {!JsonObject} */ (jsonResponse); - // This is supposed to be a JSON Web Key Set, as defined in - // Section 5 of RFC 7517. However, the signing service could - // misbehave and send an arbitrary JSON value, so we have to - // type-check at runtime. - if (!jwkSet || !isArray(jwkSet['keys'])) { + .fetchJson(url, { + mode: 'cors', + method: 'GET', + // This should be cached across publisher domains, so don't append + // __amp_source_origin to the URL. + ampCors: false, + credentials: 'omit', + }) + .then( + response => { + // These are assertions on signing service behavior required by + // the spec. However, nothing terrible happens if they aren't met + // and there's no meaningful error recovery to be done if they + // fail, so we don't need to do them at runtime in production. + // They're included in dev mode as a debugging aid. + devAssert( + response.status === 200, + 'Fast Fetch keyset spec requires status code 200' + ); + devAssert( + response.headers.get('Content-Type') == 'application/jwk-set+json', + 'Fast Fetch keyset spec requires Content-Type: ' + + 'application/jwk-set+json' + ); + return response.json().then( + jsonResponse => { + const jwkSet = /** @type {!JsonObject} */ (jsonResponse); + // This is supposed to be a JSON Web Key Set, as defined in + // Section 5 of RFC 7517. However, the signing service could + // misbehave and send an arbitrary JSON value, so we have to + // type-check at runtime. + if (!jwkSet || !isArray(jwkSet['keys'])) { + signingServiceError( + signingServiceName, + `Key set (${JSON.stringify(jwkSet)}) has no "keys"` + ); + return false; + } + jwkSet['keys'].forEach(jwk => { + if (!jwk || typeof jwk['kid'] != 'string') { + signingServiceError( + signingServiceName, + `Key (${JSON.stringify(jwk)}) has no "kid"` + ); + } else if (keys[jwk['kid']] === undefined) { + // We haven't seen this keypair ID before. + keys[jwk['kid']] = Services.cryptoFor(this.win_) + .importPkcsKey(jwk) + .catch(err => { + // Web Cryptography rejected the key + // import attempt. Either the signing + // service sent a malformed key or the + // browser is doing something weird. + const jwkData = JSON.stringify(jwk); + const message = err && err.message; signingServiceError( - signingServiceName, - `Key set (${JSON.stringify(jwkSet)}) has no "keys"`); - return false; - } - jwkSet['keys'].forEach(jwk => { - if (!jwk || typeof jwk['kid'] != 'string') { - signingServiceError( - signingServiceName, - `Key (${JSON.stringify(jwk)}) has no "kid"`); - } else if (keys[jwk['kid']] === undefined) { - // We haven't seen this keypair ID before. - keys[jwk['kid']] = - Services.cryptoFor(this.win_).importPkcsKey(jwk) - .catch(err => { - // Web Cryptography rejected the key - // import attempt. Either the signing - // service sent a malformed key or the - // browser is doing something weird. - const jwkData = JSON.stringify(jwk); - const message = err && err.message; - signingServiceError( - signingServiceName, - `Failed to import key (${ - jwkData - }): ${message}`); - return null; - }); - } - }); - return true; - }, - err => { - // The signing service didn't send valid JSON. - signingServiceError( signingServiceName, - `Failed to parse JSON: ${err && err.response}`); - return false; - }); + `Failed to import key (${jwkData}): ${message}` + ); + return null; + }); + } + }); + return true; }, err => { - // Some kind of error occurred during the XHR. This could be a lot - // of things (and we have no type information), but if there's no - // `response` it's probably a network connectivity failure, so we - // ignore it. Unfortunately, we can't distinguish this from a CORS - // problem. - if (err && err.response) { - // This probably indicates a non-2xx HTTP status code. - signingServiceError( - signingServiceName, `Status code ${err.response.status}`); - } + // The signing service didn't send valid JSON. + signingServiceError( + signingServiceName, + `Failed to parse JSON: ${err && err.response}` + ); return false; - }); + } + ); + }, + err => { + // Some kind of error occurred during the XHR. This could be a lot + // of things (and we have no type information), but if there's no + // `response` it's probably a network connectivity failure, so we + // ignore it. Unfortunately, we can't distinguish this from a CORS + // problem. + if (err && err.response) { + // This probably indicates a non-2xx HTTP status code. + signingServiceError( + signingServiceName, + `Status code ${err.response.status}` + ); + } + return false; + } + ); } } @@ -389,5 +415,7 @@ export class SignatureVerifier { */ function signingServiceError(signingServiceName, message) { dev().error( - 'AMP-A4A', `Signing service error for ${signingServiceName}: ${message}`); + 'AMP-A4A', + `Signing service error for ${signingServiceName}: ${message}` + ); } diff --git a/extensions/amp-a4a/0.1/template-renderer.js b/extensions/amp-a4a/0.1/template-renderer.js index 6bad018f8476..eac63ebffd30 100644 --- a/extensions/amp-a4a/0.1/template-renderer.js +++ b/extensions/amp-a4a/0.1/template-renderer.js @@ -29,12 +29,10 @@ import {renderCreativeIntoFriendlyFrame} from './friendly-frame-util'; */ export let CreativeData; - /** * Render AMP creative into FriendlyFrame via templatization. */ export class TemplateRenderer extends Renderer { - /** * Constructs a TemplateRenderer instance. */ @@ -44,7 +42,6 @@ export class TemplateRenderer extends Renderer { /** @override */ render(context, element, creativeData) { - creativeData = /** @type {CreativeData} */ (creativeData); const {size, adUrl} = context; @@ -54,29 +51,34 @@ export class TemplateRenderer extends Renderer { devAssert(adUrl, 'missing ad request url'); return renderCreativeIntoFriendlyFrame( - adUrl, size, element, creativeMetadata) - .then(iframe => { - const templateData = - /** @type {!./amp-ad-type-defs.AmpTemplateCreativeDef} */ ( - creativeData.templateData); - const {data} = templateData; - if (!data) { - return Promise.resolve(); + adUrl, + size, + element, + creativeMetadata + ).then(iframe => { + const templateData = + /** @type {!./amp-ad-type-defs.AmpTemplateCreativeDef} */ (creativeData.templateData); + const {data} = templateData; + if (!data) { + return Promise.resolve(); + } + const templateHelper = getAmpAdTemplateHelper(context.win); + return templateHelper + .render(data, iframe.contentWindow.document.body) + .then(renderedElement => { + const {analytics} = templateData; + if (analytics) { + templateHelper.insertAnalytics(renderedElement, analytics); } - const templateHelper = getAmpAdTemplateHelper(context.win); - return templateHelper - .render(data, iframe.contentWindow.document.body) - .then(renderedElement => { - const {analytics} = templateData; - if (analytics) { - templateHelper.insertAnalytics(renderedElement, analytics); - } - // This element must exist, or #render() would have thrown. - const templateElement = iframe.contentWindow.document - .querySelector('template'); - templateElement.parentNode - .replaceChild(renderedElement, templateElement); - }); + // This element must exist, or #render() would have thrown. + const templateElement = iframe.contentWindow.document.querySelector( + 'template' + ); + templateElement.parentNode.replaceChild( + renderedElement, + templateElement + ); }); + }); } } diff --git a/extensions/amp-a4a/0.1/template-validator.js b/extensions/amp-a4a/0.1/template-validator.js index f82e139c394d..426c0c8c1dfa 100644 --- a/extensions/amp-a4a/0.1/template-validator.js +++ b/extensions/amp-a4a/0.1/template-validator.js @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - AdResponseType, - Validator, - ValidatorResult, -} from './amp-ad-type-defs'; +import {AdResponseType, Validator, ValidatorResult} from './amp-ad-type-defs'; import {AmpAdTemplateHelper} from '../../amp-a4a/0.1/amp-ad-template-helper'; import {Services} from '../../../src/services'; import {getAmpAdMetadata} from './amp-ad-utils'; @@ -40,8 +36,9 @@ let ampAdTemplateHelper; * @return {!AmpAdTemplateHelper} */ export function getAmpAdTemplateHelper(win) { - return ampAdTemplateHelper || - (ampAdTemplateHelper = new AmpAdTemplateHelper(win)); + return ( + ampAdTemplateHelper || (ampAdTemplateHelper = new AmpAdTemplateHelper(win)) + ); } /** @@ -50,55 +47,64 @@ export function getAmpAdTemplateHelper(win) { export class TemplateValidator extends Validator { /** @override */ validate(context, unvalidatedBytes, headers) { - const body = utf8Decode(/** @type {!ArrayBuffer} */ (unvalidatedBytes)); - const parsedResponseBody = - /** @type {./amp-ad-type-defs.AmpTemplateCreativeDef} */ ( - tryParseJson(body)); + const parsedResponseBody = /** @type {./amp-ad-type-defs.AmpTemplateCreativeDef} */ (tryParseJson( + body + )); // If we're missing the relevant header, or headers altogether, we cannot // proceed. In this case, we return a NON_AMP response, since we cannot // ensure this template will be valid AMP. We will pass the body of the // response as the creative, and downstream renderers may attempt to render // it as a non-AMP creative within a cross-domain iframe. - if (!parsedResponseBody || !headers || - (headers.get(AMP_TEMPLATED_CREATIVE_HEADER_NAME) !== 'amp-mustache' && - headers.get(DEPRECATED_AMP_TEMPLATED_CREATIVE_HEADER_NAME) !== - 'amp-mustache')) { + if ( + !parsedResponseBody || + !headers || + (headers.get(AMP_TEMPLATED_CREATIVE_HEADER_NAME) !== 'amp-mustache' && + headers.get(DEPRECATED_AMP_TEMPLATED_CREATIVE_HEADER_NAME) !== + 'amp-mustache') + ) { return Promise.resolve( - /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ - creativeData: { - creative: body, - }, - adResponseType: AdResponseType.TEMPLATE, - type: ValidatorResult.NON_AMP, - })); + /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ + creativeData: { + creative: body, + }, + adResponseType: AdResponseType.TEMPLATE, + type: ValidatorResult.NON_AMP, + }) + ); } return getAmpAdTemplateHelper(context.win) - .fetch(parsedResponseBody.templateUrl) - .then(template => { - const creativeMetadata = getAmpAdMetadata(template); - if (parsedResponseBody.analytics) { - pushIfNotExist( - creativeMetadata['customElementExtensions'], 'amp-analytics'); - } + .fetch(parsedResponseBody.templateUrl) + .then(template => { + const creativeMetadata = getAmpAdMetadata(template); + if (parsedResponseBody.analytics) { pushIfNotExist( - creativeMetadata['customElementExtensions'], 'amp-mustache'); + creativeMetadata['customElementExtensions'], + 'amp-analytics' + ); + } + pushIfNotExist( + creativeMetadata['customElementExtensions'], + 'amp-mustache' + ); - const extensions = Services.extensionsFor(context.win); - creativeMetadata.customElementExtensions.forEach( - extensionId => extensions./*OK*/preloadExtension(extensionId)); - // TODO(levitzky) Add preload logic for fonts / images. - return Promise.resolve( - /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ - creativeData: { - templateData: parsedResponseBody, - creativeMetadata, - }, - adResponseType: AdResponseType.TEMPLATE, - type: ValidatorResult.AMP, - })); - }); + const extensions = Services.extensionsFor(context.win); + creativeMetadata.customElementExtensions.forEach(extensionId => + extensions./*OK*/ preloadExtension(extensionId) + ); + // TODO(levitzky) Add preload logic for fonts / images. + return Promise.resolve( + /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ + creativeData: { + templateData: parsedResponseBody, + creativeMetadata, + }, + adResponseType: AdResponseType.TEMPLATE, + type: ValidatorResult.AMP, + }) + ); + }); } } diff --git a/extensions/amp-a4a/0.1/test/fetch-mock.js b/extensions/amp-a4a/0.1/test/fetch-mock.js index 1a07d00822a6..8ed3abb914c0 100644 --- a/extensions/amp-a4a/0.1/test/fetch-mock.js +++ b/extensions/amp-a4a/0.1/test/fetch-mock.js @@ -41,7 +41,6 @@ export let MockResponse; * it. The window is stubbed when this class's constructor is called. */ export class FetchMock { - /** @param {!Window} win */ constructor(win) { /** @private {!Window} */ @@ -116,17 +115,18 @@ export class FetchMock { } route.called = true; return Promise.resolve( - typeof route.response == 'function' ? - route.response() : route.response) - .then(data => { - if (data === null || typeof data == 'string') { - return new Response(data); - } else { - const {body, status, headers} = data; - return new Response(body, /** @type {!ResponseInit} */ ( - {status, headers})); - } - }); + typeof route.response == 'function' ? route.response() : route.response + ).then(data => { + if (data === null || typeof data == 'string') { + return new Response(data); + } else { + const {body, status, headers} = data; + return new Response( + body, + /** @type {!ResponseInit} */ ({status, headers}) + ); + } + }); } } diff --git a/extensions/amp-a4a/0.1/test/test-a4a-integration.js b/extensions/amp-a4a/0.1/test/test-a4a-integration.js index 10c413ea1349..e64030a27d1c 100644 --- a/extensions/amp-a4a/0.1/test/test-a4a-integration.js +++ b/extensions/amp-a4a/0.1/test/test-a4a-integration.js @@ -32,9 +32,7 @@ import { resetScheduledElementForTesting, upgradeOrRegisterElement, } from '../../../../src/service/custom-element-registry'; -import { - data as validCSSAmp, -} from './testdata/valid_css_at_rules_amp.reserialized'; +import {data as validCSSAmp} from './testdata/valid_css_at_rules_amp.reserialized'; // Integration tests for A4A. These stub out accesses to the outside world // (e.g., XHR requests and interfaces to ad network-specific code), but @@ -70,8 +68,10 @@ function expectRenderedInXDomainIframe(element, src) { // Note: Unlike expectRenderedInXDomainIframe, this doesn't return a Promise // because it doesn't (cannot) inspect the contents of the iframe. expect(element, 'ad element').to.be.ok; - expect(element.querySelector('iframe[srcdoc]'), - 'does not have a friendly iframe child').to.not.be.ok; + expect( + element.querySelector('iframe[srcdoc]'), + 'does not have a friendly iframe child' + ).to.not.be.ok; const child = element.querySelector('iframe[src]'); expect(child, 'iframe child').to.be.ok; expect(child.getAttribute('src')).to.contain.string(src); @@ -89,7 +89,9 @@ describe('integration test: a4a', () => { beforeEach(() => { sandbox = sinon.sandbox; a4aRegistry = getA4ARegistry(); - a4aRegistry['mock'] = () => {return true;}; + a4aRegistry['mock'] = () => { + return true; + }; return createIframePromise().then(f => { fixture = f; fetchMock = new FetchMock(fixture.win); @@ -100,8 +102,10 @@ describe('integration test: a4a', () => { }); } fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); adResponse = { headers: {'AMP-Access-Control-Allow-Source-Origin': 'about:srcdoc'}, body: validCSSAmp.reserialized, @@ -119,7 +123,7 @@ describe('integration test: a4a', () => { }); afterEach(() => { - fetchMock./*OK*/restore(); + fetchMock./*OK*/ restore(); sandbox.restore(); resetScheduledElementForTesting(window, 'amp-a4a'); delete a4aRegistry['mock']; @@ -151,8 +155,10 @@ describe('integration test: a4a', () => { // TODO(tdrl) Currently layoutCallback rejects, even though something *is* // rendered. This should be fixed in a refactor, and we should change this // .catch to a .then. - const forceCollapseStub = - sandbox.spy(MockA4AImpl.prototype, 'forceCollapse'); + const forceCollapseStub = sandbox.spy( + MockA4AImpl.prototype, + 'forceCollapse' + ); return fixture.addElement(a4aElement).catch(error => { expect(error.message).to.contain.string('Testing network error'); expect(error.message).to.contain.string('AMP-A4A-'); @@ -164,49 +170,57 @@ describe('integration test: a4a', () => { it('should collapse slot when creative response has code 204', () => { adResponse.status = 204; adResponse.body = null; - const forceCollapseStub = - sandbox.spy(MockA4AImpl.prototype, 'forceCollapse'); + const forceCollapseStub = sandbox.spy( + MockA4AImpl.prototype, + 'forceCollapse' + ); return fixture.addElement(a4aElement).then(() => { expect(forceCollapseStub).to.be.calledOnce; }); }); - it('should collapse slot when creative response.arrayBuffer() is empty', - () => { - adResponse.body = ''; - const forceCollapseStub = - sandbox.spy(MockA4AImpl.prototype, 'forceCollapse'); - return fixture.addElement(a4aElement).then(unusedElement => { - expect(forceCollapseStub).to.be.calledOnce; - }); - }); + it('should collapse slot when creative response.arrayBuffer() is empty', () => { + adResponse.body = ''; + const forceCollapseStub = sandbox.spy( + MockA4AImpl.prototype, + 'forceCollapse' + ); + return fixture.addElement(a4aElement).then(unusedElement => { + expect(forceCollapseStub).to.be.calledOnce; + }); + }); it('should continue to show old creative after refresh and no fill', () => { return fixture.addElement(a4aElement).then(() => { - return expectRenderedInFriendlyIframe(a4aElement, 'Hello, world.') - .then(() => { - const a4a = new MockA4AImpl(a4aElement); - const initiateAdRequestMock = sandbox.stub( - MockA4AImpl.prototype, 'initiateAdRequest').callsFake( - () => { - a4a.adPromise_ = Promise.resolve(); - // This simulates calling forceCollapse, without tripping - // up any unrelated asserts. - a4a.isRefreshing = false; - }); - const tearDownSlotMock = - sandbox.stub(MockA4AImpl.prototype, 'tearDownSlot'); - tearDownSlotMock.returns(undefined); - const destroyFrameSpy = - sandbox.spy(MockA4AImpl.prototype, 'destroyFrame'); - const callback = sandbox.spy(); - return a4a.refresh(callback).then(() => { - expect(initiateAdRequestMock).to.be.called; - expect(tearDownSlotMock).to.be.called; - expect(destroyFrameSpy).to.not.be.called; - expect(callback).to.be.called; + return expectRenderedInFriendlyIframe(a4aElement, 'Hello, world.').then( + () => { + const a4a = new MockA4AImpl(a4aElement); + const initiateAdRequestMock = sandbox + .stub(MockA4AImpl.prototype, 'initiateAdRequest') + .callsFake(() => { + a4a.adPromise_ = Promise.resolve(); + // This simulates calling forceCollapse, without tripping + // up any unrelated asserts. + a4a.isRefreshing = false; }); + const tearDownSlotMock = sandbox.stub( + MockA4AImpl.prototype, + 'tearDownSlot' + ); + tearDownSlotMock.returns(undefined); + const destroyFrameSpy = sandbox.spy( + MockA4AImpl.prototype, + 'destroyFrame' + ); + const callback = sandbox.spy(); + return a4a.refresh(callback).then(() => { + expect(initiateAdRequestMock).to.be.called; + expect(tearDownSlotMock).to.be.called; + expect(destroyFrameSpy).to.not.be.called; + expect(callback).to.be.called; }); + } + ); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-a4a-var-source.js b/extensions/amp-a4a/0.1/test/test-a4a-var-source.js index 40112a8534c4..82059773b9b6 100644 --- a/extensions/amp-a4a/0.1/test/test-a4a-var-source.js +++ b/extensions/amp-a4a/0.1/test/test-a4a-var-source.js @@ -16,12 +16,9 @@ import {A4AVariableSource} from '../a4a-variable-source'; import {createIframePromise} from '../../../../testing/iframe'; -import {installDocumentInfoServiceForDoc} from - '../../../../src/service/document-info-impl'; - +import {installDocumentInfoServiceForDoc} from '../../../../src/service/document-info-impl'; describe('A4AVariableSource', () => { - let varSource; beforeEach(() => { @@ -49,8 +46,9 @@ describe('A4AVariableSource', () => { }); it('should replace CANONICAL_URL', () => { - expect(expandSync('CANONICAL_URL')) - .to.equal('https://pinterest.com:8080/pin1'); + expect(expandSync('CANONICAL_URL')).to.equal( + 'https://pinterest.com:8080/pin1' + ); }); it('should replace NAV_TIMING', () => { @@ -70,7 +68,8 @@ describe('A4AVariableSource', () => { it('should replace HTML_ATTR', () => { expect(expandSync('HTML_ATTR', ['div', 'id'])).to.equal( - '[{\"id\":\"parent\"}]'); + '[{"id":"parent"}]' + ); }); it('should replace CLIENT_ID with null', () => { 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 150b6a519aad..708e28f6f9c1 100644 --- a/extensions/amp-a4a/0.1/test/test-amp-a4a.js +++ b/extensions/amp-a4a/0.1/test/test-amp-a4a.js @@ -42,9 +42,7 @@ import {FetchMock, networkFailure} from './fetch-mock'; import {FriendlyIframeEmbed} from '../../../../src/friendly-iframe-embed'; import {LayoutPriority} from '../../../../src/layout'; import {MockA4AImpl, TEST_URL} from './utils'; -import { - RealTimeConfigManager, -} from '../real-time-config-manager'; +import {RealTimeConfigManager} from '../real-time-config-manager'; import { SINGLE_PASS_EXPERIMENT_IDS, isInExperiment, @@ -63,20 +61,22 @@ import { } from '../../../amp-ad/0.1/concurrent-load'; import {installDocService} from '../../../../src/service/ampdoc-impl'; import {layoutRectLtwh} from '../../../../src/layout-rect'; -import { - resetScheduledElementForTesting, -} from '../../../../src/service/custom-element-registry'; +import {resetScheduledElementForTesting} from '../../../../src/service/custom-element-registry'; import {data as testFragments} from './testdata/test_fragments'; import {toggleExperiment} from '../../../../src/experiments'; -import { - data as validCSSAmp, -} from './testdata/valid_css_at_rules_amp.reserialized'; +import {data as validCSSAmp} from './testdata/valid_css_at_rules_amp.reserialized'; describe('amp-a4a', () => { - const IFRAME_SANDBOXING_FLAGS = ['allow-forms', 'allow-modals', - 'allow-pointer-lock', 'allow-popups', 'allow-popups-to-escape-sandbox', - 'allow-same-origin', 'allow-scripts', - 'allow-top-navigation-by-user-activation']; + const IFRAME_SANDBOXING_FLAGS = [ + 'allow-forms', + 'allow-modals', + 'allow-pointer-lock', + 'allow-popups', + 'allow-popups-to-escape-sandbox', + 'allow-same-origin', + 'allow-scripts', + 'allow-top-navigation-by-user-activation', + ]; let sandbox; let fetchMock; @@ -89,10 +89,11 @@ describe('amp-a4a', () => { beforeEach(() => { sandbox = sinon.sandbox; fetchMock = null; - getSigningServiceNamesMock = sandbox.stub(AmpA4A.prototype, - 'getSigningServiceNames'); - onCreativeRenderSpy = - sandbox.spy(AmpA4A.prototype, 'onCreativeRender'); + getSigningServiceNamesMock = sandbox.stub( + AmpA4A.prototype, + 'getSigningServiceNames' + ); + onCreativeRenderSpy = sandbox.spy(AmpA4A.prototype, 'onCreativeRender'); getSigningServiceNamesMock.returns(['google']); viewerWhenVisibleMock = sandbox.stub(Viewer.prototype, 'whenFirstVisible'); viewerWhenVisibleMock.returns(Promise.resolve()); @@ -112,7 +113,7 @@ describe('amp-a4a', () => { afterEach(() => { if (fetchMock) { - fetchMock./*OK*/restore(); + fetchMock./*OK*/ restore(); fetchMock = null; } sandbox.restore(); @@ -127,11 +128,13 @@ describe('amp-a4a', () => { expect(fetchMock).to.be.null; fetchMock = new FetchMock(fixture.win); fetchMock.getOnce( - 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', { - body: validCSSAmp.publicKeyset, - status: 200, - headers: {'Content-Type': 'application/jwk-set+json'}, - }); + 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', + { + body: validCSSAmp.publicKeyset, + status: 200, + headers: {'Content-Type': 'application/jwk-set+json'}, + } + ); installDocService(fixture.win, /* isSingleDoc */ true); const {doc} = fixture; // TODO(a4a-cam@): This is necessary in the short term, until A4A is @@ -158,14 +161,18 @@ describe('amp-a4a', () => { const ampdocService = Services.ampdocServiceFor(doc.defaultView); return ampdocService.getAmpDoc(element); }; - element.isBuilt = () => {return true;}; + element.isBuilt = () => { + return true; + }; element.getLayoutBox = () => { return opt_rect || layoutRectLtwh(0, 0, 200, 50); }; element.getPageLayoutBox = () => { return element.getLayoutBox.apply(element, arguments); }; - element.getIntersectionChangeEntry = () => {return null;}; + element.getIntersectionChangeEntry = () => { + return null; + }; const signals = new Signals(); element.signals = () => signals; element.renderStarted = () => { @@ -187,10 +194,13 @@ describe('amp-a4a', () => { baseTestDoc.lastIndexOf('') + ''.length, ]; const splicePoint = baseTestDoc.indexOf(''); - return baseTestDoc.slice(0, splicePoint) + - '' + - baseTestDoc.slice(splicePoint); + return ( + baseTestDoc.slice(0, splicePoint) + + '' + + baseTestDoc.slice(splicePoint) + ); } /** @@ -205,7 +215,8 @@ describe('amp-a4a', () => { const friendlyChild = element.querySelector('iframe[srcdoc]'); expect(friendlyChild).to.be.ok; expect(friendlyChild.getAttribute('srcdoc')).to.have.string( - ''); + '' + ); expect(element).to.be.visible; expect(friendlyChild).to.be.visible; } @@ -220,7 +231,8 @@ describe('amp-a4a', () => { expect(!!sandboxAttribute).to.equal(shouldSandbox); if (shouldSandbox) { expect(sandboxAttribute.split(' ').sort()).to.jsonEqual( - IFRAME_SANDBOXING_FLAGS); + IFRAME_SANDBOXING_FLAGS + ); } } @@ -234,8 +246,10 @@ describe('amp-a4a', () => { expect(element.tagName.toLowerCase()).to.equal('amp-a4a'); expect(element).to.be.visible; expect(element.querySelectorAll('iframe')).to.have.lengthOf(1); - const safeFrameUrl = 'https://tpc.googlesyndication.com/safeframe/' + - sfVersion + '/html/container.html'; + const safeFrameUrl = + 'https://tpc.googlesyndication.com/safeframe/' + + sfVersion + + '/html/container.html'; const child = element.querySelector(`iframe[src^="${safeFrameUrl}"][name]`); expect(child).to.be.ok; const name = child.getAttribute('name'); @@ -283,8 +297,11 @@ describe('amp-a4a', () => { * @param {string} srcUrl * @param {boolean=} shouldSandbox */ - function verifyCachedContentIframeRender(element, srcUrl, - shouldSandbox = false) { + function verifyCachedContentIframeRender( + element, + srcUrl, + shouldSandbox = false + ) { expect(element.tagName.toLowerCase()).to.equal('amp-a4a'); expect(element).to.be.visible; expect(element.querySelectorAll('iframe')).to.have.lengthOf(1); @@ -301,7 +318,9 @@ describe('amp-a4a', () => { /** @param {string} nameData */ function verifyNameData(nameData) { let attributes; - expect(() => {attributes = JSON.parse(nameData);}).not.to.throw(Error); + expect(() => { + attributes = JSON.parse(nameData); + }).not.to.throw(Error); expect(attributes).to.be.ok; verifyContext(attributes._context); } @@ -312,28 +331,44 @@ describe('amp-a4a', () => { * @param {(string|Array)=} additionalEvents */ function verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, additionalEvents = []) { - ['ad-request-start', 'ad-response-end', 'ad-render-start', - 'ad-render-end', 'ad-iframe-loaded'].concat(additionalEvents) - .forEach(evnt => expect(triggerAnalyticsEventSpy).to.be.calledWith( - a4a.element, evnt, {'time': sinon.match.number})); + a4a, + triggerAnalyticsEventSpy, + additionalEvents = [] + ) { + [ + 'ad-request-start', + 'ad-response-end', + 'ad-render-start', + 'ad-render-end', + 'ad-iframe-loaded', + ] + .concat(additionalEvents) + .forEach(evnt => + expect(triggerAnalyticsEventSpy).to.be.calledWith(a4a.element, evnt, { + 'time': sinon.match.number, + }) + ); } describe('ads are visible', () => { let a4aElement; let a4a; let fixture; - beforeEach(() => createIframePromise().then(f => { - fixture = f; - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - a4aElement = createA4aElement(fixture.doc); - a4a = new MockA4AImpl(a4aElement); - a4a.releaseType_ = '0'; - return fixture; - })); + beforeEach(() => + createIframePromise().then(f => { + fixture = f; + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + a4aElement = createA4aElement(fixture.doc); + a4a = new MockA4AImpl(a4aElement); + a4a.releaseType_ = '0'; + return fixture; + }) + ); it('for SafeFrame rendering case', () => { // Make sure there's no signature, so that we go down the 3p iframe path. @@ -342,8 +377,10 @@ describe('amp-a4a', () => { // If rendering type is safeframe, we SHOULD attach a SafeFrame. adResponse.headers[RENDERING_TYPE_HEADER] = 'safeframe'; a4a.buildCallback(); - const lifecycleEventStub = - sandbox.stub(a4a, 'maybeTriggerAnalyticsEvent_'); + const lifecycleEventStub = sandbox.stub( + a4a, + 'maybeTriggerAnalyticsEvent_' + ); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { const child = a4aElement.querySelector('iframe[name]'); @@ -395,8 +432,9 @@ describe('amp-a4a', () => { return a4a.layoutCallback().then(() => { const child = a4aElement.querySelector('iframe[srcdoc]'); expect(child).to.be.ok; - expect(child.srcdoc.indexOf('meta http-equiv=Content-Security-Policy')) - .to.not.equal(-1); + expect( + child.srcdoc.indexOf('meta http-equiv=Content-Security-Policy') + ).to.not.equal(-1); }); }); @@ -416,8 +454,10 @@ describe('amp-a4a', () => { }); it('detachedCallback should destroy FIE and detach frame', () => { - const fieDestroySpy = - sandbox./*OK*/spy(FriendlyIframeEmbed.prototype, 'destroy'); + const fieDestroySpy = sandbox./*OK*/ spy( + FriendlyIframeEmbed.prototype, + 'destroy' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -432,8 +472,11 @@ describe('amp-a4a', () => { a4a.onLayoutMeasure(); // Never resolve - sandbox.stub/*OK*/(FriendlyIframeEmbed.prototype,'whenIniLoaded') - .callsFake(() => {return new Promise(() => {});}); + sandbox + ./*OK*/ stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .callsFake(() => { + return new Promise(() => {}); + }); const creativeString = buildCreativeString(); const metaData = a4a.getAmpAdMetadata(creativeString); return a4a.renderAmpCreative_(metaData).then(() => { @@ -447,13 +490,14 @@ describe('amp-a4a', () => { const iniLoadPromise = new Promise(resolve => { iniLoadResolver = resolve; }); - const whenIniLoadedStub = sandbox.stub( - FriendlyIframeEmbed.prototype, - 'whenIniLoaded').callsFake( - () => iniLoadPromise); + const whenIniLoadedStub = sandbox + .stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .callsFake(() => iniLoadPromise); a4a.buildCallback(); - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); a4a.onLayoutMeasure(); const layoutPromise = a4a.layoutCallback(); expect(whenIniLoadedStub).to.not.be.called; @@ -483,8 +527,9 @@ describe('amp-a4a', () => { }); it('for requests from insecure HTTP pages', () => { - sandbox.stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') - .returns(false); + sandbox + .stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') + .returns(false); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -496,23 +541,29 @@ describe('amp-a4a', () => { }); it('should fire amp-analytics triggers', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); - sandbox.stub/*OK*/(FriendlyIframeEmbed.prototype, 'whenIniLoaded') - .callsFake(() => Promise.resolve()); + sandbox + ./*OK*/ stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .callsFake(() => Promise.resolve()); return a4a.layoutCallback().then(() => { verifyA4aAnalyticsTriggersWereFired(a4a, triggerAnalyticsEventSpy); }); }); it('should not fire amp-analytics triggers without config', () => { - sandbox.stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig').callsFake( - () => null); + sandbox + .stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig') + .callsFake(() => null); a4a = new MockA4AImpl(a4aElement); - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -521,22 +572,31 @@ describe('amp-a4a', () => { }); it('should insert an amp-analytics element', () => { - sandbox.stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig').callsFake( - () => ({'foo': 'bar'})); + sandbox + .stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig') + .callsFake(() => ({'foo': 'bar'})); a4a = new MockA4AImpl(a4aElement); - const insertAnalyticsElementSpy = - sandbox.spy(analyticsExtension, 'insertAnalyticsElement'); + const insertAnalyticsElementSpy = sandbox.spy( + analyticsExtension, + 'insertAnalyticsElement' + ); a4a.buildCallback(); expect(insertAnalyticsElementSpy).to.be.calledWith( - a4a.element, {'foo': 'bar'}, true /* loadAnalytics */); + a4a.element, + {'foo': 'bar'}, + true /* loadAnalytics */ + ); }); it('should not insert an amp-analytics element if config is null', () => { - sandbox.stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig').callsFake( - () => null); + sandbox + .stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig') + .callsFake(() => null); a4a = new MockA4AImpl(a4aElement); - const insertAnalyticsElementSpy = - sandbox.spy(analyticsExtension, 'insertAnalyticsElement'); + const insertAnalyticsElementSpy = sandbox.spy( + analyticsExtension, + 'insertAnalyticsElement' + ); a4a.buildCallback(); expect(insertAnalyticsElementSpy).not.to.be.called; }); @@ -546,16 +606,20 @@ describe('amp-a4a', () => { let a4aElement; let a4a; let fixture; - beforeEach(() => createIframePromise().then(f => { - fixture = f; - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - a4aElement = createA4aElement(fixture.doc); - a4a = new MockA4AImpl(a4aElement); - return fixture; - })); + beforeEach(() => + createIframePromise().then(f => { + fixture = f; + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + a4aElement = createA4aElement(fixture.doc); + a4a = new MockA4AImpl(a4aElement); + return fixture; + }) + ); it('when unlayoutCallback called after adPromise', () => { a4a.buildCallback(); @@ -567,16 +631,20 @@ describe('amp-a4a', () => { const layoutCallbackPromise = a4a.layoutCallback(); a4a.unlayoutCallback(); const renderNonAmpCreativeSpy = sandbox.spy( - AmpA4A.prototype, 'renderNonAmpCreative'); + AmpA4A.prototype, + 'renderNonAmpCreative' + ); promiseResolver(); - layoutCallbackPromise.then(() => { - // We should never get in here. - expect(false).to.be.true; - }).catch(err => { - expect(renderNonAmpCreativeSpy).to.not.be.called; - expect(err).to.be.ok; - expect(err.message).to.equal('CANCELLED'); - }); + layoutCallbackPromise + .then(() => { + // We should never get in here. + expect(false).to.be.true; + }) + .catch(err => { + expect(renderNonAmpCreativeSpy).to.not.be.called; + expect(err).to.be.ok; + expect(err.message).to.equal('CANCELLED'); + }); }); it('when unlayoutCallback called before renderAmpCreative_', () => { @@ -590,13 +658,15 @@ describe('amp-a4a', () => { a4a.unlayoutCallback(); promiseResolver(); - layoutCallbackPromise.then(() => { - // We should never get in here. - expect(false).to.be.true; - }).catch(err => { - expect(err).to.be.ok; - expect(err.message).to.equal('CANCELLED'); - }); + layoutCallbackPromise + .then(() => { + // We should never get in here. + expect(false).to.be.true; + }) + .catch(err => { + expect(err).to.be.ok; + expect(err.message).to.equal('CANCELLED'); + }); }); }); @@ -612,8 +682,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; a4aElement = createA4aElement(doc); a4a = new MockA4AImpl(a4aElement); @@ -643,8 +715,11 @@ describe('amp-a4a', () => { a4a.sandboxHTMLCreativeFrame = () => true; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifyCachedContentIframeRender(a4aElement, TEST_URL, - true /* shouldSandbox */); + verifyCachedContentIframeRender( + a4aElement, + TEST_URL, + true /* shouldSandbox */ + ); expect(fetchMock.called('ad')).to.be.true; }); }); @@ -653,13 +728,16 @@ describe('amp-a4a', () => { a4a.sandboxHTMLCreativeFrame = () => false; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifyCachedContentIframeRender(a4aElement, TEST_URL, - false /* shouldSandbox */); + verifyCachedContentIframeRender( + a4aElement, + TEST_URL, + false /* shouldSandbox */ + ); expect(fetchMock.called('ad')).to.be.true; }); }); - it('shouldn\'t set feature policy for sync-xhr with exp off-a4a', () => { + it("shouldn't set feature policy for sync-xhr with exp off-a4a", () => { a4a.sandboxHTMLCreativeFrame = () => true; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -674,8 +752,7 @@ describe('amp-a4a', () => { a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { verifyCachedContentIframeRender(a4aElement, TEST_URL, true); - expect(a4a.iframe.getAttribute('allow')) - .to.equal('sync-xhr \'none\';'); + expect(a4a.iframe.getAttribute('allow')).to.equal("sync-xhr 'none';"); }); }); }); @@ -691,26 +768,40 @@ describe('amp-a4a', () => { }); it('should render via cached iframe', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); return a4a.layoutCallback().then(() => { verifyCachedContentIframeRender(a4aElement, TEST_URL); // Should have reported an error. expect(devErrLogStub).to.be.calledOnce; expect(devErrLogStub.getCall(0).args[1]).to.have.string( - 'random illegal value'); + 'random illegal value' + ); expect(fetchMock.called('ad')).to.be.true; verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, 'ad-iframe-loaded'); + a4a, + triggerAnalyticsEventSpy, + 'ad-iframe-loaded' + ); }); }); it('should fire amp-analytics triggers for illegal render modes', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); - return a4a.layoutCallback().then(() => - verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, 'ad-iframe-loaded')); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); + return a4a + .layoutCallback() + .then(() => + verifyA4aAnalyticsTriggersWereFired( + a4a, + triggerAnalyticsEventSpy, + 'ad-iframe-loaded' + ) + ); }); }); @@ -728,17 +819,20 @@ describe('amp-a4a', () => { }); }); - it('should make only one NameFrame even if onLayoutMeasure called ' + - 'multiple times', () => { - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - verifyNameFrameRender(a4aElement); - expect(fetchMock.called('ad')).to.be.true; - }); - }); + it( + 'should make only one NameFrame even if onLayoutMeasure called ' + + 'multiple times', + () => { + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + verifyNameFrameRender(a4aElement); + expect(fetchMock.called('ad')).to.be.true; + }); + } + ); it('should apply sandbox when sandboxHTMLCreativeFrame is true', () => { a4a.sandboxHTMLCreativeFrame = () => true; @@ -759,46 +853,59 @@ describe('amp-a4a', () => { }); ['', 'client_cache', 'safeframe', 'some_random_thing'].forEach( - headerVal => { - it(`should not attach a NameFrame when header is ${headerVal}`, - () => { - const devStub = sandbox.stub(dev(), 'error'); - // Make sure there's no signature, so that we go down the 3p - // iframe path. - delete adResponse.headers['AMP-Fast-Fetch-Signature']; - delete adResponse.headers[AMP_SIGNATURE_HEADER]; - // If rendering type is anything but nameframe, we SHOULD NOT - // attach a NameFrame. - adResponse.headers[RENDERING_TYPE_HEADER] = headerVal; - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - if (headerVal == 'some_random_thing') { - expect(devStub.withArgs('AMP-A4A', - `cross-origin render mode header ${headerVal}`)) - .to.be.calledOnce; - } else { - expect(devStub).to.not.be.called; - } - const nameChild = a4aElement.querySelector( - 'iframe[src^="nameframe"]'); - expect(nameChild).to.not.be.ok; - if (headerVal != 'safeframe') { - const unsafeChild = a4aElement.querySelector('iframe'); - expect(unsafeChild).to.be.ok; - expect(unsafeChild.getAttribute('src')).to.have.string( - TEST_URL); - } - expect(fetchMock.called('ad')).to.be.true; - }); - }); + headerVal => { + it(`should not attach a NameFrame when header is ${headerVal}`, () => { + const devStub = sandbox.stub(dev(), 'error'); + // Make sure there's no signature, so that we go down the 3p + // iframe path. + delete adResponse.headers['AMP-Fast-Fetch-Signature']; + delete adResponse.headers[AMP_SIGNATURE_HEADER]; + // If rendering type is anything but nameframe, we SHOULD NOT + // attach a NameFrame. + adResponse.headers[RENDERING_TYPE_HEADER] = headerVal; + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + if (headerVal == 'some_random_thing') { + expect( + devStub.withArgs( + 'AMP-A4A', + `cross-origin render mode header ${headerVal}` + ) + ).to.be.calledOnce; + } else { + expect(devStub).to.not.be.called; + } + const nameChild = a4aElement.querySelector( + 'iframe[src^="nameframe"]' + ); + expect(nameChild).to.not.be.ok; + if (headerVal != 'safeframe') { + const unsafeChild = a4aElement.querySelector('iframe'); + expect(unsafeChild).to.be.ok; + expect(unsafeChild.getAttribute('src')).to.have.string( + TEST_URL + ); + } + expect(fetchMock.called('ad')).to.be.true; + }); }); + } + ); it('should fire amp-analytics triggers for lifecycle stages', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); - return a4a.layoutCallback().then(() => - verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, 'ad-iframe-loaded')); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); + return a4a + .layoutCallback() + .then(() => + verifyA4aAnalyticsTriggersWereFired( + a4a, + triggerAnalyticsEventSpy, + 'ad-iframe-loaded' + ) + ); }); }); @@ -824,24 +931,30 @@ describe('amp-a4a', () => { }); }); - it('should make only one SafeFrame even if onLayoutMeasure called ' + - 'multiple times', () => { - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - verifySafeFrameRender(a4aElement, DEFAULT_SAFEFRAME_VERSION); - expect(fetchMock.called('ad')).to.be.true; - }); - }); + it( + 'should make only one SafeFrame even if onLayoutMeasure called ' + + 'multiple times', + () => { + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + verifySafeFrameRender(a4aElement, DEFAULT_SAFEFRAME_VERSION); + expect(fetchMock.called('ad')).to.be.true; + }); + } + ); it('should apply sandbox when sandboxHTMLCreativeFrame is true', () => { a4a.sandboxHTMLCreativeFrame = () => true; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifySafeFrameRender(a4aElement, DEFAULT_SAFEFRAME_VERSION, - true /* shouldSandbox */); + verifySafeFrameRender( + a4aElement, + DEFAULT_SAFEFRAME_VERSION, + true /* shouldSandbox */ + ); expect(fetchMock.called('ad')).to.be.true; }); }); @@ -850,49 +963,60 @@ describe('amp-a4a', () => { a4a.sandboxHTMLCreativeFrame = () => false; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifySafeFrameRender(a4aElement, DEFAULT_SAFEFRAME_VERSION, - false /* shouldSandbox */); + verifySafeFrameRender( + a4aElement, + DEFAULT_SAFEFRAME_VERSION, + false /* shouldSandbox */ + ); expect(fetchMock.called('ad')).to.be.true; }); }); ['', 'client_cache', 'nameframe', 'some_random_thing'].forEach( - headerVal => { - it(`should not attach a SafeFrame when header is ${headerVal}`, - () => { - const devStub = sandbox.stub(dev(), 'error'); - // If rendering type is anything but safeframe, we SHOULD NOT - // attach a SafeFrame. - adResponse.headers[RENDERING_TYPE_HEADER] = headerVal; - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - if (headerVal == 'some_random_thing') { - expect(devStub.withArgs('AMP-A4A', - `cross-origin render mode header ${headerVal}`)) - .to.be.calledOnce; - } else { - expect(devStub).to.not.be.called; - } - const safeframeUrl = 'https://tpc.googlesyndication.com/safeframe/' + - DEFAULT_SAFEFRAME_VERSION + '/html/container.html'; - const safeChild = a4aElement.querySelector( - `iframe[src^="${safeframeUrl}"]`); - expect(safeChild).to.not.be.ok; - if (headerVal != 'nameframe') { - const unsafeChild = a4aElement.querySelector('iframe'); - expect(unsafeChild).to.be.ok; - expect(unsafeChild.getAttribute('src')).to.have.string( - TEST_URL); - } - expect(fetchMock.called('ad')).to.be.true; - }); - }); + headerVal => { + it(`should not attach a SafeFrame when header is ${headerVal}`, () => { + const devStub = sandbox.stub(dev(), 'error'); + // If rendering type is anything but safeframe, we SHOULD NOT + // attach a SafeFrame. + adResponse.headers[RENDERING_TYPE_HEADER] = headerVal; + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + if (headerVal == 'some_random_thing') { + expect( + devStub.withArgs( + 'AMP-A4A', + `cross-origin render mode header ${headerVal}` + ) + ).to.be.calledOnce; + } else { + expect(devStub).to.not.be.called; + } + const safeframeUrl = + 'https://tpc.googlesyndication.com/safeframe/' + + DEFAULT_SAFEFRAME_VERSION + + '/html/container.html'; + const safeChild = a4aElement.querySelector( + `iframe[src^="${safeframeUrl}"]` + ); + expect(safeChild).to.not.be.ok; + if (headerVal != 'nameframe') { + const unsafeChild = a4aElement.querySelector('iframe'); + expect(unsafeChild).to.be.ok; + expect(unsafeChild.getAttribute('src')).to.have.string( + TEST_URL + ); + } + expect(fetchMock.called('ad')).to.be.true; + }); }); + } + ); it('should reset state to null on unlayoutCallback', () => { return a4a.layoutCallback().then(() => { - expect(a4a.experimentalNonAmpCreativeRenderMethod_) - .to.equal('safeframe'); + expect(a4a.experimentalNonAmpCreativeRenderMethod_).to.equal( + 'safeframe' + ); a4a.unlayoutCallback(); expect(a4a.experimentalNonAmpCreativeRenderMethod_).to.be.null; expect(fetchMock.called('ad')).to.be.true; @@ -900,11 +1024,19 @@ describe('amp-a4a', () => { }); it('should fire amp-analytics triggers for lifecycle stages', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); - return a4a.layoutCallback().then(() => - verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, 'ad-iframe-loaded')); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); + return a4a + .layoutCallback() + .then(() => + verifyA4aAnalyticsTriggersWereFired( + a4a, + triggerAnalyticsEventSpy, + 'ad-iframe-loaded' + ) + ); }); }); }); @@ -912,15 +1044,19 @@ describe('amp-a4a', () => { describe('cross-domain vs A4A', () => { let a4a; let a4aElement; - beforeEach(() => createIframePromise().then(fixture => { - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - const {doc} = fixture; - a4aElement = createA4aElement(doc); - a4a = new MockA4AImpl(a4aElement); - })); + beforeEach(() => + createIframePromise().then(fixture => { + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + const {doc} = fixture; + a4aElement = createA4aElement(doc); + a4a = new MockA4AImpl(a4aElement); + }) + ); afterEach(() => { expect(fetchMock.called('ad')).to.be.true; }); @@ -935,25 +1071,29 @@ describe('amp-a4a', () => { }); }); - it(`should not use ${renderType} even if onLayoutMeasure called ` + - 'multiple times', () => { - adResponse.headers[RENDERING_TYPE_HEADER] = renderType; - a4a.buildCallback(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - const safeChild = a4aElement.querySelector('iframe[name]'); - expect(safeChild).to.not.be.ok; - const crossDomainChild = a4aElement.querySelector('iframe[src]'); - expect(crossDomainChild).to.not.be.ok; - const friendlyChild = a4aElement.querySelector('iframe[srcdoc]'); - expect(friendlyChild).to.be.ok; - expect(friendlyChild.getAttribute('srcdoc')).to.have.string( - ''); - }); - }); + it( + `should not use ${renderType} even if onLayoutMeasure called ` + + 'multiple times', + () => { + adResponse.headers[RENDERING_TYPE_HEADER] = renderType; + a4a.buildCallback(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + const safeChild = a4aElement.querySelector('iframe[name]'); + expect(safeChild).to.not.be.ok; + const crossDomainChild = a4aElement.querySelector('iframe[src]'); + expect(crossDomainChild).to.not.be.ok; + const friendlyChild = a4aElement.querySelector('iframe[srcdoc]'); + expect(friendlyChild).to.be.ok; + expect(friendlyChild.getAttribute('srcdoc')).to.have.string( + '' + ); + }); + } + ); }); }); @@ -965,8 +1105,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); a4aElement.setAttribute('width', 480); @@ -994,26 +1136,33 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const s = doc.createElement('style'); s.textContent = '.fixed {position:fixed;}'; doc.head.appendChild(s); const a4a = new MockA4AImpl(a4aElement); - const renderNonAmpCreativeSpy = - sandbox.spy(a4a, 'renderNonAmpCreative'); + const renderNonAmpCreativeSpy = sandbox.spy( + a4a, + 'renderNonAmpCreative' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.ok; return a4a.layoutCallback().then(() => { - expect(renderNonAmpCreativeSpy.calledOnce, - 'renderNonAmpCreative called exactly once').to.be.true; + expect( + renderNonAmpCreativeSpy.calledOnce, + 'renderNonAmpCreative called exactly once' + ).to.be.true; a4a.unlayoutCallback(); getResourceStub.returns({ 'hasBeenMeasured': () => true, - 'isMeasureRequested': () => false}); + 'isMeasureRequested': () => false, + }); const onLayoutMeasureSpy = sandbox.spy(a4a, 'onLayoutMeasure'); a4a.resumeCallback(); expect(onLayoutMeasureSpy).to.be.calledOnce; @@ -1025,8 +1174,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const s = doc.createElement('style'); @@ -1038,8 +1189,10 @@ describe('amp-a4a', () => { a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.ok; return a4a.layoutCallback().then(() => { - expect(renderAmpCreativeSpy.calledOnce, - 'renderAmpCreative_ called exactly once').to.be.true; + expect( + renderAmpCreativeSpy.calledOnce, + 'renderAmpCreative_ called exactly once' + ).to.be.true; sandbox.stub(a4a, 'unlayoutCallback').callsFake(() => false); const onLayoutMeasureSpy = sandbox.spy(a4a, 'onLayoutMeasure'); a4a.resumeCallback(); @@ -1055,22 +1208,28 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const s = doc.createElement('style'); s.textContent = '.fixed {position:fixed;}'; doc.head.appendChild(s); const a4a = new MockA4AImpl(a4aElement); - const renderNonAmpCreativeSpy = - sandbox.spy(a4a, 'renderNonAmpCreative'); + const renderNonAmpCreativeSpy = sandbox.spy( + a4a, + 'renderNonAmpCreative' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.ok; return a4a.layoutCallback().then(() => { - expect(renderNonAmpCreativeSpy.calledOnce, - 'renderNonAmpCreative called exactly once').to.be.true; + expect( + renderNonAmpCreativeSpy.calledOnce, + 'renderNonAmpCreative called exactly once' + ).to.be.true; a4a.unlayoutCallback(); const onLayoutMeasureSpy = sandbox.spy(a4a, 'onLayoutMeasure'); getResourceStub.returns({'hasBeenMeasured': () => false}); @@ -1084,28 +1243,41 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); a4a.releaseType_ = '0'; const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); - const rtcResponse = Promise.resolve( - [{response: 'a', rtcTime: 1, callout: 'https://a.com'}]); - const maybeExecuteRealTimeConfigStub = sandbox.stub().returns( - rtcResponse); + const rtcResponse = Promise.resolve([ + {response: 'a', rtcTime: 1, callout: 'https://a.com'}, + ]); + const maybeExecuteRealTimeConfigStub = sandbox + .stub() + .returns(rtcResponse); AMP.RealTimeConfigManager = RealTimeConfigManager; - sandbox.stub(AMP.RealTimeConfigManager.prototype, - 'maybeExecuteRealTimeConfig').callsFake( - maybeExecuteRealTimeConfigStub); - const tryExecuteRealTimeConfigSpy = - sandbox.spy(a4a, 'tryExecuteRealTimeConfig_'); + sandbox + .stub( + AMP.RealTimeConfigManager.prototype, + 'maybeExecuteRealTimeConfig' + ) + .callsFake(maybeExecuteRealTimeConfigStub); + const tryExecuteRealTimeConfigSpy = sandbox.spy( + a4a, + 'tryExecuteRealTimeConfig_' + ); const updateLayoutPriorityStub = sandbox.stub( - a4a, 'updateLayoutPriority'); + a4a, + 'updateLayoutPriority' + ); const renderAmpCreativeSpy = sandbox.spy(a4a, 'renderAmpCreative_'); - const preloadExtensionSpy = - sandbox.spy(Extensions.prototype, 'preloadExtension'); + const preloadExtensionSpy = sandbox.spy( + Extensions.prototype, + 'preloadExtension' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); @@ -1115,49 +1287,62 @@ describe('amp-a4a', () => { expect(a4a.isVerifiedAmpCreative()).to.be.true; expect(tryExecuteRealTimeConfigSpy.calledOnce).to.be.true; expect(maybeExecuteRealTimeConfigStub.calledOnce).to.be.true; - expect(maybeExecuteRealTimeConfigStub.calledWith( - {}, null)).to.be.true; - expect(getAdUrlSpy.calledOnce, 'getAdUrl called exactly once') - .to.be.true; + expect(maybeExecuteRealTimeConfigStub.calledWith({}, null)).to.be + .true; + expect(getAdUrlSpy.calledOnce, 'getAdUrl called exactly once').to.be + .true; expect(getAdUrlSpy.calledWith(null, rtcResponse)).to.be.true; expect(fetchMock.called('ad')).to.be.true; expect(preloadExtensionSpy.withArgs('amp-font')).to.be.calledOnce; - expect(doc.querySelector('link[rel=preload]' + - '[href="https://fonts.googleapis.com/css?family=Questrial"]')) - .to.be.ok; + expect( + doc.querySelector( + 'link[rel=preload]' + + '[href="https://fonts.googleapis.com/css?family=Questrial"]' + ) + ).to.be.ok; return a4a.layoutCallback().then(() => { - expect(renderAmpCreativeSpy.calledOnce, - 'renderAmpCreative_ called exactly once').to.be.true; - expect(a4aElement.getElementsByTagName('iframe').length) - .to.equal(1); + expect( + renderAmpCreativeSpy.calledOnce, + 'renderAmpCreative_ called exactly once' + ).to.be.true; + expect(a4aElement.getElementsByTagName('iframe').length).to.equal( + 1 + ); const friendlyIframe = a4aElement.querySelector('iframe[srcdoc]'); expect(friendlyIframe).to.not.be.null; expect(friendlyIframe.getAttribute('src')).to.be.null; const expectedAttributes = { - 'frameborder': '0', 'allowfullscreen': '', - 'allowtransparency': '', 'scrolling': 'no'}; + 'frameborder': '0', + 'allowfullscreen': '', + 'allowtransparency': '', + 'scrolling': 'no', + }; Object.keys(expectedAttributes).forEach(key => { expect(friendlyIframe.getAttribute(key)).to.equal( - expectedAttributes[key]); + expectedAttributes[key] + ); }); // Should not contain v0.js, any extensions, or amp-boilerplate. const iframeDoc = friendlyIframe.contentDocument; expect(iframeDoc.querySelector('script[src]')).to.not.be.ok; - expect(iframeDoc.querySelector('script[custom-element]')) - .to.not.be.ok; - expect(iframeDoc.querySelector('style[amp-boilerplate]')) - .to.not.be.ok; + expect(iframeDoc.querySelector('script[custom-element]')).to.not.be + .ok; + expect(iframeDoc.querySelector('style[amp-boilerplate]')).to.not.be + .ok; expect(iframeDoc.querySelector('noscript')).to.not.be.ok; // Should contain font link and extension in main document. - expect(iframeDoc.querySelector( - 'link[href="https://fonts.googleapis.com/css?family=Questrial"]')) - .to.be.ok; + expect( + iframeDoc.querySelector( + 'link[href="https://fonts.googleapis.com/css?family=Questrial"]' + ) + ).to.be.ok; expect(doc.querySelector('script[src*="amp-font-0.1"]')).to.be.ok; - expect(onCreativeRenderSpy.withArgs(sinon.match.object)) - .to.be.calledOnce; + expect(onCreativeRenderSpy.withArgs(sinon.match.object)).to.be + .calledOnce; expect(updateLayoutPriorityStub).to.be.calledOnce; expect(updateLayoutPriorityStub.args[0][0]).to.equal( - LayoutPriority.CONTENT); + LayoutPriority.CONTENT + ); }); }); }); @@ -1168,26 +1353,36 @@ describe('amp-a4a', () => { delete adResponse.headers['AMP-Fast-Fetch-Signature']; delete adResponse.headers[AMP_SIGNATURE_HEADER]; adResponse.headers[EXPERIMENT_FEATURE_HEADER_NAME] = - 'pref_neutral_enabled=1,'; + 'pref_neutral_enabled=1,'; adResponse.headers[CREATIVE_SIZE_HEADER] = '123x456'; fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const element = createA4aElement(fixture.doc); element.setAttribute('type', 'adsense'); const a4a = new MockA4AImpl(element); const updateLayoutPriorityStub = sandbox.stub( - a4a, 'updateLayoutPriority'); - const renderNonAmpCreativeSpy = - sandbox.spy(a4a, 'renderNonAmpCreative'); - sandbox.stub(a4a, 'maybeValidateAmpCreative').returns( - Promise.resolve()); + a4a, + 'updateLayoutPriority' + ); + const renderNonAmpCreativeSpy = sandbox.spy( + a4a, + 'renderNonAmpCreative' + ); + sandbox + .stub(a4a, 'maybeValidateAmpCreative') + .returns(Promise.resolve()); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - expect(renderNonAmpCreativeSpy.calledOnce, - 'renderNonAmpCreative_ called exactly once').to.be.true; + expect( + renderNonAmpCreativeSpy.calledOnce, + 'renderNonAmpCreative_ called exactly once' + ).to.be.true; expect(updateLayoutPriorityStub.args[0][0]).to.equal( - LayoutPriority.CONTENT); + LayoutPriority.CONTENT + ); expect(is3pThrottled(a4a.win)).to.be.false; }); }); @@ -1198,14 +1393,18 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); sandbox.stub(a4a, 'getAmpAdMetadata').callsFake(creative => { - const metaData = AmpA4A.prototype.getAmpAdMetadata.call(a4a, - creative); + const metaData = AmpA4A.prototype.getAmpAdMetadata.call( + a4a, + creative + ); metaData.images = [ 'https://prefetch.me.com?a=b', 'http://do.not.prefetch.me.com?c=d', @@ -1216,12 +1415,21 @@ describe('amp-a4a', () => { a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - expect(doc.querySelector('link[rel=preload]' + - '[href="https://prefetch.me.com?a=b"]')).to.be.ok; - expect(doc.querySelector('link[rel=preload]' + - '[href="https://prefetch.metoo.com?e=f"]')).to.be.ok; - expect(doc.querySelector('link[rel=preload]' + - '[href="http://do.not.prefetch.me.com?c=d"]')).to.not.be.ok; + expect( + doc.querySelector( + 'link[rel=preload][href="https://prefetch.me.com?a=b"]' + ) + ).to.be.ok; + expect( + doc.querySelector( + 'link[rel=preload][href="https://prefetch.metoo.com?e=f"]' + ) + ).to.be.ok; + expect( + doc.querySelector( + 'link[rel=preload][href="http://do.not.prefetch.me.com?c=d"]' + ) + ).to.not.be.ok; }); }); }); @@ -1229,8 +1437,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const s = doc.createElement('style'); @@ -1246,8 +1456,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const rect = layoutRectLtwh(0, 0, 200, 0); const a4aElement = createA4aElement(doc, rect); @@ -1276,29 +1488,34 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); const updateLayoutPriorityStub = sandbox.stub( - a4a, 'updateLayoutPriority'); + a4a, + 'updateLayoutPriority' + ); if (!isValidCreative) { delete adResponse.headers['AMP-Fast-Fetch-Signature']; delete adResponse.headers[AMP_SIGNATURE_HEADER]; } a4a.promiseErrorHandler_ = () => {}; if (opt_failAmpRender) { - sandbox.stub(a4a, 'renderAmpCreative_').returns( - Promise.reject('amp render failure')); + sandbox + .stub(a4a, 'renderAmpCreative_') + .returns(Promise.reject('amp render failure')); } a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); return a4a.adPromise_.then(promiseResult => { - expect(getAdUrlSpy.calledOnce, 'getAdUrl called exactly once') - .to.be.true; + expect(getAdUrlSpy.calledOnce, 'getAdUrl called exactly once').to.be + .true; expect(fetchMock.called('ad')).to.be.true; expect(a4a.isVerifiedAmpCreative()).to.equal(isValidCreative); if (isValidCreative) { @@ -1308,21 +1525,23 @@ describe('amp-a4a', () => { expect(promiseResult).to.not.be.ok; } return a4a.layoutCallback().then(() => { - expect(a4aElement.getElementsByTagName('iframe').length) - .to.not.equal(0); + expect( + a4aElement.getElementsByTagName('iframe').length + ).to.not.equal(0); const iframe = a4aElement.getElementsByTagName('iframe')[0]; if (isValidCreative && !opt_failAmpRender) { expect(iframe.getAttribute('src')).to.be.null; - expect(onCreativeRenderSpy.withArgs(sinon.match.object)) - .to.be.calledOnce; + expect(onCreativeRenderSpy.withArgs(sinon.match.object)).to.be + .calledOnce; expect(updateLayoutPriorityStub).to.be.calledOnce; expect(updateLayoutPriorityStub.args[0][0]).to.equal( - LayoutPriority.CONTENT); + LayoutPriority.CONTENT + ); } else { expect(iframe.getAttribute('srcdoc')).to.be.null; - expect(iframe.src, 'verify iframe src w/ origin').to - .equal(TEST_URL + - '&__amp_source_origin=about%3Asrcdoc'); + expect(iframe.src, 'verify iframe src w/ origin').to.equal( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc' + ); expect(onCreativeRenderSpy.withArgs(null)).to.be.called; if (!opt_failAmpRender) { expect(updateLayoutPriorityStub).to.not.be.called; @@ -1345,8 +1564,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - Promise.reject(networkFailure()), {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + Promise.reject(networkFailure()), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1357,8 +1578,8 @@ describe('amp-a4a', () => { expect(a4a.adPromise_).to.be.instanceof(Promise); return a4a.layoutCallback().then(() => { expect(getAdUrlSpy, 'getAdUrl called exactly once').to.be.calledOnce; - expect(onNetworkFailureSpy, - 'onNetworkFailureSpy called exactly once').to.be.calledOnce; + expect(onNetworkFailureSpy, 'onNetworkFailureSpy called exactly once') + .to.be.calledOnce; // Verify iframe presence and lack of visibility hidden const iframe = a4aElement.querySelector('iframe[src]'); expect(iframe).to.be.ok; @@ -1372,17 +1593,24 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - Promise.reject(networkFailure()), {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + Promise.reject(networkFailure()), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); - sandbox.stub(a4a, 'onNetworkFailure') - .withArgs(sinon.match(val => - val.message && val.message.indexOf('XHR Failed fetching') == 0), - TEST_URL) - .returns({adUrl: TEST_URL + '&err=true'}); + sandbox + .stub(a4a, 'onNetworkFailure') + .withArgs( + sinon.match( + val => + val.message && val.message.indexOf('XHR Failed fetching') == 0 + ), + TEST_URL + ) + .returns({adUrl: TEST_URL + '&err=true'}); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); @@ -1402,18 +1630,25 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - Promise.reject(networkFailure()), {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + Promise.reject(networkFailure()), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); a4a.promiseErrorHandler_ = () => {}; const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); - sandbox.stub(a4a, 'onNetworkFailure') - .withArgs(sinon.match(val => - val.message && val.message.indexOf('XHR Failed fetching') == 0), - TEST_URL) - .returns({frameGetDisabled: true}); + sandbox + .stub(a4a, 'onNetworkFailure') + .withArgs( + sinon.match( + val => + val.message && val.message.indexOf('XHR Failed fetching') == 0 + ), + TEST_URL + ) + .returns({frameGetDisabled: true}); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -1427,21 +1662,25 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - Promise.reject(networkFailure()), {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + Promise.reject(networkFailure()), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); a4a.buildCallback(); a4a.onLayoutMeasure(); - return a4a.adPromise_.then(() => a4a.layoutCallback().then(() => { - // Verify iframe presence and lack of visibility hidden - expect(a4aElement.querySelectorAll('iframe').length).to.equal(1); - const iframe = a4aElement.querySelectorAll('iframe')[0]; - expect(iframe.src.indexOf(TEST_URL)).to.equal(0); - expect(iframe).to.be.visible; - expect(onCreativeRenderSpy.withArgs(null)).to.be.called; - })); + return a4a.adPromise_.then(() => + a4a.layoutCallback().then(() => { + // Verify iframe presence and lack of visibility hidden + expect(a4aElement.querySelectorAll('iframe').length).to.equal(1); + const iframe = a4aElement.querySelectorAll('iframe')[0]; + expect(iframe.src.indexOf(TEST_URL)).to.equal(0); + expect(iframe).to.be.visible; + expect(onCreativeRenderSpy.withArgs(null)).to.be.called; + }) + ); }); }); it('should handle XHR error when resolves after layoutCallback', () => { @@ -1449,11 +1688,12 @@ describe('amp-a4a', () => { setupForAdTesting(fixture); let rejectXhr; fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - new Promise((unusedResolve, reject) => { - rejectXhr = reject; - }), - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + new Promise((unusedResolve, reject) => { + rejectXhr = reject; + }), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1491,11 +1731,11 @@ describe('amp-a4a', () => { }, { name: 'empty body', - fn: () => adResponse.body = '', + fn: () => (adResponse.body = ''), }, { name: 'no fill header', - fn: () => adResponse.headers['amp-ff-empty-creative'] = '', + fn: () => (adResponse.headers['amp-ff-empty-creative'] = ''), }, ].forEach(test => { it(`should collapse ${test.name}`, () => { @@ -1503,9 +1743,10 @@ describe('amp-a4a', () => { setupForAdTesting(fixture); test.fn(); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1514,11 +1755,14 @@ describe('amp-a4a', () => { const noContentUISpy = sandbox.spy(); const unlayoutUISpy = sandbox.spy(); a4a.uiHandler = { - applyNoContentUI: () => {noContentUISpy();}, - applyUnlayoutUI: () => {unlayoutUISpy();}, + applyNoContentUI: () => { + noContentUISpy(); + }, + applyUnlayoutUI: () => { + unlayoutUISpy(); + }, }; - sandbox.stub(a4a, 'getLayoutBox').returns( - {width: 123, height: 456}); + sandbox.stub(a4a, 'getLayoutBox').returns({width: 123, height: 456}); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.ok; return a4a.adPromise_.then(() => { @@ -1529,21 +1773,26 @@ describe('amp-a4a', () => { expect(a4aElement.querySelector('iframe')).to.not.be.ok; expect(onCreativeRenderSpy).to.not.be.called; // call unlayout callback & verify it attempts to revert size - expect(a4a.originalSlotSize_).to.deep - .equal({width: 123, height: 456}); + expect(a4a.originalSlotSize_).to.deep.equal({ + width: 123, + height: 456, + }); let attemptChangeSizeResolver; const attemptChangeSizePromise = new Promise(resolve => { attemptChangeSizeResolver = resolve; }); - sandbox.stub(AMP.BaseElement.prototype, 'attemptChangeSize') - .returns(attemptChangeSizePromise); + sandbox + .stub(AMP.BaseElement.prototype, 'attemptChangeSize') + .returns(attemptChangeSizePromise); a4a.unlayoutCallback(); expect(unlayoutUISpy).to.be.calledOnce; expect(a4a.originalSlotSize_).to.be.ok; attemptChangeSizeResolver(); - return Services.timerFor(a4a.win).promise(1).then(() => { - expect(a4a.originalSlotSize_).to.not.be.ok; - }); + return Services.timerFor(a4a.win) + .promise(1) + .then(() => { + expect(a4a.originalSlotSize_).to.not.be.ok; + }); }); }); }); @@ -1558,8 +1807,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - () => adResponse, {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1570,9 +1821,13 @@ describe('amp-a4a', () => { return a4a.layoutCallback().then(() => { verifySafeFrameRender(a4aElement, '1-2-3'); // Verify preload to safeframe with header version. - expect(doc.querySelector('link[rel=preload]' + - '[href="https://tpc.googlesyndication.com/safeframe/' + - '1-2-3/html/container.html"]')).to.be.ok; + expect( + doc.querySelector( + 'link[rel=preload]' + + '[href="https://tpc.googlesyndication.com/safeframe/' + + '1-2-3/html/container.html"]' + ) + ).to.be.ok; }); }); }); @@ -1585,65 +1840,69 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - () => adResponse, {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); a4a = new MockA4AImpl(a4aElement); getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); }); }); - it('should delay request until within renderOutsideViewport',() => { + it('should delay request until within renderOutsideViewport', () => { sandbox.stub(a4a, 'delayAdRequestEnabled').returns(true); let whenWithinViewportResolve; - getResourceStub.returns( - { - getUpgradeDelayMs: () => 1, - renderOutsideViewport: () => 3, - whenWithinViewport: viewport => { - expect(viewport).to.equal(3); - return new Promise(resolve => { - whenWithinViewportResolve = resolve; - }); - }, + getResourceStub.returns({ + getUpgradeDelayMs: () => 1, + renderOutsideViewport: () => 3, + whenWithinViewport: viewport => { + expect(viewport).to.equal(3); + return new Promise(resolve => { + whenWithinViewportResolve = resolve; }); + }, + }); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); // Delay to all getAdUrl to potentially execute. - return Services.timerFor(a4a.win).promise(1).then(() => { - expect(getAdUrlSpy).to.not.be.called; - whenWithinViewportResolve(); - return a4a.adPromise_.then(() => { - expect(getAdUrlSpy).to.be.calledOnce; + return Services.timerFor(a4a.win) + .promise(1) + .then(() => { + expect(getAdUrlSpy).to.not.be.called; + whenWithinViewportResolve(); + return a4a.adPromise_.then(() => { + expect(getAdUrlSpy).to.be.calledOnce; + }); }); - }); }); - it('should delay request until numeric value',() => { + it('should delay request until numeric value', () => { sandbox.stub(a4a, 'delayAdRequestEnabled').returns(6); let whenWithinViewportResolve; - getResourceStub.returns( - { - getUpgradeDelayMs: () => 1, - renderOutsideViewport: () => 3, - whenWithinViewport: viewport => { - expect(viewport).to.equal(6); - return new Promise(resolve => { - whenWithinViewportResolve = resolve; - }); - }, + getResourceStub.returns({ + getUpgradeDelayMs: () => 1, + renderOutsideViewport: () => 3, + whenWithinViewport: viewport => { + expect(viewport).to.equal(6); + return new Promise(resolve => { + whenWithinViewportResolve = resolve; }); + }, + }); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); // Delay to all getAdUrl to potentially execute. - return Services.timerFor(a4a.win).promise(1).then(() => { - expect(getAdUrlSpy).to.not.be.called; - whenWithinViewportResolve(); - return a4a.adPromise_.then(() => { - expect(getAdUrlSpy).to.be.calledOnce; + return Services.timerFor(a4a.win) + .promise(1) + .then(() => { + expect(getAdUrlSpy).to.not.be.called; + whenWithinViewportResolve(); + return a4a.adPromise_.then(() => { + expect(getAdUrlSpy).to.be.calledOnce; + }); }); - }); }); }); it('should ignore invalid safeframe version header', () => { @@ -1654,8 +1913,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1688,7 +1949,9 @@ describe('amp-a4a', () => { expect(preconnects).to.have.lengthOf(1); // AdSense origin. expect(preconnects[0]).to.have.property( - 'href', 'https://googleads.g.doubleclick.net/'); + 'href', + 'https://googleads.g.doubleclick.net/' + ); }); }); }); @@ -1723,28 +1986,36 @@ describe('amp-a4a', () => { // fixed. it('should parse metadata with wrong opening tag', () => { const creative = buildCreativeString(metaData).replace( - '' + - baseTestDoc.slice(splicePoint))).to.be.null; + '' + + baseTestDoc.slice(splicePoint) + ) + ).to.be.null; }); it('should return null if invalid extensions', () => { metaData.customElementExtensions = 'amp-vine'; @@ -1764,18 +2035,24 @@ describe('amp-a4a', () => { it('should not include amp images if not an array', () => { metaData.images = 'https://foo.com'; const actual = a4a.getAmpAdMetadata(buildCreativeString(metaData)); - const expected = Object.assign({ - minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, - }, metaData); + const expected = Object.assign( + { + minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, + }, + metaData + ); delete expected.images; expect(actual).to.deep.equal(expected); }); it('should tolerate missing images', () => { delete metaData.images; const actual = a4a.getAmpAdMetadata(buildCreativeString(metaData)); - const expected = Object.assign({ - minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, - }, metaData); + const expected = Object.assign( + { + minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, + }, + metaData + ); delete expected.images; expect(actual).to.deep.equal(expected); }); @@ -1783,22 +2060,25 @@ describe('amp-a4a', () => { while (metaData.images.length < 10) { metaData.images.push('https://another.image.com?abc=def'); } - expect(a4a.getAmpAdMetadata(buildCreativeString(metaData)).images.length) - .to.equal(5); + expect( + a4a.getAmpAdMetadata(buildCreativeString(metaData)).images.length + ).to.equal(5); }); it('should throw due to missing CTA type', () => { metaData.ctaUrl = 'http://foo.com'; a4a.isSinglePageStoryAd = true; - expect(() => a4a.getAmpAdMetadata(buildCreativeString(metaData))) - .to.throw(new RegExp(INVALID_SPSA_RESPONSE)); + expect(() => + a4a.getAmpAdMetadata(buildCreativeString(metaData)) + ).to.throw(new RegExp(INVALID_SPSA_RESPONSE)); }); it('should throw due to missing outlink', () => { metaData.ctaType = '0'; a4a.isSinglePageStoryAd = true; - expect(() => a4a.getAmpAdMetadata(buildCreativeString(metaData))) - .to.throw(new RegExp(INVALID_SPSA_RESPONSE)); + expect(() => + a4a.getAmpAdMetadata(buildCreativeString(metaData)) + ).to.throw(new RegExp(INVALID_SPSA_RESPONSE)); }); it('should set appropriate attributes and return metadata object', () => { @@ -1806,9 +2086,12 @@ describe('amp-a4a', () => { metaData.ctaUrl = 'http://foo.com'; a4a.isSinglePageStoryAd = true; const actual = a4a.getAmpAdMetadata(buildCreativeString(metaData)); - const expected = Object.assign({ - minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, - }, metaData); + const expected = Object.assign( + { + minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, + }, + metaData + ); delete expected.ctaType; delete expected.ctaUrl; expect(actual).to.deep.equal(expected); @@ -1820,7 +2103,6 @@ describe('amp-a4a', () => { }); describe('#maybeValidateAmpCreative', () => { - let a4a; let verifier; @@ -1876,7 +2158,9 @@ describe('amp-a4a', () => { }); it('should return 1.25 if prefer-viewability-over-views', () => { a4aElement.setAttribute( - 'data-loading-strategy', 'prefer-viewability-over-views'); + 'data-loading-strategy', + 'prefer-viewability-over-views' + ); expect(a4a.renderOutsideViewport()).to.equal(1.25); a4a.isVerifiedAmpCreative_ = true; expect(a4a.renderOutsideViewport()).to.equal(1.25); @@ -1893,7 +2177,9 @@ describe('amp-a4a', () => { const {doc} = fixture; a4aElement = createA4aElement(doc); a4a = new AmpA4A(a4aElement); - sandbox.stub(a4a, 'getFallback').callsFake(() => {return true;}); + sandbox.stub(a4a, 'getFallback').callsFake(() => { + return true; + }); a4a.buildCallback(); a4a.adUrl_ = 'https://nowhere.org'; }); @@ -1908,44 +2194,54 @@ describe('amp-a4a', () => { expect(friendlyIframe.srcdoc).to.be.ok; const frameDoc = friendlyIframe.contentDocument; const styles = frameDoc.querySelectorAll('style[amp-custom]'); - expect(Array.prototype.some.call(styles, - s => { - return s.innerHTML == 'p { background: green }'; - }), - 'Some style is "background: green"').to.be.true; + expect( + Array.prototype.some.call(styles, s => { + return s.innerHTML == 'p { background: green }'; + }), + 'Some style is "background: green"' + ).to.be.true; expect(frameDoc.body.innerHTML.trim()).to.equal('

    some text

    '); - expect(Services.urlReplacementsForDoc(frameDoc.documentElement)) - .to.not.equal(Services.urlReplacementsForDoc(a4aElement)); + expect( + Services.urlReplacementsForDoc(frameDoc.documentElement) + ).to.not.equal(Services.urlReplacementsForDoc(a4aElement)); }); }); }); describe('#getLayoutPriority', () => { - describes.realWin('with shadow AmpDoc', { - amp: { - ampdoc: 'shadow', + describes.realWin( + 'with shadow AmpDoc', + { + amp: { + ampdoc: 'shadow', + }, }, - }, env => { - it('should return priority of 1', () => { - const body = env.ampdoc.getBody(); - const a4aElement = createA4aElement(env.win.document, null, body); - const a4a = new MockA4AImpl(a4aElement); - expect(a4a.getLayoutPriority()).to.equal(LayoutPriority.METADATA); - }); - }); + env => { + it('should return priority of 1', () => { + const body = env.ampdoc.getBody(); + const a4aElement = createA4aElement(env.win.document, null, body); + const a4a = new MockA4AImpl(a4aElement); + expect(a4a.getLayoutPriority()).to.equal(LayoutPriority.METADATA); + }); + } + ); - describes.realWin('with single AmpDoc', { - amp: { - ampdoc: 'single', + describes.realWin( + 'with single AmpDoc', + { + amp: { + ampdoc: 'single', + }, }, - }, env => { - it('should return priority of 2', () => { - const body = env.ampdoc.getBody(); - const a4aElement = createA4aElement(env.win.document, null, body); - const a4a = new MockA4AImpl(a4aElement); - expect(a4a.getLayoutPriority()).to.equal(LayoutPriority.ADS); - }); - }); + env => { + it('should return priority of 2', () => { + const body = env.ampdoc.getBody(); + const a4aElement = createA4aElement(env.win.document, null, body); + const a4a = new MockA4AImpl(a4aElement); + expect(a4a.getLayoutPriority()).to.equal(LayoutPriority.ADS); + }); + } + ); }); describe('#unlayoutCallback', () => { @@ -1953,8 +2249,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1970,15 +2268,19 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); a4a.buildCallback(); a4a.onLayoutMeasure(); - const attemptChangeSizeStub = - sandbox.stub(AMP.BaseElement.prototype, 'attemptChangeSize'); + const attemptChangeSizeStub = sandbox.stub( + AMP.BaseElement.prototype, + 'attemptChangeSize' + ); // Expect called twice: one for resize and second for reverting. attemptChangeSizeStub.withArgs(123, 456).returns(Promise.resolve()); attemptChangeSizeStub.withArgs(200, 50).returns(Promise.resolve()); @@ -1994,9 +2296,11 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); let whenFirstVisibleResolve = null; - viewerWhenVisibleMock.returns(new Promise(resolve => { - whenFirstVisibleResolve = resolve; - })); + viewerWhenVisibleMock.returns( + new Promise(resolve => { + whenFirstVisibleResolve = resolve; + }) + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -2009,144 +2313,155 @@ describe('amp-a4a', () => { a4a.uiHandler.state = 0; a4a.unlayoutCallback(); whenFirstVisibleResolve(); - return adPromise.then(unusedError => { - assert.fail('cancelled ad promise should not succeed'); - }).catch(reason => { - expect(getAdUrlSpy.called, 'getAdUrl never called') - .to.be.false; - expect(reason.message).to.equal(cancellation().message); - expect(errorHandlerSpy).to.be.calledOnce; - }); + return adPromise + .then(unusedError => { + assert.fail('cancelled ad promise should not succeed'); + }) + .catch(reason => { + expect(getAdUrlSpy.called, 'getAdUrl never called').to.be.false; + expect(reason.message).to.equal(cancellation().message); + expect(errorHandlerSpy).to.be.calledOnce; + }); }); }); describe('consent integration', () => { - let fixture, a4aElement, a4a, consentString; - beforeEach(() => createIframePromise().then(f => { - fixture = f; - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - a4aElement = createA4aElement(fixture.doc); - a4a = new MockA4AImpl(a4aElement); - consentString = 'test-consent-string'; - return fixture; - })); + beforeEach(() => + createIframePromise().then(f => { + fixture = f; + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + a4aElement = createA4aElement(fixture.doc); + a4a = new MockA4AImpl(a4aElement); + consentString = 'test-consent-string'; + return fixture; + }) + ); it('should delay ad url by getConsentPolicyState', () => { - sandbox.stub(AMP.BaseElement.prototype, 'getConsentPolicy') - .returns('default'); + sandbox + .stub(AMP.BaseElement.prototype, 'getConsentPolicy') + .returns('default'); let inResolver; - const policyPromise = new Promise(resolver => inResolver = resolver); - sandbox.stub(Services, 'consentPolicyServiceForDocOrNull') - .returns(Promise.resolve({ - whenPolicyResolved: () => policyPromise, - getConsentStringInfo: () => consentString, - })); - - const getAdUrlSpy = sandbox.spy( - a4a, - 'getAdUrl' + const policyPromise = new Promise(resolver => (inResolver = resolver)); + sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( + Promise.resolve({ + whenPolicyResolved: () => policyPromise, + getConsentStringInfo: () => consentString, + }) ); + + const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); const tryExecuteRealTimeConfigSpy = sandbox.spy( - a4a, - 'tryExecuteRealTimeConfig_' + a4a, + 'tryExecuteRealTimeConfig_' ); a4a.buildCallback(); a4a.onLayoutMeasure(); // allow ad promise to start execution, unfortunately timer is only way. - return Services.timerFor(a4a.win).promise(50).then(() => { - expect(getAdUrlSpy).to.not.be.called; - inResolver(CONSENT_POLICY_STATE.SUFFICIENT); - return a4a.layoutCallback().then(() => { - expect(getAdUrlSpy.withArgs( - CONSENT_POLICY_STATE.SUFFICIENT - )).calledOnce; - expect(tryExecuteRealTimeConfigSpy.withArgs( - CONSENT_POLICY_STATE.SUFFICIENT, - consentString - )).calledOnce; + return Services.timerFor(a4a.win) + .promise(50) + .then(() => { + expect(getAdUrlSpy).to.not.be.called; + inResolver(CONSENT_POLICY_STATE.SUFFICIENT); + return a4a.layoutCallback().then(() => { + expect(getAdUrlSpy.withArgs(CONSENT_POLICY_STATE.SUFFICIENT)) + .calledOnce; + expect( + tryExecuteRealTimeConfigSpy.withArgs( + CONSENT_POLICY_STATE.SUFFICIENT, + consentString + ) + ).calledOnce; + }); }); - }); }); it('should not wait on consent if no policy', () => { - sandbox.stub(AMP.BaseElement.prototype, 'getConsentPolicy') - .returns(null); - const consentServiceSpy = - sandbox.spy(Services, 'consentPolicyServiceForDocOrNull'); + sandbox + .stub(AMP.BaseElement.prototype, 'getConsentPolicy') + .returns(null); + const consentServiceSpy = sandbox.spy( + Services, + 'consentPolicyServiceForDocOrNull' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => - expect(consentServiceSpy).to.not.be.called); + return a4a + .layoutCallback() + .then(() => expect(consentServiceSpy).to.not.be.called); }); it('should pass consent state to getAdUrl', () => { - sandbox.stub(AMP.BaseElement.prototype, 'getConsentPolicy') - .returns('default'); - sandbox.stub(Services, 'consentPolicyServiceForDocOrNull') - .returns(Promise.resolve({ - whenPolicyResolved: () => - Promise.resolve(CONSENT_POLICY_STATE.SUFFICIENT), - getConsentStringInfo: () => consentString, - })); - - const getAdUrlSpy = sandbox.spy( - a4a, - 'getAdUrl' + sandbox + .stub(AMP.BaseElement.prototype, 'getConsentPolicy') + .returns('default'); + sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( + Promise.resolve({ + whenPolicyResolved: () => + Promise.resolve(CONSENT_POLICY_STATE.SUFFICIENT), + getConsentStringInfo: () => consentString, + }) ); + + const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); const tryExecuteRealTimeConfigSpy = sandbox.spy( - a4a, - 'tryExecuteRealTimeConfig_' + a4a, + 'tryExecuteRealTimeConfig_' ); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - expect(getAdUrlSpy.withArgs( - CONSENT_POLICY_STATE.SUFFICIENT - )).calledOnce; - expect(tryExecuteRealTimeConfigSpy.withArgs( + expect(getAdUrlSpy.withArgs(CONSENT_POLICY_STATE.SUFFICIENT)) + .calledOnce; + expect( + tryExecuteRealTimeConfigSpy.withArgs( CONSENT_POLICY_STATE.SUFFICIENT, consentString - )).calledOnce; + ) + ).calledOnce; }); }); it('should return UNKNOWN if consent exception', () => { expectAsyncConsoleError(/Error determining consent state.*consent err/); - sandbox.stub(AMP.BaseElement.prototype, 'getConsentPolicy') - .returns('default'); - sandbox.stub(Services, 'consentPolicyServiceForDocOrNull') - .returns(Promise.resolve({ - whenPolicyResolved: () => {throw new Error('consent err!');}, - getConsentStringInfo: () => {throw new Error('consent err!');}, - })); - - const getAdUrlSpy = sandbox.spy( - a4a, - 'getAdUrl' + sandbox + .stub(AMP.BaseElement.prototype, 'getConsentPolicy') + .returns('default'); + sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( + Promise.resolve({ + whenPolicyResolved: () => { + throw new Error('consent err!'); + }, + getConsentStringInfo: () => { + throw new Error('consent err!'); + }, + }) ); + + const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); const tryExecuteRealTimeConfigSpy = sandbox.spy( - a4a, - 'tryExecuteRealTimeConfig_' + a4a, + 'tryExecuteRealTimeConfig_' ); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - - expect(getAdUrlSpy.withArgs( - CONSENT_POLICY_STATE.UNKNOWN - )).calledOnce; - expect(tryExecuteRealTimeConfigSpy.withArgs( + expect(getAdUrlSpy.withArgs(CONSENT_POLICY_STATE.UNKNOWN)).calledOnce; + expect( + tryExecuteRealTimeConfigSpy.withArgs( CONSENT_POLICY_STATE.UNKNOWN, null - )).calledOnce; - + ) + ).calledOnce; }); }); }); @@ -2154,38 +2469,58 @@ describe('amp-a4a', () => { describe('protectFunctionWrapper', () => { it('works properly with no error', () => { let errorCalls = 0; - expect(protectFunctionWrapper(name => { - return `hello ${name}`; - }, null, () => {errorCalls++;})('world')).to.equal('hello world'); + expect( + protectFunctionWrapper( + name => { + return `hello ${name}`; + }, + null, + () => { + errorCalls++; + } + )('world') + ).to.equal('hello world'); expect(errorCalls).to.equal(0); }); it('handles error properly', () => { const err = new Error('test fail'); - expect(protectFunctionWrapper((name, suffix) => { - expect(name).to.equal('world'); - expect(suffix).to.equal('!'); - throw err; - }, null, (currErr, name, suffix) => { - expect(currErr).to.equal(err); - expect(name).to.equal('world'); - expect(suffix).to.equal('!'); - return 'pass'; - })('world', '!')).to.equal('pass'); + expect( + protectFunctionWrapper( + (name, suffix) => { + expect(name).to.equal('world'); + expect(suffix).to.equal('!'); + throw err; + }, + null, + (currErr, name, suffix) => { + expect(currErr).to.equal(err); + expect(name).to.equal('world'); + expect(suffix).to.equal('!'); + return 'pass'; + } + )('world', '!') + ).to.equal('pass'); }); it('returns undefined if error thrown in error handler', () => { const err = new Error('test fail within fn'); - expect(protectFunctionWrapper((name, suffix) => { - expect(name).to.equal('world'); - expect(suffix).to.be.undefined; - throw err; - }, null, (currErr, name, suffix) => { - expect(currErr).to.equal(err); - expect(name).to.equal('world'); - expect(suffix).to.be.undefined; - throw new Error('test fail within error fn'); - })('world')).to.be.undefined; + expect( + protectFunctionWrapper( + (name, suffix) => { + expect(name).to.equal('world'); + expect(suffix).to.be.undefined; + throw err; + }, + null, + (currErr, name, suffix) => { + expect(currErr).to.equal(err); + expect(name).to.equal('world'); + expect(suffix).to.be.undefined; + throw new Error('test fail within error fn'); + } + )('world') + ).to.be.undefined; }); }); }); @@ -2276,7 +2611,6 @@ describe('amp-a4a', () => { }); describe('#assignAdUrlToError', () => { - it('should attach info to error correctly', () => { const error = new Error('foo'); let queryString = ''; @@ -2299,11 +2633,14 @@ describe('amp-a4a', () => { }); describe('#extractSize', () => { - it('should return a size', () => { - expect(AmpA4A.prototype.extractSize(new Headers({ - 'X-CreativeSize': '320x50', - }))).to.deep.equal({width: 320, height: 50}); + expect( + AmpA4A.prototype.extractSize( + new Headers({ + 'X-CreativeSize': '320x50', + }) + ) + ).to.deep.equal({width: 320, height: 50}); }); it('should return no size', () => { @@ -2342,8 +2679,10 @@ describe('amp-a4a', () => { // long as they're called the appropriate number of times. We stub them // out here because they would otherwise throw errors unrelated to the // behavior actually being tested. - const initiateAdRequestMock = - sandbox.stub(AmpA4A.prototype, 'initiateAdRequest'); + const initiateAdRequestMock = sandbox.stub( + AmpA4A.prototype, + 'initiateAdRequest' + ); initiateAdRequestMock.returns(undefined); const tearDownSlotMock = sandbox.stub(AmpA4A.prototype, 'tearDownSlot'); tearDownSlotMock.returns(undefined); @@ -2352,17 +2691,18 @@ describe('amp-a4a', () => { sandbox.stub(analytics, 'triggerAnalyticsEvent'); expect(a4a.isRefreshing).to.be.false; - return a4a.refresh(() => {}).then(() => { - expect(initiateAdRequestMock).to.be.calledOnce; - expect(tearDownSlotMock).to.be.calledOnce; - expect(a4a.togglePlaceholder).to.be.calledOnce; - expect(a4a.isRefreshing).to.be.true; - expect(a4a.isRelayoutNeededFlag).to.be.true; - }); + return a4a + .refresh(() => {}) + .then(() => { + expect(initiateAdRequestMock).to.be.calledOnce; + expect(tearDownSlotMock).to.be.calledOnce; + expect(a4a.togglePlaceholder).to.be.calledOnce; + expect(a4a.isRefreshing).to.be.true; + expect(a4a.isRelayoutNeededFlag).to.be.true; + }); }); }); - it('should fire an analytics event when refreshing', () => { return createIframePromise().then(f => { const fixture = f; @@ -2383,22 +2723,30 @@ describe('amp-a4a', () => { // long as they're called the appropriate number of times. We stub them // out here because they would otherwise throw errors unrelated to the // behavior actually being tested. - const initiateAdRequestMock = - sandbox.stub(AmpA4A.prototype, 'initiateAdRequest'); + const initiateAdRequestMock = sandbox.stub( + AmpA4A.prototype, + 'initiateAdRequest' + ); initiateAdRequestMock.returns(undefined); const tearDownSlotMock = sandbox.stub(AmpA4A.prototype, 'tearDownSlot'); tearDownSlotMock.returns(undefined); const destroyFrameMock = sandbox.stub(AmpA4A.prototype, 'destroyFrame'); destroyFrameMock.returns(undefined); - const triggerAnalyticsEventStub = sandbox.stub(analytics, - 'triggerAnalyticsEvent'); + const triggerAnalyticsEventStub = sandbox.stub( + analytics, + 'triggerAnalyticsEvent' + ); expect(a4a.isRefreshing).to.be.false; - return a4a.refresh(() => {}).then(() => { - expect(triggerAnalyticsEventStub) - .calledWith(a4a.element, 'ad-refresh'); - }); + return a4a + .refresh(() => {}) + .then(() => { + expect(triggerAnalyticsEventStub).calledWith( + a4a.element, + 'ad-refresh' + ); + }); }); }); @@ -2464,52 +2812,58 @@ describe('amp-a4a', () => { let a4aElement; let a4a; let fixture; - beforeEach(() => createIframePromise().then(f => { - fixture = f; - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - a4aElement = createA4aElement(fixture.doc); - a4a = new MockA4AImpl(a4aElement); - a4a.releaseType_ = '0'; - return fixture; - })); - - it('by default not allowed if crypto signature present but no SSL', - () => { - sandbox.stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') - .returns(false); - a4a.buildCallback(); - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - expect(a4aElement.querySelector('iframe[src]')).to.be.ok; - expect(a4aElement.querySelector('iframe[srcdoc]')).to.not.be.ok; - }); - }); - - it('allowed if crypto signature present, no SSL, and overrided' + - ' shouldPreferentialRenderWithoutCrypto', () => { - sandbox.stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') - .returns(false); - sandbox.stub( - AmpA4A.prototype, - 'shouldPreferentialRenderWithoutCrypto').callsFake( - () => true); + beforeEach(() => + createIframePromise().then(f => { + fixture = f; + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + a4aElement = createA4aElement(fixture.doc); + a4a = new MockA4AImpl(a4aElement); + a4a.releaseType_ = '0'; + return fixture; + }) + ); + + it('by default not allowed if crypto signature present but no SSL', () => { + sandbox + .stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') + .returns(false); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifyA4ARender(a4aElement); + expect(a4aElement.querySelector('iframe[src]')).to.be.ok; + expect(a4aElement.querySelector('iframe[srcdoc]')).to.not.be.ok; }); }); + it( + 'allowed if crypto signature present, no SSL, and overrided' + + ' shouldPreferentialRenderWithoutCrypto', + () => { + sandbox + .stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') + .returns(false); + sandbox + .stub(AmpA4A.prototype, 'shouldPreferentialRenderWithoutCrypto') + .callsFake(() => true); + a4a.buildCallback(); + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + verifyA4ARender(a4aElement); + }); + } + ); + it('not allowed if no crypto signature present', () => { delete adResponse.headers['AMP-Fast-Fetch-Signature']; delete adResponse.headers[AMP_SIGNATURE_HEADER]; - sandbox.stub( - AmpA4A.prototype, - 'shouldPreferentialRenderWithoutCrypto').callsFake( - () => true); + sandbox + .stub(AmpA4A.prototype, 'shouldPreferentialRenderWithoutCrypto') + .callsFake(() => true); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -2542,7 +2896,6 @@ describe('amp-a4a', () => { // - All }); - describes.realWin('AmpA4a-RTC', {amp: true}, env => { let element; let a4a; @@ -2574,14 +2927,19 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { expect(a4a.tryExecuteRealTimeConfig_()).to.be.undefined; }); it('should log user error if RTC Config set but RTC not supported', () => { - element.setAttribute('rtc-config', - JSON.stringify({'urls': ['https://a.com']})); - expect(allowConsoleError(() => a4a.tryExecuteRealTimeConfig_())) - .to.be.undefined; + element.setAttribute( + 'rtc-config', + JSON.stringify({'urls': ['https://a.com']}) + ); + expect(allowConsoleError(() => a4a.tryExecuteRealTimeConfig_())).to.be + .undefined; expect(errorSpy.calledOnce).to.be.true; - expect(errorSpy.calledWith( + expect( + errorSpy.calledWith( 'amp-a4a', - 'RTC not supported for ad network doubleclick')).to.be.true; + 'RTC not supported for ad network doubleclick' + ) + ).to.be.true; }); }); @@ -2610,7 +2968,8 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { } a4a.postAdResponseExperimentFeatures['pref_neutral_enabled'] = prefVal; expect(a4a.inNonAmpPreferenceExp()).to.equal(!!expected); - })); + }) + ); }); describe('#sandboxHTMLCreativeFrame', () => { @@ -2631,9 +2990,13 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { a4a.maybeAddSinglePassExperiment(); const isInSinglePass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS + ); const isInMultiPass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS + ); expect(isInSinglePass).to.be.true; expect(isInMultiPass).to.be.false; @@ -2646,9 +3009,13 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { a4a.maybeAddSinglePassExperiment(); const isInSinglePass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS + ); const isInMultiPass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS + ); expect(isInSinglePass).to.be.false; expect(isInMultiPass).to.be.true; @@ -2661,9 +3028,13 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { a4a.maybeAddSinglePassExperiment(); const isInSinglePass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS + ); const isInMultiPass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS + ); expect(isInSinglePass).to.be.false; expect(isInMultiPass).to.be.false; diff --git a/extensions/amp-a4a/0.1/test/test-amp-ad-template-helper.js b/extensions/amp-a4a/0.1/test/test-amp-ad-template-helper.js index 8c0c9e22989b..afad52d41ed0 100644 --- a/extensions/amp-a4a/0.1/test/test-amp-ad-template-helper.js +++ b/extensions/amp-a4a/0.1/test/test-amp-ad-template-helper.js @@ -18,11 +18,10 @@ import {AmpAdTemplateHelper} from '../amp-ad-template-helper'; import {AmpMustache} from '../../../amp-mustache/0.1/amp-mustache'; import {Xhr} from '../../../../src/service/xhr-impl'; - describes.fakeWin('AmpAdTemplateHelper', {amp: true}, env => { - - const cdnUrl = 'https://adserver-com.cdn.ampproject.org/ad/s/' + - 'adserver.com/amp_template_1'; + const cdnUrl = + 'https://adserver-com.cdn.ampproject.org/ad/s/' + + 'adserver.com/amp_template_1'; const canonicalUrl = 'https://adserver.com/amp_template_1'; let win, doc; @@ -39,21 +38,22 @@ describes.fakeWin('AmpAdTemplateHelper', {amp: true}, env => { it('should return a promise resolving to a string template', () => { const template = 'content not important here'; - fetchTextMock.withArgs( - cdnUrl, - { - mode: 'cors', - method: 'GET', - ampCors: false, - credentials: 'omit', + fetchTextMock + .withArgs(cdnUrl, { + mode: 'cors', + method: 'GET', + ampCors: false, + credentials: 'omit', + }) + .returns( + Promise.resolve({ + headers: {}, + text: () => template, }) - .returns(Promise.resolve( - { - headers: {}, - text: () => template, - })); - return ampAdTemplateHelper.fetch(canonicalUrl) - .then(fetchedTemplate => expect(fetchedTemplate).to.equal(template)); + ); + return ampAdTemplateHelper + .fetch(canonicalUrl) + .then(fetchedTemplate => expect(fetchedTemplate).to.equal(template)); }); it('should use CDN url if one is supplied', () => { @@ -61,8 +61,9 @@ describes.fakeWin('AmpAdTemplateHelper', {amp: true}, env => { }); it('should convert canonical to CDN', () => { - expect(ampAdTemplateHelper.getTemplateProxyUrl_(canonicalUrl)) - .to.equal(cdnUrl); + expect(ampAdTemplateHelper.getTemplateProxyUrl_(canonicalUrl)).to.equal( + cdnUrl + ); }); it('should render a template with correct values', () => { @@ -72,35 +73,38 @@ describes.fakeWin('AmpAdTemplateHelper', {amp: true}, env => { it('should render a template with correct values', () => { win.AMP.registerTemplate('amp-mustache', AmpMustache); const parentDiv = doc.createElement('div'); - parentDiv./*OK*/innerHTML = - ''; + parentDiv./*OK*/ innerHTML = + ''; doc.body.appendChild(parentDiv); return ampAdTemplateHelper.render({foo: 'bar'}, parentDiv).then(result => { expect(result).to.not.be.null; - expect(result./*OK*/innerHTML).to.equal('bar'); + expect(result./*OK*/ innerHTML).to.equal('bar'); }); }); it('should insert analytics component', () => { const parentDiv = doc.createElement('div'); - parentDiv./*OK*/innerHTML = - '

    123

    '; + parentDiv./*OK*/ innerHTML = '

    123

    '; doc.body.appendChild(parentDiv); - const analytics = [{ - 'remote': 'remoteUrl', - 'inline': { - 'requests': 'r', + const analytics = [ + { + 'remote': 'remoteUrl', + 'inline': { + 'requests': 'r', + }, }, - }, { - 'type': 'googleanalytics', - }]; + { + 'type': 'googleanalytics', + }, + ]; ampAdTemplateHelper.insertAnalytics(parentDiv, analytics); expect(parentDiv.childNodes.length).to.equal(3); - expect(parentDiv.innerHTML).to.equal('

    123

    ' + + expect(parentDiv.innerHTML).to.equal( + '

    123

    ' + '' + '' + '' + - ''); + '' + ); }); }); - diff --git a/extensions/amp-a4a/0.1/test/test-amp-ad-utils.js b/extensions/amp-a4a/0.1/test/test-amp-ad-utils.js index a408d0d6a5bb..47cde5ec42b2 100644 --- a/extensions/amp-a4a/0.1/test/test-amp-ad-utils.js +++ b/extensions/amp-a4a/0.1/test/test-amp-ad-utils.js @@ -18,7 +18,6 @@ import {data} from './testdata/valid_css_at_rules_amp.reserialized'; import {getAmpAdMetadata} from '../amp-ad-utils'; describe('getAmpAdMetadata', () => { - it('should parse metadata successfully', () => { const creativeMetadata = getAmpAdMetadata(data.reserialized); expect(creativeMetadata).to.be.ok; @@ -52,8 +51,8 @@ describe('getAmpAdMetadata', () => { it('should return null -- missing closing script tag', () => { const creativeMetadata = getAmpAdMetadata( - data.reserializedMissingScriptTag); + data.reserializedMissingScriptTag + ); expect(creativeMetadata).to.be.null; }); - }); diff --git a/extensions/amp-a4a/0.1/test/test-callout-vendors.js b/extensions/amp-a4a/0.1/test/test-callout-vendors.js index 9b95a5cb0814..67193a00e1a0 100644 --- a/extensions/amp-a4a/0.1/test/test-callout-vendors.js +++ b/extensions/amp-a4a/0.1/test/test-callout-vendors.js @@ -21,13 +21,15 @@ import {isSecureUrlDeprecated} from '../../../../src/url'; // This test acts as a presubmit to enforce that. describe('RTC_VENDORS', () => { it('should have all lowercase keys', () => - Object.keys(RTC_VENDORS).forEach( - key => expect(key).to.equal(key.toLowerCase()) + Object.keys(RTC_VENDORS).forEach(key => + expect(key).to.equal(key.toLowerCase()) )); it('should all use https', () => Object.keys(RTC_VENDORS).forEach(key => { expect(isSecureUrlDeprecated(RTC_VENDORS[key].url)).to.be.true; - expect(!RTC_VENDORS[key].errorReportingUrl || isSecureUrlDeprecated( - RTC_VENDORS[key].errorReportingUrl)).to.be.true; + expect( + !RTC_VENDORS[key].errorReportingUrl || + isSecureUrlDeprecated(RTC_VENDORS[key].errorReportingUrl) + ).to.be.true; })); }); diff --git a/extensions/amp-a4a/0.1/test/test-cryptographic-validator.js b/extensions/amp-a4a/0.1/test/test-cryptographic-validator.js index 85ef549c488a..9b8df7b278b3 100644 --- a/extensions/amp-a4a/0.1/test/test-cryptographic-validator.js +++ b/extensions/amp-a4a/0.1/test/test-cryptographic-validator.js @@ -31,7 +31,6 @@ const realWinConfig = { }; describes.realWin('CryptographicValidator', realWinConfig, env => { - const headers = {'Content-Type': 'application/jwk-set+json'}; let sandbox; let userErrorStub; @@ -53,56 +52,59 @@ describes.realWin('CryptographicValidator', realWinConfig, env => { env.win[SIGNATURE_VERIFIER_PROPERTY_NAME] = { verify: () => Promise.resolve(VerificationStatus.OK), }; - return validator.validate( - {win: env.win}, utf8Encode(data.reserialized), headers) - .then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.AMP); - expect(validatorOutput.adResponseType).to.equal( - AdResponseType.CRYPTO); - expect(validatorOutput.creativeData).to.be.ok; + return validator + .validate({win: env.win}, utf8Encode(data.reserialized), headers) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.AMP); + expect(validatorOutput.adResponseType).to.equal(AdResponseType.CRYPTO); + expect(validatorOutput.creativeData).to.be.ok; - const {creativeMetadata} = validatorOutput.creativeData; - expect(creativeMetadata.minifiedCreative).to.equal( - data.minifiedCreative); - }); + const {creativeMetadata} = validatorOutput.creativeData; + expect(creativeMetadata.minifiedCreative).to.equal( + data.minifiedCreative + ); + }); }); it('should have non-AMP validator result', () => { env.win[SIGNATURE_VERIFIER_PROPERTY_NAME] = { verify: () => Promise.resolve(VerificationStatus.UNVERIFIED), }; - return validator.validate( - {win: env.win}, utf8Encode(data.reserialized), headers) - .then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); - expect(validatorOutput.adResponseType).to.equal( - AdResponseType.CRYPTO); - expect(validatorOutput.creativeData).to.be.ok; + return validator + .validate({win: env.win}, utf8Encode(data.reserialized), headers) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); + expect(validatorOutput.adResponseType).to.equal(AdResponseType.CRYPTO); + expect(validatorOutput.creativeData).to.be.ok; - const {creativeMetadata} = validatorOutput.creativeData; - expect(creativeMetadata.minifiedCreative).to.equal( - data.minifiedCreative); - expect(userErrorStub).to.be.calledOnce; - }); + const {creativeMetadata} = validatorOutput.creativeData; + expect(creativeMetadata.minifiedCreative).to.equal( + data.minifiedCreative + ); + expect(userErrorStub).to.be.calledOnce; + }); }); it('should have non-AMP validator result due to bad metadata', () => { env.win[SIGNATURE_VERIFIER_PROPERTY_NAME] = { verify: () => Promise.resolve(VerificationStatus.UNVERIFIED), }; - return validator.validate( - {win: env.win}, utf8Encode(data.reserializedInvalidOffset), headers) - .then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); - expect(validatorOutput.adResponseType).to.equal( - AdResponseType.CRYPTO); - expect(validatorOutput.creativeData).to.be.ok; - expect(validatorOutput.creativeData.creativeMetadata).to.not.be.ok; + return validator + .validate( + {win: env.win}, + utf8Encode(data.reserializedInvalidOffset), + headers + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); + expect(validatorOutput.adResponseType).to.equal(AdResponseType.CRYPTO); + expect(validatorOutput.creativeData).to.be.ok; + expect(validatorOutput.creativeData.creativeMetadata).to.not.be.ok; - expect(userErrorStub).to.be.calledOnce; - }); + expect(userErrorStub).to.be.calledOnce; + }); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-friendly-frame-renderer.js b/extensions/amp-a4a/0.1/test/test-friendly-frame-renderer.js index b685d574f030..4cf13a2214e0 100644 --- a/extensions/amp-a4a/0.1/test/test-friendly-frame-renderer.js +++ b/extensions/amp-a4a/0.1/test/test-friendly-frame-renderer.js @@ -23,7 +23,6 @@ const realWinConfig = { }; describes.realWin('FriendlyFrameRenderer', realWinConfig, env => { - const minifiedCreative = '

    Hello, World!

    '; let containerElement; @@ -52,7 +51,10 @@ describes.realWin('FriendlyFrameRenderer', realWinConfig, env => { }); containerElement.renderStarted = () => {}; containerElement.getLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.isInViewport = () => true; containerElement.getAmpDoc = () => env.ampdoc; @@ -69,16 +71,18 @@ describes.realWin('FriendlyFrameRenderer', realWinConfig, env => { return renderPromise.then(() => { const iframe = containerElement.querySelector('iframe'); expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.innerHTML) - .to.equal(minifiedCreative); + expect(iframe.contentWindow.document.body.innerHTML).to.equal( + minifiedCreative + ); }); }); it('should set the correct srcdoc on the iframe', () => { - const srcdoc = '' - + '' - + '

    Hello, World!

    '; + const srcdoc = + '' + + '" + + '

    Hello, World!

    '; return renderPromise.then(() => { const iframe = containerElement.querySelector('iframe'); expect(iframe).to.be.ok; @@ -103,8 +107,9 @@ describes.realWin('FriendlyFrameRenderer', realWinConfig, env => { return renderPromise.then(() => { const iframe = containerElement.querySelector('iframe'); expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.style.visibility) - .to.equal('visible'); + expect(iframe.contentWindow.document.body.style.visibility).to.equal( + 'visible' + ); }); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-friendly-frame-util.js b/extensions/amp-a4a/0.1/test/test-friendly-frame-util.js index 9ca3e610c4c7..f1eff93b587b 100644 --- a/extensions/amp-a4a/0.1/test/test-friendly-frame-util.js +++ b/extensions/amp-a4a/0.1/test/test-friendly-frame-util.js @@ -23,7 +23,6 @@ const realWinConfig = { }; describes.realWin('FriendlyFrameUtil', realWinConfig, env => { - const minifiedCreative = '

    Hello, World!

    '; let containerElement; @@ -36,7 +35,10 @@ describes.realWin('FriendlyFrameUtil', realWinConfig, env => { }); containerElement.renderStarted = () => {}; containerElement.getLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.isInViewport = () => true; containerElement.getAmpDoc = () => env.ampdoc; @@ -51,7 +53,11 @@ describes.realWin('FriendlyFrameUtil', realWinConfig, env => { }; renderPromise = renderCreativeIntoFriendlyFrame( - adUrl, size, containerElement, creativeMetadata); + adUrl, + size, + containerElement, + creativeMetadata + ); }); afterEach(() => { @@ -61,16 +67,18 @@ describes.realWin('FriendlyFrameUtil', realWinConfig, env => { it('should append iframe child', () => { return renderPromise.then(iframe => { expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.innerHTML) - .to.equal(minifiedCreative); + expect(iframe.contentWindow.document.body.innerHTML).to.equal( + minifiedCreative + ); }); }); it('should set the correct srcdoc on the iframe', () => { - const srcdoc = '' - + '' - + '

    Hello, World!

    '; + const srcdoc = + '' + + '" + + '

    Hello, World!

    '; return renderPromise.then(iframe => { expect(iframe).to.be.ok; expect(iframe.getAttribute('srcdoc')).to.equal(srcdoc); @@ -92,9 +100,9 @@ describes.realWin('FriendlyFrameUtil', realWinConfig, env => { it('should style body of iframe document to be visible', () => { return renderPromise.then(iframe => { expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.style.visibility) - .to.equal('visible'); + expect(iframe.contentWindow.document.body.style.visibility).to.equal( + 'visible' + ); }); }); }); - diff --git a/extensions/amp-a4a/0.1/test/test-name-frame-renderer.js b/extensions/amp-a4a/0.1/test/test-name-frame-renderer.js index e79193b80f72..e4a05c736e4d 100644 --- a/extensions/amp-a4a/0.1/test/test-name-frame-renderer.js +++ b/extensions/amp-a4a/0.1/test/test-name-frame-renderer.js @@ -25,7 +25,6 @@ const realWinConfig = { }; describes.realWin('NameFrameRenderer', realWinConfig, env => { - const minifiedCreative = '

    Hello, World!

    '; let containerElement; @@ -50,7 +49,10 @@ describes.realWin('NameFrameRenderer', realWinConfig, env => { containerElement.setAttribute('height', 50); containerElement.setAttribute('width', 320); containerElement.getPageLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.getIntersectionChangeEntry = () => ({}); document.body.appendChild(containerElement); diff --git a/extensions/amp-a4a/0.1/test/test-real-time-config-manager.js b/extensions/amp-a4a/0.1/test/test-real-time-config-manager.js index f8a9a7b1e86d..fe999677fc71 100644 --- a/extensions/amp-a4a/0.1/test/test-real-time-config-manager.js +++ b/extensions/amp-a4a/0.1/test/test-real-time-config-manager.js @@ -28,10 +28,7 @@ import { import {Services} from '../../../../src/services'; import {Xhr} from '../../../../src/service/xhr-impl'; import {createElementWithAttributes} from '../../../../src/dom'; -import { - dev, - user, -} from '../../../../src/log'; +import {dev, user} from '../../../../src/log'; import {isFiniteNumber} from '../../../../src/types'; describes.realWin('real-time-config-manager', {amp: true}, env => { @@ -63,8 +60,10 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { // RealTimeConfigManager uses the UrlReplacements service scoped to the A4A // (FIE), but for testing stub in the parent service for simplicity. const urlReplacements = Services.urlReplacementsForDoc(element); - sandbox.stub(Services, 'urlReplacementsForDoc') - .withArgs(a4aElement.element).returns(urlReplacements); + sandbox + .stub(Services, 'urlReplacementsForDoc') + .withArgs(a4aElement.element) + .returns(urlReplacements); rtc = new RealTimeConfigManager(a4aElement); maybeExecuteRealTimeConfig_ = rtc.maybeExecuteRealTimeConfig.bind(rtc); @@ -84,13 +83,16 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { fetchJsonStub.withArgs(params).returns(Promise.reject('FAIL')); } else { const textFunction = () => { - return !isString ? Promise.resolve(JSON.stringify(response)) : - Promise.resolve(response); + return !isString + ? Promise.resolve(JSON.stringify(response)) + : Promise.resolve(response); }; - fetchJsonStub.withArgs(params).returns(Promise.resolve({ - status: 200, - text: textFunction, - })); + fetchJsonStub.withArgs(params).returns( + Promise.resolve({ + status: 200, + text: textFunction, + }) + ); } } @@ -119,27 +121,40 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { }); it('should convert & trunc url when parseable', () => { - const url = 'https://www.example.com/thisIsTooMany' + - 'Characters1234567891011121314.php'; + const url = + 'https://www.example.com/thisIsTooMany' + + 'Characters1234567891011121314.php'; const ard = getCalloutParam_(url); expect(ard).to.equal( - 'www.example.com/thisIsTooManyCharacters12345678910'); + 'www.example.com/thisIsTooManyCharacters12345678910' + ); }); }); describe('#maybeExecuteRealTimeConfig_', () => { function executeTest(args) { - const {urls, vendors, timeoutMillis, rtcCalloutResponses, - expectedCalloutUrls, responseIsString, failXhr, - expectedRtcArray, calloutCount} = args; + const { + urls, + vendors, + timeoutMillis, + rtcCalloutResponses, + expectedCalloutUrls, + responseIsString, + failXhr, + expectedRtcArray, + calloutCount, + } = args; setRtcConfig({urls, vendors, timeoutMillis}); (expectedCalloutUrls || []).forEach((expectedUrl, i) => { - setFetchJsonStubBehavior(expectedUrl, rtcCalloutResponses[i], - responseIsString, failXhr); + setFetchJsonStubBehavior( + expectedUrl, + rtcCalloutResponses[i], + responseIsString, + failXhr + ); }); const customMacros = args['customMacros'] || {}; - const rtcResponsePromiseArray = maybeExecuteRealTimeConfig_( - customMacros); + const rtcResponsePromiseArray = maybeExecuteRealTimeConfig_(customMacros); return rtcResponsePromiseArray.then(rtcResponseArray => { expect(rtcResponseArray.length).to.equal(expectedRtcArray.length); expect(fetchJsonStub.callCount).to.equal(calloutCount); @@ -148,19 +163,23 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { }); rtcResponseArray.forEach((rtcResponse, i) => { expect(rtcResponse.response).to.deep.equal( - expectedRtcArray[i].response); - expect(rtcResponse.callout).to.equal( - expectedRtcArray[i].callout); + expectedRtcArray[i].response + ); + expect(rtcResponse.callout).to.equal(expectedRtcArray[i].callout); expect(rtcResponse.error).to.equal(expectedRtcArray[i].error); expect(Object.keys(rtcResponse).sort()).to.deep.equal( - Object.keys(expectedRtcArray[i]).sort()); + Object.keys(expectedRtcArray[i]).sort() + ); expect(isFiniteNumber(rtcResponse.rtcTime)).to.be.true; }); }); } const urlMacros = [ - 'slot_id=SLOT_ID', 'page_id=PAGE_ID', 'adx=ADX', 'ady=ADY', + 'slot_id=SLOT_ID', + 'page_id=PAGE_ID', + 'adx=ADX', + 'ady=ADY', ]; function generateUrls(numUrls, numMacroUrls) { @@ -169,7 +188,11 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { urls.push(`https://www.${i}.com/`); } for (let i = 0; i < numMacroUrls; i++) { - urls.push(`https://www.${i + numUrls}.com/?${urlMacros.slice(0,i + 1).join('&')}`); + urls.push( + `https://www.${i + numUrls}.com/?${urlMacros + .slice(0, i + 1) + .join('&')}` + ); } return urls; } @@ -178,8 +201,9 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { // If this is an entry for a vendor, then the callout is just // the vendor name passed in to url here. const callout = !!isVendor ? url : getCalloutParam_(url); - return response ? {response, callout, rtcTime: 10} : - {callout, error, rtcTime: 10}; + return response + ? {response, callout, rtcTime: 10} + : {callout, error, rtcTime: 10}; } function generateCalloutResponses(numGoodResponses) { @@ -188,7 +212,7 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { for (let i = 0; i < numGoodResponses; i++) { response = {}; response[`response${i}`] = {}; - response[`response${i}`][`foo${i}`] = [`a${i}`,`b${i}`,`c${i}`]; + response[`response${i}`][`foo${i}`] = [`a${i}`, `b${i}`, `c${i}`]; rtcCalloutResponses.push(response); } return rtcCalloutResponses; @@ -201,19 +225,30 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { {'response1': {'fooArray': ['foo']}}, {'response2': {'test': 'test2'}}, {'response3': {'apple': 'banana'}}, - {'response4': {'animalArray': ['cat', 'dog'], - 'foodObject': {'apple': true, 'car': false}}}, + { + 'response4': { + 'animalArray': ['cat', 'dog'], + 'foodObject': {'apple': true, 'car': false}, + }, + }, {'response5': [1, 2, 3]}, ]; const expectedRtcArray = []; urls.forEach((url, i) => { expectedRtcArray.push({ - callout: getCalloutParam_(url), rtcTime: 10, - response: rtcCalloutResponses[i]}); + callout: getCalloutParam_(url), + rtcTime: 10, + response: rtcCalloutResponses[i], + }); }); return executeTest({ - urls, inflatedUrls: urls, rtcCalloutResponses, calloutCount, - expectedCalloutUrls: urls, expectedRtcArray}); + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: urls, + expectedRtcArray, + }); }); it('should send only 5 RTC callouts for all URLS without macros', () => { @@ -225,17 +260,24 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { for (let i = 0; i < 5; i++) { expectedRtcArray.push(rtcEntry(rtcCalloutResponses[i], urls[i])); } - expectedRtcArray.push(rtcEntry(null, urls[5], - RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED)); - expectedRtcArray.push(rtcEntry(null, urls[6], - RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED)); + expectedRtcArray.push( + rtcEntry(null, urls[5], RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED) + ); + expectedRtcArray.push( + rtcEntry(null, urls[6], RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED) + ); return executeTest({ - urls, inflatedUrls: urls, rtcCalloutResponses, calloutCount, - expectedCalloutUrls, expectedRtcArray}); + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls, + expectedRtcArray, + }); }); it('should send RTC callouts to inflated publisher URLs', () => { - const urls = generateUrls(1,2); + const urls = generateUrls(1, 2); const inflatedUrls = [ 'https://www.0.com/', 'https://www.1.com/?slot_id=1', @@ -252,8 +294,15 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { expectedRtcArray.push(rtcEntry(rtcResponse, inflatedUrls[i])); }); const calloutCount = 3; - return executeTest({urls, customMacros, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + return executeTest({ + urls, + customMacros, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should send RTC callouts to inflated vendor URLs', () => { const vendors = { @@ -262,46 +311,65 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const inflatedUrls = [ 'https://localhost:8000/examples/rtcE1.json?slot_id=1&page_id=3&foo_id=4', ]; - const rtcCalloutResponses = [ - {'response1': {'fooArray': ['foo']}}, - ]; + const rtcCalloutResponses = [{'response1': {'fooArray': ['foo']}}]; const customMacros = { PAGE_ID: () => 3, FOO_ID: () => 4, }; const calloutCount = 1; const expectedRtcArray = []; - expectedRtcArray.push(rtcEntry(rtcCalloutResponses[0], - Object.keys(vendors)[0].toLowerCase(), undefined, true)); + expectedRtcArray.push( + rtcEntry( + rtcCalloutResponses[0], + Object.keys(vendors)[0].toLowerCase(), + undefined, + true + ) + ); return executeTest({ - vendors, customMacros, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + vendors, + customMacros, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should send callouts to vendor URLs with object/array macros', () => { const vendors = { 'fAkeVeNdOR': { SLOT_ID: {'key': 'value'}, - PAGE_ID: [1,2,3], + PAGE_ID: [1, 2, 3], FOO_ID: 'String', }, }; const inflatedUrls = [ 'https://localhost:8000/examples/rtcE1.json?slot_id=%7B%22key%22%3A%22' + - 'value%22%7D&page_id=%5B1%2C2%2C3%5D&foo_id=String', - ]; - const rtcCalloutResponses = [ - {'response1': {'fooArray': ['foo']}}, + 'value%22%7D&page_id=%5B1%2C2%2C3%5D&foo_id=String', ]; + const rtcCalloutResponses = [{'response1': {'fooArray': ['foo']}}]; const calloutCount = 1; const expectedRtcArray = []; - expectedRtcArray.push(rtcEntry(rtcCalloutResponses[0], - Object.keys(vendors)[0].toLowerCase(), undefined, true)); + expectedRtcArray.push( + rtcEntry( + rtcCalloutResponses[0], + Object.keys(vendors)[0].toLowerCase(), + undefined, + true + ) + ); return executeTest({ - vendors, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + vendors, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should send RTC callouts to inflated publisher and vendor URLs', () => { - const urls = generateUrls(2,2); + const urls = generateUrls(2, 2); const vendors = { 'fAkeVeNdOR': {SLOT_ID: 0, PAGE_ID: 1}, }; @@ -321,14 +389,28 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; for (let i = 0; i < 4; i++) { expectedRtcArray.push( - rtcEntry(rtcCalloutResponses[i], inflatedUrls[i])); + rtcEntry(rtcCalloutResponses[i], inflatedUrls[i]) + ); } - expectedRtcArray.push(rtcEntry(rtcCalloutResponses[4], - Object.keys(vendors)[0].toLowerCase(), undefined, true)); + expectedRtcArray.push( + rtcEntry( + rtcCalloutResponses[4], + Object.keys(vendors)[0].toLowerCase(), + undefined, + true + ) + ); const calloutCount = 5; return executeTest({ - urls, vendors, customMacros, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + urls, + vendors, + customMacros, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should ignore bad macros for vendor urls', () => { const vendors = { @@ -341,18 +423,28 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; for (let i = 0; i < 1; i++) { expectedRtcArray.push( - rtcEntry(rtcCalloutResponses[i], - Object.keys(vendors)[0].toLowerCase(), undefined, true)); + rtcEntry( + rtcCalloutResponses[i], + Object.keys(vendors)[0].toLowerCase(), + undefined, + true + ) + ); } const calloutCount = 1; sandbox.stub(user(), 'error').callsFake(() => {}); return executeTest({ - vendors, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + vendors, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should favor publisher URLs over vendor URLs', () => { - const urls = generateUrls(3,2); + const urls = generateUrls(3, 2); const vendors = { 'fAkeVeNdOR': {SLOT_ID: 0, PAGE_ID: 1}, }; @@ -372,35 +464,53 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; for (let i = 0; i < 5; i++) { expectedRtcArray.push( - rtcEntry(rtcCalloutResponses[i], inflatedUrls[i])); + rtcEntry(rtcCalloutResponses[i], inflatedUrls[i]) + ); } expectedRtcArray.push( - rtcEntry(null, Object.keys(vendors)[0].toLowerCase(), - RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED, true)); + rtcEntry( + null, + Object.keys(vendors)[0].toLowerCase(), + RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED, + true + ) + ); const calloutCount = 5; return executeTest({ - urls, vendors, customMacros, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + urls, + vendors, + customMacros, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should not send more than one RTC callout to the same url', () => { - const urls = [ - 'https://www.0.com/', - 'https://www.0.com/', - ]; + const urls = ['https://www.0.com/', 'https://www.0.com/']; const rtcCalloutResponses = generateCalloutResponses(1); const calloutCount = 1; - const expectedCalloutUrls = [ - 'https://www.0.com/', - ]; + const expectedCalloutUrls = ['https://www.0.com/']; const expectedRtcArray = [ - {response: rtcCalloutResponses[0], - callout: getCalloutParam_(urls[0]), rtcTime: 10}, - {callout: getCalloutParam_(urls[1]), - error: RTC_ERROR_ENUM.DUPLICATE_URL, rtcTime: 10}, + { + response: rtcCalloutResponses[0], + callout: getCalloutParam_(urls[0]), + rtcTime: 10, + }, + { + callout: getCalloutParam_(urls[1]), + error: RTC_ERROR_ENUM.DUPLICATE_URL, + rtcTime: 10, + }, ]; return executeTest({ - urls, inflatedUrls: urls, rtcCalloutResponses, calloutCount, - expectedCalloutUrls, expectedRtcArray}); + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls, + expectedRtcArray, + }); }); it('should not send an RTC callout to an insecure url', () => { @@ -414,21 +524,32 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { {'response2': {'insecure': ['virus']}}, ]; const calloutCount = 2; - const expectedCalloutUrls = [ - 'https://www.1.com/', - 'https://www.2.com', - ]; + const expectedCalloutUrls = ['https://www.1.com/', 'https://www.2.com']; const expectedRtcArray = [ - {response: rtcCalloutResponses[0], - callout: getCalloutParam_(urls[0]), rtcTime: 10}, - {response: rtcCalloutResponses[1], - callout: getCalloutParam_(urls[1]), rtcTime: 10}, - {callout: getCalloutParam_(urls[2]), - error: RTC_ERROR_ENUM.INSECURE_URL, rtcTime: 10}, + { + response: rtcCalloutResponses[0], + callout: getCalloutParam_(urls[0]), + rtcTime: 10, + }, + { + response: rtcCalloutResponses[1], + callout: getCalloutParam_(urls[1]), + rtcTime: 10, + }, + { + callout: getCalloutParam_(urls[2]), + error: RTC_ERROR_ENUM.INSECURE_URL, + rtcTime: 10, + }, ]; return executeTest({ - urls, inflatedUrls: urls, rtcCalloutResponses, calloutCount, - expectedCalloutUrls, expectedRtcArray}); + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls, + expectedRtcArray, + }); }); it('should not send RTC callout to unknown vendor', () => { const vendors = { @@ -437,8 +558,13 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const calloutCount = 0; const expectedRtcArray = []; expectedRtcArray.push( - rtcEntry(null, Object.keys(vendors)[0].toLowerCase(), - RTC_ERROR_ENUM.UNKNOWN_VENDOR, true)); + rtcEntry( + null, + Object.keys(vendors)[0].toLowerCase(), + RTC_ERROR_ENUM.UNKNOWN_VENDOR, + true + ) + ); return executeTest({vendors, calloutCount, expectedRtcArray}); }); it('should handle bad JSON response', () => { @@ -447,12 +573,19 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; rtcCalloutResponses.forEach((rtcResponse, i) => { expectedRtcArray.push( - rtcEntry(null, urls[i], RTC_ERROR_ENUM.MALFORMED_JSON_RESPONSE)); + rtcEntry(null, urls[i], RTC_ERROR_ENUM.MALFORMED_JSON_RESPONSE) + ); }); const calloutCount = 1; - return executeTest({urls, inflatedUrls: urls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: urls, expectedRtcArray, - responseIsString: true}); + return executeTest({ + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: urls, + expectedRtcArray, + responseIsString: true, + }); }); it('should catch errors due to network failure', () => { const urls = generateUrls(1); @@ -460,24 +593,32 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; rtcCalloutResponses.forEach((rtcResponse, i) => { expectedRtcArray.push( - rtcEntry(null, urls[i], RTC_ERROR_ENUM.NETWORK_FAILURE)); + rtcEntry(null, urls[i], RTC_ERROR_ENUM.NETWORK_FAILURE) + ); }); const calloutCount = 1; - return executeTest({urls, inflatedUrls: urls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: urls, expectedRtcArray, - failXhr: true}); + return executeTest({ + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: urls, + expectedRtcArray, + failXhr: true, + }); }); for (const consentState in CONSENT_POLICY_STATE) { it(`should handle consentState ${consentState}`, () => { setRtcConfig({urls: ['https://foo.com']}); const rtcResult = maybeExecuteRealTimeConfig_( - {}, CONSENT_POLICY_STATE[consentState]); + {}, + CONSENT_POLICY_STATE[consentState] + ); switch (CONSENT_POLICY_STATE[consentState]) { case CONSENT_POLICY_STATE.SUFFICIENT: case CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED: expect(rtcResult).to.be.ok; - return rtcResult.then(() => - expect(fetchJsonStub).to.be.calledOnce); + return rtcResult.then(() => expect(fetchJsonStub).to.be.calledOnce); case CONSENT_POLICY_STATE.UNKNOWN: case CONSENT_POLICY_STATE.INSUFFICIENT: return rtcResult.then(result => { @@ -500,12 +641,17 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should return parsed rtcConfig for valid rtcConfig', () => { const rtcConfig = { - 'vendors': {'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + 'vendors': { + 'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, 'nonexistent-vendor': {'SLOT_ID': '1'}, - 'fakeVendor2': {'SLOT_ID': '1'}}, - 'urls': ['https://localhost:4443/posts?slot_id=SLOT_ID', - 'https://broken.zzzzzzz'], - 'timeoutMillis': 500}; + 'fakeVendor2': {'SLOT_ID': '1'}, + }, + 'urls': [ + 'https://localhost:4443/posts?slot_id=SLOT_ID', + 'https://broken.zzzzzzz', + ], + 'timeoutMillis': 500, + }; setRtcConfig(rtcConfig); validateRtcConfig_(element); expect(rtc.rtcConfig_).to.be.ok; @@ -514,12 +660,17 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should allow timeout of 0', () => { const rtcConfig = { - 'vendors': {'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + 'vendors': { + 'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, 'nonexistent-vendor': {'SLOT_ID': '1'}, - 'fakeVendor2': {'SLOT_ID': '1'}}, - 'urls': ['https://localhost:4443/posts?slot_id=SLOT_ID', - 'https://broken.zzzzzzz'], - 'timeoutMillis': 0}; + 'fakeVendor2': {'SLOT_ID': '1'}, + }, + 'urls': [ + 'https://localhost:4443/posts?slot_id=SLOT_ID', + 'https://broken.zzzzzzz', + ], + 'timeoutMillis': 0, + }; setRtcConfig(rtcConfig); validateRtcConfig_(element); expect(rtc.rtcConfig_).to.be.ok; @@ -528,19 +679,29 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should not allow timeout greater than default', () => { const rtcConfig = { - 'vendors': {'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + 'vendors': { + 'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, 'nonexistent-vendor': {'SLOT_ID': '1'}, - 'fakeVendor2': {'SLOT_ID': '1'}}, - 'urls': ['https://localhost:4443/posts?slot_id=SLOT_ID', - 'https://broken.zzzzzzz'], - 'timeoutMillis': 1000000}; + 'fakeVendor2': {'SLOT_ID': '1'}, + }, + 'urls': [ + 'https://localhost:4443/posts?slot_id=SLOT_ID', + 'https://broken.zzzzzzz', + ], + 'timeoutMillis': 1000000, + }; const expectedRtcConfig = { - 'vendors': {'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + 'vendors': { + 'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, 'nonexistent-vendor': {'SLOT_ID': '1'}, - 'fakeVendor2': {'SLOT_ID': '1'}}, - 'urls': ['https://localhost:4443/posts?slot_id=SLOT_ID', - 'https://broken.zzzzzzz'], - 'timeoutMillis': 1000}; + 'fakeVendor2': {'SLOT_ID': '1'}, + }, + 'urls': [ + 'https://localhost:4443/posts?slot_id=SLOT_ID', + 'https://broken.zzzzzzz', + ], + 'timeoutMillis': 1000, + }; setRtcConfig(rtcConfig); validateRtcConfig_(element); expect(rtc.rtcConfig_).to.be.ok; @@ -552,9 +713,13 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { }); // Test various misconfigurations that are missing vendors or urls. - [{'timeoutMillis': 500}, {'vendors': {}}, {'urls': []}, + [ + {'timeoutMillis': 500}, + {'vendors': {}}, + {'urls': []}, {'vendors': {}, 'urls': []}, - {'vendors': 'incorrect', 'urls': 'incorrect'}].forEach(rtcConfig => { + {'vendors': 'incorrect', 'urls': 'incorrect'}, + ].forEach(rtcConfig => { it('should return false for rtcConfig missing required values', () => { setRtcConfig(rtcConfig); allowConsoleError(() => { @@ -582,15 +747,18 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const macroDelay = 20; const macros = { DUMMY: () => { - return Services.timerFor(env.win).promise(macroDelay).then( - () => {return 'foo';} - ); + return Services.timerFor(env.win) + .promise(macroDelay) + .then(() => { + return 'foo'; + }); }, }; inflateAndSendRtc_(url, macros); return rtc.promiseArray_[0].then(errorResponse => { expect(errorResponse.error).to.equal( - RTC_ERROR_ENUM.MACRO_EXPAND_TIMEOUT); + RTC_ERROR_ENUM.MACRO_EXPAND_TIMEOUT + ); }); }); @@ -606,12 +774,10 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { return rtc.promiseArray_[0].then(errorResponse => { expect(errorResponse).to.be.undefined; }); - }); }); describe('modifyRtcConfigForConsentStateSettings', () => { - beforeEach(() => { rtc.rtcConfig_ = { 'vendors': { @@ -623,7 +789,8 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { 'https://www.rtc.com/example1', 'https://www.other-rtc.com/example2', ], - 'timeoutMillis': 500}; + 'timeoutMillis': 500, + }; }); it('should not modify rtcConfig if consent state is valid', () => { @@ -669,12 +836,18 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should clear just invalid custom URLs', () => { rtc.rtcConfig_.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, - 'vendorB': {'sendRegardlessOfConsentState': ['INSUFFICIENT', 'UNKNOWN'], - 'macros': {'SLOT_ID': '1'}}, - 'vendorC': {'sendRegardlessOfConsentState': ['UNKNOWN'], - 'macros': {'SLOT_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, + 'vendorB': { + 'sendRegardlessOfConsentState': ['INSUFFICIENT', 'UNKNOWN'], + 'macros': {'SLOT_ID': '1'}, + }, + 'vendorC': { + 'sendRegardlessOfConsentState': ['UNKNOWN'], + 'macros': {'SLOT_ID': '1'}, + }, }; const expectedRtcConfig = Object.assign({}, rtc.rtcConfig_); expectedRtcConfig.urls = []; @@ -685,10 +858,14 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should clear just invalid vendor callouts', () => { rtc.rtcConfig_.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, - {'sendRegardlessOfConsentState': ['INSUFFICIENT', 'UNKNOWN'], - 'url': 'https://www.other-rtc.com/example2'}, + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, + { + 'sendRegardlessOfConsentState': ['INSUFFICIENT', 'UNKNOWN'], + 'url': 'https://www.other-rtc.com/example2', + }, ]; const expectedRtcConfig = Object.assign({}, rtc.rtcConfig_); expectedRtcConfig.vendors = {}; @@ -699,25 +876,35 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should not clear callouts if per-callout setting valid', () => { rtc.rtcConfig_.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, - 'vendorB': {'sendRegardlessOfConsentState': ['UNKNOWN'], - 'macros': {'SLOT_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, + 'vendorB': { + 'sendRegardlessOfConsentState': ['UNKNOWN'], + 'macros': {'SLOT_ID': '1'}, + }, 'vendorC': {'SLOT_ID': '1'}, }; rtc.rtcConfig_.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, 'https://www.other-rtc.com/example2', ]; const expectedRtcConfig = Object.assign({}, rtc.rtcConfig_); expectedRtcConfig.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, }; expectedRtcConfig.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, ]; rtc.consentState_ = CONSENT_POLICY_STATE.INSUFFICIENT; rtc.modifyRtcConfigForConsentStateSettings(); @@ -726,28 +913,39 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should handle mix of global and individual consent settings', () => { rtc.rtcConfig_.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, - 'vendorB': {'sendRegardlessOfConsentState': ['UNKNOWN'], - 'macros': {'SLOT_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, + 'vendorB': { + 'sendRegardlessOfConsentState': ['UNKNOWN'], + 'macros': {'SLOT_ID': '1'}, + }, 'vendorC': {'SLOT_ID': '1'}, }; rtc.rtcConfig_.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, 'https://www.other-rtc.com/example2', ]; rtc.rtcConfig_.sendRegardlessOfConsentState = ['INSUFFICIENT']; const expectedRtcConfig = Object.assign({}, rtc.rtcConfig_); expectedRtcConfig.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, 'vendorC': {'SLOT_ID': '1'}, }; expectedRtcConfig.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, - 'https://www.other-rtc.com/example2']; + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, + 'https://www.other-rtc.com/example2', + ]; rtc.consentState_ = CONSENT_POLICY_STATE.INSUFFICIENT; rtc.modifyRtcConfigForConsentStateSettings(); expect(rtc.rtcConfig_).to.deep.equal(expectedRtcConfig); @@ -769,7 +967,6 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { rtc.modifyRtcConfigForConsentStateSettings(); expect(rtc.rtcConfig_).to.deep.equal(expectedRtcConfig); }); - }); describe('sendErrorMessage', () => { @@ -793,8 +990,9 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { HREF: env.win.location.href, }; - requestUrl = Services.urlReplacementsForDoc(a4aElement.element) - .expandUrlSync(errorReportingUrl, macros, whitelist); + requestUrl = Services.urlReplacementsForDoc( + a4aElement.element + ).expandUrlSync(errorReportingUrl, macros, whitelist); }); it('should send error message pingback to correct url', () => { diff --git a/extensions/amp-a4a/0.1/test/test-refresh.js b/extensions/amp-a4a/0.1/test/test-refresh.js index 80cf14ea1b39..0cd21cd91664 100644 --- a/extensions/amp-a4a/0.1/test/test-refresh.js +++ b/extensions/amp-a4a/0.1/test/test-refresh.js @@ -21,13 +21,10 @@ import { RefreshManager, getPublisherSpecifiedRefreshInterval, } from '../refresh-manager'; -import { - RefreshIntersectionObserverWrapper, -} from '../refresh-intersection-observer-wrapper'; +import {RefreshIntersectionObserverWrapper} from '../refresh-intersection-observer-wrapper'; import {Services} from '../../../../src/services'; describe('refresh', () => { - let mockA4a; let sandbox; const config = { @@ -45,7 +42,6 @@ describe('refresh', () => { return div; } - beforeEach(() => { sandbox = sinon.sandbox; mockA4a = { @@ -60,16 +56,25 @@ describe('refresh', () => { }); describe('refresh-manager', () => { - it('should get null refreshInterval', () => { mockA4a.element.removeAttribute(DATA_ATTR_NAME); - expect(getPublisherSpecifiedRefreshInterval( - mockA4a.element, window, 'doubleclick')).to.be.null; + expect( + getPublisherSpecifiedRefreshInterval( + mockA4a.element, + window, + 'doubleclick' + ) + ).to.be.null; }); it('should get refreshInterval from slot', () => { - expect(getPublisherSpecifiedRefreshInterval( - mockA4a.element, window, 'doubleclick')).to.equal(35000); + expect( + getPublisherSpecifiedRefreshInterval( + mockA4a.element, + window, + 'doubleclick' + ) + ).to.equal(35000); }); it('should get refreshInterval from meta tag', () => { @@ -78,17 +83,28 @@ describe('refresh', () => { meta.setAttribute('name', METATAG_NAME); meta.setAttribute('content', 'doubleclick=40'); window.document.head.appendChild(meta); - expect(getPublisherSpecifiedRefreshInterval( - mockA4a.element, window, 'doubleclick')).to.equal(40000); + expect( + getPublisherSpecifiedRefreshInterval( + mockA4a.element, + window, + 'doubleclick' + ) + ).to.equal(40000); }); it('should call convertConfiguration_ and set proper units', () => { const getConfigurationSpy = sandbox.spy( - RefreshManager.prototype, 'convertAndSanitizeConfiguration_'); - const refreshManager = new RefreshManager(mockA4a, { - visiblePercentageMin: 50, - continuousTimeMin: 1, - }, 30000); + RefreshManager.prototype, + 'convertAndSanitizeConfiguration_' + ); + const refreshManager = new RefreshManager( + mockA4a, + { + visiblePercentageMin: 50, + continuousTimeMin: 1, + }, + 30000 + ); expect(getConfigurationSpy).to.be.calledOnce; expect(refreshManager.config_).to.not.be.null; expect(refreshManager.config_.visiblePercentageMin).to.equal(0.5); @@ -98,12 +114,13 @@ describe('refresh', () => { describe('#ioCallback_', () => { let refreshManager; beforeEach( - () => refreshManager = new RefreshManager(mockA4a, config, 30000)); + () => (refreshManager = new RefreshManager(mockA4a, config, 30000)) + ); it('should stay in INITIAL state', () => { const ioEntry = { target: { - getAttribute: name => name == DATA_MANAGER_ID_NAME ? '0' : null, + getAttribute: name => (name == DATA_MANAGER_ID_NAME ? '0' : null), }, intersectionRatio: refreshManager.config_.visiblePercentageMin, }; @@ -158,15 +175,16 @@ describe('refresh', () => { }; refreshManager.refreshInterval_ = 0; refreshManager.initiateRefreshCycle(); - return Services.timerFor(window).promise(500).then(() => { - expect(refreshSpy).to.be.calledOnce; - window.document.body.removeChild(mockA4a.element); - }); + return Services.timerFor(window) + .promise(500) + .then(() => { + expect(refreshSpy).to.be.calledOnce; + window.document.body.removeChild(mockA4a.element); + }); }); }); describe('RefreshIntersectionObserverWrapper', () => { - let callback; let callbackPromise; let getRect; @@ -191,9 +209,13 @@ describe('refresh', () => { }); sandbox.stub(Services, 'ampdoc').callsFake(() => { return { - getRootNode: () => {return window.document;}, + getRootNode: () => { + return window.document; + }, win: window, - isSingleDoc: () => {return true;}, + isSingleDoc: () => { + return true; + }, }; }); @@ -209,7 +231,10 @@ describe('refresh', () => { }); callback = entries => resolver(entries); observerWrapper = new RefreshIntersectionObserverWrapper( - callback, mockA4a, {threshold: 0.5}); + callback, + mockA4a, + {threshold: 0.5} + ); }); it('should invoke callback with intersection ratio 1', () => { @@ -257,9 +282,11 @@ describe('refresh', () => { }), }; observerWrapper.observe(mockA4a.element); - return Services.timerFor(window).promise(500).then(() => { - expect(callbackSpy).to.not.be.called; - }); + return Services.timerFor(window) + .promise(500) + .then(() => { + expect(callbackSpy).to.not.be.called; + }); }); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-signature-verifier.js b/extensions/amp-a4a/0.1/test/test-signature-verifier.js index 19b44cb64f5a..82961149abbe 100644 --- a/extensions/amp-a4a/0.1/test/test-signature-verifier.js +++ b/extensions/amp-a4a/0.1/test/test-signature-verifier.js @@ -16,7 +16,6 @@ // TODO(@jridgewell, #11081): fix linter to allow fixing weird indentation - import {SignatureVerifier, VerificationStatus} from '../signature-verifier'; import {base64EncodeFromBytes} from '../../../../src/utils/base64'; import {dev, user} from '../../../../src/log'; @@ -25,7 +24,6 @@ import {utf8Encode} from '../../../../src/utils/bytes'; const networkFailure = {throws: new TypeError('Failed to fetch')}; describes.fakeWin('SignatureVerifier', {amp: true}, env => { - let mockDevError; beforeEach(() => { mockDevError = env.sandbox.mock(dev()).expects('error'); @@ -37,8 +35,9 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { /** @param {string} signingServiceName */ const expectSigningServiceError = signingServiceName => { - mockDevError.withExactArgs('AMP-A4A', sinon.match(signingServiceName)) - .once(); + mockDevError + .withExactArgs('AMP-A4A', sinon.match(signingServiceName)) + .once(); }; const creative1 = utf8Encode('Hello world!'); @@ -51,12 +50,16 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { const verifier = new SignatureVerifier(env.win); verifier.loadKeyset('service-1'); return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', creative1, new Uint8Array(256)) - .then(status => { - expect(status).to.equal(VerificationStatus.CRYPTO_UNAVAILABLE); - expect(env.fetchMock.called()).to.be.false; - }); + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + creative1, + new Uint8Array(256) + ) + .then(status => { + expect(status).to.equal(VerificationStatus.CRYPTO_UNAVAILABLE); + expect(env.fetchMock.called()).to.be.false; + }); }); const crypto = window.crypto || window.msCrypto; @@ -76,13 +79,15 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { this.kid = kid; /** @const {!Promise<{privateKey: !webCrypto.CryptoKey, publicKey: !webCrypto.CryptoKey}>} */ this.keysPromise = subtle.generateKey( - { - name: 'RSASSA-PKCS1-v1_5', - modulusLength: 2048, - publicExponent: Uint8Array.of(1, 0, 1), - hash: {name: 'SHA-256'}, - }, - true, ['sign', 'verify']); + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 2048, + publicExponent: Uint8Array.of(1, 0, 1), + hash: {name: 'SHA-256'}, + }, + true, + ['sign', 'verify'] + ); } /** @@ -91,20 +96,24 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { */ sign(data) { return this.keysPromise - .then(({privateKey}) => subtle.sign( - {name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'}}, - privateKey, data)) - .then(signature => new Uint8Array(signature)); + .then(({privateKey}) => + subtle.sign( + {name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'}}, + privateKey, + data + ) + ) + .then(signature => new Uint8Array(signature)); } /** @return {!Promise} */ jwk() { return this.keysPromise - .then(({publicKey}) => subtle.exportKey('jwk', publicKey)) - .then(jwk => { - jwk['kid'] = this.kid; - return jwk; - }); + .then(({publicKey}) => subtle.exportKey('jwk', publicKey)) + .then(jwk => { + jwk['kid'] = this.kid; + return jwk; + }); } }; @@ -115,8 +124,10 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { * @return {!Promise} */ const jwkSet = keys => - Promise.all(keys.map(key => key.jwk())) - .then(jwks => ({body: {'keys': jwks}, headers})); + Promise.all(keys.map(key => key.jwk())).then(jwks => ({ + body: {'keys': jwks}, + headers, + })); const key1 = new Keypair('key-1'); const key2 = new Keypair('key-2'); @@ -134,322 +145,456 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { expect(env.fetchMock.done()).to.be.true; }); - it('should verify a signature', - () => key1.sign(creative1).then(signature => { + it('should verify a signature', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.OK); + })); + + it('should verify multiple signatures with only one network request', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.OK); - })); - - it('should verify multiple signatures with only one network request', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.OK); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.OK); - }); - }))); - - it('should verify signatures from multiple signing services', - () => key1.sign(creative1).then( - signature1 => key2.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.OK); verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.OK); - env.fetchMock.getOnce( - 'https://signingservice2.net/keyset.json', - jwkSet([key2])); - verifier.loadKeyset('service-2'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-2', 'key-2', signature2, creative2)) - .to.eventually.equal(VerificationStatus.OK); - }); - }))); - - it('should verify signatures when different signing services share a kid', - () => key1.sign(creative1).then(signature1 => { - const key1FromService2 = new Keypair('key-1'); - return key1FromService2.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.OK); - env.fetchMock.getOnce( - 'https://signingservice2.net/keyset.json', - jwkSet([key1FromService2])); - verifier.loadKeyset('service-2'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-2', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.OK); - }); - }); - })); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.OK); + }); + }) + )); - it('should verify a signature from a newly added key', - () => key2.sign(creative1).then(signature => { + it('should verify signatures from multiple signing services', () => + key1.sign(creative1).then(signature1 => + key2.sign(creative2).then(signature2 => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', () => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json?kid=key-2', - jwkSet([key2])); - return jwkSet([key1]); - }); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-2', signature, creative1)) - .to.eventually.equal(VerificationStatus.OK); - })); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.OK); + env.fetchMock.getOnce( + 'https://signingservice2.net/keyset.json', + jwkSet([key2]) + ); + verifier.loadKeyset('service-2'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-2', + 'key-2', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.OK); + }); + }) + )); - it('should return ERROR_KEY_NOT_FOUND for a nonexistent kid', - () => key1.sign(creative1).then(signature => { + it('should verify signatures when different signing services share a kid', () => + key1.sign(creative1).then(signature1 => { + const key1FromService2 = new Keypair('key-1'); + return key1FromService2.sign(creative2).then(signature2 => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', () => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json?kid=key-1', - jwkSet([])); - return jwkSet([]); - }); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.ERROR_KEY_NOT_FOUND); - })); - - it('should not make more network requests retrying a nonexistent kid', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.OK); env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', () => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json?kid=key-1', - jwkSet([])); - return jwkSet([]); - }); - verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal( - VerificationStatus.ERROR_KEY_NOT_FOUND); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal( - VerificationStatus.ERROR_KEY_NOT_FOUND); - }); - }))); - - it('should return ERROR_SIGNATURE_MISMATCH for a wrong signature', - () => key2.sign(creative1).then(signature => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.ERROR_SIGNATURE_MISMATCH); - })); + 'https://signingservice2.net/keyset.json', + jwkSet([key1FromService2]) + ); + verifier.loadKeyset('service-2'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-2', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.OK); + }); + }); + })); - it('should return UNVERIFIED and report on Web Cryptography error', - () => key1.sign(creative1).then(signature => { + it('should verify a signature from a newly added key', () => + key2.sign(creative1).then(signature => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', () => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - const errorMessage = 'Web Cryptography failed'; - env.stubService('crypto', 'verifyPkcs') - .returns(Promise.reject(new Error(errorMessage))); - mockDevError.withExactArgs('AMP-A4A', sinon.match(errorMessage)) - .once(); + 'https://signingservice1.net/keyset.json?kid=key-2', + jwkSet([key2]) + ); + return jwkSet([key1]); + }); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-2', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.OK); + })); + + it('should return ERROR_KEY_NOT_FOUND for a nonexistent kid', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', () => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json?kid=key-1', + jwkSet([]) + ); + return jwkSet([]); + }); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.ERROR_KEY_NOT_FOUND); + })); + + it('should not make more network requests retrying a nonexistent kid', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + () => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json?kid=key-1', + jwkSet([]) + ); + return jwkSet([]); + } + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - })); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.ERROR_KEY_NOT_FOUND); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.ERROR_KEY_NOT_FOUND); + }); + }) + )); - it('should return UNVERIFIED on network connectivity error', - () => key1.sign(creative1).then(signature => { + it('should return ERROR_SIGNATURE_MISMATCH for a wrong signature', () => + key2.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.ERROR_SIGNATURE_MISMATCH); + })); + + it('should return UNVERIFIED and report on Web Cryptography error', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + const errorMessage = 'Web Cryptography failed'; + env + .stubService('crypto', 'verifyPkcs') + .returns(Promise.reject(new Error(errorMessage))); + mockDevError.withExactArgs('AMP-A4A', sinon.match(errorMessage)).once(); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + })); + + it('should return UNVERIFIED on network connectivity error', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + networkFailure + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + })); + + it('should not retry for same service on network connectivity error', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', networkFailure); + 'https://signingservice1.net/keyset.json', + networkFailure + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - })); - - it('should not retry for same service on network connectivity error', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', networkFailure); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.UNVERIFIED); verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.UNVERIFIED); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); - }))); - - it('should return UNVERIFIED, report, and not retry on malformed JSON', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', - {body: '{"keys":', headers}); - expectSigningServiceError('service-1'); - verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.UNVERIFIED); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); - }))); - - it('should return UNVERIFIED, report, and not retry on non-JWK Set JSON', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', - {body: '{"foo":"bar"}', headers}); - expectSigningServiceError('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); + }) + )); + + it('should return UNVERIFIED, report, and not retry on malformed JSON', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', { + body: '{"keys":', + headers, + }); + expectSigningServiceError('service-1'); + verifier.loadKeyset('service-1'); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.UNVERIFIED); verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.UNVERIFIED); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); - }))); - - it('should report on extraneous malformed data', - () => key1.sign(creative1).then(signature => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', - jwkSet([key1]).then(keyset => { - keyset.body['keys'].push({'foo': 'bar'}); - return keyset; - })); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); + }) + )); + + it('should return UNVERIFIED, report, and not retry on non-JWK Set JSON', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', { + body: '{"foo":"bar"}', + headers, + }); expectSigningServiceError('service-1'); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.OK); - })); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.UNVERIFIED); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); + }) + )); - it('should return UNVERIFIED, report, and not retry on malformed key', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', - {body: {'keys': [{'kid': 'key-1', 'foo': 'bar'}]}, headers}); - expectSigningServiceError('service-1'); + it('should report on extraneous malformed data', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]).then(keyset => { + keyset.body['keys'].push({'foo': 'bar'}); + return keyset; + }) + ); + expectSigningServiceError('service-1'); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.OK); + })); + + it('should return UNVERIFIED, report, and not retry on malformed key', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', { + body: {'keys': [{'kid': 'key-1', 'foo': 'bar'}]}, + headers, + }); + expectSigningServiceError('service-1'); + verifier.loadKeyset('service-1'); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.UNVERIFIED); verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.UNVERIFIED); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); - }))); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); + }) + )); describe('#verify', () => { - it('should verify a signature header', - () => key1.sign(creative1).then(signature => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return expect( - verifier.verify( - creative1, - new Headers({ - 'AMP-Fast-Fetch-Signature': - `service-1:key-1:${base64EncodeFromBytes(signature)}`, - }))) - .to.eventually.equal(VerificationStatus.OK); - })); + it('should verify a signature header', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verify( + creative1, + new Headers({ + 'AMP-Fast-Fetch-Signature': `service-1:key-1:${base64EncodeFromBytes( + signature + )}`, + }) + ) + ).to.eventually.equal(VerificationStatus.OK); + })); it('should return UNVERIFIED on no header', () => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); verifier.loadKeyset('service-1'); - return expect(verifier.verify(creative1, new Headers())) - .to.eventually.equal(VerificationStatus.UNVERIFIED); + return expect( + verifier.verify(creative1, new Headers()) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); }); - it('should return UNVERIFIED on no header when crypto unavailable', - () => { - env.sandbox.stub(env.win, 'crypto').callsFake(undefined); - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return expect(verifier.verify(creative1, new Headers())) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); + it('should return UNVERIFIED on no header when crypto unavailable', () => { + env.sandbox.stub(env.win, 'crypto').callsFake(undefined); + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verify(creative1, new Headers()) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); it('should return ERROR_SIGNATURE_MISMATCH on malformed header', () => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); env.sandbox.stub(user(), 'error'); verifier.loadKeyset('service-1'); return expect( - verifier.verify( - creative1, - new Headers({ - 'AMP-Fast-Fetch-Signature': 'service-1:key-1:Invalid base64!', - }))) - .to.eventually.equal(VerificationStatus.ERROR_SIGNATURE_MISMATCH); + verifier.verify( + creative1, + new Headers({ + 'AMP-Fast-Fetch-Signature': 'service-1:key-1:Invalid base64!', + }) + ) + ).to.eventually.equal(VerificationStatus.ERROR_SIGNATURE_MISMATCH); }); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-template-renderer.js b/extensions/amp-a4a/0.1/test/test-template-renderer.js index 619d81034651..aaf99812b350 100644 --- a/extensions/amp-a4a/0.1/test/test-template-renderer.js +++ b/extensions/amp-a4a/0.1/test/test-template-renderer.js @@ -32,7 +32,6 @@ const realWinConfig = { }; describes.realWin('TemplateRenderer', realWinConfig, env => { - const templateUrl = 'https://adnetwork.com/amp-template.html'; const headers = { get: name => { @@ -61,10 +60,16 @@ describes.realWin('TemplateRenderer', realWinConfig, env => { }); containerElement.renderStarted = () => {}; containerElement.getPageLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.getLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.getIntersectionChangeEntry = () => ({}); containerElement.isInViewport = () => true; @@ -84,12 +89,17 @@ describes.realWin('TemplateRenderer', realWinConfig, env => { return Promise.resolve(data.adTemplate); }); - validatorPromise = validator.validate(context, - utf8Encode(JSON.stringify({ + validatorPromise = validator.validate( + context, + utf8Encode( + JSON.stringify({ templateUrl, data: {url: 'https://buy.com/buy-1'}, analytics: {foo: 'bar'}, - })), headers); + }) + ), + headers + ); }); afterEach(() => { @@ -100,86 +110,89 @@ describes.realWin('TemplateRenderer', realWinConfig, env => { it('should append iframe child with correct template values', () => { env.win.AMP.registerTemplate('amp-mustache', AmpMustache); return validatorPromise.then(validatorOutput => { - // Sanity check. This behavior is tested in test-template-validator.js. expect(validatorOutput).to.be.ok; expect(validatorOutput.type).to.equal(ValidatorResult.AMP); const {creativeData} = validatorOutput; expect(creativeData).to.be.ok; - return renderer.render(context, containerElement, creativeData) - .then(() => { - const iframe = containerElement.querySelector('iframe'); - expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.innerHTML.trim()).to - .equal('
    '); - }); + return renderer + .render(context, containerElement, creativeData) + .then(() => { + const iframe = containerElement.querySelector('iframe'); + expect(iframe).to.be.ok; + expect(iframe.contentWindow.document.body.innerHTML.trim()).to.equal( + '
    \n

    ipsum lorem

    \n Click for ad!' + + '\n
    ' + ); + }); }); }); it('should set correct attributes on the iframe', () => { env.win.AMP.registerTemplate('amp-mustache', AmpMustache); return validatorPromise.then(validatorOutput => { - // Sanity check. This behavior is tested in test-template-validator.js. expect(validatorOutput).to.be.ok; expect(validatorOutput.type).to.equal(ValidatorResult.AMP); const {creativeData} = validatorOutput; expect(creativeData).to.be.ok; - return renderer.render(context, containerElement, creativeData) - .then(() => { - const iframe = containerElement.querySelector('iframe'); - expect(iframe).to.be.ok; - expect(iframe.getAttribute('width')).to.equal('320'); - expect(iframe.getAttribute('height')).to.equal('50'); - expect(iframe.getAttribute('frameborder')).to.equal('0'); - expect(iframe.getAttribute('allowfullscreen')).to.equal(''); - expect(iframe.getAttribute('allowtransparency')).to.equal(''); - expect(iframe.getAttribute('scrolling')).to.equal('no'); - }); + return renderer + .render(context, containerElement, creativeData) + .then(() => { + const iframe = containerElement.querySelector('iframe'); + expect(iframe).to.be.ok; + expect(iframe.getAttribute('width')).to.equal('320'); + expect(iframe.getAttribute('height')).to.equal('50'); + expect(iframe.getAttribute('frameborder')).to.equal('0'); + expect(iframe.getAttribute('allowfullscreen')).to.equal(''); + expect(iframe.getAttribute('allowtransparency')).to.equal(''); + expect(iframe.getAttribute('scrolling')).to.equal('no'); + }); }); }); it('should style body of iframe document to be visible', () => { env.win.AMP.registerTemplate('amp-mustache', AmpMustache); return validatorPromise.then(validatorOutput => { - // Sanity check. This behavior is tested in test-template-validator.js. expect(validatorOutput).to.be.ok; expect(validatorOutput.type).to.equal(ValidatorResult.AMP); const {creativeData} = validatorOutput; expect(creativeData).to.be.ok; - return renderer.render(context, containerElement, creativeData) - .then(() => { - const iframe = containerElement.querySelector('iframe'); - expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.style.visibility) - .to.equal('visible'); - }); + return renderer + .render(context, containerElement, creativeData) + .then(() => { + const iframe = containerElement.querySelector('iframe'); + expect(iframe).to.be.ok; + expect(iframe.contentWindow.document.body.style.visibility).to.equal( + 'visible' + ); + }); }); }); it('should insert analytics', () => { env.win.AMP.registerTemplate('amp-mustache', AmpMustache); const insertAnalyticsSpy = sandbox.spy( - getAmpAdTemplateHelper(env.win), 'insertAnalytics'); + getAmpAdTemplateHelper(env.win), + 'insertAnalytics' + ); return validatorPromise.then(validatorOutput => { - // Sanity check. This behavior is tested in test-template-validator.js. expect(validatorOutput).to.be.ok; expect(validatorOutput.type).to.equal(ValidatorResult.AMP); const {creativeData} = validatorOutput; expect(creativeData).to.be.ok; - return renderer.render(context, containerElement, creativeData) - .then(() => { - expect(insertAnalyticsSpy).to.be.calledOnce; - }); + return renderer + .render(context, containerElement, creativeData) + .then(() => { + expect(insertAnalyticsSpy).to.be.calledOnce; + }); }); }); }); - diff --git a/extensions/amp-a4a/0.1/test/test-template-validator.js b/extensions/amp-a4a/0.1/test/test-template-validator.js index 066cc80a515a..d38aba0d94b2 100644 --- a/extensions/amp-a4a/0.1/test/test-template-validator.js +++ b/extensions/amp-a4a/0.1/test/test-template-validator.js @@ -31,7 +31,6 @@ const realWinConfig = { }; describes.realWin('TemplateValidator', realWinConfig, env => { - const templateUrl = 'https://adnetwork.com/amp-template.html'; const headers = { get: name => { @@ -47,7 +46,6 @@ describes.realWin('TemplateValidator', realWinConfig, env => { }); describe('AMP Result', () => { - let sandbox; let validatorPromise; @@ -58,12 +56,17 @@ describes.realWin('TemplateValidator', realWinConfig, env => { return Promise.resolve(data.adTemplate); }); - validatorPromise = validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ + validatorPromise = validator.validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ templateUrl, data: {url: 'https://buy.com/buy-1'}, analytics: {foo: 'bar'}, - })), headers); + }) + ), + headers + ); }); afterEach(() => sandbox.restore()); @@ -76,28 +79,36 @@ describes.realWin('TemplateValidator', realWinConfig, env => { }); it('should have AMP validator result w/ deprecated header name', () => { - validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ - templateUrl, - data: {url: 'https://buy.com/buy-1'}, - analytics: {foo: 'bar'}, - })), { + validator + .validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ + templateUrl, + data: {url: 'https://buy.com/buy-1'}, + analytics: {foo: 'bar'}, + }) + ), + { get: name => { if (name == DEPRECATED_AMP_TEMPLATED_CREATIVE_HEADER_NAME) { return 'amp-mustache'; } }, - }).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.AMP); - }); + } + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.AMP); + }); }); it('should have TEMPLATE ad response type', () => { return validatorPromise.then(validatorOutput => { expect(validatorOutput).to.be.ok; expect(validatorOutput.adResponseType).to.equal( - AdResponseType.TEMPLATE); + AdResponseType.TEMPLATE + ); }); }); @@ -106,79 +117,99 @@ describes.realWin('TemplateValidator', realWinConfig, env => { expect(validatorOutput).to.be.ok; expect(validatorOutput.creativeData).to.be.ok; const {creativeMetadata} = validatorOutput.creativeData; - expect(creativeMetadata.minifiedCreative) - .to.equal(data.minifiedTemplateCreative); + expect(creativeMetadata.minifiedCreative).to.equal( + data.minifiedTemplateCreative + ); }); }); - it('should have amp-analytics and mustache in customElementExtensions', - () => { - return validatorPromise.then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.creativeData).to.be.ok; - const {creativeMetadata} = validatorOutput.creativeData; - expect(creativeMetadata.customElementExtensions) - .to.deep.equal(['amp-analytics', 'amp-mustache']); - }); - }); + it('should have amp-analytics and mustache in customElementExtensions', () => { + return validatorPromise.then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.creativeData).to.be.ok; + const {creativeMetadata} = validatorOutput.creativeData; + expect(creativeMetadata.customElementExtensions).to.deep.equal([ + 'amp-analytics', + 'amp-mustache', + ]); + }); + }); }); describe('Non-AMP Result', () => { it('should have NON_AMP validator result due to lack of headers', () => { - return validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ - templateUrl, - data: {url: 'https://buy.com/buy-1'}, - analytics: {foo: 'bar'}, - }))).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); - }); + return validator + .validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ + templateUrl, + data: {url: 'https://buy.com/buy-1'}, + analytics: {foo: 'bar'}, + }) + ) + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); + }); }); - it('should have NON_AMP validator result due to lack of mustache header', - () => { - return validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ - templateUrl, - data: {url: 'https://buy.com/buy-1'}, - analytics: {foo: 'bar'}, - })), - { - get: () => null, - }).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); - }); + it('should have NON_AMP validator result due to lack of mustache header', () => { + return validator + .validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ + templateUrl, + data: {url: 'https://buy.com/buy-1'}, + analytics: {foo: 'bar'}, + }) + ), + { + get: () => null, + } + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); }); + }); it('should have TEMPLATE ad response type', () => { - return validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ - templateUrl, - data: {url: 'https://buy.com/buy-1'}, - analytics: {foo: 'bar'}, - }))).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.adResponseType) - .to.equal(AdResponseType.TEMPLATE); - }); + return validator + .validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ + templateUrl, + data: {url: 'https://buy.com/buy-1'}, + analytics: {foo: 'bar'}, + }) + ) + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.adResponseType).to.equal( + AdResponseType.TEMPLATE + ); + }); }); it('should have the response body as the creative in creativeData', () => { - return validator.validate({win: env.win}, - utf8Encode(JSON.stringify({templateUrl})), - { - get: () => null, - }).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.creativeData).to.be.ok; - const {creativeData} = validatorOutput; - expect(creativeData).to.be.ok; - expect(creativeData.creative).to.deep.equal( - '{"templateUrl":"https://adnetwork.com/amp-template.html"}'); - }); + return validator + .validate({win: env.win}, utf8Encode(JSON.stringify({templateUrl})), { + get: () => null, + }) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.creativeData).to.be.ok; + const {creativeData} = validatorOutput; + expect(creativeData).to.be.ok; + expect(creativeData.creative).to.deep.equal( + '{"templateUrl":"https://adnetwork.com/amp-template.html"}' + ); + }); }); }); }); - diff --git a/extensions/amp-a4a/0.1/test/utils.js b/extensions/amp-a4a/0.1/test/utils.js index 0c8e61d80d17..0060b8842df6 100644 --- a/extensions/amp-a4a/0.1/test/utils.js +++ b/extensions/amp-a4a/0.1/test/utils.js @@ -18,8 +18,10 @@ import {AmpA4A} from '../amp-a4a'; import {dict} from '../../../../src/utils/object'; /** @type {string} @private */ -export const TEST_URL = 'http://iframe.localhost:' + location.port + - '/test/fixtures/served/iframe.html?args'; +export const TEST_URL = + 'http://iframe.localhost:' + + location.port + + '/test/fixtures/served/iframe.html?args'; export class MockA4AImpl extends AmpA4A { getAdUrl() { diff --git a/extensions/amp-access-laterpay/0.1/amp-access-laterpay.js b/extensions/amp-access-laterpay/0.1/amp-access-laterpay.js index 40f42e744370..3944b8dd4f78 100644 --- a/extensions/amp-access-laterpay/0.1/amp-access-laterpay.js +++ b/extensions/amp-access-laterpay/0.1/amp-access-laterpay.js @@ -19,18 +19,19 @@ import {Services} from '../../../src/services'; AMP.extension('amp-access-laterpay', '0.1', function(AMP) { AMP.registerServiceForDoc( - 'laterpay', - /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ - function(ampdoc) { - const element = ampdoc.getHeadNode(); - return Services.accessServiceForDoc(element).then(accessService => { - const source = accessService.getVendorSource('laterpay'); - const vendor = new LaterpayVendor(accessService, source); - const adapter = /** @type { + 'laterpay', + /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ + function(ampdoc) { + const element = ampdoc.getHeadNode(); + return Services.accessServiceForDoc(element).then(accessService => { + const source = accessService.getVendorSource('laterpay'); + const vendor = new LaterpayVendor(accessService, source); + const adapter = /** @type { !../../amp-access/0.1/amp-access-vendor.AccessVendorAdapter } */ (source.getAdapter()); - adapter.registerVendor(vendor); - return vendor; - }); + adapter.registerVendor(vendor); + return vendor; }); + } + ); }); diff --git a/extensions/amp-access-laterpay/0.1/laterpay-impl.js b/extensions/amp-access-laterpay/0.1/laterpay-impl.js index 5811007f4313..2af62e28d22b 100644 --- a/extensions/amp-access-laterpay/0.1/laterpay-impl.js +++ b/extensions/amp-access-laterpay/0.1/laterpay-impl.js @@ -38,10 +38,11 @@ const CONFIG_URLS = { const DEFAULT_REGION = 'eu'; -const CONFIG_BASE_PATH = '/api/v1/public/amp?' + - 'article_url=CANONICAL_URL' + - '&_reader_id=READER_ID' + - '&return_url=RETURN_URL'; +const CONFIG_BASE_PATH = + '/api/v1/public/amp?' + + 'article_url=CANONICAL_URL' + + '&_reader_id=READER_ID' + + '&return_url=RETURN_URL'; const AUTHORIZATION_TIMEOUT = 3000; const DEFAULT_MESSAGES = { @@ -91,12 +92,10 @@ let PurchaseOptionDef; */ let PurchaseConfigDef; - /** * @implements {../../amp-access/0.1/access-vendor.AccessVendor} */ export class LaterpayVendor { - /** * @param {!../../amp-access/0.1/amp-access.AccessService} accessService * @param {!../../amp-access/0.1/amp-access-source.AccessSource} accessSource @@ -142,8 +141,11 @@ export class LaterpayVendor { this.currentLocale_ = this.laterpayConfig_['locale'] || 'en'; /** @private {!JsonObject} */ - this.i18n_ = /** @type {!JsonObject} */ (Object.assign(dict(), - DEFAULT_MESSAGES, this.laterpayConfig_['localeMessages'] || dict())); + this.i18n_ = /** @type {!JsonObject} */ (Object.assign( + dict(), + DEFAULT_MESSAGES, + this.laterpayConfig_['localeMessages'] || dict() + )); /** @private {string} */ this.purchaseConfigBaseUrl_ = this.getConfigUrl_() + CONFIG_BASE_PATH; @@ -188,36 +190,41 @@ export class LaterpayVendor { * @return {!Promise} */ authorize() { - return this.getPurchaseConfig_() - .then(response => { - if (response.status === 204) { - throw user() - .createError('No merchant domains have been matched for this ' + - 'article, or no paid content configurations are setup.'); - } - - if (this.laterpayConfig_['scrollToTopAfterAuth']) { - this.vsync_.mutate(() => this.viewport_.setScrollTop(0)); - } - this.emptyContainer_(); - return {access: response.access}; - }, err => { - if (!err || !err.response) { - throw err; - } - const {response} = err; - if (response.status !== 402) { - throw err; - } - return response.json().catch(() => undefined).then(responseJson => { + return this.getPurchaseConfig_().then( + response => { + if (response.status === 204) { + throw user().createError( + 'No merchant domains have been matched for this ' + + 'article, or no paid content configurations are setup.' + ); + } + + if (this.laterpayConfig_['scrollToTopAfterAuth']) { + this.vsync_.mutate(() => this.viewport_.setScrollTop(0)); + } + this.emptyContainer_(); + return {access: response.access}; + }, + err => { + if (!err || !err.response) { + throw err; + } + const {response} = err; + if (response.status !== 402) { + throw err; + } + return response + .json() + .catch(() => undefined) + .then(responseJson => { this.purchaseConfig_ = responseJson; // empty before rendering, in case authorization is being called // again with the same state - this.emptyContainer_() - .then(this.renderPurchaseOverlay_.bind(this)); + this.emptyContainer_().then(this.renderPurchaseOverlay_.bind(this)); return {access: false}; }); - }); + } + ); } /** @@ -225,20 +232,29 @@ export class LaterpayVendor { * @private */ getPurchaseConfig_() { - const url = this.purchaseConfigBaseUrl_ + - '&article_title=' + encodeURIComponent(this.getArticleTitle_()); + const url = + this.purchaseConfigBaseUrl_ + + '&article_title=' + + encodeURIComponent(this.getArticleTitle_()); const urlPromise = this.accessSource_.buildUrl( - url, /* useAuthData */ false); - return urlPromise.then(url => { - return this.accessSource_.getLoginUrl(url); - }).then(url => { - dev().info(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( - AUTHORIZATION_TIMEOUT, - this.xhr_.fetchJson(url, { - credentials: 'include', - })).then(res => res.json()); - }); + url, + /* useAuthData */ false + ); + return urlPromise + .then(url => { + return this.accessSource_.getLoginUrl(url); + }) + .then(url => { + dev().info(TAG, 'Authorization URL: ', url); + return this.timer_ + .timeoutPromise( + AUTHORIZATION_TIMEOUT, + this.xhr_.fetchJson(url, { + credentials: 'include', + }) + ) + .then(res => res.json()); + }); } /** @@ -255,11 +271,14 @@ export class LaterpayVendor { * @private */ getArticleTitle_() { - const title = this.ampdoc.getRootNode().querySelector( - this.laterpayConfig_['articleTitleSelector']); + const title = this.ampdoc + .getRootNode() + .querySelector(this.laterpayConfig_['articleTitleSelector']); userAssert( - title, 'No article title element found with selector %s', - this.laterpayConfig_['articleTitleSelector']); + title, + 'No article title element found with selector %s', + this.laterpayConfig_['articleTitleSelector'] + ); return title.textContent.trim(); } @@ -271,8 +290,8 @@ export class LaterpayVendor { const id = TAG + '-dialog'; const dialogContainer = this.ampdoc.getElementById(id); return user().assertElement( - dialogContainer, - 'No element found with id ' + id + dialogContainer, + 'No element found with id ' + id ); } @@ -316,12 +335,14 @@ export class LaterpayVendor { } this.renderTextBlock_('header'); const listContainer = this.createElement_('ul'); - this.purchaseConfig_['premiumcontent']['title'] = - this.i18n_['premiumContentTitle']; - this.purchaseConfig_['premiumcontent']['description'] = - this.getArticleTitle_(); + this.purchaseConfig_['premiumcontent']['title'] = this.i18n_[ + 'premiumContentTitle' + ]; + this.purchaseConfig_['premiumcontent'][ + 'description' + ] = this.getArticleTitle_(); listContainer.appendChild( - this.createPurchaseOption_(this.purchaseConfig_['premiumcontent']) + this.createPurchaseOption_(this.purchaseConfig_['premiumcontent']) ); this.purchaseConfig_['timepasses'].forEach(timepass => { listContainer.appendChild(this.createPurchaseOption_(timepass)); @@ -341,13 +362,15 @@ export class LaterpayVendor { this.innerContainer_.appendChild(listContainer); this.innerContainer_.appendChild(purchaseButton); this.innerContainer_.appendChild( - this.createAlreadyPurchasedLink_(this.purchaseConfig_['apl'])); + this.createAlreadyPurchasedLink_(this.purchaseConfig_['apl']) + ); this.renderTextBlock_('footer'); dialogContainer.appendChild(this.innerContainer_); dialogContainer.appendChild(this.createLaterpayBadge_()); this.containerEmpty_ = false; this.preselectFirstOption_( - dev().assertElement(listContainer.firstElementChild)); + dev().assertElement(listContainer.firstElementChild) + ); } /** @@ -426,15 +449,14 @@ export class LaterpayVendor { radio.type = 'radio'; radio.id = option['title']; radio.value = option['purchase_url']; - const purchaseType = option['purchase_type'] === 'ppu' ? - 'payLater' : - 'payNow'; + const purchaseType = + option['purchase_type'] === 'ppu' ? 'payLater' : 'payNow'; const purchaseActionLabel = this.i18n_[purchaseType + 'Button']; radio.setAttribute('data-purchase-action-label', purchaseActionLabel); radio.setAttribute('data-purchase-type', purchaseType); - this.purchaseOptionListeners_.push(listen( - radio, 'change', this.handlePurchaseOptionSelection_.bind(this) - )); + this.purchaseOptionListeners_.push( + listen(radio, 'change', this.handlePurchaseOptionSelection_.bind(this)) + ); return radio; } @@ -465,7 +487,7 @@ export class LaterpayVendor { * @private */ formatPrice_(priceValue) { - const value = (priceValue / 100); + const value = priceValue / 100; const props = { style: 'decimal', minimumFractionDigits: 0, @@ -476,7 +498,7 @@ export class LaterpayVendor { /** * @param {string} href * @return {!Element} - */ + */ createAlreadyPurchasedLink_(href) { const p = this.createElement_('p'); p.className = TAG + '-already-purchased-link-container'; @@ -507,8 +529,10 @@ export class LaterpayVendor { const selectedOptionClassname = TAG + '-selected'; const prevPurchaseOption = this.selectedPurchaseOption_; const purchaseActionLabel = target.dataset['purchaseActionLabel']; - if (prevPurchaseOption && - prevPurchaseOption.classList.contains(selectedOptionClassname)) { + if ( + prevPurchaseOption && + prevPurchaseOption.classList.contains(selectedOptionClassname) + ) { prevPurchaseOption.classList.remove(selectedOptionClassname); } this.selectedPurchaseOption_ = target; @@ -525,7 +549,9 @@ export class LaterpayVendor { handlePurchase_(ev, purchaseUrl, purchaseType) { ev.preventDefault(); const urlPromise = this.accessSource_.buildUrl( - purchaseUrl, /* useAuthData */ false); + purchaseUrl, + /* useAuthData */ false + ); return urlPromise.then(url => { dev().fine(TAG, 'Authorization URL: ', url); this.accessSource_.loginWithUrl(url, purchaseType); diff --git a/extensions/amp-access-laterpay/0.1/test/test-amp-access-laterpay.js b/extensions/amp-access-laterpay/0.1/test/test-amp-access-laterpay.js index ceb2259139de..a0df170c8547 100644 --- a/extensions/amp-access-laterpay/0.1/test/test-amp-access-laterpay.js +++ b/extensions/amp-access-laterpay/0.1/test/test-amp-access-laterpay.js @@ -18,276 +18,290 @@ import {LaterpayVendor} from '../laterpay-impl'; const TAG = 'amp-access-laterpay'; -describes.fakeWin('LaterpayVendor', { - amp: true, - location: 'https://pub.com/doc1', -}, env => { - let win, document, ampdoc; - let accessSource; - let accessService; - let accessSourceMock; - let xhrMock; - let articleTitle; - let laterpayConfig; - let vendor; +describes.fakeWin( + 'LaterpayVendor', + { + amp: true, + location: 'https://pub.com/doc1', + }, + env => { + let win, document, ampdoc; + let accessSource; + let accessService; + let accessSourceMock; + let xhrMock; + let articleTitle; + let laterpayConfig; + let vendor; - beforeEach(() => { - win = env.win; - ampdoc = env.ampdoc; - document = win.document; - - laterpayConfig = { - articleTitleSelector: '#laterpay-test-title', - region: 'us', - }; + beforeEach(() => { + win = env.win; + ampdoc = env.ampdoc; + document = win.document; - accessSource = { - getAdapterConfig: () => { return laterpayConfig; }, - buildUrl: () => {}, - loginWithUrl: () => {}, - getLoginUrl: () => {}, - }; + laterpayConfig = { + articleTitleSelector: '#laterpay-test-title', + region: 'us', + }; - accessService = { - ampdoc, - getSource: () => accessSource, - }; + accessSource = { + getAdapterConfig: () => { + return laterpayConfig; + }, + buildUrl: () => {}, + loginWithUrl: () => {}, + getLoginUrl: () => {}, + }; - accessSourceMock = sandbox.mock(accessSource); + accessService = { + ampdoc, + getSource: () => accessSource, + }; - articleTitle = document.createElement('h1'); - articleTitle.id = 'laterpay-test-title'; - articleTitle.textContent = 'test title'; - document.body.appendChild(articleTitle); + accessSourceMock = sandbox.mock(accessSource); - vendor = new LaterpayVendor(accessService, accessSource); - xhrMock = sandbox.mock(vendor.xhr_); - }); + articleTitle = document.createElement('h1'); + articleTitle.id = 'laterpay-test-title'; + articleTitle.textContent = 'test title'; + document.body.appendChild(articleTitle); - afterEach(() => { - articleTitle.parentNode.removeChild(articleTitle); - accessSourceMock.verify(); - xhrMock.verify(); - }); + vendor = new LaterpayVendor(accessService, accessSource); + xhrMock = sandbox.mock(vendor.xhr_); + }); - describe('authorize', () => { - let emptyContainerStub; - beforeEach(() => { - emptyContainerStub = sandbox.stub(vendor, 'emptyContainer_'); - sandbox.stub(vendor, 'renderPurchaseOverlay_'); + afterEach(() => { + articleTitle.parentNode.removeChild(articleTitle); + accessSourceMock.verify(); + xhrMock.verify(); }); - it('uses a non default region', () => { - const buildUrl = accessSourceMock.expects('buildUrl') + describe('authorize', () => { + let emptyContainerStub; + beforeEach(() => { + emptyContainerStub = sandbox.stub(vendor, 'emptyContainer_'); + sandbox.stub(vendor, 'renderPurchaseOverlay_'); + }); + + it('uses a non default region', () => { + const buildUrl = accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(() => { - expect( + return vendor.authorize().then(() => { + expect( /connector\.uselaterpay\.com/g.test(buildUrl.firstCall.args[0]) - ).to.be.true; + ).to.be.true; + }); }); - }); - it('successful authorization', () => { - vendor.purchaseConfigBaseUrl_ = 'https://baseurl?param'; - accessSourceMock.expects('buildUrl') - .withExactArgs('https://baseurl?param&article_title=test%20title', false) + it('successful authorization', () => { + vendor.purchaseConfigBaseUrl_ = 'https://baseurl?param'; + accessSourceMock + .expects('buildUrl') + .withExactArgs( + 'https://baseurl?param&article_title=test%20title', + false + ) .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(resp => { - expect(resp.access).to.be.true; - expect(emptyContainerStub.called).to.be.true; + return vendor.authorize().then(resp => { + expect(resp.access).to.be.true; + expect(emptyContainerStub.called).to.be.true; + }); }); - }); - it('authorization fails due to lack of server config', () => { - accessSourceMock.expects('buildUrl') + it('authorization fails due to lack of server config', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) .returns(Promise.resolve({status: 204})) .once(); - return vendor.authorize().catch(err => { - expect(err.message).to.exist; + return vendor.authorize().catch(err => { + expect(err.message).to.exist; + }); }); - }); - it('authorization response from server fails', () => { - accessSourceMock.expects('buildUrl') + it('authorization response from server fails', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) - .returns(Promise.reject({ - response: { - status: 402, - json() { - return Promise.resolve({access: false}); + .returns( + Promise.reject({ + response: { + status: 402, + json() { + return Promise.resolve({access: false}); + }, }, - }, - })) + }) + ) .once(); - emptyContainerStub.returns(Promise.resolve()); - return vendor.authorize().then(err => { - expect(err.access).to.be.false; + emptyContainerStub.returns(Promise.resolve()); + return vendor.authorize().then(err => { + expect(err.access).to.be.false; + }); }); }); - }); - - describe('create purchase overlay', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - premiumcontent: { - price: {}, - }, - subscriptions: [ - {price: {}}, - ], - timepasses: [ - {price: {}}, - ], - }; - vendor.renderPurchaseOverlay_(); - }); + describe('create purchase overlay', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + premiumcontent: { + price: {}, + }, + subscriptions: [{price: {}}], + timepasses: [{price: {}}], + }; + vendor.renderPurchaseOverlay_(); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('renders list', () => { - expect(container.querySelector('ul')).to.not.be.null; - }); + it('renders list', () => { + expect(container.querySelector('ul')).to.not.be.null; + }); - it('renders 3 purchase options', () => { - expect(container.querySelector('ul').childNodes.length).to.equal(3); + it('renders 3 purchase options', () => { + expect(container.querySelector('ul').childNodes.length).to.equal(3); + }); }); - }); - - describe('purchase option selection', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - premiumcontent: { - price: {}, - }, - subscriptions: [ - {price: {}}, - ], - timepasses: [ - {price: {}}, - ], - }; - vendor.renderPurchaseOverlay_(); - const ev = new Event('change'); - container.querySelector('input').dispatchEvent(ev); - }); + describe('purchase option selection', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + premiumcontent: { + price: {}, + }, + subscriptions: [{price: {}}], + timepasses: [{price: {}}], + }; + vendor.renderPurchaseOverlay_(); + const ev = new Event('change'); + container.querySelector('input').dispatchEvent(ev); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('purchase option is selected', () => { - expect(vendor.selectedPurchaseOption_).to.not.be.null; - expect(vendor.selectedPurchaseOption_.classList - .contains(TAG + '-selected')).to.be.true; + it('purchase option is selected', () => { + expect(vendor.selectedPurchaseOption_).to.not.be.null; + expect( + vendor.selectedPurchaseOption_.classList.contains(TAG + '-selected') + ).to.be.true; + }); }); - }); - - describe('purchase', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - premiumcontent: { - price: {}, - }, - subscriptions: [ - {price: {}}, - ], - timepasses: [ - {price: {}}, - ], - apl: 'http://apllink', - }; - vendor.renderPurchaseOverlay_(); - }); + describe('purchase', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + premiumcontent: { + price: {}, + }, + subscriptions: [{price: {}}], + timepasses: [{price: {}}], + apl: 'http://apllink', + }; + vendor.renderPurchaseOverlay_(); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('sends request for purchase', done => { - const changeEv = new Event('change'); - container.querySelector('input').dispatchEvent(changeEv); - accessSourceMock.expects('buildUrl') + it('sends request for purchase', done => { + const changeEv = new Event('change'); + container.querySelector('input').dispatchEvent(changeEv); + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('loginWithUrl') - .once(); - const clickEv = new Event('click'); - container.querySelector('button').dispatchEvent(clickEv); - setTimeout(() => {done();}, 500); - }); + accessSourceMock.expects('loginWithUrl').once(); + const clickEv = new Event('click'); + container.querySelector('button').dispatchEvent(clickEv); + setTimeout(() => { + done(); + }, 500); + }); - it('sends request for already purchased', done => { - accessSourceMock.expects('buildUrl') + it('sends request for already purchased', done => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://apllink')) .once(); - accessSourceMock.expects('loginWithUrl') - .once(); - const clickEv = new Event('click'); - container + accessSourceMock.expects('loginWithUrl').once(); + const clickEv = new Event('click'); + container .querySelector('.' + TAG + '-already-purchased-link-container > a') .dispatchEvent(clickEv); - setTimeout(() => {done();}, 500); + setTimeout(() => { + done(); + }, 500); + }); }); - - - }); -}); + } +); diff --git a/extensions/amp-access-laterpay/0.2/amp-access-laterpay.js b/extensions/amp-access-laterpay/0.2/amp-access-laterpay.js index eebc46238de5..caa806d8b7c5 100644 --- a/extensions/amp-access-laterpay/0.2/amp-access-laterpay.js +++ b/extensions/amp-access-laterpay/0.2/amp-access-laterpay.js @@ -19,18 +19,19 @@ import {Services} from '../../../src/services'; AMP.extension('amp-access-laterpay', '0.2', function(AMP) { AMP.registerServiceForDoc( - 'laterpay', - /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ - function(ampdoc) { - const element = ampdoc.getHeadNode(); - return Services.accessServiceForDoc(element).then(accessService => { - const source = accessService.getVendorSource('laterpay'); - const vendor = new LaterpayVendor(accessService, source); - const adapter = /** @type { + 'laterpay', + /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ + function(ampdoc) { + const element = ampdoc.getHeadNode(); + return Services.accessServiceForDoc(element).then(accessService => { + const source = accessService.getVendorSource('laterpay'); + const vendor = new LaterpayVendor(accessService, source); + const adapter = /** @type { !../../amp-access/0.1/amp-access-vendor.AccessVendorAdapter } */ (source.getAdapter()); - adapter.registerVendor(vendor); - return vendor; - }); + adapter.registerVendor(vendor); + return vendor; }); + } + ); }); diff --git a/extensions/amp-access-laterpay/0.2/laterpay-impl.js b/extensions/amp-access-laterpay/0.2/laterpay-impl.js index e21038cc8b34..642c66774194 100644 --- a/extensions/amp-access-laterpay/0.2/laterpay-impl.js +++ b/extensions/amp-access-laterpay/0.2/laterpay-impl.js @@ -36,14 +36,16 @@ const CONFIG_URLS = { }, }; -const LATERPAY_BADGE_URL = 'https://blog.laterpay.net/laterpay-academy/what-is-laterpay'; +const LATERPAY_BADGE_URL = + 'https://blog.laterpay.net/laterpay-academy/what-is-laterpay'; const DEFAULT_REGION = 'eu'; -const CONFIG_BASE_PATH = '/api/v2/fetch/amp/?' + - 'article_url=CANONICAL_URL' + - '&_reader_id=READER_ID' + - '&return_url=RETURN_URL'; +const CONFIG_BASE_PATH = + '/api/v2/fetch/amp/?' + + 'article_url=CANONICAL_URL' + + '&_reader_id=READER_ID' + + '&return_url=RETURN_URL'; const AUTHORIZATION_TIMEOUT = 3000; const DEFAULT_MESSAGES = { @@ -116,12 +118,10 @@ let PurchaseConfigDef; */ let PurchaseOptionsDef; - /** * @implements {../../amp-access/0.1/access-vendor.AccessVendor} */ export class LaterpayVendor { - /** * @param {!../../amp-access/0.1/amp-access.AccessService} accessService * @param {!../../amp-access/0.1/amp-access-source.AccessSource} accessSource @@ -170,8 +170,11 @@ export class LaterpayVendor { this.currentLocale_ = this.laterpayConfig_['locale'] || 'en'; /** @private {!JsonObject} */ - this.i18n_ = /** @type {!JsonObject} */ (Object.assign(dict(), - DEFAULT_MESSAGES, this.laterpayConfig_['localeMessages'] || dict())); + this.i18n_ = /** @type {!JsonObject} */ (Object.assign( + dict(), + DEFAULT_MESSAGES, + this.laterpayConfig_['localeMessages'] || dict() + )); /** @private {string} */ this.purchaseConfigBaseUrl_ = this.getConfigUrl_() + CONFIG_BASE_PATH; @@ -182,11 +185,9 @@ export class LaterpayVendor { } const jwt = this.laterpayConfig_['jwt']; if (jwt) { - this.purchaseConfigBaseUrl_ += - '&jwt=' + encodeURIComponent(jwt); + this.purchaseConfigBaseUrl_ += '&jwt=' + encodeURIComponent(jwt); } - /** @const @private {!../../../src/service/timer-impl.Timer} */ this.timer_ = Services.timerFor(this.ampdoc.win); @@ -222,38 +223,44 @@ export class LaterpayVendor { * @return {!Promise} */ authorize() { - return this.getPurchaseConfig_() - .then(response => { - if (response.status === 204) { - throw user() - .createError('No merchant domains have been matched for this ' + - 'article, or no paid content configurations are setup.'); - } - - if (this.laterpayConfig_['scrollToTopAfterAuth']) { - this.vsync_.mutate(() => this.viewport_.setScrollTop(0)); - } - this.emptyContainer_(); - return {access: response.access}; - }, err => { - if (!err || !err.response) { - throw err; - } - const {response} = err; - if (response.status !== 402) { - throw err; - } - return response.json().catch(() => undefined).then(responseJson => { + return this.getPurchaseConfig_().then( + response => { + if (response.status === 204) { + throw user().createError( + 'No merchant domains have been matched for this ' + + 'article, or no paid content configurations are setup.' + ); + } + + if (this.laterpayConfig_['scrollToTopAfterAuth']) { + this.vsync_.mutate(() => this.viewport_.setScrollTop(0)); + } + this.emptyContainer_(); + return {access: response.access}; + }, + err => { + if (!err || !err.response) { + throw err; + } + const {response} = err; + if (response.status !== 402) { + throw err; + } + return response + .json() + .catch(() => undefined) + .then(responseJson => { this.purchaseConfig_ = responseJson; this.purchaseOptions_ = this.parseConfigIntoOptions_( - responseJson.purchase_options); + responseJson.purchase_options + ); // empty before rendering, in case authorization is being called // again with the same state - this.emptyContainer_() - .then(this.renderPurchaseOverlay_.bind(this)); + this.emptyContainer_().then(this.renderPurchaseOverlay_.bind(this)); return {access: false}; }); - }); + } + ); } /** @@ -261,20 +268,29 @@ export class LaterpayVendor { * @private */ getPurchaseConfig_() { - const url = this.purchaseConfigBaseUrl_ + - '&article_title=' + encodeURIComponent(this.getArticleTitle_()); + const url = + this.purchaseConfigBaseUrl_ + + '&article_title=' + + encodeURIComponent(this.getArticleTitle_()); const urlPromise = this.accessSource_.buildUrl( - url, /* useAuthData */ false); - return urlPromise.then(url => { - return this.accessSource_.getLoginUrl(url); - }).then(url => { - dev().info(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( - AUTHORIZATION_TIMEOUT, - this.xhr_.fetchJson(url, { - credentials: 'include', - })).then(res => res.json()); - }); + url, + /* useAuthData */ false + ); + return urlPromise + .then(url => { + return this.accessSource_.getLoginUrl(url); + }) + .then(url => { + dev().info(TAG, 'Authorization URL: ', url); + return this.timer_ + .timeoutPromise( + AUTHORIZATION_TIMEOUT, + this.xhr_.fetchJson(url, { + credentials: 'include', + }) + ) + .then(res => res.json()); + }); } /** @@ -286,15 +302,16 @@ export class LaterpayVendor { const articleTitle = this.getArticleTitle_(); const purchaseOptions = {}; purchaseOptions['singlePurchases'] = purchaseOptionsList.filter( - option => option['sales_model'] === 'single_purchase' + option => option['sales_model'] === 'single_purchase' ); purchaseOptions['singlePurchases'].forEach( - option => option.description = articleTitle); + option => (option.description = articleTitle) + ); purchaseOptions['timepasses'] = purchaseOptionsList.filter( - option => option['sales_model'] === 'timepass' + option => option['sales_model'] === 'timepass' ); purchaseOptions['subscriptions'] = purchaseOptionsList.filter( - option => option['sales_model'] === 'subscription' + option => option['sales_model'] === 'subscription' ); return purchaseOptions; } @@ -313,11 +330,14 @@ export class LaterpayVendor { * @private */ getArticleTitle_() { - const title = this.ampdoc.getRootNode().querySelector( - this.laterpayConfig_['articleTitleSelector']); + const title = this.ampdoc + .getRootNode() + .querySelector(this.laterpayConfig_['articleTitleSelector']); userAssert( - title, 'No article title element found with selector %s', - this.laterpayConfig_['articleTitleSelector']); + title, + 'No article title element found with selector %s', + this.laterpayConfig_['articleTitleSelector'] + ); return title.textContent.trim(); } @@ -329,8 +349,8 @@ export class LaterpayVendor { const id = TAG + '-dialog'; const dialogContainer = this.ampdoc.getElementById(id); return user().assertElement( - dialogContainer, - 'No element found with id ' + id + dialogContainer, + 'No element found with id ' + id ); } @@ -395,13 +415,15 @@ export class LaterpayVendor { this.innerContainer_.appendChild(listContainer); this.innerContainer_.appendChild(purchaseButton); this.innerContainer_.appendChild( - this.createAlreadyPurchasedLink_(this.purchaseConfig_['identify_url'])); + this.createAlreadyPurchasedLink_(this.purchaseConfig_['identify_url']) + ); this.renderTextBlock_('footer'); dialogContainer.appendChild(this.innerContainer_); dialogContainer.appendChild(this.createLaterpayBadge_()); this.containerEmpty_ = false; this.preselectFirstOption_( - dev().assertElement(listContainer.firstElementChild)); + dev().assertElement(listContainer.firstElementChild) + ); } /** @@ -485,15 +507,14 @@ export class LaterpayVendor { radio.type = 'radio'; radio.id = option['title']; radio.value = option['purchase_url']; - const purchaseType = option['price']['payment_model'] === 'pay_later' ? - 'payLater' : - 'payNow'; + const purchaseType = + option['price']['payment_model'] === 'pay_later' ? 'payLater' : 'payNow'; const purchaseActionLabel = this.i18n_[purchaseType + 'Button']; radio.setAttribute('data-purchase-action-label', purchaseActionLabel); radio.setAttribute('data-purchase-type', purchaseType); - this.purchaseOptionListeners_.push(listen( - radio, 'change', this.handlePurchaseOptionSelection_.bind(this) - )); + this.purchaseOptionListeners_.push( + listen(radio, 'change', this.handlePurchaseOptionSelection_.bind(this)) + ); return radio; } @@ -523,7 +544,7 @@ export class LaterpayVendor { * @private */ formatPrice_(priceValue) { - const value = (priceValue / 100); + const value = priceValue / 100; const props = { style: 'decimal', minimumFractionDigits: 0, @@ -534,7 +555,7 @@ export class LaterpayVendor { /** * @param {string} href * @return {!Element} - */ + */ createAlreadyPurchasedLink_(href) { const p = this.createElement_('p'); p.className = TAG + '-already-purchased-link-container'; @@ -565,8 +586,10 @@ export class LaterpayVendor { const selectedOptionClassname = TAG + '-selected'; const prevPurchaseOption = this.selectedPurchaseOption_; const purchaseActionLabel = target.dataset['purchaseActionLabel']; - if (prevPurchaseOption && - prevPurchaseOption.classList.contains(selectedOptionClassname)) { + if ( + prevPurchaseOption && + prevPurchaseOption.classList.contains(selectedOptionClassname) + ) { prevPurchaseOption.classList.remove(selectedOptionClassname); } this.selectedPurchaseOption_ = target; @@ -583,7 +606,9 @@ export class LaterpayVendor { handlePurchase_(ev, purchaseUrl, purchaseType) { ev.preventDefault(); const urlPromise = this.accessSource_.buildUrl( - purchaseUrl, /* useAuthData */ false); + purchaseUrl, + /* useAuthData */ false + ); return urlPromise.then(url => { dev().fine(TAG, 'Authorization URL: ', url); this.accessSource_.loginWithUrl(url, purchaseType); diff --git a/extensions/amp-access-laterpay/0.2/test/test-amp-access-laterpay.js b/extensions/amp-access-laterpay/0.2/test/test-amp-access-laterpay.js index ab5e225e1a58..f5eafad0b88d 100644 --- a/extensions/amp-access-laterpay/0.2/test/test-amp-access-laterpay.js +++ b/extensions/amp-access-laterpay/0.2/test/test-amp-access-laterpay.js @@ -18,304 +18,351 @@ import {LaterpayVendor} from '../laterpay-impl'; const TAG = 'amp-access-laterpay'; -describes.fakeWin('LaterpayVendor', { - amp: true, - location: 'https://pub.com/doc1', -}, env => { - let win, document, ampdoc; - let accessSource; - let accessService; - let accessSourceMock; - let xhrMock; - let articleTitle; - let laterpayConfig; - let vendor; - let priceData; +describes.fakeWin( + 'LaterpayVendor', + { + amp: true, + location: 'https://pub.com/doc1', + }, + env => { + let win, document, ampdoc; + let accessSource; + let accessService; + let accessSourceMock; + let xhrMock; + let articleTitle; + let laterpayConfig; + let vendor; + let priceData; - beforeEach(() => { - win = env.win; - ampdoc = env.ampdoc; - document = win.document; - - laterpayConfig = { - articleTitleSelector: '#laterpay-test-title', - region: 'us', - }; + beforeEach(() => { + win = env.win; + ampdoc = env.ampdoc; + document = win.document; - accessSource = { - getAdapterConfig: () => { return laterpayConfig; }, - buildUrl: () => {}, - loginWithUrl: () => {}, - getLoginUrl: () => {}, - }; + laterpayConfig = { + articleTitleSelector: '#laterpay-test-title', + region: 'us', + }; - accessService = { - ampdoc, - getSource: () => accessSource, - }; + accessSource = { + getAdapterConfig: () => { + return laterpayConfig; + }, + buildUrl: () => {}, + loginWithUrl: () => {}, + getLoginUrl: () => {}, + }; - priceData = { - price: 123, - currency: 'EUR', - 'payment_model': 'pay_later', - }; + accessService = { + ampdoc, + getSource: () => accessSource, + }; - accessSourceMock = sandbox.mock(accessSource); + priceData = { + price: 123, + currency: 'EUR', + 'payment_model': 'pay_later', + }; - articleTitle = document.createElement('h1'); - articleTitle.id = 'laterpay-test-title'; - articleTitle.textContent = 'test title'; - document.body.appendChild(articleTitle); + accessSourceMock = sandbox.mock(accessSource); - vendor = new LaterpayVendor(accessService, accessSource); - xhrMock = sandbox.mock(vendor.xhr_); - }); + articleTitle = document.createElement('h1'); + articleTitle.id = 'laterpay-test-title'; + articleTitle.textContent = 'test title'; + document.body.appendChild(articleTitle); - afterEach(() => { - articleTitle.parentNode.removeChild(articleTitle); - accessSourceMock.verify(); - xhrMock.verify(); - }); + vendor = new LaterpayVendor(accessService, accessSource); + xhrMock = sandbox.mock(vendor.xhr_); + }); - describe('authorize', () => { - let emptyContainerStub; - beforeEach(() => { - emptyContainerStub = sandbox.stub(vendor, 'emptyContainer_'); - sandbox.stub(vendor, 'renderPurchaseOverlay_'); + afterEach(() => { + articleTitle.parentNode.removeChild(articleTitle); + accessSourceMock.verify(); + xhrMock.verify(); }); - it('uses a non default region', () => { - const buildUrl = accessSourceMock.expects('buildUrl') + describe('authorize', () => { + let emptyContainerStub; + beforeEach(() => { + emptyContainerStub = sandbox.stub(vendor, 'emptyContainer_'); + sandbox.stub(vendor, 'renderPurchaseOverlay_'); + }); + + it('uses a non default region', () => { + const buildUrl = accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(() => { - expect( + return vendor.authorize().then(() => { + expect( /connector\.uselaterpay\.com/g.test(buildUrl.firstCall.args[0]) - ).to.be.true; + ).to.be.true; + }); }); - }); - it('successful authorization', () => { - vendor.purchaseConfigBaseUrl_ = 'https://baseurl?param'; - accessSourceMock.expects('buildUrl') - .withExactArgs('https://baseurl?param&article_title=test%20title', false) + it('successful authorization', () => { + vendor.purchaseConfigBaseUrl_ = 'https://baseurl?param'; + accessSourceMock + .expects('buildUrl') + .withExactArgs( + 'https://baseurl?param&article_title=test%20title', + false + ) .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(resp => { - expect(resp.access).to.be.true; - expect(emptyContainerStub.called).to.be.true; + return vendor.authorize().then(resp => { + expect(resp.access).to.be.true; + expect(emptyContainerStub.called).to.be.true; + }); }); - }); - it('authorization fails due to lack of server config', () => { - accessSourceMock.expects('buildUrl') + it('authorization fails due to lack of server config', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) .returns(Promise.resolve({status: 204})) .once(); - return vendor.authorize().catch(err => { - expect(err.message).to.exist; + return vendor.authorize().catch(err => { + expect(err.message).to.exist; + }); }); - }); - it('authorization response from server fails', () => { - accessSourceMock.expects('buildUrl') + it('authorization response from server fails', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) - .returns(Promise.reject({ - response: { - status: 402, - json() { - return Promise.resolve({'purchase_options': [ - {'sales_model': 'single_purchase'}, - {'sales_model': 'timepass'}, - {'sales_model': 'subscription'}, - ]}); + .returns( + Promise.reject({ + response: { + status: 402, + json() { + return Promise.resolve({ + 'purchase_options': [ + {'sales_model': 'single_purchase'}, + {'sales_model': 'timepass'}, + {'sales_model': 'subscription'}, + ], + }); + }, }, - }, - })) + }) + ) .once(); - emptyContainerStub.returns(Promise.resolve()); - return vendor.authorize().then(err => { - expect(err.access).to.be.false; - expect(vendor.purchaseOptions_.singlePurchases).to.have.lengthOf(1); - expect(vendor.purchaseOptions_.timepasses).to.have.lengthOf(1); - expect(vendor.purchaseOptions_.subscriptions).to.have.lengthOf(1); + emptyContainerStub.returns(Promise.resolve()); + return vendor.authorize().then(err => { + expect(err.access).to.be.false; + expect(vendor.purchaseOptions_.singlePurchases).to.have.lengthOf(1); + expect(vendor.purchaseOptions_.timepasses).to.have.lengthOf(1); + expect(vendor.purchaseOptions_.subscriptions).to.have.lengthOf(1); + }); }); }); - }); - - describe('create purchase overlay', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - 'identify_url': 'http://id.url', - }; - vendor.purchaseOptions_ = { - singlePurchases: [{ - price: priceData, - }], - timepasses: [{ - price: priceData, - }], - subscriptions: [{ - price: priceData, - }], - }; - vendor.renderPurchaseOverlay_(); - }); + describe('create purchase overlay', () => { + let container; + + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + 'identify_url': 'http://id.url', + }; + vendor.purchaseOptions_ = { + singlePurchases: [ + { + price: priceData, + }, + ], + timepasses: [ + { + price: priceData, + }, + ], + subscriptions: [ + { + price: priceData, + }, + ], + }; + vendor.renderPurchaseOverlay_(); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('renders list', () => { - expect(container.querySelector('ul')).to.not.be.null; - }); + it('renders list', () => { + expect(container.querySelector('ul')).to.not.be.null; + }); - it('renders 3 purchase options', () => { - expect(container.querySelector('ul').childNodes.length).to.equal(3); - }); + it('renders 3 purchase options', () => { + expect(container.querySelector('ul').childNodes.length).to.equal(3); + }); - it('renders identify url link', () => { - expect(container.querySelector('p > a').href).to - .match(/^http\:\/\/id\.url/); + it('renders identify url link', () => { + expect(container.querySelector('p > a').href).to.match( + /^http\:\/\/id\.url/ + ); + }); }); - }); - - describe('purchase option selection', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - 'identify_url': 'http://id.url', - }; - vendor.purchaseOptions_ = { - singlePurchases: [{ - price: priceData, - }], - timepasses: [{ - price: priceData, - }], - subscriptions: [{ - price: priceData, - }], - }; - vendor.renderPurchaseOverlay_(); - const ev = new Event('change'); - container.querySelector('input').dispatchEvent(ev); - }); + describe('purchase option selection', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + 'identify_url': 'http://id.url', + }; + vendor.purchaseOptions_ = { + singlePurchases: [ + { + price: priceData, + }, + ], + timepasses: [ + { + price: priceData, + }, + ], + subscriptions: [ + { + price: priceData, + }, + ], + }; + vendor.renderPurchaseOverlay_(); + const ev = new Event('change'); + container.querySelector('input').dispatchEvent(ev); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('purchase option is selected', () => { - expect(vendor.selectedPurchaseOption_).to.not.be.null; - expect(vendor.selectedPurchaseOption_.classList - .contains(TAG + '-selected')).to.be.true; + it('purchase option is selected', () => { + expect(vendor.selectedPurchaseOption_).to.not.be.null; + expect( + vendor.selectedPurchaseOption_.classList.contains(TAG + '-selected') + ).to.be.true; + }); }); - }); - - describe('purchase', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - 'identify_url': 'http://id.url', - }; - vendor.purchaseOptions_ = { - singlePurchases: [{ - price: priceData, - }], - timepasses: [{ - price: priceData, - }], - subscriptions: [{ - price: priceData, - }], - }; - vendor.renderPurchaseOverlay_(); - }); + describe('purchase', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + 'identify_url': 'http://id.url', + }; + vendor.purchaseOptions_ = { + singlePurchases: [ + { + price: priceData, + }, + ], + timepasses: [ + { + price: priceData, + }, + ], + subscriptions: [ + { + price: priceData, + }, + ], + }; + vendor.renderPurchaseOverlay_(); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('sends request for purchase', done => { - const changeEv = new Event('change'); - container.querySelector('input').dispatchEvent(changeEv); - accessSourceMock.expects('buildUrl') + it('sends request for purchase', done => { + const changeEv = new Event('change'); + container.querySelector('input').dispatchEvent(changeEv); + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('loginWithUrl') - .once(); - const clickEv = new Event('click'); - container.querySelector('button').dispatchEvent(clickEv); - setTimeout(() => {done();}, 500); - }); + accessSourceMock.expects('loginWithUrl').once(); + const clickEv = new Event('click'); + container.querySelector('button').dispatchEvent(clickEv); + setTimeout(() => { + done(); + }, 500); + }); - it('sends request for already purchased', done => { - accessSourceMock.expects('buildUrl') + it('sends request for already purchased', done => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://apllink')) .once(); - accessSourceMock.expects('loginWithUrl') - .once(); - const clickEv = new Event('click'); - container + accessSourceMock.expects('loginWithUrl').once(); + const clickEv = new Event('click'); + container .querySelector('.' + TAG + '-already-purchased-link-container > a') .dispatchEvent(clickEv); - setTimeout(() => {done();}, 500); + setTimeout(() => { + done(); + }, 500); + }); }); - - - }); -}); + } +); diff --git a/extensions/amp-access-poool/0.1/amp-access-poool.js b/extensions/amp-access-poool/0.1/amp-access-poool.js index 59b624cb5e3d..662d138b55d8 100644 --- a/extensions/amp-access-poool/0.1/amp-access-poool.js +++ b/extensions/amp-access-poool/0.1/amp-access-poool.js @@ -19,18 +19,19 @@ import {Services} from '../../../src/services'; AMP.extension('amp-access-poool', '0.1', function(AMP) { AMP.registerServiceForDoc( - 'poool', - /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ - function(ampdoc) { - const element = ampdoc.getHeadNode(); - return Services.accessServiceForDoc(element).then(accessService => { - const source = accessService.getVendorSource('poool'); - const vendor = new PooolVendor(accessService, source); - const adapter = /** @type { + 'poool', + /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ + function(ampdoc) { + const element = ampdoc.getHeadNode(); + return Services.accessServiceForDoc(element).then(accessService => { + const source = accessService.getVendorSource('poool'); + const vendor = new PooolVendor(accessService, source); + const adapter = /** @type { !../../amp-access/0.1/amp-access-vendor.AccessVendorAdapter } */ (source.getAdapter()); - adapter.registerVendor(vendor); - return vendor; - }); + adapter.registerVendor(vendor); + return vendor; }); + } + ); }); diff --git a/extensions/amp-access-poool/0.1/poool-impl.js b/extensions/amp-access-poool/0.1/poool-impl.js index 9c713b478c94..76e1fec1bab9 100644 --- a/extensions/amp-access-poool/0.1/poool-impl.js +++ b/extensions/amp-access-poool/0.1/poool-impl.js @@ -24,14 +24,13 @@ import {resetStyles, setStyle, setStyles} from '../../../src/style'; const TAG = 'amp-access-poool'; const ACCESS_CONFIG = { - 'authorization': - 'https://api.poool.fr/api/v2/amp/access?rid=READER_ID', + 'authorization': 'https://api.poool.fr/api/v2/amp/access?rid=READER_ID', 'iframe': - 'https://assets.poool.fr/amp.html' - + '?rid=READER_ID' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&r=DOCUMENT_REFERRER', + 'https://assets.poool.fr/amp.html' + + '?rid=READER_ID' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&r=DOCUMENT_REFERRER', }; const AUTHORIZATION_TIMEOUT = 3000; @@ -59,7 +58,6 @@ export class PooolVendor { * @param {!../../amp-access/0.1/amp-access-source.AccessSource} accessSource */ constructor(accessService, accessSource) { - /** @const */ this.ampdoc = accessService.ampdoc; @@ -102,20 +100,22 @@ export class PooolVendor { * @return {!Promise} */ authorize() { - return this.getPooolAccess_() - .then(response => { - return {access: response.access}; - }, err => { - if (!err || !err.response) { - throw err; - } - const {response} = err; - if (response.status !== 402) { - throw err; - } - this.renderPoool_(); - return {access: false}; - }); + return this.getPooolAccess_().then( + response => { + return {access: response.access}; + }, + err => { + if (!err || !err.response) { + throw err; + } + const {response} = err; + if (response.status !== 402) { + throw err; + } + this.renderPoool_(); + return {access: false}; + } + ); } /** @@ -154,16 +154,18 @@ export class PooolVendor { * @private */ getPooolAccess_() { - const url = addParamToUrl(this.accessUrl_ , 'iid', this.itemID_); + const url = addParamToUrl(this.accessUrl_, 'iid', this.itemID_); const urlPromise = this.accessSource_.buildUrl(url, false); - return urlPromise.then(url => { - return this.accessSource_.getLoginUrl(url); - }).then(url => { - dev().info(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( - AUTHORIZATION_TIMEOUT, - this.xhr_.fetchJson(url)).then(res => res.json()); - }); + return urlPromise + .then(url => { + return this.accessSource_.getLoginUrl(url); + }) + .then(url => { + dev().info(TAG, 'Authorization URL: ', url); + return this.timer_ + .timeoutPromise(AUTHORIZATION_TIMEOUT, this.xhr_.fetchJson(url)) + .then(res => res.json()); + }); } /** @@ -172,18 +174,23 @@ export class PooolVendor { renderPoool_() { const pooolContainer = document.getElementById('poool'); const urlPromise = this.accessSource_.buildUrl( - addParamsToUrl(this.iframeUrl_, dict({ + addParamsToUrl( + this.iframeUrl_, + dict({ 'bi': this.pooolConfig_['bundleID'], 'iid': this.pooolConfig_['itemID'], 'ce': this.pooolConfig_['cookiesEnabled'], - 'd': typeof this.pooolConfig_['debug'] !== 'undefined' && + 'd': + typeof this.pooolConfig_['debug'] !== 'undefined' && this.pooolConfig_['debug'] !== null - ? this.pooolConfig_['debug'] - : getMode().development || getMode().localDev, + ? this.pooolConfig_['debug'] + : getMode().development || getMode().localDev, 'fw': this.pooolConfig_['forceWidget'], 'cs': this.pooolConfig_['customSegment'], - })), - false); + }) + ), + false + ); return urlPromise.then(url => { this.iframe_.src = url; diff --git a/extensions/amp-access-poool/0.1/test/test-amp-access-poool.js b/extensions/amp-access-poool/0.1/test/test-amp-access-poool.js index 6f0a6f424fb3..6491c49c92d9 100644 --- a/extensions/amp-access-poool/0.1/test/test-amp-access-poool.js +++ b/extensions/amp-access-poool/0.1/test/test-amp-access-poool.js @@ -15,120 +15,140 @@ */ import {PooolVendor} from '../poool-impl'; -describes.fakeWin('PooolVendor', { - amp: true, - location: 'https://pub.com/doc1', -}, env => { - let win, document, ampdoc; - let accessSource; - let accessService; - let accessSourceMock; - let xhrMock; - let pooolConfig; - let vendor; +describes.fakeWin( + 'PooolVendor', + { + amp: true, + location: 'https://pub.com/doc1', + }, + env => { + let win, document, ampdoc; + let accessSource; + let accessService; + let accessSourceMock; + let xhrMock; + let pooolConfig; + let vendor; - beforeEach(() => { - win = env.win; - ampdoc = env.ampdoc; - document = win.document; - - pooolConfig = { - bundleID: 'ZRGA3EYZ4GRBTSHREG345HGGZRTHZEGEH', - pageType: 'premium', - itemID: 'amp-test-article', - }; - - accessSource = { - getAdapterConfig: () => { return pooolConfig; }, - buildUrl: () => {}, - getReaderId_: () => {}, - getLoginUrl: () => {}, - }; - - accessService = { - ampdoc, - getSource: () => accessSource, - }; + beforeEach(() => { + win = env.win; + ampdoc = env.ampdoc; + document = win.document; - accessSourceMock = sandbox.mock(accessSource); + pooolConfig = { + bundleID: 'ZRGA3EYZ4GRBTSHREG345HGGZRTHZEGEH', + pageType: 'premium', + itemID: 'amp-test-article', + }; - vendor = new PooolVendor(accessService, accessSource); - xhrMock = sandbox.mock(vendor.xhr_); - }); + accessSource = { + getAdapterConfig: () => { + return pooolConfig; + }, + buildUrl: () => {}, + getReaderId_: () => {}, + getLoginUrl: () => {}, + }; - afterEach(() => { - accessSourceMock.verify(); - xhrMock.verify(); - }); + accessService = { + ampdoc, + getSource: () => accessSource, + }; - describe('authorize', () => { - let container; + accessSourceMock = sandbox.mock(accessSource); - beforeEach(() => { - container = document.createElement('div'); - container.id = 'poool-widget'; - document.body.appendChild(container); - sandbox.stub(vendor, 'renderPoool_'); + vendor = new PooolVendor(accessService, accessSource); + xhrMock = sandbox.mock(vendor.xhr_); }); afterEach(() => { - container.parentNode.removeChild(container); + accessSourceMock.verify(); + xhrMock.verify(); }); - it('successful authorization', () => { - vendor.accessUrl_ = 'https://baseurl?param'; - accessSourceMock.expects('buildUrl') + describe('authorize', () => { + let container; + + beforeEach(() => { + container = document.createElement('div'); + container.id = 'poool-widget'; + document.body.appendChild(container); + sandbox.stub(vendor, 'renderPoool_'); + }); + + afterEach(() => { + container.parentNode.removeChild(container); + }); + + it('successful authorization', () => { + vendor.accessUrl_ = 'https://baseurl?param'; + accessSourceMock + .expects('buildUrl') .withExactArgs('https://baseurl?param&iid=amp-test-article', false) .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(resp => { - expect(resp.access).to.be.true; + return vendor.authorize().then(resp => { + expect(resp.access).to.be.true; + }); }); - }); - it('authorization fails because of wrong or missing server config', () => { - accessSourceMock.expects('buildUrl') + it('authorization fails because of wrong or missing server config', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().catch(err => { - expect(err.message).to.exist; + return vendor.authorize().catch(err => { + expect(err.message).to.exist; + }); }); - }); - it('authorization response fails - 402 error', () => { - accessSourceMock.expects('buildUrl') + it('authorization response fails - 402 error', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.reject({ - response: { - status: 402, - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.reject({ + response: { + status: 402, + }, + }) + ) .once(); - return vendor.authorize().then(err => { - expect(err.access).to.be.false; + return vendor.authorize().then(err => { + expect(err.access).to.be.false; + }); }); }); - }); -}); + } +); diff --git a/extensions/amp-access-scroll/0.1/amp-access-scroll.js b/extensions/amp-access-scroll/0.1/amp-access-scroll.js index dfeabd362dcf..0b8b341af0b3 100644 --- a/extensions/amp-access-scroll/0.1/amp-access-scroll.js +++ b/extensions/amp-access-scroll/0.1/amp-access-scroll.js @@ -19,19 +19,19 @@ import {Services} from '../../../src/services'; AMP.extension('amp-access-scroll', '0.1', function(AMP) { AMP.registerServiceForDoc( - 'scroll', - /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ - function(ampdoc) { - const element = ampdoc.getHeadNode(); - return Services.accessServiceForDoc(element).then(accessService => { - const source = accessService.getVendorSource('scroll'); - const vendor = new ScrollAccessVendor(ampdoc, source); - const adapter = /** @type { + 'scroll', + /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ + function(ampdoc) { + const element = ampdoc.getHeadNode(); + return Services.accessServiceForDoc(element).then(accessService => { + const source = accessService.getVendorSource('scroll'); + const vendor = new ScrollAccessVendor(ampdoc, source); + const adapter = /** @type { !../../amp-access/0.1/amp-access-vendor.AccessVendorAdapter } */ (source.getAdapter()); - adapter.registerVendor(vendor); - return vendor; - }); - } + adapter.registerVendor(vendor); + return vendor; + }); + } ); }); diff --git a/extensions/amp-access-scroll/0.1/read-depth-tracker.js b/extensions/amp-access-scroll/0.1/read-depth-tracker.js index b47323f210df..b6cc63c73280 100644 --- a/extensions/amp-access-scroll/0.1/read-depth-tracker.js +++ b/extensions/amp-access-scroll/0.1/read-depth-tracker.js @@ -39,9 +39,9 @@ export class ReadDepthTracker { /** @private {function()} */ const debouncedFindTopParagraph_ = debounce( - ampdoc.win, - this.findTopParagraph_.bind(this), - 1000 + ampdoc.win, + this.findTopParagraph_.bind(this), + 1000 ); this.viewport_ = Services.viewportForDoc(ampdoc); @@ -59,24 +59,27 @@ export class ReadDepthTracker { * @private */ findTopParagraph_() { - return Promise.all([].slice.call(this.paragraphs_) - .map(p => this.viewport_.getClientRectAsync(p))) - .then(rects => { - let lastIdxAboveViewport = null; - let lastPosition = null; - for (let i = rects.length - 1; i >= 0; i--) { - const bottomPosition = rects[i].bottom; - if (bottomPosition <= 0 && - (lastPosition === null || lastPosition < bottomPosition) - ) { - lastIdxAboveViewport = i; - lastPosition = bottomPosition; - } - } - if (lastIdxAboveViewport !== null) { - this.recordLatestRead_(lastIdxAboveViewport); - } - }); + return Promise.all( + [].slice + .call(this.paragraphs_) + .map(p => this.viewport_.getClientRectAsync(p)) + ).then(rects => { + let lastIdxAboveViewport = null; + let lastPosition = null; + for (let i = rects.length - 1; i >= 0; i--) { + const bottomPosition = rects[i].bottom; + if ( + bottomPosition <= 0 && + (lastPosition === null || lastPosition < bottomPosition) + ) { + lastIdxAboveViewport = i; + lastPosition = bottomPosition; + } + } + if (lastIdxAboveViewport !== null) { + this.recordLatestRead_(lastIdxAboveViewport); + } + }); } /** @@ -88,7 +91,7 @@ export class ReadDepthTracker { if (this.lastReadIndex_ !== paragraphIdx) { this.lastReadIndex_ = paragraphIdx; this.updateLastRead_( - this.paragraphs_[paragraphIdx]./*OK*/innerText.substring(0, 50) + this.paragraphs_[paragraphIdx]./*OK*/ innerText.substring(0, 50) ); } } @@ -99,16 +102,19 @@ export class ReadDepthTracker { * @private */ updateLastRead_(snippet) { - this.accessSource_.buildUrl(( - `${this.connectHostname_}/amp/updatedepth` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&rd=' + encodeURIComponent(snippet) - ), false).then(url => { - Services.xhrFor(this.ampdoc_.win) - .fetch(url); - }); + this.accessSource_ + .buildUrl( + `${this.connectHostname_}/amp/updatedepth` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&rd=' + + encodeURIComponent(snippet), + false + ) + .then(url => { + Services.xhrFor(this.ampdoc_.win).fetch(url); + }); } } diff --git a/extensions/amp-access-scroll/0.1/scroll-impl.js b/extensions/amp-access-scroll/0.1/scroll-impl.js index aaa9ba485cae..9128b0272e7c 100644 --- a/extensions/amp-access-scroll/0.1/scroll-impl.js +++ b/extensions/amp-access-scroll/0.1/scroll-impl.js @@ -33,21 +33,23 @@ const TAG = 'amp-access-scroll-elt'; const accessConfig = connectHostname => { /** @const {!JsonObject} */ const ACCESS_CONFIG = /** @type {!JsonObject} */ ({ - 'authorization': `${connectHostname}/amp/access` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&x=QUERY_PARAM(scrollx)', - 'pingback': `${connectHostname}/amp/pingback` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&r=DOCUMENT_REFERRER' - + '&x=QUERY_PARAM(scrollx)' - + '&d=AUTHDATA(scroll)' - + '&v=AUTHDATA(visitId)', + 'authorization': + `${connectHostname}/amp/access` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&x=QUERY_PARAM(scrollx)', + 'pingback': + `${connectHostname}/amp/pingback` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&r=DOCUMENT_REFERRER' + + '&x=QUERY_PARAM(scrollx)' + + '&d=AUTHDATA(scroll)' + + '&v=AUTHDATA(visitId)', 'namespace': 'scroll', }); return ACCESS_CONFIG; @@ -60,17 +62,18 @@ const accessConfig = connectHostname => { const analyticsConfig = connectHostname => { const ANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'scroll': `${connectHostname}/amp/analytics` - + '?rid=ACCESS_READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&r=DOCUMENT_REFERRER' - + '&x=QUERY_PARAM(scrollx)' - + '&d=AUTHDATA(scroll.scroll)' - + '&v=AUTHDATA(scroll.visitId)' - + '&h=SOURCE_HOSTNAME' - + '&s=${totalEngagedTime}', + 'scroll': + `${connectHostname}/amp/analytics` + + '?rid=ACCESS_READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&r=DOCUMENT_REFERRER' + + '&x=QUERY_PARAM(scrollx)' + + '&d=AUTHDATA(scroll.scroll)' + + '&v=AUTHDATA(scroll.visitId)' + + '&h=SOURCE_HOSTNAME' + + '&s=${totalEngagedTime}', }, 'triggers': { 'trackInterval': { @@ -150,35 +153,37 @@ export class ScrollAccessVendor extends AccessClientAdapter { /** @override */ authorize() { // TODO(dbow): Handle timeout? - return super.authorize() - .then(response => { - const isStory = this.ampdoc.getRootNode().querySelector( - 'amp-story[standalone]'); - if (response && response['scroll']) { - if (!isStory) { - const config = this.accessSource_.getAdapterConfig(); - new ScrollElement(this.ampdoc).handleScrollUser( - this.accessSource_, config); - addAnalytics(this.ampdoc, config); - if (response['features'] && response['features']['readDepth']) { - new ReadDepthTracker( - this.ampdoc, - this.accessSource_, - connectHostname(config) - ); - } - } - } else { - if ( - response && - response['blocker'] && - ScrollContentBlocker.shouldCheck(this.ampdoc) - ) { - new ScrollContentBlocker(this.ampdoc, this.accessSource_).check(); - } + return super.authorize().then(response => { + const isStory = this.ampdoc + .getRootNode() + .querySelector('amp-story[standalone]'); + if (response && response['scroll']) { + if (!isStory) { + const config = this.accessSource_.getAdapterConfig(); + new ScrollElement(this.ampdoc).handleScrollUser( + this.accessSource_, + config + ); + addAnalytics(this.ampdoc, config); + if (response['features'] && response['features']['readDepth']) { + new ReadDepthTracker( + this.ampdoc, + this.accessSource_, + connectHostname(config) + ); } - return response; - }); + } + } else { + if ( + response && + response['blocker'] && + ScrollContentBlocker.shouldCheck(this.ampdoc) + ) { + new ScrollContentBlocker(this.ampdoc, this.accessSource_).check(); + } + } + return response; + }); } } @@ -213,17 +218,19 @@ class ScrollContentBlocker { */ check() { Services.xhrFor(this.ampdoc_.win) - .fetchJson('https://block.scroll.com/check.json') - .then(() => false, e => this.blockedByScrollApp_(e.message)) - .then(blockedByScrollApp => { - if (blockedByScrollApp === true) { - // TODO(dbow): Ideally we would automatically redirect to the page - // here, but for now we are adding a button so we redirect on user - // action. - new ScrollElement(this.ampdoc_).addActivateButton( - this.accessSource_, this.accessSource_.getAdapterConfig()); - } - }); + .fetchJson('https://block.scroll.com/check.json') + .then(() => false, e => this.blockedByScrollApp_(e.message)) + .then(blockedByScrollApp => { + if (blockedByScrollApp === true) { + // TODO(dbow): Ideally we would automatically redirect to the page + // here, but for now we are adding a button so we redirect on user + // action. + new ScrollElement(this.ampdoc_).addActivateButton( + this.accessSource_, + this.accessSource_.getAdapterConfig() + ); + } + }); } /** @@ -237,7 +244,7 @@ class ScrollContentBlocker { blockedByScrollApp_(message) { return ( message.indexOf( - 'XHR Failed fetching (https://block.scroll.com/...): ' + + 'XHR Failed fetching (https://block.scroll.com/...): ' + 'Resource blocked by content blocker' ) === 0 ); @@ -271,9 +278,12 @@ class ScrollElement { this.iframe_.setAttribute('title', 'Scroll'); this.iframe_.setAttribute('width', '100%'); this.iframe_.setAttribute('height', '100%'); - this.iframe_.setAttribute('sandbox', 'allow-scripts allow-same-origin ' + - 'allow-top-navigation allow-popups ' + - 'allow-popups-to-escape-sandbox'); + this.iframe_.setAttribute( + 'sandbox', + 'allow-scripts allow-same-origin ' + + 'allow-top-navigation allow-popups ' + + 'allow-popups-to-escape-sandbox' + ); this.scrollBar_.appendChild(this.iframe_); ampdoc.getBody().appendChild(this.scrollBar_); @@ -293,8 +303,10 @@ class ScrollElement { placeholder.classList.add('amp-access-scroll-bar'); placeholder.classList.add('amp-access-scroll-placeholder'); const img = document.createElement('img'); - img.setAttribute('src', - 'https://static.scroll.com/assets/icn-scroll-logo32-9f4ceef399905139bbd26b87bfe94542.svg'); + img.setAttribute( + 'src', + 'https://static.scroll.com/assets/icn-scroll-logo32-9f4ceef399905139bbd26b87bfe94542.svg' + ); img.setAttribute('layout', 'fixed'); img.setAttribute('width', 32); img.setAttribute('height', 32); @@ -302,19 +314,22 @@ class ScrollElement { this.ampdoc_.getBody().appendChild(placeholder); // Set iframe to scrollbar URL. - accessSource.buildUrl(( - `${connectHostname(vendorConfig)}/amp/scrollbar` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - ), false).then(scrollbarUrl => { - this.iframe_.onload = () => { - // On iframe load, remove placeholder element. - this.ampdoc_.getBody().removeChild(placeholder); - }; - this.iframe_.setAttribute('src', scrollbarUrl); - }); + accessSource + .buildUrl( + `${connectHostname(vendorConfig)}/amp/scrollbar` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL', + false + ) + .then(scrollbarUrl => { + this.iframe_.onload = () => { + // On iframe load, remove placeholder element. + this.ampdoc_.getBody().removeChild(placeholder); + }; + this.iframe_.setAttribute('src', scrollbarUrl); + }); } /** @@ -324,16 +339,19 @@ class ScrollElement { * @param {!JsonObject} vendorConfig */ addActivateButton(accessSource, vendorConfig) { - accessSource.buildUrl(( - `${scrollHostname(vendorConfig)}/activateamp` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&x=QUERY_PARAM(scrollx)' - ), false).then(url => { - this.iframe_.setAttribute('src', url); - }); + accessSource + .buildUrl( + `${scrollHostname(vendorConfig)}/activateamp` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&x=QUERY_PARAM(scrollx)', + false + ) + .then(url => { + this.iframe_.setAttribute('src', url); + }); } } @@ -353,13 +371,18 @@ function addAnalytics(ampdoc, vendorConfig) { if (vendorConfig['dataConsentId']) { attributes['data-block-on-consent'] = ''; } - const analyticsElem = createElementWithAttributes(doc, 'amp-analytics', - attributes); + const analyticsElem = createElementWithAttributes( + doc, + 'amp-analytics', + attributes + ); const scriptElem = createElementWithAttributes( - doc, - 'script', dict({ - 'type': 'application/json', - })); + doc, + 'script', + dict({ + 'type': 'application/json', + }) + ); const ANALYTICS_CONFIG = analyticsConfig(connectHostname(vendorConfig)); scriptElem.textContent = JSON.stringify(ANALYTICS_CONFIG); analyticsElem.appendChild(scriptElem); @@ -367,7 +390,7 @@ function addAnalytics(ampdoc, vendorConfig) { // Get extensions service and force load analytics extension const extensions = Services.extensionsFor(ampdoc.win); - extensions./*OK*/installExtensionForDoc(ampdoc, 'amp-analytics'); + extensions./*OK*/ installExtensionForDoc(ampdoc, 'amp-analytics'); // Append ampdoc.getBody().appendChild(analyticsElem); diff --git a/extensions/amp-access-scroll/0.1/test/read-depth-tracker.js b/extensions/amp-access-scroll/0.1/test/read-depth-tracker.js index 7537b82bdfc1..30e78b29e56e 100644 --- a/extensions/amp-access-scroll/0.1/test/read-depth-tracker.js +++ b/extensions/amp-access-scroll/0.1/test/read-depth-tracker.js @@ -16,27 +16,30 @@ import {AccessSource} from '../../../amp-access/0.1/amp-access-source'; import {ReadDepthTracker} from '../read-depth-tracker'; -describes.realWin('ReadDepthTracker', { - amp: { - extensions: ['amp-access-scroll'], +describes.realWin( + 'ReadDepthTracker', + { + amp: { + extensions: ['amp-access-scroll'], + }, }, -}, env => { - let win; - let doc; - let ampdoc; - let sandbox; - let accessSource; - let readDepthTracker; + env => { + let win; + let doc; + let ampdoc; + let sandbox; + let accessSource; + let readDepthTracker; - beforeEach(() => { - win = env.win; - doc = win.document; - ampdoc = env.ampdoc; - sandbox = env.sandbox; + beforeEach(() => { + win = env.win; + doc = win.document; + ampdoc = env.ampdoc; + sandbox = env.sandbox; - // Undefined initialization params for AccessSource - let scheduleViewFn, onReauthorizeFn; - accessSource = new AccessSource( + // Undefined initialization params for AccessSource + let scheduleViewFn, onReauthorizeFn; + accessSource = new AccessSource( ampdoc, { 'authorization': 'https://acme.com/a', @@ -50,54 +53,55 @@ describes.realWin('ReadDepthTracker', { scheduleViewFn, onReauthorizeFn, doc.documentElement - ); + ); - for (let i = 0; i < 5; i++) { - const elem = doc.createElement('p'); - elem./*OK*/innerText = `Scroll amp test paragraph ${i}`; - elem.id = `${i}`; - doc.body.appendChild(elem); - } + for (let i = 0; i < 5; i++) { + const elem = doc.createElement('p'); + elem./*OK*/ innerText = `Scroll amp test paragraph ${i}`; + elem.id = `${i}`; + doc.body.appendChild(elem); + } - readDepthTracker = new ReadDepthTracker( + readDepthTracker = new ReadDepthTracker( ampdoc, accessSource, 'api.test.com' - ); + ); - // Stub viewport to fake paragraph positions - sandbox.stub(readDepthTracker.viewport_,'getClientRectAsync') + // Stub viewport to fake paragraph positions + sandbox + .stub(readDepthTracker.viewport_, 'getClientRectAsync') .callsFake(returnRectPosition); - // Stub updateLastRead_ call to check content sent - sandbox.stub(readDepthTracker, 'updateLastRead_'); - }); + // Stub updateLastRead_ call to check content sent + sandbox.stub(readDepthTracker, 'updateLastRead_'); + }); - function returnRectPosition(paragraph) { - if (paragraph.id === '0') { - return {bottom: -50}; - } else if (paragraph.id === '1') { - return {bottom: -30}; - } else { - return {bottom: 10}; + function returnRectPosition(paragraph) { + if (paragraph.id === '0') { + return {bottom: -50}; + } else if (paragraph.id === '1') { + return {bottom: -30}; + } else { + return {bottom: 10}; + } } - } - it('updates last read position to API with correct snippet', () => { - readDepthTracker.findTopParagraph_() - .then(() => { - expect(readDepthTracker.updateLastRead_.calledOnce).to.be.true; - expect(readDepthTracker.updateLastRead_.getCall(0).args[0]) - .to.equal('Scroll amp test paragraph 1'); - }); - }); + it('updates last read position to API with correct snippet', () => { + readDepthTracker.findTopParagraph_().then(() => { + expect(readDepthTracker.updateLastRead_.calledOnce).to.be.true; + expect(readDepthTracker.updateLastRead_.getCall(0).args[0]).to.equal( + 'Scroll amp test paragraph 1' + ); + }); + }); - it('does not update last read position if position has not changed', () => { - readDepthTracker.lastReadIndex_ = 1; + it('does not update last read position if position has not changed', () => { + readDepthTracker.lastReadIndex_ = 1; - readDepthTracker.findTopParagraph_() - .then(() => { - expect(readDepthTracker.updateLastRead_.calledOnce).to.be.false; - }); - }); -}); + readDepthTracker.findTopParagraph_().then(() => { + expect(readDepthTracker.updateLastRead_.calledOnce).to.be.false; + }); + }); + } +); diff --git a/extensions/amp-access/0.1/access-expr.js b/extensions/amp-access/0.1/access-expr.js index 5f67c0392170..b6339220726b 100644 --- a/extensions/amp-access/0.1/access-expr.js +++ b/extensions/amp-access/0.1/access-expr.js @@ -16,7 +16,6 @@ import {accessParser as parser} from './access-expr-impl'; - /** * Evaluates access expression. * diff --git a/extensions/amp-access/0.1/access-vars.js b/extensions/amp-access/0.1/access-vars.js index 49cdda89e143..85f3a77f2ca7 100644 --- a/extensions/amp-access/0.1/access-vars.js +++ b/extensions/amp-access/0.1/access-vars.js @@ -14,14 +14,12 @@ * limitations under the License. */ - /** * Exports the substitution variables for access services. * * @interface */ export class AccessVars { - /** * Returns the promise that will yield the access READER_ID. * diff --git a/extensions/amp-access/0.1/access-vendor.js b/extensions/amp-access/0.1/access-vendor.js index f78096792671..ec7f73014322 100644 --- a/extensions/amp-access/0.1/access-vendor.js +++ b/extensions/amp-access/0.1/access-vendor.js @@ -14,14 +14,12 @@ * limitations under the License. */ - /** * This interface is intended to be implemented by AMP Access vendors to * provide authorization and pingback. * @interface */ export class AccessVendor { - /** * Requests authorization from the vendor. Returns a promise that yields * a JSON authorization response. diff --git a/extensions/amp-access/0.1/amp-access-client.js b/extensions/amp-access/0.1/amp-access-client.js index 92b98c092722..df6ffa585255 100644 --- a/extensions/amp-access/0.1/amp-access-client.js +++ b/extensions/amp-access/0.1/amp-access-client.js @@ -26,10 +26,8 @@ const TAG = 'amp-access-client'; /** @const {number} */ const DEFAULT_AUTHORIZATION_TIMEOUT = 3000; - /** @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessClientAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -43,8 +41,10 @@ export class AccessClientAdapter { this.context_ = context; /** @const @private {string} */ - this.authorizationUrl_ = userAssert(configJson['authorization'], - '"authorization" URL must be specified'); + this.authorizationUrl_ = userAssert( + configJson['authorization'], + '"authorization" URL must be specified' + ); assertHttpsUrl(this.authorizationUrl_, '"authorization"'); /** @const @private {boolean} */ @@ -59,7 +59,8 @@ export class AccessClientAdapter { /** @const @private {number} */ this.authorizationTimeout_ = this.buildConfigAuthorizationTimeout_( - configJson); + configJson + ); /** @const @private {!../../../src/service/xhr-impl.Xhr} */ this.xhr_ = Services.xhrFor(ampdoc.win); @@ -78,8 +79,10 @@ export class AccessClientAdapter { } let timeout = configJson['authorizationTimeout']; - userAssert(typeof timeout == 'number', - '"authorizationTimeout" must be a number'); + userAssert( + typeof timeout == 'number', + '"authorizationTimeout" must be a number' + ); if (!(getMode().localDev || getMode().development)) { timeout = Math.min(timeout, DEFAULT_AUTHORIZATION_TIMEOUT); } @@ -118,15 +121,20 @@ export class AccessClientAdapter { /** @override */ authorize() { dev().fine(TAG, 'Start authorization via ', this.authorizationUrl_); - const urlPromise = this.context_.buildUrl(this.authorizationUrl_, - /* useAuthData */ false); + const urlPromise = this.context_.buildUrl( + this.authorizationUrl_, + /* useAuthData */ false + ); return urlPromise.then(url => { dev().fine(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( + return this.timer_ + .timeoutPromise( this.authorizationTimeout_, this.xhr_.fetchJson(url, { credentials: 'include', - })).then(res => res.json()); + }) + ) + .then(res => res.json()); }); } @@ -137,8 +145,10 @@ export class AccessClientAdapter { /** @override */ pingback() { - const promise = this.context_.buildUrl(devAssert(this.pingbackUrl_), - /* useAuthData */ true); + const promise = this.context_.buildUrl( + devAssert(this.pingbackUrl_), + /* useAuthData */ true + ); return promise.then(url => { dev().fine(TAG, 'Pingback URL: ', url); return this.xhr_.sendSignal(url, { diff --git a/extensions/amp-access/0.1/amp-access-iframe.js b/extensions/amp-access/0.1/amp-access-iframe.js index 7909911be5fb..8904b5c4138a 100644 --- a/extensions/amp-access/0.1/amp-access-iframe.js +++ b/extensions/amp-access/0.1/amp-access-iframe.js @@ -29,10 +29,8 @@ const AUTHORIZATION_TIMEOUT = 3000; const EXPIRATION_TIMEOUT = 1000 * 60 * 60 * 24 * 7; // 7 days const TAG = 'amp-access-iframe'; - /** @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessIframeAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -52,20 +50,23 @@ export class AccessIframeAdapter { this.timer_ = Services.timerFor(ampdoc.win); /** @const @private {string} */ - this.iframeSrc_ = userAssert(configJson['iframeSrc'], - '"iframeSrc" URL must be specified'); + this.iframeSrc_ = userAssert( + configJson['iframeSrc'], + '"iframeSrc" URL must be specified' + ); assertHttpsUrl(this.iframeSrc_, '"iframeSrc"'); /** @const @private {?Array} */ this.iframeVars_ = configJson['iframeVars'] || null; if (this.iframeVars_) { - userAssert(isArray(this.iframeVars_), - '"iframeVars" must be an array'); + userAssert(isArray(this.iframeVars_), '"iframeVars" must be an array'); } /** @const @private {!JsonObject} */ - this.defaultResponse_ = userAssert(configJson['defaultResponse'], - '"defaultResponse" must be specified'); + this.defaultResponse_ = userAssert( + configJson['defaultResponse'], + '"defaultResponse" must be specified' + ); /** @private @const {string} */ this.targetOrigin_ = parseUrlDeprecated(this.iframeSrc_).origin; @@ -82,9 +83,10 @@ export class AccessIframeAdapter { /** @private @const {!Messenger} */ this.messenger_ = new Messenger( - this.ampdoc.win, - () => this.iframe_.contentWindow, - this.targetOrigin_); + this.ampdoc.win, + () => this.iframe_.contentWindow, + this.targetOrigin_ + ); /** @private {?Promise} */ this.configPromise_ = null; @@ -113,10 +115,7 @@ export class AccessIframeAdapter { /** @override */ authorize() { - return Promise.race([ - this.authorizeLocal_(), - this.authorizeRemote_(), - ]); + return Promise.race([this.authorizeLocal_(), this.authorizeRemote_()]); } /** @override */ @@ -166,12 +165,15 @@ export class AccessIframeAdapter { if (this.iframeVars_) { const varsString = this.iframeVars_.join('&'); const varsPromise = this.context_.collectUrlVars( - varsString, - /* useAuthData */ false); - resolve(varsPromise.then(vars => { - configJson['iframeVars'] = vars; - return configJson; - })); + varsString, + /* useAuthData */ false + ); + resolve( + varsPromise.then(vars => { + configJson['iframeVars'] = vars; + return configJson; + }) + ); } else { resolve(configJson); } @@ -194,15 +196,17 @@ export class AccessIframeAdapter { * @private */ authorizeRemote_() { - return this.connect().then(() => { - return this.messenger_.sendCommandRsvp('authorize', {}); - }).then(data => { - if (data) { - // Store the value in a non-blocking microtask. - Promise.resolve().then(() => this.store_(data)); - } - return data; - }); + return this.connect() + .then(() => { + return this.messenger_.sendCommandRsvp('authorize', {}); + }) + .then(data => { + if (data) { + // Store the value in a non-blocking microtask. + Promise.resolve().then(() => this.store_(data)); + } + return data; + }); } /** @@ -222,7 +226,7 @@ export class AccessIframeAdapter { } const parsed = parseJson(raw); const time = parsed['t']; - if ((time + EXPIRATION_TIMEOUT) < this.ampdoc.win.Date.now()) { + if (time + EXPIRATION_TIMEOUT < this.ampdoc.win.Date.now()) { // Already expired. return null; } @@ -251,10 +255,15 @@ export class AccessIframeAdapter { } try { if (data) { - storage.setItem(TAG, JSON.stringify(dict({ - 't': this.ampdoc.win.Date.now(), - 'd': data, - }))); + storage.setItem( + TAG, + JSON.stringify( + dict({ + 't': this.ampdoc.win.Date.now(), + 'd': data, + }) + ) + ); } else { storage.removeItem(TAG); } @@ -273,16 +282,18 @@ export class AccessIframeAdapter { if (cmd == 'connect') { // First ever message. Indicates that the receiver is listening. this.configPromise_.then(configJson => { - this.messenger_.sendCommandRsvp('start', { - 'protocol': 'amp-access', - 'config': configJson, - }).then(() => { - // Confirmation that connection has been successful. - if (this.connectedResolver_) { - this.connectedResolver_(); - this.connectedResolver_ = null; - } - }); + this.messenger_ + .sendCommandRsvp('start', { + 'protocol': 'amp-access', + 'config': configJson, + }) + .then(() => { + // Confirmation that connection has been successful. + if (this.connectedResolver_) { + this.connectedResolver_(); + this.connectedResolver_ = null; + } + }); }); return; } diff --git a/extensions/amp-access/0.1/amp-access-other.js b/extensions/amp-access/0.1/amp-access-other.js index fb9c767b6fd6..f729b9c6194c 100644 --- a/extensions/amp-access/0.1/amp-access-other.js +++ b/extensions/amp-access/0.1/amp-access-other.js @@ -20,10 +20,8 @@ import {isProxyOrigin} from '../../../src/url'; /** @const {string} */ const TAG = 'amp-access-other'; - /** @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessOtherAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -38,7 +36,7 @@ export class AccessOtherAdapter { /** @private {?JsonObject} */ this.authorizationResponse_ = - configJson['authorizationFallbackResponse'] || null; + configJson['authorizationFallbackResponse'] || null; /** @const @private {boolean} */ this.isProxyOrigin_ = isProxyOrigin(ampdoc.win.location); @@ -55,7 +53,7 @@ export class AccessOtherAdapter { isAuthorizationEnabled() { // The `type=other` is allowed to use the authorization fallback, but // only if it's not on `cdn.ampproject.org`. - return (!!this.authorizationResponse_ && !this.isProxyOrigin_); + return !!this.authorizationResponse_ && !this.isProxyOrigin_; } /** @override */ diff --git a/extensions/amp-access/0.1/amp-access-server-jwt.js b/extensions/amp-access/0.1/amp-access-server-jwt.js index ddd5ad6fa11b..8079ef7b88c9 100644 --- a/extensions/amp-access/0.1/amp-access-server-jwt.js +++ b/extensions/amp-access/0.1/amp-access-server-jwt.js @@ -40,7 +40,6 @@ const AUTHORIZATION_TIMEOUT = 3000; /** @const {string} */ const AMP_AUD = 'ampproject.org'; - /** * This class implements server-side authorization protocol with JWT. In this * approach only immediately visible sections are downloaded. For authorization, @@ -75,7 +74,6 @@ const AMP_AUD = 'ampproject.org'; * @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessServerJwtAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -103,24 +101,27 @@ export class AccessServerJwtAdapter { /** @const @private {!../../../src/service/vsync-impl.Vsync} */ this.vsync_ = Services.vsyncFor(ampdoc.win); - const stateElement = ampdoc.getRootNode().querySelector( - 'meta[name="i-amphtml-access-state"]'); + const stateElement = ampdoc + .getRootNode() + .querySelector('meta[name="i-amphtml-access-state"]'); /** @private @const {?string} */ - this.serverState_ = stateElement ? - stateElement.getAttribute('content') : null; + this.serverState_ = stateElement + ? stateElement.getAttribute('content') + : null; const isInExperiment = isExperimentOn(ampdoc.win, 'amp-access-server-jwt'); /** @private @const {boolean} */ this.isProxyOrigin_ = isProxyOrigin(ampdoc.win.location) || isInExperiment; - const serviceUrlOverride = isInExperiment ? - this.viewer_.getParam('serverAccessService') : null; + const serviceUrlOverride = isInExperiment + ? this.viewer_.getParam('serverAccessService') + : null; /** @private @const {string} */ - this.serviceUrl_ = serviceUrlOverride || - removeFragment(ampdoc.win.location.href); + this.serviceUrl_ = + serviceUrlOverride || removeFragment(ampdoc.win.location.href); /** @const @private {?string} */ this.key_ = configJson['publicKey'] || null; @@ -128,16 +129,20 @@ export class AccessServerJwtAdapter { /** @const @private {?string} */ this.keyUrl_ = configJson['publicKeyUrl'] || null; - userAssert(this.key_ || this.keyUrl_, - '"publicKey" or "publicKeyUrl" must be specified'); + userAssert( + this.key_ || this.keyUrl_, + '"publicKey" or "publicKeyUrl" must be specified' + ); if (this.keyUrl_) { assertHttpsUrl(this.keyUrl_, '"publicKeyUrl"'); } if (this.key_ && this.keyUrl_) { // TODO(dvoytenko): Remove "publicKey" option eventually. - user().warn(TAG, - 'Both "publicKey" and "publicKeyUrl" specified. ' + - 'The "publicKeyUrl" will be ignored.'); + user().warn( + TAG, + 'Both "publicKey" and "publicKeyUrl" specified. ' + + 'The "publicKeyUrl" will be ignored.' + ); } /** @private @const {!JwtHelper} */ @@ -162,10 +167,13 @@ export class AccessServerJwtAdapter { /** @override */ authorize() { - dev().fine(TAG, 'Start authorization with ', - this.isProxyOrigin_ ? 'proxy' : 'non-proxy', - this.serverState_, - this.clientAdapter_.getAuthorizationUrl()); + dev().fine( + TAG, + 'Start authorization with ', + this.isProxyOrigin_ ? 'proxy' : 'non-proxy', + this.serverState_, + this.clientAdapter_.getAuthorizationUrl() + ); if (!this.isProxyOrigin_ || !this.serverState_) { return this.authorizeOnClient_(); } @@ -193,34 +201,44 @@ export class AccessServerJwtAdapter { */ fetchJwt_() { const urlPromise = this.context_.buildUrl( - this.clientAdapter_.getAuthorizationUrl(), - /* useAuthData */ false); - let jwtPromise = urlPromise.then(url => { - dev().fine(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( + this.clientAdapter_.getAuthorizationUrl(), + /* useAuthData */ false + ); + let jwtPromise = urlPromise + .then(url => { + dev().fine(TAG, 'Authorization URL: ', url); + return this.timer_.timeoutPromise( AUTHORIZATION_TIMEOUT, this.xhr_.fetchText(url, { credentials: 'include', - })); - }).then(resp => { - return resp.text(); - }).then(encoded => { - const jwt = this.jwtHelper_.decode(encoded); - userAssert(jwt['amp_authdata'], - '"amp_authdata" must be present in JWT'); - return {encoded, jwt}; - }); + }) + ); + }) + .then(resp => { + return resp.text(); + }) + .then(encoded => { + const jwt = this.jwtHelper_.decode(encoded); + userAssert( + jwt['amp_authdata'], + '"amp_authdata" must be present in JWT' + ); + return {encoded, jwt}; + }); if (this.shouldBeValidated_()) { // Validate JWT in the development mode. if (this.jwtHelper_.isVerificationSupported()) { jwtPromise = jwtPromise.then(resp => { return this.jwtHelper_ - .decodeAndVerify(resp.encoded, this.loadKeyPem_()) - .then(() => resp); + .decodeAndVerify(resp.encoded, this.loadKeyPem_()) + .then(() => resp); }); } else { - user().warn(TAG, 'Cannot verify signature on this browser since' + - ' it doesn\'t support WebCrypto APIs'); + user().warn( + TAG, + 'Cannot verify signature on this browser since' + + " it doesn't support WebCrypto APIs" + ); } jwtPromise = jwtPromise.then(resp => { this.validateJwt_(resp.jwt); @@ -240,8 +258,9 @@ export class AccessServerJwtAdapter { if (this.key_) { return Promise.resolve(this.key_); } - return this.xhr_.fetchText(dev().assertString(this.keyUrl_)) - .then(res => res.text()); + return this.xhr_ + .fetchText(dev().assertString(this.keyUrl_)) + .then(res => res.text()); } /** @@ -262,8 +281,7 @@ export class AccessServerJwtAdapter { // exp: expiration time. const exp = jwt['exp']; userAssert(exp, '"exp" field must be specified'); - userAssert(parseFloat(exp) * 1000 > now, - 'token has expired: %s', exp); + userAssert(parseFloat(exp) * 1000 > now, 'token has expired: %s', exp); // aud: audience. const aud = jwt['aud']; @@ -277,7 +295,7 @@ export class AccessServerJwtAdapter { } } } else { - audForAmp = (aud == AMP_AUD); + audForAmp = aud == AMP_AUD; } userAssert(audForAmp, '"aud" must be "%s": %s', AMP_AUD, aud); } @@ -287,8 +305,11 @@ export class AccessServerJwtAdapter { * @private */ authorizeOnClient_() { - dev().fine(TAG, 'Proceed via client protocol via ', - this.clientAdapter_.getAuthorizationUrl()); + dev().fine( + TAG, + 'Proceed via client protocol via ', + this.clientAdapter_.getAuthorizationUrl() + ); return this.fetchJwt_().then(resp => { return resp.jwt['amp_authdata']; }); @@ -303,16 +324,19 @@ export class AccessServerJwtAdapter { return this.fetchJwt_().then(resp => { const {encoded, jwt} = resp; const accessData = jwt['amp_authdata']; - const request = serializeQueryString(dict({ - 'url': removeFragment(this.ampdoc.win.location.href), - 'state': this.serverState_, - 'jwt': encoded, - })); + const request = serializeQueryString( + dict({ + 'url': removeFragment(this.ampdoc.win.location.href), + 'state': this.serverState_, + 'jwt': encoded, + }) + ); dev().fine(TAG, 'Authorization request: ', this.serviceUrl_, request); dev().fine(TAG, '- access data: ', accessData); // Note that `application/x-www-form-urlencoded` is used to avoid // CORS preflight request. - return this.timer_.timeoutPromise( + return this.timer_ + .timeoutPromise( AUTHORIZATION_TIMEOUT, fetchDocument(this.ampdoc.win, this.serviceUrl_, { method: 'POST', @@ -321,10 +345,13 @@ export class AccessServerJwtAdapter { 'Content-Type': 'application/x-www-form-urlencoded', }), requireAmpResponseSourceOrigin: false, - })).then(response => { - dev().fine(TAG, 'Authorization response: ', response); - return this.replaceSections_(response); - }).then(() => accessData); + }) + ) + .then(response => { + dev().fine(TAG, 'Authorization response: ', response); + return this.replaceSections_(response); + }) + .then(() => accessData); }); } @@ -339,15 +366,19 @@ export class AccessServerJwtAdapter { for (let i = 0; i < sections.length; i++) { const section = sections[i]; const sectionId = section.getAttribute('i-amphtml-access-id'); - const target = this.ampdoc.getRootNode().querySelector( - `[i-amphtml-access-id="${escapeCssSelectorIdent(sectionId)}"]`); + const target = this.ampdoc + .getRootNode() + .querySelector( + `[i-amphtml-access-id="${escapeCssSelectorIdent(sectionId)}"]` + ); if (!target) { dev().warn(TAG, 'Section not found: ', sectionId); continue; } target.parentElement.replaceChild( - this.ampdoc.win.document.importNode(section, /* deep */ true), - target); + this.ampdoc.win.document.importNode(section, /* deep */ true), + target + ); } }); } diff --git a/extensions/amp-access/0.1/amp-access-server.js b/extensions/amp-access/0.1/amp-access-server.js index 4ffe4f62420b..5a20caae8ed5 100644 --- a/extensions/amp-access/0.1/amp-access-server.js +++ b/extensions/amp-access/0.1/amp-access-server.js @@ -27,7 +27,6 @@ import {parseJson} from '../../../src/json'; /** @const {string} */ const TAG = 'amp-access-server'; - /** * This class implements server-side authorization protocol. In this approach * only immediately visible sections are downloaded. For authorization, the @@ -58,7 +57,6 @@ const TAG = 'amp-access-server'; * @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessServerAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -86,24 +84,27 @@ export class AccessServerAdapter { /** @const @private {!../../../src/service/vsync-impl.Vsync} */ this.vsync_ = Services.vsyncFor(ampdoc.win); - const stateElement = ampdoc.getRootNode().querySelector( - 'meta[name="i-amphtml-access-state"]'); + const stateElement = ampdoc + .getRootNode() + .querySelector('meta[name="i-amphtml-access-state"]'); /** @private @const {?string} */ - this.serverState_ = stateElement ? - stateElement.getAttribute('content') : null; + this.serverState_ = stateElement + ? stateElement.getAttribute('content') + : null; const isInExperiment = isExperimentOn(ampdoc.win, 'amp-access-server'); /** @private @const {boolean} */ this.isProxyOrigin_ = isProxyOrigin(ampdoc.win.location) || isInExperiment; - const serviceUrlOverride = isInExperiment ? - this.viewer_.getParam('serverAccessService') : null; + const serviceUrlOverride = isInExperiment + ? this.viewer_.getParam('serverAccessService') + : null; /** @private @const {string} */ - this.serviceUrl_ = serviceUrlOverride || - removeFragment(ampdoc.win.location.href); + this.serviceUrl_ = + serviceUrlOverride || removeFragment(ampdoc.win.location.href); } /** @override */ @@ -122,10 +123,13 @@ export class AccessServerAdapter { /** @override */ authorize() { - dev().fine(TAG, 'Start authorization with ', - this.isProxyOrigin_ ? 'proxy' : 'non-proxy', - this.serverState_, - this.clientAdapter_.getAuthorizationUrl()); + dev().fine( + TAG, + 'Start authorization with ', + this.isProxyOrigin_ ? 'proxy' : 'non-proxy', + this.serverState_, + this.clientAdapter_.getAuthorizationUrl() + ); if (!this.isProxyOrigin_ || !this.serverState_) { dev().fine(TAG, 'Proceed via client protocol'); return this.clientAdapter_.authorize(); @@ -134,24 +138,26 @@ export class AccessServerAdapter { dev().fine(TAG, 'Proceed via server protocol'); const varsPromise = this.context_.collectUrlVars( - this.clientAdapter_.getAuthorizationUrl(), - /* useAuthData */ false); - return varsPromise.then(vars => { - const requestVars = {}; - for (const k in vars) { - if (vars[k] != null) { - requestVars[k] = String(vars[k]); + this.clientAdapter_.getAuthorizationUrl(), + /* useAuthData */ false + ); + return varsPromise + .then(vars => { + const requestVars = {}; + for (const k in vars) { + if (vars[k] != null) { + requestVars[k] = String(vars[k]); + } } - } - const request = dict({ - 'url': removeFragment(this.ampdoc.win.location.href), - 'state': this.serverState_, - 'vars': requestVars, - }); - dev().fine(TAG, 'Authorization request: ', this.serviceUrl_, request); - // Note that `application/x-www-form-urlencoded` is used to avoid - // CORS preflight request. - return this.timer_.timeoutPromise( + const request = dict({ + 'url': removeFragment(this.ampdoc.win.location.href), + 'state': this.serverState_, + 'vars': requestVars, + }); + dev().fine(TAG, 'Authorization request: ', this.serviceUrl_, request); + // Note that `application/x-www-form-urlencoded` is used to avoid + // CORS preflight request. + return this.timer_.timeoutPromise( this.clientAdapter_.getAuthorizationTimeout(), fetchDocument(this.ampdoc.win, this.serviceUrl_, { method: 'POST', @@ -160,19 +166,22 @@ export class AccessServerAdapter { 'Content-Type': 'application/x-www-form-urlencoded', }), requireAmpResponseSourceOrigin: false, - })); - }).then(responseDoc => { - dev().fine(TAG, 'Authorization response: ', responseDoc); - const accessDataString = devAssert( + }) + ); + }) + .then(responseDoc => { + dev().fine(TAG, 'Authorization response: ', responseDoc); + const accessDataString = devAssert( responseDoc.querySelector('script[id="amp-access-data"]'), - 'No authorization data available').textContent; - const accessData = parseJson(accessDataString); - dev().fine(TAG, '- access data: ', accessData); - - return this.replaceSections_(responseDoc).then(() => { - return accessData; + 'No authorization data available' + ).textContent; + const accessData = parseJson(accessDataString); + dev().fine(TAG, '- access data: ', accessData); + + return this.replaceSections_(responseDoc).then(() => { + return accessData; + }); }); - }); } /** @override */ @@ -201,15 +210,19 @@ export class AccessServerAdapter { for (let i = 0; i < sections.length; i++) { const section = sections[i]; const sectionId = section.getAttribute('i-amphtml-access-id'); - const target = this.ampdoc.getRootNode().querySelector( - `[i-amphtml-access-id="${escapeCssSelectorIdent(sectionId)}"]`); + const target = this.ampdoc + .getRootNode() + .querySelector( + `[i-amphtml-access-id="${escapeCssSelectorIdent(sectionId)}"]` + ); if (!target) { dev().warn(TAG, 'Section not found: ', sectionId); continue; } target.parentElement.replaceChild( - this.ampdoc.win.document.importNode(section, /* deep */ true), - target); + this.ampdoc.win.document.importNode(section, /* deep */ true), + target + ); } }); } diff --git a/extensions/amp-access/0.1/amp-access-source.js b/extensions/amp-access/0.1/amp-access-source.js index 67b7873b5c6a..702318c76e20 100644 --- a/extensions/amp-access/0.1/amp-access-source.js +++ b/extensions/amp-access/0.1/amp-access-source.js @@ -22,10 +22,7 @@ import {AccessServerJwtAdapter} from './amp-access-server-jwt'; import {AccessVendorAdapter} from './amp-access-vendor'; import {Deferred} from '../../../src/utils/promise'; import {Services} from '../../../src/services'; -import { - assertHttpsUrl, - parseQueryString, -} from '../../../src/url'; +import {assertHttpsUrl, parseQueryString} from '../../../src/url'; import {dev, user, userAssert} from '../../../src/log'; import {dict} from '../../../src/utils/object'; import {getLoginUrl, openLoginDialog} from './login-dialog'; @@ -34,7 +31,6 @@ import {isExperimentOn} from '../../../src/experiments'; import {isObject} from '../../../src/types'; import {triggerAnalyticsEvent} from '../../../src/analytics'; - /** @const */ const TAG = 'amp-access'; @@ -50,7 +46,6 @@ export const AccessType = { OTHER: 'other', }; - /** * AccessSource represents a single source of authentication information for a * page. These sources are constructed, unified and attached to the document by @@ -65,9 +60,14 @@ export class AccessSource { * @param {function(!Promise)} onReauthorizeFn * @param {!Element} accessElement */ - constructor(ampdoc, configJson, readerIdFn, scheduleViewFn, - onReauthorizeFn, accessElement) { - + constructor( + ampdoc, + configJson, + readerIdFn, + scheduleViewFn, + onReauthorizeFn, + accessElement + ) { /** @const */ this.ampdoc = ampdoc; @@ -97,7 +97,7 @@ export class AccessSource { /** @const {?JsonObject} */ this.authorizationFallbackResponse_ = - configJson['authorizationFallbackResponse']; + configJson['authorizationFallbackResponse']; /** @const {?string} */ this.namespace_ = configJson['namespace'] || null; @@ -168,7 +168,7 @@ export class AccessSource { buildUrl: this.buildUrl.bind(this), collectUrlVars: this.collectUrlVars.bind(this), }); - const isJwt = (this.isJwtEnabled_ && configJson['jwt'] === true); + const isJwt = this.isJwtEnabled_ && configJson['jwt'] === true; switch (this.type_) { case AccessType.CLIENT: if (isJwt) { @@ -197,7 +197,6 @@ export class AccessSource { return this.adapter_.getConfig(); } - /** * @return {!Promise} Returns a promise for the initial authorization. */ @@ -210,9 +209,9 @@ export class AccessSource { * @return {!AccessType} */ buildConfigType_(configJson) { - let type = configJson['type'] ? - user().assertEnumValue(AccessType, configJson['type'], 'access type') : - null; + let type = configJson['type'] + ? user().assertEnumValue(AccessType, configJson['type'], 'access type') + : null; if (!type) { if (configJson['vendor']) { type = AccessType.VENDOR; @@ -228,8 +227,10 @@ export class AccessSource { user().info(TAG, 'Forcing access type: SERVER'); type = AccessType.SERVER; } - if (type == AccessType.IFRAME && - !isExperimentOn(this.ampdoc.win, 'amp-access-iframe')) { + if ( + type == AccessType.IFRAME && + !isExperimentOn(this.ampdoc.win, 'amp-access-iframe') + ) { user().error(TAG, 'Experiment "amp-access-iframe" is not enabled.'); type = AccessType.CLIENT; } @@ -253,8 +254,7 @@ export class AccessSource { loginMap[k] = loginConfig[k]; } } else { - userAssert(false, - '"login" must be either a single URL or a map of URLs'); + userAssert(false, '"login" must be either a single URL or a map of URLs'); } // Check that all URLs are valid. @@ -285,8 +285,13 @@ export class AccessSource { * Do some initial setup. */ start() { - dev().fine(TAG, 'config:', this.type_, this.loginConfig_, - this.adapter_.getConfig()); + dev().fine( + TAG, + 'config:', + this.type_, + this.loginConfig_, + this.adapter_.getConfig() + ); // Calculate login URLs right away. this.buildLoginUrls_(); @@ -350,30 +355,31 @@ export class AccessSource { return Promise.resolve(); } - const responsePromise = - this.adapter_.authorize().catch(error => { - this.analyticsEvent_('access-authorization-failed'); - if (this.authorizationFallbackResponse_ && !opt_disableFallback) { - // Use fallback. - user().error(TAG, 'Authorization failed: ', error); - return this.authorizationFallbackResponse_; - } else { - // Rethrow the error, it will be processed in the bottom `catch`. - throw error; - } - }); - - const promise = responsePromise.then(response => { - dev().fine(TAG, 'Authorization response: ', response); - this.setAuthResponse_(response); - this.buildLoginUrls_(); - return response; - }).catch(error => { - user().error(TAG, 'Authorization failed: ', error); - this.firstAuthorizationResolver_(); - throw error; + const responsePromise = this.adapter_.authorize().catch(error => { + this.analyticsEvent_('access-authorization-failed'); + if (this.authorizationFallbackResponse_ && !opt_disableFallback) { + // Use fallback. + user().error(TAG, 'Authorization failed: ', error); + return this.authorizationFallbackResponse_; + } else { + // Rethrow the error, it will be processed in the bottom `catch`. + throw error; + } }); + const promise = responsePromise + .then(response => { + dev().fine(TAG, 'Authorization response: ', response); + this.setAuthResponse_(response); + this.buildLoginUrls_(); + return response; + }) + .catch(error => { + user().error(TAG, 'Authorization failed: ', error); + this.firstAuthorizationResolver_(); + throw error; + }); + return promise; } @@ -390,13 +396,16 @@ export class AccessSource { * @return {!Promise} */ reportViewToServer() { - return this.adapter_.pingback().then(() => { - dev().fine(TAG, 'Pingback complete'); - this.analyticsEvent_('access-pingback-sent'); - }).catch(error => { - this.analyticsEvent_('access-pingback-failed'); - throw user().createError('Pingback failed: ', error); - }); + return this.adapter_ + .pingback() + .then(() => { + dev().fine(TAG, 'Pingback complete'); + this.analyticsEvent_('access-pingback-sent'); + }) + .catch(error => { + this.analyticsEvent_('access-pingback-failed'); + throw user().createError('Pingback failed: ', error); + }); } /** @@ -416,11 +425,17 @@ export class AccessSource { * @return {!Promise} */ loginWithType(type) { - userAssert(this.loginConfig_[type], - 'Login URL is not configured: %s', type); + userAssert( + this.loginConfig_[type], + 'Login URL is not configured: %s', + type + ); // Login URL should always be available at this time. - const loginUrl = userAssert(this.loginUrlMap_[type], - 'Login URL is not ready: %s', type); + const loginUrl = userAssert( + this.loginUrlMap_[type], + 'Login URL is not ready: %s', + type + ); return this.login_(loginUrl, type); } @@ -455,7 +470,7 @@ export class AccessSource { // 1 second, however, the new login request will be allowed to proceed, // given that we cannot always determine fully if the previous attempt is // "stuck". - if (this.loginPromise_ && (now - this.loginStartTime_ < 1000)) { + if (this.loginPromise_ && now - this.loginStartTime_ < 1000) { return this.loginPromise_; } @@ -463,38 +478,41 @@ export class AccessSource { this.loginAnalyticsEvent_(eventLabel, 'started'); const dialogPromise = this.openLoginDialog_(loginUrl); - const loginPromise = dialogPromise.then(result => { - dev().fine(TAG, 'Login dialog completed: ', eventLabel, result); - this.loginPromise_ = null; - const query = parseQueryString(result); - const s = query['success']; - const success = (s == 'true' || s == 'yes' || s == '1'); - if (success) { - this.loginAnalyticsEvent_(eventLabel, 'success'); - } else { - this.loginAnalyticsEvent_(eventLabel, 'rejected'); - } - if (success || !s) { - // In case of a success, repeat the authorization and pingback flows. - // Also do this for an empty response to avoid false negatives. - // Pingback is repeated in this case since this could now be a new - // "view" with a different access profile. - this.adapter_.postAction(); - const authorizationPromise = this.runAuthorization( - /* disableFallback */ true); - this.onReauthorize_(authorizationPromise); - return authorizationPromise.then(() => { - this.scheduleView_(/* timeToView */ 0); - }); - } - }).catch(reason => { - dev().fine(TAG, 'Login dialog failed: ', eventLabel, reason); - this.loginAnalyticsEvent_(eventLabel, 'failed'); - if (this.loginPromise_ == loginPromise) { + const loginPromise = dialogPromise + .then(result => { + dev().fine(TAG, 'Login dialog completed: ', eventLabel, result); this.loginPromise_ = null; - } - throw reason; - }); + const query = parseQueryString(result); + const s = query['success']; + const success = s == 'true' || s == 'yes' || s == '1'; + if (success) { + this.loginAnalyticsEvent_(eventLabel, 'success'); + } else { + this.loginAnalyticsEvent_(eventLabel, 'rejected'); + } + if (success || !s) { + // In case of a success, repeat the authorization and pingback flows. + // Also do this for an empty response to avoid false negatives. + // Pingback is repeated in this case since this could now be a new + // "view" with a different access profile. + this.adapter_.postAction(); + const authorizationPromise = this.runAuthorization( + /* disableFallback */ true + ); + this.onReauthorize_(authorizationPromise); + return authorizationPromise.then(() => { + this.scheduleView_(/* timeToView */ 0); + }); + } + }) + .catch(reason => { + dev().fine(TAG, 'Login dialog failed: ', eventLabel, reason); + this.loginAnalyticsEvent_(eventLabel, 'failed'); + if (this.loginPromise_ == loginPromise) { + this.loginPromise_ = null; + } + throw reason; + }); this.loginPromise_ = loginPromise; this.loginStartTime_ = now; return this.loginPromise_; @@ -523,11 +541,13 @@ export class AccessSource { const promises = []; for (const k in this.loginConfig_) { promises.push( - this.buildUrl(this.loginConfig_[k], /* useAuthData */ true) - .then(url => { - this.loginUrlMap_[k] = url; - return {type: k, url}; - })); + this.buildUrl(this.loginConfig_[k], /* useAuthData */ true).then( + url => { + this.loginUrlMap_[k] = url; + return {type: k, url}; + } + ) + ); } return Promise.all(promises); } @@ -542,12 +562,10 @@ export class AccessSource { */ export let AccessTypeAdapterContextDef; - /** * @interface */ export class AccessTypeAdapterDef { - /** * @return {!JsonObject} */ diff --git a/extensions/amp-access/0.1/amp-access-vendor.js b/extensions/amp-access/0.1/amp-access-vendor.js index bea0f630bfe0..21bb90daa113 100644 --- a/extensions/amp-access/0.1/amp-access-vendor.js +++ b/extensions/amp-access/0.1/amp-access-vendor.js @@ -21,7 +21,6 @@ import {dev, userAssert} from '../../../src/log'; /** @const {string} */ const TAG = 'amp-access-vendor'; - /** * The adapter for a vendor implementation that implements `AccessVendor` * interface and delivered via a separate extension. The vendor implementation @@ -30,7 +29,6 @@ const TAG = 'amp-access-vendor'; * @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessVendorAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -40,8 +38,10 @@ export class AccessVendorAdapter { this.ampdoc = ampdoc; /** @const @private {string} */ - this.vendorName_ = userAssert(configJson['vendor'], - '"vendor" name must be specified'); + this.vendorName_ = userAssert( + configJson['vendor'], + '"vendor" name must be specified' + ); /** @const @private {!JsonObject} */ this.vendorConfig_ = configJson[this.vendorName_] || {}; diff --git a/extensions/amp-access/0.1/amp-access.js b/extensions/amp-access/0.1/amp-access.js index ba3d60a18b75..8dd477147c3e 100644 --- a/extensions/amp-access/0.1/amp-access.js +++ b/extensions/amp-access/0.1/amp-access.js @@ -34,7 +34,6 @@ import {listenOnce} from '../../../src/event-helper'; import {startsWith} from '../../../src/string'; import {triggerAnalyticsEvent} from '../../../src/analytics'; - /** @const */ const TAG = 'amp-access'; @@ -44,7 +43,6 @@ const VIEW_TIMEOUT = 2000; /** @const {string} */ const TEMPLATE_PROP = '__AMP_ACCESS__TEMPLATE'; - /** * AccessService implements the complete lifecycle of the AMP Access system. * @implements {AccessVars} @@ -138,8 +136,9 @@ export class AccessService { }); // Re-authorize newly added sections. - ampdoc.getRootNode().addEventListener(AmpEvents.DOM_UPDATE, - this.onDomUpdate_.bind(this)); + ampdoc + .getRootNode() + .addEventListener(AmpEvents.DOM_UPDATE, this.onDomUpdate_.bind(this)); } /** @override from AccessVars */ @@ -159,8 +158,10 @@ export class AccessService { // No consent - an essential part of the access system. const consent = Promise.resolve(); this.readerIdPromise_ = this.cid_.then(cid => { - return cid.get({scope: 'amp-access', createCookieIfNotPresent: true}, - consent); + return cid.get( + {scope: 'amp-access', createCookieIfNotPresent: true}, + consent + ); }); } return this.readerIdPromise_; @@ -186,9 +187,11 @@ export class AccessService { * @private */ parseConfig_() { - userAssert(isJsonScriptTag(this.accessElement_), - `${TAG} config should ` + - 'be inside a '); - expect(creative).to.not.contain( - ''); - expect(impl.getAmpAdMetadata()).to.jsonEqual({ - minifiedCreative: creative, - customElementExtensions: ['amp-mustache'], - extensions: [], - }); + () => {} + ) + .then(buffer => Promise.resolve(utf8Decode(buffer))) + .then(creative => { + expect(creative).to.not.contain( + '' + ); + expect(creative).to.not.contain( + '' + ); + expect(impl.getAmpAdMetadata()).to.jsonEqual({ + minifiedCreative: creative, + customElementExtensions: ['amp-mustache'], + extensions: [], }); + }); }); }); @@ -152,18 +164,22 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => {

    {{foo}}

    `; - fetchTextMock.withArgs( + fetchTextMock + .withArgs( 'https://www-adzerk-com.cdn.ampproject.org/ad/s/www.adzerk.com/456', { mode: 'cors', method: 'GET', ampCors: false, credentials: 'omit', - }).returns(Promise.resolve( - { + } + ) + .returns( + Promise.resolve({ headers: {}, text: () => template, - })); + }) + ); }); it('should auto add amp-analytics if required', () => { @@ -171,7 +187,8 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => { templateUrl: 'https://www.adzerk.com/456', analytics: {'type': 'googleanalytics'}, }; - return impl.maybeValidateAmpCreative( + return impl + .maybeValidateAmpCreative( utf8Encode(JSON.stringify(adResponseBody)).buffer, { get: name => { @@ -179,21 +196,22 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => { return 'amp-mustache'; }, }, - () => {}) - .then(buffer => utf8Decode(buffer)) - .then(creative => { - expect(impl.getAmpAdMetadata()).to.jsonEqual({ - minifiedCreative: creative, - customElementExtensions: ['amp-analytics', 'amp-mustache'], - extensions: [], - }); - // Won't insert duplicate - expect(impl.getAmpAdMetadata()).to.jsonEqual({ - minifiedCreative: creative, - customElementExtensions: ['amp-analytics', 'amp-mustache'], - extensions: [], - }); + () => {} + ) + .then(buffer => utf8Decode(buffer)) + .then(creative => { + expect(impl.getAmpAdMetadata()).to.jsonEqual({ + minifiedCreative: creative, + customElementExtensions: ['amp-analytics', 'amp-mustache'], + extensions: [], + }); + // Won't insert duplicate + expect(impl.getAmpAdMetadata()).to.jsonEqual({ + minifiedCreative: creative, + customElementExtensions: ['amp-analytics', 'amp-mustache'], + extensions: [], }); + }); }); it('should not add amp-analytics if not', () => { @@ -201,7 +219,8 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => { templateUrl: 'https://www.adzerk.com/456', analytics: undefined, }; - return impl.maybeValidateAmpCreative( + return impl + .maybeValidateAmpCreative( utf8Encode(JSON.stringify(adResponseBody)).buffer, { get: name => { @@ -209,15 +228,16 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => { return 'amp-mustache'; }, }, - () => {}) - .then(buffer => utf8Decode(buffer)) - .then(creative => { - expect(impl.getAmpAdMetadata()).to.jsonEqual({ - minifiedCreative: creative, - customElementExtensions: ['amp-mustache'], - extensions: [], - }); + () => {} + ) + .then(buffer => utf8Decode(buffer)) + .then(creative => { + expect(impl.getAmpAdMetadata()).to.jsonEqual({ + minifiedCreative: creative, + customElementExtensions: ['amp-mustache'], + extensions: [], }); + }); }); }); }); diff --git a/extensions/amp-ad-network-cloudflare-impl/0.1/amp-ad-network-cloudflare-impl.js b/extensions/amp-ad-network-cloudflare-impl/0.1/amp-ad-network-cloudflare-impl.js index 1ec0377a134e..533bfa488795 100644 --- a/extensions/amp-ad-network-cloudflare-impl/0.1/amp-ad-network-cloudflare-impl.js +++ b/extensions/amp-ad-network-cloudflare-impl/0.1/amp-ad-network-cloudflare-impl.js @@ -27,7 +27,6 @@ import {startsWith} from '../../../src/string'; * additional guidance on other implementation details. */ export class AmpAdNetworkCloudflareImpl extends AmpA4A { - /** * Validate the tag parameters. If invalid, ad ad will not be displayed. * @override @@ -103,10 +102,16 @@ export class AmpAdNetworkCloudflareImpl extends AmpA4A { let pre = url.indexOf('?') < 0 ? '?' : '&'; for (let i = 0; i < el.attributes.length; i++) { const attrib = el.attributes[i]; - if (attrib.specified && startsWith(attrib.name, 'data-') - && !startsWith(attrib.name, 'data-cf-')) { - url += pre + encodeURIComponent(attrib.name.substring(5)) + - '=' + encodeURIComponent(this.replacements(attrib.value, values)); + if ( + attrib.specified && + startsWith(attrib.name, 'data-') && + !startsWith(attrib.name, 'data-cf-') + ) { + url += + pre + + encodeURIComponent(attrib.name.substring(5)) + + '=' + + encodeURIComponent(this.replacements(attrib.value, values)); pre = '&'; } } @@ -115,8 +120,9 @@ export class AmpAdNetworkCloudflareImpl extends AmpA4A { } } - AMP.extension('amp-ad-network-cloudflare-impl', '0.1', AMP => { - AMP.registerElement('amp-ad-network-cloudflare-impl', - AmpAdNetworkCloudflareImpl); + AMP.registerElement( + 'amp-ad-network-cloudflare-impl', + AmpAdNetworkCloudflareImpl + ); }); diff --git a/extensions/amp-ad-network-cloudflare-impl/0.1/test/test-amp-ad-network-cloudflare-impl.js b/extensions/amp-ad-network-cloudflare-impl/0.1/test/test-amp-ad-network-cloudflare-impl.js index 7a017f27562a..6f80ab193410 100644 --- a/extensions/amp-ad-network-cloudflare-impl/0.1/test/test-amp-ad-network-cloudflare-impl.js +++ b/extensions/amp-ad-network-cloudflare-impl/0.1/test/test-amp-ad-network-cloudflare-impl.js @@ -23,155 +23,168 @@ import { import {cloudflareIsA4AEnabled} from '../cloudflare-a4a-config'; import {createElementWithAttributes} from '../../../../src/dom'; - -describes.realWin('cloudflare-a4a-config', { - amp: { - extensions: ['amp-ad', 'amp-ad-network-cloudflare-impl'], +describes.realWin( + 'cloudflare-a4a-config', + { + amp: { + extensions: ['amp-ad', 'amp-ad-network-cloudflare-impl'], + }, }, -}, env => { - let doc; - let win; - beforeEach(() => { - win = env.win; - doc = env.win.document; - }); - it('should pass a4a config predicate', () => { - const el = createElementWithAttributes(doc, 'amp-ad', { - 'data-cf-network': 'cloudflare', - src: '/ad.html', - 'data-cf-a4a': 'true', - }); - expect(cloudflareIsA4AEnabled(win, el)).to.be.true; - }); - - it('should not pass a4a config predicate when useRemoteHtml is true', () => { - const el = createElementWithAttributes(doc, 'amp-ad', { - 'data-cf-network': 'cloudflare', - src: '/ad.html', - 'data-cf-a4a': 'true', + env => { + let doc; + let win; + beforeEach(() => { + win = env.win; + doc = env.win.document; }); - const useRemoteHtml = true; - expect(cloudflareIsA4AEnabled(win, el, useRemoteHtml)).to.be.false; - }); -}); - -describes.realWin('amp-ad-network-cloudflare-impl', { - amp: { - extensions: ['amp-ad', 'amp-ad-network-cloudflare-impl'], - }, -}, env => { - - let cloudflareImpl; - let el; - let doc; - - beforeEach(() => { - doc = env.win.document; - el = doc.createElement('amp-ad'); - el.setAttribute('type', 'cloudflare'); - el.setAttribute('data-cf-network', 'cloudflare'); - el.setAttribute('src', - 'https://firebolt.cloudflaredemo.com/a4a-ad.html'); - sandbox.stub( - AmpAdNetworkCloudflareImpl.prototype, - 'getSigningServiceNames').callsFake( - () => { - return ['cloudflare','cloudflare-dev']; - }); - sandbox.stub(vendors, 'NETWORKS').callsFake({ - cloudflare: { - base: 'https://firebolt.cloudflaredemo.com', - }, - - 'cf-test': { - base: 'https://cf-test.com', - src: 'https://cf-test.com/path/ad?width=SLOT_WIDTH&height=SLOT_HEIGHT', - }, + it('should pass a4a config predicate', () => { + const el = createElementWithAttributes(doc, 'amp-ad', { + 'data-cf-network': 'cloudflare', + src: '/ad.html', + 'data-cf-a4a': 'true', + }); + expect(cloudflareIsA4AEnabled(win, el)).to.be.true; }); - sandbox.stub(el, 'tryUpgrade_').callsFake(() => {}); - doc.body.appendChild(el); - cloudflareImpl = new AmpAdNetworkCloudflareImpl(el); - }); - - describe('#isValidElement', () => { - it('should be valid', () => { - expect(cloudflareImpl.isValidElement()).to.be.true; + + it('should not pass a4a config predicate when useRemoteHtml is true', () => { + const el = createElementWithAttributes(doc, 'amp-ad', { + 'data-cf-network': 'cloudflare', + src: '/ad.html', + 'data-cf-a4a': 'true', + }); + const useRemoteHtml = true; + expect(cloudflareIsA4AEnabled(win, el, useRemoteHtml)).to.be.false; }); - it('should NOT be valid (impl tag name)', () => { - el = doc.createElement('amp-ad-network-cloudflare-impl'); + } +); + +describes.realWin( + 'amp-ad-network-cloudflare-impl', + { + amp: { + extensions: ['amp-ad', 'amp-ad-network-cloudflare-impl'], + }, + }, + env => { + let cloudflareImpl; + let el; + let doc; + + beforeEach(() => { + doc = env.win.document; + el = doc.createElement('amp-ad'); el.setAttribute('type', 'cloudflare'); + el.setAttribute('data-cf-network', 'cloudflare'); + el.setAttribute('src', 'https://firebolt.cloudflaredemo.com/a4a-ad.html'); + sandbox + .stub(AmpAdNetworkCloudflareImpl.prototype, 'getSigningServiceNames') + .callsFake(() => { + return ['cloudflare', 'cloudflare-dev']; + }); + sandbox.stub(vendors, 'NETWORKS').callsFake({ + cloudflare: { + base: 'https://firebolt.cloudflaredemo.com', + }, + + 'cf-test': { + base: 'https://cf-test.com', + src: + 'https://cf-test.com/path/ad?width=SLOT_WIDTH&height=SLOT_HEIGHT', + }, + }); + sandbox.stub(el, 'tryUpgrade_').callsFake(() => {}); + doc.body.appendChild(el); cloudflareImpl = new AmpAdNetworkCloudflareImpl(el); - expect(cloudflareImpl.isValidElement()).to.be.false; }); - }); - describe('#getAdUrl', () => { - - it('should be valid', () => { - expect(cloudflareImpl.getAdUrl()).to.equal( - 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html'); + describe('#isValidElement', () => { + it('should be valid', () => { + expect(cloudflareImpl.isValidElement()).to.be.true; + }); + it('should NOT be valid (impl tag name)', () => { + el = doc.createElement('amp-ad-network-cloudflare-impl'); + el.setAttribute('type', 'cloudflare'); + cloudflareImpl = new AmpAdNetworkCloudflareImpl(el); + expect(cloudflareImpl.isValidElement()).to.be.false; + }); }); - it('should handle non-a4a URLs', () => { - el.setAttribute('data-cf-a4a', 'false'); - expect(cloudflareImpl.getAdUrl()).to.equal( - 'https://firebolt.cloudflaredemo.com/a4a-ad.html'); - }); + describe('#getAdUrl', () => { + it('should be valid', () => { + expect(cloudflareImpl.getAdUrl()).to.equal( + 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html' + ); + }); - it('should accept a4a src', () => { - el.setAttribute('src', - 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html'); - expect(cloudflareImpl.getAdUrl()).to.equal( - 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html'); - }); + it('should handle non-a4a URLs', () => { + el.setAttribute('data-cf-a4a', 'false'); + expect(cloudflareImpl.getAdUrl()).to.equal( + 'https://firebolt.cloudflaredemo.com/a4a-ad.html' + ); + }); - it('should handle additional templated width/height', () => { - el.setAttribute('src', 'https://firebolt.cloudflaredemo.com/' - + 'ad?width=SLOT_WIDTH&height=SLOT_HEIGHT'); - expect(cloudflareImpl.getAdUrl()).to.equal( - 'https://firebolt.cloudflaredemo.com/_a4a/ad?width=0&height=0'); - }); + it('should accept a4a src', () => { + el.setAttribute( + 'src', + 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html' + ); + expect(cloudflareImpl.getAdUrl()).to.equal( + 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html' + ); + }); - function parseQuery(query) { - const kvs = query.split(/&/); - const params = {}; - for (let i = 0; i < kvs.length; i++) { - const parts = kvs[i].match(/^([^=]+)=?(.*)/); - params[parts[1]] = parts[2]; + it('should handle additional templated width/height', () => { + el.setAttribute( + 'src', + 'https://firebolt.cloudflaredemo.com/' + + 'ad?width=SLOT_WIDTH&height=SLOT_HEIGHT' + ); + expect(cloudflareImpl.getAdUrl()).to.equal( + 'https://firebolt.cloudflaredemo.com/_a4a/ad?width=0&height=0' + ); + }); + + function parseQuery(query) { + const kvs = query.split(/&/); + const params = {}; + for (let i = 0; i < kvs.length; i++) { + const parts = kvs[i].match(/^([^=]+)=?(.*)/); + params[parts[1]] = parts[2]; + } + return params; } - return params; - } - - it('should handle data parameters', () => { - el.setAttribute('src', 'https://firebolt.cloudflaredemo.com/ad'); - el.setAttribute('data-key', 'value'); - el.setAttribute('data-another', 'more'); - - const url = cloudflareImpl.getAdUrl(); - const base = 'https://firebolt.cloudflaredemo.com/_a4a/ad?'; - expect(url.substring(0, base.length)).to.equal(base); - expect(parseQuery(url.substring(base.length))).to.deep.equal({ - another: 'more', - key: 'value', + + it('should handle data parameters', () => { + el.setAttribute('src', 'https://firebolt.cloudflaredemo.com/ad'); + el.setAttribute('data-key', 'value'); + el.setAttribute('data-another', 'more'); + + const url = cloudflareImpl.getAdUrl(); + const base = 'https://firebolt.cloudflaredemo.com/_a4a/ad?'; + expect(url.substring(0, base.length)).to.equal(base); + expect(parseQuery(url.substring(base.length))).to.deep.equal({ + another: 'more', + key: 'value', + }); }); - }); - // TODO(bradfrizzell, #12476): Make this test work with sinon 4.0. - it.skip('should handle default src with data parameters', () => { - el.setAttribute('data-cf-network', 'cf-test'); - el.removeAttribute('src'); - el.setAttribute('data-key', 'value'); - el.setAttribute('data-another', 'more'); - - const url = cloudflareImpl.getAdUrl(); - const base = 'https://cf-test.com/_a4a/path/ad?'; - expect(url.substring(0, base.length)).to.equal(base); - expect(parseQuery(url.substring(base.length))).to.deep.equal({ - another: 'more', - height: '0', - key: 'value', - width: '0', + // TODO(bradfrizzell, #12476): Make this test work with sinon 4.0. + it.skip('should handle default src with data parameters', () => { + el.setAttribute('data-cf-network', 'cf-test'); + el.removeAttribute('src'); + el.setAttribute('data-key', 'value'); + el.setAttribute('data-another', 'more'); + + const url = cloudflareImpl.getAdUrl(); + const base = 'https://cf-test.com/_a4a/path/ad?'; + expect(url.substring(0, base.length)).to.equal(base); + expect(parseQuery(url.substring(base.length))).to.deep.equal({ + another: 'more', + height: '0', + key: 'value', + width: '0', + }); }); }); - }); -}); + } +); 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 5fd640fdebb6..2bf8ebc3a90a 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 @@ -106,7 +106,7 @@ const TAG = 'amp-ad-network-doubleclick-impl'; /** @const {string} */ const DOUBLECLICK_BASE_URL = - 'https://securepubads.g.doubleclick.net/gampad/ads'; + 'https://securepubads.g.doubleclick.net/gampad/ads'; /** @const {string} */ const RTC_SUCCESS = '2'; @@ -159,7 +159,6 @@ let LayoutRectOrDimsDef; /** @final */ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { - /** * @param {!Element} element */ @@ -252,8 +251,11 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { this.forceSafeframe = false; if ('forceSafeframe' in this.element.dataset) { if (!/^(1|(true))$/i.test(this.element.dataset['forceSafeframe'])) { - user().warn(TAG, 'Ignoring invalid data-force-safeframe attribute: ' + - this.element.dataset['forceSafeframe']); + user().warn( + TAG, + 'Ignoring invalid data-force-safeframe attribute: ' + + this.element.dataset['forceSafeframe'] + ); } else { this.forceSafeframe = true; } @@ -322,8 +324,9 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // being schedule due to being beyond viewport max offset. If slot // comes within standard outside viewport range, then ensure throttling // will not be applied. - this.getResource().whenWithinViewport(renderOutsideViewport).then( - () => this.isIdleRender_ = false); + this.getResource() + .whenWithinViewport(renderOutsideViewport) + .then(() => (this.isIdleRender_ = false)); return vpRange; } @@ -346,8 +349,10 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { * @visibleForTesting */ setPageLevelExperiments(urlExperimentId) { - if (!isCdnProxy(this.win) && !isExperimentOn( - this.win, 'expDfpInvOrigDeprecated')) { + if ( + !isCdnProxy(this.win) && + !isExperimentOn(this.win, 'expDfpInvOrigDeprecated') + ) { this.experimentIds.push('21060933'); } let forcedExperimentId; @@ -357,41 +362,44 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { '7': DOUBLECLICK_SRA_EXP_BRANCHES.SRA_CONTROL, '8': DOUBLECLICK_SRA_EXP_BRANCHES.SRA, '9': DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER, - }[urlExperimentId]; if (forcedExperimentId) { this.experimentIds.push(forcedExperimentId); } } - const experimentInfoMap = - /** @type {!Object} */ ({ - // Only select into SRA experiments if SRA not already explicitly - // enabled and refresh is not being used by any slot. - [DOUBLECLICK_SRA_EXP]: { - isTrafficEligible: () => !forcedExperimentId && - !this.win.document./*OK*/querySelector( - 'meta[name=amp-ad-enable-refresh], ' + - 'amp-ad[type=doubleclick][data-enable-refresh], ' + - 'meta[name=amp-ad-doubleclick-sra]'), - branches: Object.keys(DOUBLECLICK_SRA_EXP_BRANCHES).map( - key => DOUBLECLICK_SRA_EXP_BRANCHES[key]), - }, - [FLEXIBLE_AD_SLOTS_EXP]: { - isTrafficEligible: () => true, - branches: Object.values(FLEXIBLE_AD_SLOTS_BRANCHES), - }, - [[ADX_ADY_EXP.branch]]: { - isTrafficEligible: () => true, - branches: [[ADX_ADY_EXP.control], [ADX_ADY_EXP.experiment]], - }, - }); + // Only select into SRA experiments if SRA not already explicitly + // enabled and refresh is not being used by any slot. + [DOUBLECLICK_SRA_EXP]: { + isTrafficEligible: () => + !forcedExperimentId && + !this.win.document./*OK*/ querySelector( + 'meta[name=amp-ad-enable-refresh], ' + + 'amp-ad[type=doubleclick][data-enable-refresh], ' + + 'meta[name=amp-ad-doubleclick-sra]' + ), + branches: Object.keys(DOUBLECLICK_SRA_EXP_BRANCHES).map( + key => DOUBLECLICK_SRA_EXP_BRANCHES[key] + ), + }, + [FLEXIBLE_AD_SLOTS_EXP]: { + isTrafficEligible: () => true, + branches: Object.values(FLEXIBLE_AD_SLOTS_BRANCHES), + }, + [[ADX_ADY_EXP.branch]]: { + isTrafficEligible: () => true, + branches: [[ADX_ADY_EXP.control], [ADX_ADY_EXP.experiment]], + }, + }); const setExps = this.randomlySelectUnsetExperiments_(experimentInfoMap); - Object.keys(setExps).forEach(expName => - setExps[expName] && this.experimentIds.push(setExps[expName])); - if (setExps[FLEXIBLE_AD_SLOTS_EXP] && - setExps[FLEXIBLE_AD_SLOTS_EXP] == - FLEXIBLE_AD_SLOTS_BRANCHES.EXPERIMENT) { + Object.keys(setExps).forEach( + expName => setExps[expName] && this.experimentIds.push(setExps[expName]) + ); + if ( + setExps[FLEXIBLE_AD_SLOTS_EXP] && + setExps[FLEXIBLE_AD_SLOTS_EXP] == FLEXIBLE_AD_SLOTS_BRANCHES.EXPERIMENT + ) { this.sendFlexibleAdSlotParams_ = true; } } @@ -415,19 +423,24 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { /** @private */ maybeDeprecationWarn_() { - const warnDeprecation = feature => user().warn( - TAG, `${feature} is no longer supported for DoubleClick.` + + const warnDeprecation = feature => + user().warn( + TAG, + `${feature} is no longer supported for DoubleClick.` + 'Please refer to ' + 'https://github.com/ampproject/amphtml/issues/11834 ' + - 'for more information'); + 'for more information' + ); const usdrd = 'useSameDomainRenderingUntilDeprecated'; - const hasUSDRD = usdrd in this.element.dataset || - (tryParseJson(this.element.getAttribute('json')) || {})[usdrd]; + const hasUSDRD = + usdrd in this.element.dataset || + (tryParseJson(this.element.getAttribute('json')) || {})[usdrd]; if (hasUSDRD) { warnDeprecation(usdrd); } - const useRemoteHtml = - !!this.win.document.querySelector('meta[name=amp-3p-iframe-src]'); + const useRemoteHtml = !!this.win.document.querySelector( + 'meta[name=amp-3p-iframe-src]' + ); if (useRemoteHtml) { warnDeprecation('remote.html'); } @@ -438,24 +451,27 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { super.buildCallback(); this.maybeDeprecationWarn_(); this.setPageLevelExperiments(this.extractUrlExperimentId_()); - this.useSra = (getMode().localDev && /(\?|&)force_sra=true(&|$)/.test( - this.win.location.search)) || - !!this.win.document.querySelector( - 'meta[name=amp-ad-doubleclick-sra]') || - [DOUBLECLICK_SRA_EXP_BRANCHES.SRA, - DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER].some( - eid => this.experimentIds.indexOf(eid) >= 0); + this.useSra = + (getMode().localDev && + /(\?|&)force_sra=true(&|$)/.test(this.win.location.search)) || + !!this.win.document.querySelector('meta[name=amp-ad-doubleclick-sra]') || + [ + DOUBLECLICK_SRA_EXP_BRANCHES.SRA, + DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER, + ].some(eid => this.experimentIds.indexOf(eid) >= 0); this.identityTokenPromise_ = Services.viewerForDoc(this.getAmpDoc()) - .whenFirstVisible().then(() => - getIdentityToken( - this.win, this.getAmpDoc(), super.getConsentPolicy())); + .whenFirstVisible() + .then(() => + getIdentityToken(this.win, this.getAmpDoc(), super.getConsentPolicy()) + ); this.troubleshootData_.slotId = this.element.getAttribute('data-slot'); - this.troubleshootData_.slotIndex = - this.element.getAttribute('data-amp-slot-index'); + this.troubleshootData_.slotIndex = this.element.getAttribute( + 'data-amp-slot-index' + ); if (!this.isFluidRequest_) { const multiSizeStr = this.element.getAttribute('data-multi-size'); - this.isFluidRequest_ = !!multiSizeStr && - multiSizeStr.indexOf('fluid') != -1; + this.isFluidRequest_ = + !!multiSizeStr && multiSizeStr.indexOf('fluid') != -1; } this.maybeAddSinglePassExperiment(); } @@ -476,8 +492,11 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { instances = instances || [this]; const tokens = getPageviewStateTokensForAdRequest(instances); return { - 'npa': consentState == CONSENT_POLICY_STATE.INSUFFICIENT || - consentState == CONSENT_POLICY_STATE.UNKNOWN ? 1 : null, + 'npa': + consentState == CONSENT_POLICY_STATE.INSUFFICIENT || + consentState == CONSENT_POLICY_STATE.UNKNOWN + ? 1 + : null, 'gdfp_req': '1', 'sfv': DEFAULT_SAFEFRAME_VERSION, 'u_sd': this.win.devicePixelRatio, @@ -496,47 +515,58 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { devAssert(this.jsonTargeting); const tfcd = this.jsonTargeting && this.jsonTargeting[TFCD]; this.win['ampAdGoogleIfiCounter'] = this.win['ampAdGoogleIfiCounter'] || 1; - this.ifi_ = (this.isRefreshing && this.ifi_) || - this.win['ampAdGoogleIfiCounter']++; - const pageLayoutBox = this.isSinglePageStoryAd ? - this.element.getPageLayoutBox() : null; + this.ifi_ = + (this.isRefreshing && this.ifi_) || this.win['ampAdGoogleIfiCounter']++; + const pageLayoutBox = this.isSinglePageStoryAd + ? this.element.getPageLayoutBox() + : null; let psz = null; let msz = null; if (this.sendFlexibleAdSlotParams_) { const parentWidth = getContainerWidth( - this.win, this.element.parentElement); + this.win, + this.element.parentElement + ); let slotWidth = getContainerWidth( - this.win, this.element, 1 /* maxDepth */); + this.win, + this.element, + 1 /* maxDepth */ + ); slotWidth = slotWidth == -1 ? parentWidth : slotWidth; psz = `${parentWidth}x-1`; msz = `${slotWidth}x-1`; } - return Object.assign({ - 'iu': this.element.getAttribute('data-slot'), - 'co': this.jsonTargeting && - this.jsonTargeting['cookieOptOut'] ? '1' : null, - 'adk': this.adKey, - 'sz': this.isSinglePageStoryAd ? '1x1' : this.parameterSize, - 'output': 'html', - 'impl': 'ifr', - 'tfcd': tfcd == undefined ? null : tfcd, - 'adtest': isInManualExperiment(this.element) ? 'on' : null, - 'ifi': this.ifi_, - 'rc': this.refreshCount_ || null, - 'frc': Number(this.fromResumeCallback) || null, - 'fluid': this.isFluidRequest_ ? 'height' : null, - 'fsf': this.forceSafeframe ? '1' : null, - // Both msz/psz send a height of -1 because height expansion is - // disallowed in AMP. - 'msz': msz, - 'psz': psz, - 'scp': serializeTargeting( + return Object.assign( + { + 'iu': this.element.getAttribute('data-slot'), + 'co': + this.jsonTargeting && this.jsonTargeting['cookieOptOut'] ? '1' : null, + 'adk': this.adKey, + 'sz': this.isSinglePageStoryAd ? '1x1' : this.parameterSize, + 'output': 'html', + 'impl': 'ifr', + 'tfcd': tfcd == undefined ? null : tfcd, + 'adtest': isInManualExperiment(this.element) ? 'on' : null, + 'ifi': this.ifi_, + 'rc': this.refreshCount_ || null, + 'frc': Number(this.fromResumeCallback) || null, + 'fluid': this.isFluidRequest_ ? 'height' : null, + 'fsf': this.forceSafeframe ? '1' : null, + // Both msz/psz send a height of -1 because height expansion is + // disallowed in AMP. + 'msz': msz, + 'psz': psz, + 'scp': serializeTargeting( (this.jsonTargeting && this.jsonTargeting['targeting']) || null, - (this.jsonTargeting && - this.jsonTargeting['categoryExclusions']) || null), - 'spsa': this.isSinglePageStoryAd ? - `${pageLayoutBox.width}x${pageLayoutBox.height}` : null, - }, googleBlockParameters(this)); + (this.jsonTargeting && this.jsonTargeting['categoryExclusions']) || + null + ), + 'spsa': this.isSinglePageStoryAd + ? `${pageLayoutBox.width}x${pageLayoutBox.height}` + : null, + }, + googleBlockParameters(this) + ); } /** @@ -547,38 +577,42 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { populateAdUrlState(consentState) { this.consentState = consentState; // Allow for pub to override height/width via override attribute. - const width = Number(this.element.getAttribute('data-override-width')) || + const width = + Number(this.element.getAttribute('data-override-width')) || Number(this.element.getAttribute('width')); - const height = Number(this.element.getAttribute('data-override-height')) || + const height = + Number(this.element.getAttribute('data-override-height')) || Number(this.element.getAttribute('height')); - this.initialSize_ = this.isFluidPrimaryRequest_ ? {width: 0, height: 0} : - (width && height ? - // width/height could be 'auto' in which case we fallback to measured. - {width, height} : this.getIntersectionElementLayoutBox()); - this.jsonTargeting = - tryParseJson(this.element.getAttribute('json')) || {}; + this.initialSize_ = this.isFluidPrimaryRequest_ + ? {width: 0, height: 0} + : width && height + ? // width/height could be 'auto' in which case we fallback to measured. + {width, height} + : this.getIntersectionElementLayoutBox(); + this.jsonTargeting = tryParseJson(this.element.getAttribute('json')) || {}; this.adKey = this.generateAdKey_( - `${this.initialSize_.width}x${this.initialSize_.height}`); + `${this.initialSize_.width}x${this.initialSize_.height}` + ); this.parameterSize = this.isFluidPrimaryRequest_ ? DUMMY_FLUID_SIZE : `${this.initialSize_.width}x${this.initialSize_.height}`; const multiSizeDataStr = this.element.getAttribute('data-multi-size'); if (multiSizeDataStr) { - const multiSizeValidation = this.element - .getAttribute('data-multi-size-validation') || 'true'; + const multiSizeValidation = + this.element.getAttribute('data-multi-size-validation') || 'true'; // The following call will check all specified multi-size dimensions, // verify that they meet all requirements, and then return all the valid // dimensions in an array. const dimensions = getMultiSizeDimensions( - multiSizeDataStr, - this.initialSize_.width, - this.initialSize_.height, - multiSizeValidation == 'true', - this.isFluidPrimaryRequest_); + multiSizeDataStr, + this.initialSize_.width, + this.initialSize_.height, + multiSizeValidation == 'true', + this.isFluidPrimaryRequest_ + ); if (dimensions.length) { - this.parameterSize += '|' + dimensions - .map(dimension => dimension.join('x')) - .join('|'); + this.parameterSize += + '|' + dimensions.map(dimension => dimension.join('x')).join('|'); } } } @@ -595,8 +629,10 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { if (this.useSra) { this.sraDeferred = this.sraDeferred || new Deferred(); } - if (consentState == CONSENT_POLICY_STATE.UNKNOWN && - this.element.getAttribute('data-npa-on-unknown-consent') != 'true') { + if ( + consentState == CONSENT_POLICY_STATE.UNKNOWN && + this.element.getAttribute('data-npa-on-unknown-consent') != 'true' + ) { user().info(TAG, 'Ad request suppressed due to unknown consent'); this.getAdUrlDeferred.resolve(''); return Promise.resolve(''); @@ -615,24 +651,29 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // validateData, from 3p/3p/js, after noving it someplace common. const startTime = Date.now(); const identityPromise = Services.timerFor(this.win) - .timeoutPromise(1000, this.identityTokenPromise_) - .catch(() => { - // On error/timeout, proceed. - return /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/({}); - }); + .timeoutPromise(1000, this.identityTokenPromise_) + .catch(() => { + // On error/timeout, proceed. + return /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/ ({}); + }); const checkStillCurrent = this.verifyStillCurrent(); - Promise.all([opt_rtcResponsesPromise, identityPromise]) - .then(results => { - checkStillCurrent(); - const rtcParams = this.mergeRtcResponses_(results[0]); - this.identityToken = results[1]; - googleAdUrl( - this, DOUBLECLICK_BASE_URL, startTime, Object.assign( - this.getBlockParameters_(), this.buildIdentityParams(), - this.getPageParameters(consentState), rtcParams), - this.experimentIds) - .then(adUrl => this.getAdUrlDeferred.resolve(adUrl)); - }); + Promise.all([opt_rtcResponsesPromise, identityPromise]).then(results => { + checkStillCurrent(); + const rtcParams = this.mergeRtcResponses_(results[0]); + this.identityToken = results[1]; + googleAdUrl( + this, + DOUBLECLICK_BASE_URL, + startTime, + Object.assign( + this.getBlockParameters_(), + this.buildIdentityParams(), + this.getPageParameters(consentState), + rtcParams + ), + this.experimentIds + ).then(adUrl => this.getAdUrlDeferred.resolve(adUrl)); + }); this.troubleshootData_.adUrl = this.getAdUrlDeferred.promise; return this.getAdUrlDeferred.promise; } @@ -642,11 +683,13 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { * @return {!Object} */ buildIdentityParams() { - return this.identityToken ? { - adsid: this.identityToken.token || null, - jar: this.identityToken.jar || null, - pucrd: this.identityToken.pucrd || null, - } : {}; + return this.identityToken + ? { + adsid: this.identityToken.token || null, + jar: this.identityToken.jar || null, + pucrd: this.identityToken.pucrd || null, + } + : {}; } /** @@ -673,13 +716,12 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { if (rtcResponse.response) { if (rtcResponse.response['targeting']) { const rewrittenResponse = this.rewriteRtcKeys_( - rtcResponse.response['targeting'], - rtcResponse.callout); - this.jsonTargeting['targeting'] = - !!this.jsonTargeting['targeting'] ? - deepMerge(this.jsonTargeting['targeting'], - rewrittenResponse) : - rewrittenResponse; + rtcResponse.response['targeting'], + rtcResponse.callout + ); + this.jsonTargeting['targeting'] = !!this.jsonTargeting['targeting'] + ? deepMerge(this.jsonTargeting['targeting'], rewrittenResponse) + : rewrittenResponse; } if (rtcResponse.response['categoryExclusions']) { if (!exclusions) { @@ -724,11 +766,15 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { REFERRER: opt_timeout => this.getReferrer_(opt_timeout), TGT: () => JSON.stringify( - (tryParseJson( - this.element.getAttribute('json')) || {})['targeting']), - ADCID: opt_timeout => getOrCreateAdCid( - this.getAmpDoc(), 'AMP_ECID_GOOGLE', '_ga', - parseInt(opt_timeout, 10)), + (tryParseJson(this.element.getAttribute('json')) || {})['targeting'] + ), + ADCID: opt_timeout => + getOrCreateAdCid( + this.getAmpDoc(), + 'AMP_ECID_GOOGLE', + '_ga', + parseInt(opt_timeout, 10) + ), ATTR: name => { if (!whitelist[name.toLowerCase()]) { dev().warn('TAG', `Invalid attribute ${name}`); @@ -751,14 +797,15 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { */ getReferrer_(opt_timeout) { const timeoutInt = parseInt(opt_timeout, 10); - const referrerPromise = Services.viewerForDoc(this.getAmpDoc()) - .getReferrerUrl(); + const referrerPromise = Services.viewerForDoc( + this.getAmpDoc() + ).getReferrerUrl(); if (isNaN(timeoutInt) || timeoutInt < 0) { return referrerPromise; } return Services.timerFor(this.win) - .timeoutPromise(timeoutInt, referrerPromise) - .catch(() => undefined); + .timeoutPromise(timeoutInt, referrerPromise) + .catch(() => undefined); } /** @@ -794,7 +841,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } const checksum = headers.get('AMP-Verification-Checksum'); return Promise.resolve( - checksum && stringHash32(utf8Decode(bytes)) == checksum ? bytes : null); + checksum && stringHash32(utf8Decode(bytes)) == checksum ? bytes : null + ); } /** @override */ @@ -802,14 +850,18 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { this.ampAnalyticsConfig_ = extractAmpAnalyticsConfig(this, responseHeaders); this.qqid_ = responseHeaders.get(QQID_HEADER); this.shouldSandbox_ = responseHeaders.get(SANDBOX_HEADER) == 'true'; - this.troubleshootData_.creativeId = - dev().assertString(responseHeaders.get('google-creative-id') || '-1'); - this.troubleshootData_.lineItemId = - dev().assertString(responseHeaders.get('google-lineitem-id') || '-1'); + this.troubleshootData_.creativeId = dev().assertString( + responseHeaders.get('google-creative-id') || '-1' + ); + this.troubleshootData_.lineItemId = dev().assertString( + responseHeaders.get('google-lineitem-id') || '-1' + ); if (this.ampAnalyticsConfig_) { // Load amp-analytics extensions - this.extensions_./*OK*/installExtensionForDoc( - this.getAmpDoc(), 'amp-analytics'); + this.extensions_./*OK*/ installExtensionForDoc( + this.getAmpDoc(), + 'amp-analytics' + ); } // If the server returned a size, use that, otherwise use the size that we // sent in the ad request. @@ -831,7 +883,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { if (responseHeaders.get('amp-ff-pageview-tokens')) { this.removePageviewStateToken(); this.setPageviewStateToken( - dev().assertString(responseHeaders.get('amp-ff-pageview-tokens'))); + dev().assertString(responseHeaders.get('amp-ff-pageview-tokens')) + ); } return size; @@ -853,15 +906,17 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { const height = Number(this.element.getAttribute('height')); return width && height ? {width, height} - // width/height could be 'auto' in which case we fallback to measured. - : this.getIntersectionElementLayoutBox(); + : // width/height could be 'auto' in which case we fallback to measured. + this.getIntersectionElementLayoutBox(); } /** @override */ tearDownSlot() { super.tearDownSlot(); - this.element.setAttribute('data-amp-slot-index', - this.win.ampAdSlotIdCounter++); + this.element.setAttribute( + 'data-amp-slot-index', + this.win.ampAdSlotIdCounter++ + ); if (this.ampAnalyticsElement_) { removeElement(this.ampAnalyticsElement_); this.ampAnalyticsElement_ = null; @@ -891,8 +946,10 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // non-AMP creatives. This is not done in the scheduler to ensure as many // slots as possible are marked for layout given scheduler imposes 5 seconds // past previous execution. - if (this.postAdResponseExperimentFeatures['render-idle-throttle'] && - this.isIdleRender_) { + if ( + this.postAdResponseExperimentFeatures['render-idle-throttle'] && + this.isIdleRender_ + ) { if (is3pThrottled(this.win)) { return waitFor3pThrottle().then(() => super.renderNonAmpCreative()); } else { @@ -951,27 +1008,35 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { onCreativeRender(creativeMetaData, opt_onLoadPromise) { super.onCreativeRender(creativeMetaData); this.isAmpCreative_ = !!creativeMetaData; - if (creativeMetaData && - !creativeMetaData.customElementExtensions.includes('amp-ad-exit')) { + if ( + creativeMetaData && + !creativeMetaData.customElementExtensions.includes('amp-ad-exit') + ) { // Capture phase click handlers on the ad if amp-ad-exit not present // (assume it will handle capture). devAssert(this.iframe); Navigation.installAnchorClickInterceptor( - this.getAmpDoc(), this.iframe.contentWindow); + this.getAmpDoc(), + this.iframe.contentWindow + ); } if (this.ampAnalyticsConfig_) { devAssert(!this.ampAnalyticsElement_); if (isReportingEnabled(this)) { addCsiSignalsToAmpAnalyticsConfig( - this.win, - this.element, - this.ampAnalyticsConfig_, - this.qqid_, - !!creativeMetaData); + this.win, + this.element, + this.ampAnalyticsConfig_, + this.qqid_, + !!creativeMetaData + ); } this.ampAnalyticsElement_ = insertAnalyticsElement( - this.element, this.ampAnalyticsConfig_, /*loadAnalytics*/ true, - !!this.postAdResponseExperimentFeatures['avr_disable_immediate']); + this.element, + this.ampAnalyticsConfig_, + /*loadAnalytics*/ true, + !!this.postAdResponseExperimentFeatures['avr_disable_immediate'] + ); } if (this.isRefreshing) { devAssert(this.refreshManager_); @@ -984,11 +1049,13 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // the slot. This ensures that the creative is centered in the former case, // and not truncated in the latter. const size = this.returnedSize_ || this.getSlotSize(); - const isMultiSizeFluid = this.isFluidRequest_ && this.returnedSize_ && - // TODO(@glevitzky, 11583) Remove this clause once we stop sending back - // the size header for fluid ads. Fluid size headers always come back as - // 0x0. - !(size.width == 0 && size.height == 0); + const isMultiSizeFluid = + this.isFluidRequest_ && + this.returnedSize_ && + // TODO(@glevitzky, 11583) Remove this clause once we stop sending back + // the size header for fluid ads. Fluid size headers always come back as + // 0x0. + !(size.width == 0 && size.height == 0); setStyles(dev().assertElement(this.iframe), { width: `${size.width}px`, height: `${size.height}px`, @@ -1012,22 +1079,29 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { }); } - this.refreshManager_ = this.refreshManager_ || - getRefreshManager(this, () => { - if (this.useSra) { - user().warn(TAG, 'Refresh not compatible with SRA.'); - return false; - } - if (getEnclosingContainerTypes(this.element).filter(container => - container != ValidAdContainerTypes['AMP-CAROUSEL'] && - container != ValidAdContainerTypes['AMP-STICKY-AD']).length) { - user().warn(TAG, - 'Refresh not compatible with ad-containers, except for ' + - 'AMP-CAROUSEL and AMP-STICKY-AD'); - return false; - } - return true; - }); + this.refreshManager_ = + this.refreshManager_ || + getRefreshManager(this, () => { + if (this.useSra) { + user().warn(TAG, 'Refresh not compatible with SRA.'); + return false; + } + if ( + getEnclosingContainerTypes(this.element).filter( + container => + container != ValidAdContainerTypes['AMP-CAROUSEL'] && + container != ValidAdContainerTypes['AMP-STICKY-AD'] + ).length + ) { + user().warn( + TAG, + 'Refresh not compatible with ad-containers, except for ' + + 'AMP-CAROUSEL and AMP-STICKY-AD' + ); + return false; + } + return true; + }); this.postTroubleshootMessage(); } @@ -1043,32 +1117,41 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { * testing. */ expandFluidCreative_() { - if (this.isFluidRequest_ && - // If a size was returned in the response, then this is a multi-size - // response, not a fluid response. - !this.returnedSize_ && - this.isVerifiedAmpCreative()) { + if ( + this.isFluidRequest_ && + // If a size was returned in the response, then this is a multi-size + // response, not a fluid response. + !this.returnedSize_ && + this.isVerifiedAmpCreative() + ) { // This is an AMP fluid creative that will be rendered in a friendly // frame. - if (!this.iframe || !this.iframe.contentWindow || - !this.iframe.contentWindow.document || - !this.iframe.contentWindow.document.body) { - dev().error(TAG, 'Attempting to expand fluid creative without ' + + if ( + !this.iframe || + !this.iframe.contentWindow || + !this.iframe.contentWindow.document || + !this.iframe.contentWindow.document.body + ) { + dev().error( + TAG, + 'Attempting to expand fluid creative without ' + 'a properly set up friendly frame. Slot id: ' + - this.element.getAttribute('data-amp-slot-index')); + this.element.getAttribute('data-amp-slot-index') + ); return Promise.reject('Cannot access body of friendly frame'); } return this.setCssPosition_('static').then(() => { return this.attemptChangeHeight( - this.iframe.contentWindow.document.body./*OK*/clientHeight) - .then(() => { - this.fireFluidDelayedImpression(); - this.reattemptToExpandFluidCreative_ = false; - }) - .catch(() => { - this.reattemptToExpandFluidCreative_ = true; - this.setCssPosition_('absolute'); - }); + this.iframe.contentWindow.document.body./*OK*/ clientHeight + ) + .then(() => { + this.fireFluidDelayedImpression(); + this.reattemptToExpandFluidCreative_ = false; + }) + .catch(() => { + this.reattemptToExpandFluidCreative_ = true; + this.setCssPosition_('absolute'); + }); }); } return Promise.resolve(); @@ -1113,9 +1196,11 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // We want to resize only if neither returned dimension is larger than its // primary counterpart, and if at least one of the returned dimensions // differ from its primary counterpart. - if ((this.isFluidRequest_ && width && height) || - ((width != pWidth || height != pHeight) && - (width <= pWidth && height <= pHeight))) { + if ( + (this.isFluidRequest_ && width && height) || + ((width != pWidth || height != pHeight) && + (width <= pWidth && height <= pHeight)) + ) { this.attemptChangeSize(height, width).catch(() => {}); } } @@ -1134,10 +1219,19 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { if (!this.sraDeferred) { dev().warn(TAG, `SRA failed to include element ${this.ifi_}`); if (isExperimentOn(this.win, 'doubleclickSraReportExcludedBlock')) { - this.getAmpDoc().getBody().appendChild(createElementWithAttributes( - this.win.document, 'amp-pixel', dict({'src': - 'https://pagead2.googlesyndication.com/pagead/gen_204?' + - `id=${encodeURIComponent('a4a::sra')}&ifi=${this.ifi_}`}))); + this.getAmpDoc() + .getBody() + .appendChild( + createElementWithAttributes( + this.win.document, + 'amp-pixel', + dict({ + 'src': + 'https://pagead2.googlesyndication.com/pagead/gen_204?' + + `id=${encodeURIComponent('a4a::sra')}&ifi=${this.ifi_}`, + }) + ) + ); } } }); @@ -1168,13 +1262,15 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } // Create amp-pixel and append to document to send impression. this.win.document.body.appendChild( - createElementWithAttributes( - this.win.document, - 'amp-pixel', - dict({ - 'src': url, - 'referrerpolicy': scrubReferer ? 'no-referrer' : '', - }))); + createElementWithAttributes( + this.win.document, + 'amp-pixel', + dict({ + 'src': url, + 'referrerpolicy': scrubReferer ? 'no-referrer' : '', + }) + ) + ); } catch (unusedError) {} }); } @@ -1197,7 +1293,10 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { */ groupSlotsForSra() { return groupAmpAdsByType( - this.win, this.element.getAttribute('type'), getNetworkId); + this.win, + this.element.getAttribute('type'), + getNetworkId + ); } /** @@ -1216,33 +1315,38 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // be called for all and we should cancel SRA execution. const checkStillCurrent = this.verifyStillCurrent(); const noFallbackExp = this.experimentIds.includes( - DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER); - sraRequests = sraRequests || this.groupSlotsForSra() - .then(groupIdToBlocksAry => { - checkStillCurrent(); - const sraRequestPromises = []; - Object.keys(groupIdToBlocksAry).forEach(networkId => { - const blocks = devAssert(groupIdToBlocksAry[networkId]); - // TODO: filter blocks with SRA disabled? - sraRequestPromises.push(Promise.all(blocks).then(instances => { + DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER + ); + sraRequests = + sraRequests || + this.groupSlotsForSra().then(groupIdToBlocksAry => { + checkStillCurrent(); + const sraRequestPromises = []; + Object.keys(groupIdToBlocksAry).forEach(networkId => { + const blocks = devAssert(groupIdToBlocksAry[networkId]); + // TODO: filter blocks with SRA disabled? + sraRequestPromises.push( + Promise.all(blocks).then(instances => { devAssert(instances.length); checkStillCurrent(); // Exclude any instances that do not have an adPromise_ as this // indicates they were invalid. - const typeInstances = - /** @type {!Array}*/(instances) - .filter(instance => { - const isValid = instance.hasAdPromise(); - if (!isValid) { - dev().info(TAG, - 'Ignoring instance without ad promise as ' + - 'likely invalid', - instance.element); - } - return isValid; - }); + const typeInstances = /** @type {!Array}*/ (instances).filter( + instance => { + const isValid = instance.hasAdPromise(); + if (!isValid) { + dev().info( + TAG, + 'Ignoring instance without ad promise as ' + + 'likely invalid', + instance.element + ); + } + return isValid; + } + ); if (!typeInstances.length) { - // Only contained invalid elements. + // Only contained invalid elements. return; } // If not within no recovery SRA experiment, determine if more @@ -1253,8 +1357,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { dev().info(TAG, `single block in network ${networkId}`); // Ensure deferred exists, may not if getAdUrl did not yet // execute. - typeInstances[0].sraDeferred = typeInstances[0].sraDeferred || - new Deferred(); + typeInstances[0].sraDeferred = + typeInstances[0].sraDeferred || new Deferred(); typeInstances[0].sraDeferred.resolve(null); return; } @@ -1262,71 +1366,87 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // Construct and send SRA request. // TODO(keithwrightbos) - how do we handle per slot 204 response? return constructSRARequest_(this, typeInstances) - .then(sraUrlIn => { - checkStillCurrent(); - sraUrl = sraUrlIn; - return Services.xhrFor(this.win).fetch(sraUrl, { - mode: 'cors', - method: 'GET', - credentials: 'include', - }); - }) - .then(response => { - checkStillCurrent(); - // Chunk handler called with metadata and creative for each - // slot in order of URLs given which is then passed to - // resolver used for sendXhrRequest. - const sraRequestAdUrlResolvers = - typeInstances.map(instance => instance.sraDeferred.resolve); - const slotCallback = metaJsonCreativeGrouper( - (creative, headersObj, done) => { - checkStillCurrent(); - sraBlockCallbackHandler(creative, headersObj, done, - sraRequestAdUrlResolvers, sraUrl); - }); - lineDelimitedStreamer(this.win, response, slotCallback); - return Promise.all(typeInstances.map( - instance => instance.sraDeferred.promise)); - }) - .catch(error => { - if (isCancellation(error)) { - // Cancellation should be propagated to slot promises - // causing their adPromise chains within A4A to handle - // appropriately. - typeInstances.forEach(instance => instance.sraDeferred && - instance.sraDeferred.reject(error)); - } else if (noFallbackExp || - !!this.win.document.querySelector( - 'meta[name=amp-ad-doubleclick-sra]')) { - // If publisher has explicitly enabled SRA mode (not - // experiment), then assume error is network failure, - // collapse slot, reset url to empty string to ensure - // no fallback to frame GET (given expectation of SRA - // consistency), and propagate error to A4A ad promise - // chain. - assignAdUrlToError(/** @type {!Error} */(error), sraUrl); - this.warnOnError('SRA request failure', error); - // Publisher explicitly wants SRA so do not attempt to - // recover as SRA guarantees cannot be enforced. - typeInstances.forEach(instance => { - // Reset ad url to ensure layoutCallback does not - // fallback to frame get which would lose SRA - // guarantees. - instance.resetAdUrl(); - instance.attemptCollapse(); - instance.sraDeferred.reject(error); - }); - } else { - // Opportunistic SRA used so fallback to individual - // XHR requests. - typeInstances.forEach(instance => - instance.sraDeferred.resolve(null)); - } + .then(sraUrlIn => { + checkStillCurrent(); + sraUrl = sraUrlIn; + return Services.xhrFor(this.win).fetch(sraUrl, { + mode: 'cors', + method: 'GET', + credentials: 'include', }); - })); - }); - return Promise.all(sraRequestPromises); + }) + .then(response => { + checkStillCurrent(); + // Chunk handler called with metadata and creative for each + // slot in order of URLs given which is then passed to + // resolver used for sendXhrRequest. + const sraRequestAdUrlResolvers = typeInstances.map( + instance => instance.sraDeferred.resolve + ); + const slotCallback = metaJsonCreativeGrouper( + (creative, headersObj, done) => { + checkStillCurrent(); + sraBlockCallbackHandler( + creative, + headersObj, + done, + sraRequestAdUrlResolvers, + sraUrl + ); + } + ); + lineDelimitedStreamer(this.win, response, slotCallback); + return Promise.all( + typeInstances.map(instance => instance.sraDeferred.promise) + ); + }) + .catch(error => { + if (isCancellation(error)) { + // Cancellation should be propagated to slot promises + // causing their adPromise chains within A4A to handle + // appropriately. + typeInstances.forEach( + instance => + instance.sraDeferred && + instance.sraDeferred.reject(error) + ); + } else if ( + noFallbackExp || + !!this.win.document.querySelector( + 'meta[name=amp-ad-doubleclick-sra]' + ) + ) { + // If publisher has explicitly enabled SRA mode (not + // experiment), then assume error is network failure, + // collapse slot, reset url to empty string to ensure + // no fallback to frame GET (given expectation of SRA + // consistency), and propagate error to A4A ad promise + // chain. + assignAdUrlToError(/** @type {!Error} */ (error), sraUrl); + this.warnOnError('SRA request failure', error); + // Publisher explicitly wants SRA so do not attempt to + // recover as SRA guarantees cannot be enforced. + typeInstances.forEach(instance => { + // Reset ad url to ensure layoutCallback does not + // fallback to frame get which would lose SRA + // guarantees. + instance.resetAdUrl(); + instance.attemptCollapse(); + instance.sraDeferred.reject(error); + }); + } else { + // Opportunistic SRA used so fallback to individual + // XHR requests. + typeInstances.forEach(instance => + instance.sraDeferred.resolve(null) + ); + } + }); + }) + ); }); + return Promise.all(sraRequestPromises); + }); return sraRequests; } @@ -1358,8 +1478,9 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { * @visibleForTesting */ getLocationQueryParameterValue(parameterName) { - windowLocationQueryParameters = windowLocationQueryParameters || - parseQueryString((this.win.location && this.win.location.search) || ''); + windowLocationQueryParameters = + windowLocationQueryParameters || + parseQueryString((this.win.location && this.win.location.search) || ''); return windowLocationQueryParameters[parameterName]; } @@ -1370,10 +1491,13 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } const creativeSize = this.getCreativeSize(); devAssert(creativeSize, 'this.getCreativeSize returned null'); - this.safeframeApi_ = this.safeframeApi_ || - new SafeframeHostApi( - this, this.isFluidRequest_, - /** @type {{height, width}} */(creativeSize)); + this.safeframeApi_ = + this.safeframeApi_ || + new SafeframeHostApi( + this, + this.isFluidRequest_, + /** @type {{height, width}} */ (creativeSize) + ); return this.safeframeApi_.getSafeframeNameAttr(); } @@ -1392,29 +1516,35 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } devAssert(this.troubleshootData_.adUrl, 'ad URL does not exist yet'); return this.troubleshootData_.adUrl.then(adUrl => { - const slotId = this.troubleshootData_.slotId + '_' + - this.troubleshootData_.slotIndex; + const slotId = + this.troubleshootData_.slotId + '_' + this.troubleshootData_.slotIndex; const payload = dict({ - 'gutData': JSON.stringify(dict({ - 'events': [{ - 'timestamp': Date.now(), - 'slotid': slotId, - 'messageId': 4, - }], - 'slots': [{ - 'contentUrl': adUrl || '', - 'id': slotId, - 'leafAdUnitName': this.troubleshootData_.slotId, - 'domId': slotId, - 'lineItemId': this.troubleshootData_.lineItemId, - 'creativeId': this.troubleshootData_.creativeId, - }], - })), + 'gutData': JSON.stringify( + dict({ + 'events': [ + { + 'timestamp': Date.now(), + 'slotid': slotId, + 'messageId': 4, + }, + ], + 'slots': [ + { + 'contentUrl': adUrl || '', + 'id': slotId, + 'leafAdUnitName': this.troubleshootData_.slotId, + 'domId': slotId, + 'lineItemId': this.troubleshootData_.lineItemId, + 'creativeId': this.troubleshootData_.creativeId, + }, + ], + }) + ), 'userAgent': navigator.userAgent, 'referrer': this.win.location.href, 'messageType': 'LOAD', }); - this.win.opener./*OK*/postMessage(payload, '*'); + this.win.opener./*OK*/ postMessage(payload, '*'); }); } @@ -1480,12 +1610,12 @@ export function resetLocationQueryParametersForTesting() { */ export function getNetworkId(element) { const networkId = /^(?:\/)?(\d+)/.exec( - dev().assertString(element.getAttribute('data-slot'))); + dev().assertString(element.getAttribute('data-slot')) + ); // TODO: guarantee data-ad-slot format as part of isValidElement? return networkId ? networkId[1] : ''; } - /** * @param {!../../../extensions/amp-a4a/0.1/amp-a4a.AmpA4A} a4a * @param {!Array} instances @@ -1496,15 +1626,21 @@ function constructSRARequest_(a4a, instances) { devAssert(instances && instances.length); const startTime = Date.now(); return Promise.all( - instances.map(instance => instance.getAdUrlDeferred.promise)) - .then(() => googlePageParameters(a4a, startTime)) - .then(googPageLevelParameters => { - const blockParameters = constructSRABlockParameters(instances); - return truncAndTimeUrl(DOUBLECLICK_BASE_URL, - Object.assign(blockParameters, googPageLevelParameters, - instances[0].getPageParameters(instances[0].consentState, - instances)), startTime); - }); + instances.map(instance => instance.getAdUrlDeferred.promise) + ) + .then(() => googlePageParameters(a4a, startTime)) + .then(googPageLevelParameters => { + const blockParameters = constructSRABlockParameters(instances); + return truncAndTimeUrl( + DOUBLECLICK_BASE_URL, + Object.assign( + blockParameters, + googPageLevelParameters, + instances[0].getPageParameters(instances[0].consentState, instances) + ), + startTime + ); + }); } /** @@ -1517,8 +1653,7 @@ function constructSRARequest_(a4a, instances) { export function getPageviewStateTokensForAdRequest(instancesInAdRequest) { const pageviewStateTokensInAdRequest = []; for (const token in tokensToInstances) { - if (!instancesInAdRequest.includes( - tokensToInstances[token])) { + if (!instancesInAdRequest.includes(tokensToInstances[token])) { pageviewStateTokensInAdRequest.push(token); } } diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/safeframe-host.js b/extensions/amp-ad-network-doubleclick-impl/0.1/safeframe-host.js index 93bea028fef2..00cf36c4382b 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/safeframe-host.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/safeframe-host.js @@ -91,8 +91,10 @@ export function safeframeListener(event) { safeframeHost.connectMessagingChannel(data[MESSAGE_FIELDS.CHANNEL]); } else if (payload) { // Currently we do not expect a payload on initial connection messages. - safeframeHost.processMessage(/** @type {!JsonObject} */(payload), - data[MESSAGE_FIELDS.SERVICE]); + safeframeHost.processMessage( + /** @type {!JsonObject} */ (payload), + data[MESSAGE_FIELDS.SERVICE] + ); } } @@ -117,7 +119,6 @@ export function safeframeListener(event) { * then calls the instance of send() below whenever an update occurs. */ export class SafeframeHostApi { - /** * @param {!./amp-ad-network-doubleclick-impl.AmpAdNetworkDoubleclickImpl} baseInstance * @param {boolean} isFluid @@ -129,7 +130,8 @@ export class SafeframeHostApi { /** @private {!Function} */ this.checkStillCurrent_ = this.baseInstance_.verifyStillCurrent.bind( - this.baseInstance_)(); + this.baseInstance_ + )(); /** @private {!Window} */ this.win_ = this.baseInstance_.win; @@ -159,9 +161,10 @@ export class SafeframeHostApi { this.creativeSize_ = creativeSize; /** @private {{width:number, height:number}} */ - this.initialCreativeSize_ = - /** @private {{width:number, height:number}} */ - (Object.assign({}, creativeSize)); + this.initialCreativeSize_ = /** @type {{width:number, height:number}} */ (Object.assign( + {}, + creativeSize + )); /** @private {?Promise} */ this.delay_ = null; @@ -176,16 +179,20 @@ export class SafeframeHostApi { this.isRegistered_ = false; // TODO: Make this page-level. - const sfConfig = Object(tryParseJson( - this.baseInstance_.element.getAttribute( - 'data-safeframe-config')) || {}); + const sfConfig = Object( + tryParseJson( + this.baseInstance_.element.getAttribute('data-safeframe-config') + ) || {} + ); /** @private {boolean} */ - this.expandByOverlay_ = hasOwn(sfConfig, 'expandByOverlay') ? - sfConfig['expandByOverlay'] : true; + this.expandByOverlay_ = hasOwn(sfConfig, 'expandByOverlay') + ? sfConfig['expandByOverlay'] + : true; /** @private {boolean} */ - this.expandByPush_ = hasOwn(sfConfig, 'expandByPush') ? - sfConfig['expandByPush'] : true; + this.expandByPush_ = hasOwn(sfConfig, 'expandByPush') + ? sfConfig['expandByPush'] + : true; /** @private {?Function} */ this.unlisten_ = null; @@ -204,26 +211,28 @@ export class SafeframeHostApi { attributes['hostPeerName'] = this.win_.location.origin; attributes['initialGeometry'] = this.getInitialGeometry(); attributes['permissions'] = JSON.stringify( - dict({ - 'expandByOverlay': this.expandByOverlay_, - 'expandByPush': this.expandByPush_, - 'readCookie': false, - 'writeCookie': false, - })); + dict({ + 'expandByOverlay': this.expandByOverlay_, + 'expandByPush': this.expandByPush_, + 'readCookie': false, + 'writeCookie': false, + }) + ); attributes['metadata'] = JSON.stringify( - dict({ - 'shared': { - 'sf_ver': this.baseInstance_.safeframeVersion, - 'ck_on': 1, - 'flash_ver': '26.0.0', - // Once GPT Safeframe is updated to look in amp object, - // remove this canonical_url here. + dict({ + 'shared': { + 'sf_ver': this.baseInstance_.safeframeVersion, + 'ck_on': 1, + 'flash_ver': '26.0.0', + // Once GPT Safeframe is updated to look in amp object, + // remove this canonical_url here. + 'canonical_url': this.maybeGetCanonicalUrl(), + 'amp': { 'canonical_url': this.maybeGetCanonicalUrl(), - 'amp': { - 'canonical_url': this.maybeGetCanonicalUrl(), - }, }, - })); + }, + }) + ); attributes['reportCreativeGeometry'] = this.isFluid_; attributes['isDifferentSourceWindow'] = false; attributes['sentinel'] = this.sentinel_; @@ -241,9 +250,11 @@ export class SafeframeHostApi { // as Safeframe will always be a different origin. // Don't allow for no-referrer. const {canonicalUrl} = Services.documentInfoForDoc( - this.baseInstance_.getAmpDoc()); + this.baseInstance_.getAmpDoc() + ); const metaReferrer = this.win_.document.querySelector( - "meta[name='referrer']"); + "meta[name='referrer']" + ); if (!metaReferrer) { return canonicalUrl; } @@ -311,10 +322,13 @@ export class SafeframeHostApi { this.iframe_ = this.baseInstance_.iframe; this.channel = channel; this.setupGeom_(); - this.sendMessage_({ - 'message': 'connect', - 'c': this.channel, - }, ''); + this.sendMessage_( + { + 'message': 'connect', + 'c': this.channel, + }, + '' + ); } /** @@ -326,10 +340,12 @@ export class SafeframeHostApi { * @private */ setupGeom_() { - devAssert(this.iframe_.contentWindow, - 'Frame contentWindow unavailable.'); + devAssert(this.iframe_.contentWindow, 'Frame contentWindow unavailable.'); const throttledUpdate = throttle( - this.win_, this.updateGeometry_.bind(this), 1000); + this.win_, + this.updateGeometry_.bind(this), + 1000 + ); const scrollUnlistener = this.viewport_.onScroll(throttledUpdate); const changedUnlistener = this.viewport_.onChanged(throttledUpdate); this.unlisten_ = () => { @@ -347,14 +363,20 @@ export class SafeframeHostApi { if (!this.iframe_) { return; } - this.viewport_.getClientRectAsync(this.iframe_).then(iframeBox => { - this.checkStillCurrent_(); - const formattedGeom = this.formatGeom_(iframeBox); - this.sendMessage_({ - newGeometry: formattedGeom, - uid: this.uid_, - }, SERVICE.GEOMETRY_UPDATE); - }).catch(err => dev().error(TAG, err)); + this.viewport_ + .getClientRectAsync(this.iframe_) + .then(iframeBox => { + this.checkStillCurrent_(); + const formattedGeom = this.formatGeom_(iframeBox); + this.sendMessage_( + { + newGeometry: formattedGeom, + uid: this.uid_, + }, + SERVICE.GEOMETRY_UPDATE + ); + }) + .catch(err => dev().error(TAG, err)); } /** @@ -368,7 +390,7 @@ export class SafeframeHostApi { const viewportSize = this.viewport_.getSize(); const scrollLeft = this.viewport_.getScrollLeft(); const scrollTop = this.viewport_.getScrollTop(); - const currentGeometry = /** @type {JsonObject} */({ + const currentGeometry = /** @type {JsonObject} */ ({ 'windowCoords_t': 0, 'windowCoords_r': viewportSize.width, 'windowCoords_b': viewportSize.height, @@ -384,16 +406,20 @@ export class SafeframeHostApi { 'styleZIndex': getStyle(this.baseInstance_.element, 'zIndex'), // AMP's built in resize methodology that we use only allows expansion // to the right and bottom, so we enforce that here. - 'allowedExpansion_r': viewportSize.width - - iframeBox.width, - 'allowedExpansion_b': viewportSize.height - - iframeBox.height, + 'allowedExpansion_r': viewportSize.width - iframeBox.width, + 'allowedExpansion_b': viewportSize.height - iframeBox.height, 'allowedExpansion_t': 0, 'allowedExpansion_l': 0, - 'yInView': this.getPercInView(viewportSize.height, - iframeBox.top, iframeBox.bottom), - 'xInView': this.getPercInView(viewportSize.width, - iframeBox.left, iframeBox.right), + 'yInView': this.getPercInView( + viewportSize.height, + iframeBox.top, + iframeBox.bottom + ), + 'xInView': this.getPercInView( + viewportSize.width, + iframeBox.left, + iframeBox.right + ), }); this.currentGeometry_ = currentGeometry; return JSON.stringify(currentGeometry); @@ -411,8 +437,10 @@ export class SafeframeHostApi { * @return {number} */ getPercInView(rootBoundEnd, boundingRectStart, boundingRectEnd) { - const lengthInView = (boundingRectEnd >= rootBoundEnd) ? - rootBoundEnd - boundingRectStart : boundingRectEnd; + const lengthInView = + boundingRectEnd >= rootBoundEnd + ? rootBoundEnd - boundingRectStart + : boundingRectEnd; const percInView = lengthInView / (boundingRectEnd - boundingRectStart); return Math.max(0, Math.min(1, percInView)) || 0; } @@ -431,12 +459,15 @@ export class SafeframeHostApi { const message = dict(); message[MESSAGE_FIELDS.CHANNEL] = this.channel; message[MESSAGE_FIELDS.PAYLOAD] = JSON.stringify( - /** @type {!JsonObject} */(payload)); + /** @type {!JsonObject} */ (payload) + ); message[MESSAGE_FIELDS.SERVICE] = serviceName; message[MESSAGE_FIELDS.SENTINEL] = this.sentinel_; message[MESSAGE_FIELDS.ENDPOINT_IDENTITY] = this.endpointIdentity_; - this.iframe_.contentWindow./*OK*/postMessage( - JSON.stringify(message), SAFEFRAME_ORIGIN); + this.iframe_.contentWindow./*OK*/ postMessage( + JSON.stringify(message), + SAFEFRAME_ORIGIN + ); } /** @@ -467,7 +498,6 @@ export class SafeframeHostApi { } } - /** * @param {!JsonObject} payload * @private @@ -476,33 +506,39 @@ export class SafeframeHostApi { if (!this.isRegistered_) { return; } - const expandHeight = Number(this.creativeSize_.height) + - payload['expand_b'] + payload['expand_t']; - const expandWidth = Number(this.creativeSize_.width) + - payload['expand_r'] + payload['expand_l']; + const expandHeight = + Number(this.creativeSize_.height) + + payload['expand_b'] + + payload['expand_t']; + const expandWidth = + Number(this.creativeSize_.width) + + payload['expand_r'] + + payload['expand_l']; // Verify that if expanding by push, that expandByPush is allowed. // If expanding by overlay, verify that expandByOverlay is allowed, // and that we are only expanding within the bounds of the amp-ad. - if (isNaN(expandHeight) || isNaN(expandWidth) || - (payload['push'] && !this.expandByPush_) || - (!payload['push'] && !this.expandByOverlay_ && - (expandWidth > this.creativeSize_.width || - expandHeight > this.creativeSize_.height))) { + if ( + isNaN(expandHeight) || + isNaN(expandWidth) || + (payload['push'] && !this.expandByPush_) || + (!payload['push'] && + !this.expandByOverlay_ && + (expandWidth > this.creativeSize_.width || + expandHeight > this.creativeSize_.height)) + ) { dev().error(TAG, 'Invalid expand values.'); - this.sendResizeResponse( - /* SUCCESS? */ false, SERVICE.EXPAND_RESPONSE); + this.sendResizeResponse(/* SUCCESS? */ false, SERVICE.EXPAND_RESPONSE); return; } // Can't expand to greater than the viewport size - if (expandHeight > this.viewport_.getSize().height || - expandWidth > this.viewport_.getSize().width) { - this.sendResizeResponse( - /* SUCCESS? */ false, SERVICE.EXPAND_RESPONSE); + if ( + expandHeight > this.viewport_.getSize().height || + expandWidth > this.viewport_.getSize().width + ) { + this.sendResizeResponse(/* SUCCESS? */ false, SERVICE.EXPAND_RESPONSE); return; } - this.handleSizeChange(expandHeight, - expandWidth, - SERVICE.EXPAND_RESPONSE); + this.handleSizeChange(expandHeight, expandWidth, SERVICE.EXPAND_RESPONSE); } /** @@ -511,14 +547,15 @@ export class SafeframeHostApi { handleCollapseRequest_() { // Only collapse if expanded. if (this.isCollapsed_ || !this.isRegistered_) { - this.sendResizeResponse( - /* SUCCESS? */ false, SERVICE.COLLAPSE_RESPONSE); + this.sendResizeResponse(/* SUCCESS? */ false, SERVICE.COLLAPSE_RESPONSE); return; } - this.handleSizeChange(this.initialCreativeSize_.height, - this.initialCreativeSize_.width, - SERVICE.COLLAPSE_RESPONSE, - /** isCollapse */ true); + this.handleSizeChange( + this.initialCreativeSize_.height, + this.initialCreativeSize_.width, + SERVICE.COLLAPSE_RESPONSE, + /** isCollapse */ true + ); } /** @@ -529,21 +566,21 @@ export class SafeframeHostApi { resizeSafeframe(height, width, messageType) { this.isCollapsed_ = messageType == SERVICE.COLLAPSE_RESPONSE; this.baseInstance_.measureMutateElement( - /** MEASURER */ () => { - this.baseInstance_.getResource().measure(); - }, - /** MUTATOR */ () => { - if (this.iframe_) { - setStyles(this.iframe_, { - 'height': height + 'px', - 'width': width + 'px', - }); - this.creativeSize_.height = height; - this.creativeSize_.width = width; - } - this.sendResizeResponse(/** SUCCESS */ true, messageType); - }, - this.iframe_ + /** MEASURER */ () => { + this.baseInstance_.getResource().measure(); + }, + /** MUTATOR */ () => { + if (this.iframe_) { + setStyles(this.iframe_, { + 'height': height + 'px', + 'width': width + 'px', + }); + this.creativeSize_.height = height; + this.creativeSize_.width = width; + } + this.sendResizeResponse(/** SUCCESS */ true, messageType); + }, + this.iframe_ ); } @@ -565,15 +602,20 @@ export class SafeframeHostApi { * @param {boolean=} optIsCollapse Whether this is a collapse attempt. */ handleSizeChange(height, width, messageType, optIsCollapse) { - return this.viewport_.getClientRectAsync( - this.baseInstance_.element).then(box => { - if (!optIsCollapse && width <= box.width && height <= box.height) { - this.resizeSafeframe(height, width, messageType); - } else { - this.resizeAmpAdAndSafeframe(height, width, messageType, - optIsCollapse); - } - }); + return this.viewport_ + .getClientRectAsync(this.baseInstance_.element) + .then(box => { + if (!optIsCollapse && width <= box.width && height <= box.height) { + this.resizeSafeframe(height, width, messageType); + } else { + this.resizeAmpAdAndSafeframe( + height, + width, + messageType, + optIsCollapse + ); + } + }); } /** @@ -584,10 +626,12 @@ export class SafeframeHostApi { if (!this.isRegistered_) { return; } - const resizeHeight = Number(this.creativeSize_.height) + - (payload['resize_b'] + payload['resize_t']); - const resizeWidth = Number(this.creativeSize_.width) + - (payload['resize_r'] + payload['resize_l']); + const resizeHeight = + Number(this.creativeSize_.height) + + (payload['resize_b'] + payload['resize_t']); + const resizeWidth = + Number(this.creativeSize_.width) + + (payload['resize_r'] + payload['resize_l']); // Make sure we are actually resizing here. if (isNaN(resizeWidth) || isNaN(resizeHeight)) { @@ -595,8 +639,12 @@ export class SafeframeHostApi { return; } - this.resizeAmpAdAndSafeframe(resizeHeight, resizeWidth, - SERVICE.RESIZE_RESPONSE, true); + this.resizeAmpAdAndSafeframe( + resizeHeight, + resizeWidth, + SERVICE.RESIZE_RESPONSE, + true + ); } /** @@ -607,20 +655,26 @@ export class SafeframeHostApi { if (!this.iframe_) { return; } - this.viewport_.getClientRectAsync(this.iframe_).then(iframeBox => { - this.checkStillCurrent_(); - const formattedGeom = this.formatGeom_(iframeBox); - this.sendMessage_({ - uid: this.uid_, - success, - newGeometry: formattedGeom, - 'expand_t': this.currentGeometry_['allowedExpansion_t'], - 'expand_b': this.currentGeometry_['allowedExpansion_b'], - 'expand_r': this.currentGeometry_['allowedExpansion_r'], - 'expand_l': this.currentGeometry_['allowedExpansion_l'], - push: true, - }, messageType); - }).catch(err => dev().error(TAG, err)); + this.viewport_ + .getClientRectAsync(this.iframe_) + .then(iframeBox => { + this.checkStillCurrent_(); + const formattedGeom = this.formatGeom_(iframeBox); + this.sendMessage_( + { + uid: this.uid_, + success, + newGeometry: formattedGeom, + 'expand_t': this.currentGeometry_['allowedExpansion_t'], + 'expand_b': this.currentGeometry_['allowedExpansion_b'], + 'expand_r': this.currentGeometry_['allowedExpansion_r'], + 'expand_l': this.currentGeometry_['allowedExpansion_l'], + push: true, + }, + messageType + ); + }) + .catch(err => dev().error(TAG, err)); } /** @@ -635,38 +689,44 @@ export class SafeframeHostApi { resizeAmpAdAndSafeframe(height, width, messageType, opt_isShrinking) { // First, attempt to resize the Amp-Ad that is the parent of the // safeframe - this.baseInstance_.attemptChangeSize(height, width).then(() => { - this.checkStillCurrent_(); - // If this resize succeeded, we always resize the safeframe. - // resizeSafeframe also sends the resize response. - this.resizeSafeframe(height, width, messageType); - }, /** REJECT CALLBACK */ () => { - // If the resize initially failed, it may have been queued - // as a pendingChangeSize, which will cause the size change - // to execute upon the next user interaction. We don't want - // that for safeframe, so we reset it here. - this.baseInstance_.getResource().resetPendingChangeSize(); - if (opt_isShrinking) { - // If this is a collapse or resize request, then even if resizing - // the amp-ad failed, still resize the iframe. - // resizeSafeframe also sends the resize response. - // Only register as collapsed if explicitly a collapse request. - this.resizeSafeframe(height, width, messageType); - } else { - // We were attempting to - // expand past the bounds of the amp-ad, and it failed. Thus, - // we need to send a failure message, and the safeframe is - // not resized. + this.baseInstance_ + .attemptChangeSize(height, width) + .then( + () => { + this.checkStillCurrent_(); + // If this resize succeeded, we always resize the safeframe. + // resizeSafeframe also sends the resize response. + this.resizeSafeframe(height, width, messageType); + }, + /** REJECT CALLBACK */ () => { + // If the resize initially failed, it may have been queued + // as a pendingChangeSize, which will cause the size change + // to execute upon the next user interaction. We don't want + // that for safeframe, so we reset it here. + this.baseInstance_.getResource().resetPendingChangeSize(); + if (opt_isShrinking) { + // If this is a collapse or resize request, then even if resizing + // the amp-ad failed, still resize the iframe. + // resizeSafeframe also sends the resize response. + // Only register as collapsed if explicitly a collapse request. + this.resizeSafeframe(height, width, messageType); + } else { + // We were attempting to + // expand past the bounds of the amp-ad, and it failed. Thus, + // we need to send a failure message, and the safeframe is + // not resized. + this.sendResizeResponse(false, messageType); + } + } + ) + .catch(err => { + if (err.message == 'CANCELLED') { + dev().error(TAG, err); + return; + } + dev().error(TAG, `Resizing failed: ${err}`); this.sendResizeResponse(false, messageType); - } - }).catch(err => { - if (err.message == 'CANCELLED') { - dev().error(TAG, err); - return; - } - dev().error(TAG, `Resizing failed: ${err}`); - this.sendResizeResponse(false, messageType); - }); + }); } /** @@ -679,16 +739,18 @@ export class SafeframeHostApi { if (!payload || !(newHeight = parseInt(payload['height'], 10))) { return; } - this.baseInstance_.attemptChangeHeight(newHeight) - .then(() => { - this.checkStillCurrent_(); - this.onFluidResize_(newHeight); - }).catch(err => { - if (err.message == 'CANCELLED') { - dev().error(TAG, err); - return; - } - }); + this.baseInstance_ + .attemptChangeHeight(newHeight) + .then(() => { + this.checkStillCurrent_(); + this.onFluidResize_(newHeight); + }) + .catch(err => { + if (err.message == 'CANCELLED') { + dev().error(TAG, err); + return; + } + }); } /** @@ -704,9 +766,10 @@ export class SafeframeHostApi { setStyles(iframe, {height: `${newHeight}px`}); } this.baseInstance_.fireFluidDelayedImpression(); - this.iframe_.contentWindow./*OK*/postMessage( - JSON.stringify(dict({'message': 'resize-complete', 'c': this.channel})), - SAFEFRAME_ORIGIN); + this.iframe_.contentWindow./*OK*/ postMessage( + JSON.stringify(dict({'message': 'resize-complete', 'c': this.channel})), + SAFEFRAME_ORIGIN + ); } /** diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/sra-utils.js b/extensions/amp-ad-network-doubleclick-impl/0.1/sra-utils.js index 6d07ec7fb94a..2755fbd34e8a 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/sra-utils.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/sra-utils.js @@ -17,9 +17,7 @@ import {RENDERING_TYPE_HEADER, XORIGIN_MODE} from '../../amp-a4a/0.1/amp-a4a'; import {dev, devAssert} from '../../../src/log'; import {getEnclosingContainerTypes} from '../../../ads/google/a4a/utils'; -import { - isInManualExperiment, -} from '../../../ads/google/a4a/traffic-experiments'; +import {isInManualExperiment} from '../../../ads/google/a4a/traffic-experiments'; import {isObject} from '../../../src/types'; import {tryResolve} from '../../../src/utils/promise'; import {utf8Encode} from '../../../src/utils/bytes'; @@ -35,14 +33,25 @@ export const TFCD = 'tagForChildDirectedTreatment'; /** @private {!Array):?Object>} */ const SRA_JOINERS = [ - combineInventoryUnits, getCookieOptOut, getAdks, getSizes, getTfcd, isAdTest, - getTargetingAndExclusions, getExperimentIds, getIdentity, getForceSafeframe, - getPageOffsets, getContainers, getIsFluid]; + combineInventoryUnits, + getCookieOptOut, + getAdks, + getSizes, + getTfcd, + isAdTest, + getTargetingAndExclusions, + getExperimentIds, + getIdentity, + getForceSafeframe, + getPageOffsets, + getContainers, + getIsFluid, +]; /** - * @param {!Array} impls - * @return {!Object} - */ + * @param {!Array} impls + * @return {!Object} + */ export function constructSRABlockParameters(impls) { const parameters = {'output': 'ldjh', 'impl': 'fifs'}; SRA_JOINERS.forEach(joiner => Object.assign(parameters, joiner(impls))); @@ -95,7 +104,7 @@ export function combineInventoryUnits(impls) { let index = uniqueIuNames[componentNames[i]]; if (index == undefined) { iuNamesOutput.push(componentNames[i]); - uniqueIuNames[componentNames[i]] = (index = uniqueIuNamesCount++); + uniqueIuNames[componentNames[i]] = index = uniqueIuNamesCount++; } encodedNames.push(index); } @@ -116,8 +125,10 @@ export function combineInventoryUnits(impls) { */ export function getCookieOptOut(impls) { return getFirstInstanceValue_(impls, impl => - impl.jsonTargeting && - impl.jsonTargeting['cookieOptOut'] ? {'co': '1'} : null); + impl.jsonTargeting && impl.jsonTargeting['cookieOptOut'] + ? {'co': '1'} + : null + ); } /** @@ -127,7 +138,7 @@ export function getCookieOptOut(impls) { * @visibleForTesting */ export function getAdks(impls) { - return ({'adks': impls.map(impl => devAssert(impl.adKey)).join()}); + return {'adks': impls.map(impl => devAssert(impl.adKey)).join()}; } /** @@ -137,8 +148,9 @@ export function getAdks(impls) { * @visibleForTesting */ export function getSizes(impls) { - return ({'prev_iu_szs': impls.map(impl => - devAssert(impl.parameterSize)).join()}); + return { + 'prev_iu_szs': impls.map(impl => devAssert(impl.parameterSize)).join(), + }; } /** @@ -150,8 +162,10 @@ export function getSizes(impls) { */ export function getTfcd(impls) { return getFirstInstanceValue_(impls, impl => - impl.jsonTargeting && impl.jsonTargeting[TFCD] ? - {'tfcd': impl.jsonTargeting[TFCD]} : null); + impl.jsonTargeting && impl.jsonTargeting[TFCD] + ? {'tfcd': impl.jsonTargeting[TFCD]} + : null + ); } /** @@ -163,7 +177,8 @@ export function getTfcd(impls) { */ export function isAdTest(impls) { return getFirstInstanceValue_(impls, impl => - isInManualExperiment(impl.element) ? {'adtest': 'on'} : null); + isInManualExperiment(impl.element) ? {'adtest': 'on'} : null + ); } /** @@ -178,12 +193,18 @@ export function getTargetingAndExclusions(impls) { let hasScp = false; const scps = []; impls.forEach(impl => { - if (impl.jsonTargeting && (impl.jsonTargeting['targeting'] || - impl.jsonTargeting['categoryExclusions'])) { + if ( + impl.jsonTargeting && + (impl.jsonTargeting['targeting'] || + impl.jsonTargeting['categoryExclusions']) + ) { hasScp = true; - scps.push(serializeTargeting( + scps.push( + serializeTargeting( impl.jsonTargeting['targeting'] || null, - impl.jsonTargeting['categoryExclusions'] || null)); + impl.jsonTargeting['categoryExclusions'] || null + ) + ); } else { scps.push(''); } @@ -202,10 +223,12 @@ export function getTargetingAndExclusions(impls) { */ export function getExperimentIds(impls) { const eids = {}; - const deid = (impls.length && - /(?:#|,)deid=([\d,]+)/i.exec(impls[0].win.location.hash)) || []; + const deid = + (impls.length && + /(?:#|,)deid=([\d,]+)/i.exec(impls[0].win.location.hash)) || + []; (deid[1] || '').split(',').forEach(eid => eid && (eids[eid] = 1)); - impls.forEach(impl => impl.experimentIds.forEach(eid => eids[eid] = 1)); + impls.forEach(impl => impl.experimentIds.forEach(eid => (eids[eid] = 1))); const eidKeys = Object.keys(eids).join(); return eidKeys ? {'eid': eidKeys} : null; } @@ -302,9 +325,9 @@ export function getIsFluid(impls) { * @return {?string} */ export function serializeTargeting(targeting, categoryExclusions) { - const serialized = targeting ? - Object.keys(targeting).map(key => serializeItem_(key, targeting[key])) : - []; + const serialized = targeting + ? Object.keys(targeting).map(key => serializeItem_(key, targeting[key])) + : []; if (categoryExclusions) { serialized.push(serializeItem_('excl_cat', categoryExclusions)); } @@ -318,8 +341,9 @@ export function serializeTargeting(targeting, categoryExclusions) { * @private */ function serializeItem_(key, value) { - const serializedValue = - (Array.isArray(value) ? value : [value]).map(encodeURIComponent).join(); + const serializedValue = (Array.isArray(value) ? value : [value]) + .map(encodeURIComponent) + .join(); return `${encodeURIComponent(key)}=${serializedValue}`; } @@ -337,45 +361,46 @@ function serializeItem_(key, value) { * @param {string} sraUrl url of SRA request for error reporting */ export function sraBlockCallbackHandler( - creative, headersObj, done, sraRequestAdUrlResolvers, sraUrl) { + creative, + headersObj, + done, + sraRequestAdUrlResolvers, + sraUrl +) { const headerNames = Object.keys(headersObj); - if (headerNames.length == 1 && - isObject(headersObj[headerNames[0]])) { + if (headerNames.length == 1 && isObject(headersObj[headerNames[0]])) { // TODO(keithwrightbos) - fix upstream so response does // not improperly place headers under key. - headersObj = - /** @type {!Object} */(headersObj)[headerNames[0]]; - headersObj = Object.keys(headersObj).reduce( - (newObj, key) => { - newObj[key.toLowerCase()] = headersObj[key]; - return newObj; - }, {}); + headersObj = /** @type {!Object} */ (headersObj)[headerNames[0]]; + headersObj = Object.keys(headersObj).reduce((newObj, key) => { + newObj[key.toLowerCase()] = headersObj[key]; + return newObj; + }, {}); } // Force safeframe rendering method. - headersObj[RENDERING_TYPE_HEADER.toLowerCase()] = - XORIGIN_MODE.SAFEFRAME; + headersObj[RENDERING_TYPE_HEADER.toLowerCase()] = XORIGIN_MODE.SAFEFRAME; // Construct pseudo fetch response to be passed down the A4A // promise chain for this block. const headers = -/** @type {?Headers} */ -({ - get: name => { - // TODO(keithwrightbos) - fix upstream so response writes - // all metadata values as strings. - let header = headersObj[name.toLowerCase()]; - if (header && typeof header != 'string') { - header = JSON.stringify(header); - } - return header; - }, - has: name => !!headersObj[name.toLowerCase()], -}); + /** @type {?Headers} */ + ({ + get: name => { + // TODO(keithwrightbos) - fix upstream so response writes + // all metadata values as strings. + let header = headersObj[name.toLowerCase()]; + if (header && typeof header != 'string') { + header = JSON.stringify(header); + } + return header; + }, + has: name => !!headersObj[name.toLowerCase()], + }); const fetchResponse = -/** @type {?Response} */ -({ - headers, - arrayBuffer: () => tryResolve(() => utf8Encode(creative)), -}); + /** @type {?Response} */ + ({ + headers, + arrayBuffer: () => tryResolve(() => utf8Encode(creative)), + }); // Pop head off of the array of resolvers as the response // should match the order of blocks declared in the ad url. // This allows the block to start rendering while the SRA @@ -384,7 +409,11 @@ export function sraBlockCallbackHandler( // If done, expect array to be empty (ensures ad response // included data for all slots). if (done && sraRequestAdUrlResolvers.length) { - dev().warn(TAG, 'Premature end of SRA response', - sraRequestAdUrlResolvers.length, sraUrl); + dev().warn( + TAG, + 'Premature end of SRA response', + sraRequestAdUrlResolvers.length, + sraUrl + ); } } diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js index 3d0ee7a98e8e..4e39cbc5e14b 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js @@ -41,9 +41,7 @@ import {CONSENT_POLICY_STATE} from '../../../../src/consent-state'; import {Deferred} from '../../../../src/utils/promise'; import {FriendlyIframeEmbed} from '../../../../src/friendly-iframe-embed'; import {Layout} from '../../../../src/layout'; -import { - QQID_HEADER, -} from '../../../../ads/google/a4a/utils'; +import {QQID_HEADER} from '../../../../ads/google/a4a/utils'; import {Services} from '../../../../src/services'; import {createElementWithAttributes} from '../../../../src/dom'; import {toggleExperiment} from '../../../../src/experiments'; @@ -83,8 +81,10 @@ function createImplTag(config, element, impl, env) { config.type = 'doubleclick'; element = createElementWithAttributes(env.win.document, 'amp-ad', config); // To trigger CSS styling. - element.setAttribute('data-a4a-upgrade-type', - 'amp-ad-network-doubleclick-impl'); + element.setAttribute( + 'data-a4a-upgrade-type', + 'amp-ad-network-doubleclick-impl' + ); // Used to test styling which is targetted at first iframe child of // amp-ad. const iframe = env.win.document.createElement('iframe'); @@ -122,8 +122,7 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { expect(impl.isValidElement()).to.be.true; }); it('should NOT be valid (impl tag name)', () => { - element = - doc.createElement('amp-ad-network-doubleclick-impl'); + element = doc.createElement('amp-ad-network-doubleclick-impl'); element.setAttribute('type', 'doubleclick'); element.setAttribute('data-ad-client', 'doubleclick'); impl = new AmpAdNetworkDoubleclickImpl(element); @@ -144,7 +143,6 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); }); - describe('#extractSize', () => { let preloadExtensionSpy; const size = {width: 200, height: 50}; @@ -171,44 +169,50 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { impl.isFluid_ = true; impl.element.setAttribute('height', 'fluid'); const resizeSpy = sandbox.spy(impl, 'attemptChangeSize'); - expect(impl.extractSize({ - get(name) { - return name == CREATIVE_SIZE_HEADER ? '0x0' : undefined; - }, - has(name) { - return name == CREATIVE_SIZE_HEADER; - }, - })).to.deep.equal({width: 0, height: 0}); + expect( + impl.extractSize({ + get(name) { + return name == CREATIVE_SIZE_HEADER ? '0x0' : undefined; + }, + has(name) { + return name == CREATIVE_SIZE_HEADER; + }, + }) + ).to.deep.equal({width: 0, height: 0}); expect(resizeSpy).to.not.be.called; }); it('should not load amp-analytics without an analytics header', () => { - expect(impl.extractSize({ - get() { - return undefined; - }, - has() { - return false; - }, - })).to.deep.equal(size); + expect( + impl.extractSize({ + get() { + return undefined; + }, + has() { + return false; + }, + }) + ).to.deep.equal(size); expect(preloadExtensionSpy.withArgs('amp-analytics')).to.not.be.called; }); it('should load amp-analytics with an analytics header', () => { const url = ['https://foo.com?a=b', 'https://blah.com?lsk=sdk&sld=vj']; - expect(impl.extractSize({ - get(name) { - switch (name) { - case 'X-AmpAnalytics': - return JSON.stringify({url}); - default: - return undefined; - } - }, - has(name) { - return !!this.get(name); - }, - })).to.deep.equal(size); + expect( + impl.extractSize({ + get(name) { + switch (name) { + case 'X-AmpAnalytics': + return JSON.stringify({url}); + default: + return undefined; + } + }, + has(name) { + return !!this.get(name); + }, + }) + ).to.deep.equal(size); expect(preloadExtensionSpy.withArgs('amp-analytics')).to.be.called; // exact value of ampAnalyticsConfig covered in // ads/google/test/test-utils.js @@ -216,66 +220,74 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { it('should load delayed impression amp-pixels with fluid', () => { impl.isFluidRequest_ = true; - expect(impl.extractSize({ - get(name) { - switch (name) { - case 'X-AmpImps': - return 'https://a.com?a=b,https://b.com?c=d'; - default: - return undefined; - } - }, - has(name) { - return !!this.get(name); - }, - })).to.deep.equal(size); - expect(impl.fluidImpressionUrl_).to - .equal('https://a.com?a=b,https://b.com?c=d'); + expect( + impl.extractSize({ + get(name) { + switch (name) { + case 'X-AmpImps': + return 'https://a.com?a=b,https://b.com?c=d'; + default: + return undefined; + } + }, + has(name) { + return !!this.get(name); + }, + }) + ).to.deep.equal(size); + expect(impl.fluidImpressionUrl_).to.equal( + 'https://a.com?a=b,https://b.com?c=d' + ); + }); + it('should not load delayed impression amp-pixels with fluid + multi-size', () => { + sandbox.stub(impl, 'handleResize_'); + impl.isFluid_ = true; + expect( + impl.extractSize({ + get(name) { + switch (name) { + case 'X-AmpImps': + return 'https://a.com?a=b,https://b.com?c=d'; + case 'X-CreativeSize': + return '200x50'; + default: + return undefined; + } + }, + has(name) { + return !!this.get(name); + }, + }) + ).to.deep.equal(size); + expect(impl.fluidImpressionUrl_).to.not.be.ok; + }); + it('should consume pageview state tokens when header is present', () => { + const removePageviewStateTokenSpy = sandbox.spy( + impl, + 'removePageviewStateToken' + ); + const setPageviewStateTokenSpy = sandbox.spy( + impl, + 'setPageviewStateToken' + ); + expect( + impl.extractSize({ + get(name) { + switch (name) { + case 'amp-ff-pageview-tokens': + return 'DUMMY_TOKEN'; + default: + return undefined; + } + }, + has(name) { + return !!this.get(name); + }, + }) + ).to.deep.equal(size); + expect(removePageviewStateTokenSpy).to.be.calledOnce; + expect(setPageviewStateTokenSpy.withArgs('DUMMY_TOKEN')).to.be.calledOnce; }); - it('should not load delayed impression amp-pixels with fluid + multi-size', - () => { - sandbox.stub(impl, 'handleResize_'); - impl.isFluid_ = true; - expect(impl.extractSize({ - get(name) { - switch (name) { - case 'X-AmpImps': - return 'https://a.com?a=b,https://b.com?c=d'; - case 'X-CreativeSize': - return '200x50'; - default: - return undefined; - } - }, - has(name) { - return !!this.get(name); - }, - })).to.deep.equal(size); - expect(impl.fluidImpressionUrl_).to.not.be.ok; - }); - it('should consume pageview state tokens when header is present', - () => { - const removePageviewStateTokenSpy = - sandbox.spy(impl, 'removePageviewStateToken'); - const setPageviewStateTokenSpy = - sandbox.spy(impl, 'setPageviewStateToken'); - expect(impl.extractSize({ - get(name) { - switch (name) { - case 'amp-ff-pageview-tokens': - return 'DUMMY_TOKEN'; - default: - return undefined; - } - }, - has(name) { - return !!this.get(name); - }, - })).to.deep.equal(size); - expect(removePageviewStateTokenSpy).to.be.calledOnce; - expect(setPageviewStateTokenSpy.withArgs('DUMMY_TOKEN')).to.be - .calledOnce; - }); it('should consume sandbox header', () => { impl.extractSize({ @@ -327,65 +339,74 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); [true, false].forEach(exp => { - it('injects amp analytics' + - (exp ? ', trigger immediate disable exp' : ''), () => { - impl.ampAnalyticsConfig_ = { - transport: {beacon: false, xhrpost: false}, - requests: { - visibility1: 'https://foo.com?hello=world', - visibility2: 'https://bar.com?a=b', - }, - triggers: { - continuousVisible: { - on: 'visible', - request: ['visibility1', 'visibility2'], - visibilitySpec: { + it( + 'injects amp analytics' + + (exp ? ', trigger immediate disable exp' : ''), + () => { + impl.ampAnalyticsConfig_ = { + transport: {beacon: false, xhrpost: false}, + requests: { + visibility1: 'https://foo.com?hello=world', + visibility2: 'https://bar.com?a=b', + }, + triggers: { + continuousVisible: { + on: 'visible', + request: ['visibility1', 'visibility2'], + visibilitySpec: { + selector: 'amp-ad', + selectionMethod: 'closest', + visiblePercentageMin: 50, + continuousTimeMin: 1000, + }, + }, + continuousVisibleIniLoad: { + on: 'ini-load', + selector: 'amp-ad', + selectionMethod: 'closest', + }, + continuousVisibleRenderStart: { + on: 'render-start', selector: 'amp-ad', selectionMethod: 'closest', - visiblePercentageMin: 50, - continuousTimeMin: 1000, }, }, - continuousVisibleIniLoad: { - on: 'ini-load', - selector: 'amp-ad', - selectionMethod: 'closest', + }; + // To placate assertion. + impl.responseHeaders_ = { + get: function(name) { + if (name == 'X-QQID') { + return 'qqid_string'; + } }, - continuousVisibleRenderStart: { - on: 'render-start', - selector: 'amp-ad', - selectionMethod: 'closest', + has: function(name) { + if (name == 'X-QQID') { + return true; + } }, - }, - }; - // To placate assertion. - impl.responseHeaders_ = { - get: function(name) { - if (name == 'X-QQID') { - return 'qqid_string'; - } - }, - has: function(name) { - if (name == 'X-QQID') { - return true; - } - }, - }; - if (exp) { - impl.postAdResponseExperimentFeatures['avr_disable_immediate'] = '1'; + }; + if (exp) { + impl.postAdResponseExperimentFeatures['avr_disable_immediate'] = + '1'; + } + impl.onCreativeRender(false); + const ampAnalyticsElement = impl.element.querySelector( + 'amp-analytics' + ); + expect(ampAnalyticsElement).to.be.ok; + expect(ampAnalyticsElement.CONFIG).jsonEqual( + impl.ampAnalyticsConfig_ + ); + expect(ampAnalyticsElement.getAttribute('sandbox')).to.equal('true'); + expect(ampAnalyticsElement.getAttribute('trigger')).to.equal( + exp ? '' : 'immediate' + ); + expect(impl.ampAnalyticsElement_).to.be.ok; + // Exact format of amp-analytics element covered in + // test/unit/test-analytics.js. + // Just ensure extensions is loaded, and analytics element appended. } - impl.onCreativeRender(false); - const ampAnalyticsElement = impl.element.querySelector('amp-analytics'); - expect(ampAnalyticsElement).to.be.ok; - expect(ampAnalyticsElement.CONFIG).jsonEqual(impl.ampAnalyticsConfig_); - expect(ampAnalyticsElement.getAttribute('sandbox')).to.equal('true'); - expect(ampAnalyticsElement.getAttribute('trigger')).to.equal( - exp ? '' : 'immediate'); - expect(impl.ampAnalyticsElement_).to.be.ok; - // Exact format of amp-analytics element covered in - // test/unit/test-analytics.js. - // Just ensure extensions is loaded, and analytics element appended. - }); + ); }); it('should register click listener', () => { @@ -399,23 +420,25 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { e.preventDefault(); // Make the test not actually navigate. clickHandlerCalled++; }; - adBody.innerHTML = '' + - '
    '; + adBody.innerHTML = + '' + + '
    '; const button = adBody.querySelector('#target'); const a = adBody.querySelector('a'); const ev1 = new Event('click', {bubbles: true}); ev1.pageX = 10; ev1.pageY = 20; - sandbox.stub(impl, 'getResource').returns( - { - getUpgradeDelayMs: () => 1, - }); + sandbox.stub(impl, 'getResource').returns({ + getUpgradeDelayMs: () => 1, + }); // Make sure the ad iframe (FIE) has a local URL replacements service. const urlReplacements = Services.urlReplacementsForDoc(element); - sandbox.stub(Services, 'urlReplacementsForDoc') - .withArgs(a).returns(urlReplacements); + sandbox + .stub(Services, 'urlReplacementsForDoc') + .withArgs(a) + .returns(urlReplacements); impl.buildCallback(); impl.size_ = {width: 123, height: 456}; @@ -436,18 +459,18 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { e.preventDefault(); // Make the test not actually navigate. clickHandlerCalled++; }; - adBody.innerHTML = '' + - '
    '; + adBody.innerHTML = + '' + + '
    '; const button = adBody.querySelector('#target'); const a = adBody.querySelector('a'); const ev1 = new Event('click', {bubbles: true}); ev1.pageX = 10; ev1.pageY = 20; - sandbox.stub(impl, 'getResource').returns( - { - getUpgradeDelayMs: () => 1, - }); + sandbox.stub(impl, 'getResource').returns({ + getUpgradeDelayMs: () => 1, + }); impl.buildCallback(); impl.size_ = {width: 123, height: 456}; impl.onCreativeRender({customElementExtensions: ['amp-ad-exit']}); @@ -494,7 +517,12 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { // to fix failures when this is run after 'gulp build', without a 'dist'. sandbox.stub(impl, 'getPageLayoutBox').callsFake(() => { return { - top: 11, left: 12, right: 0, bottom: 0, width: 0, height: 0, + top: 11, + left: 12, + right: 0, + bottom: 0, + width: 0, + height: 0, }; }); }); @@ -594,31 +622,44 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { describe('data-force-safeframe', () => { const fsfRegexp = /(\?|&)fsf=1(&|$)/; - it('handles default', () => expect( - new AmpAdNetworkDoubleclickImpl(element).getAdUrl()) - .to.eventually.not.match(fsfRegexp)); + it('handles default', () => + expect( + new AmpAdNetworkDoubleclickImpl(element).getAdUrl() + ).to.eventually.not.match(fsfRegexp)); it('case insensitive attribute name', () => { element.setAttribute('data-FORCE-SafeFraMe', '1'); return expect( - new AmpAdNetworkDoubleclickImpl(element).getAdUrl()) - .to.eventually.match(fsfRegexp); + new AmpAdNetworkDoubleclickImpl(element).getAdUrl() + ).to.eventually.match(fsfRegexp); }); ['tRuE', 'true', 'TRUE', '1'].forEach(val => { it(`valid attribute: ${val}`, () => { element.setAttribute('data-force-safeframe', val); - return expect(new AmpAdNetworkDoubleclickImpl(element).getAdUrl()) - .to.eventually.match(fsfRegexp); + return expect( + new AmpAdNetworkDoubleclickImpl(element).getAdUrl() + ).to.eventually.match(fsfRegexp); }); }); - ['aTrUe', 'trueB', '0', '01', '10', 'false', '', ' true', 'true ', - ' true '].forEach(val => { + [ + 'aTrUe', + 'trueB', + '0', + '01', + '10', + 'false', + '', + ' true', + 'true ', + ' true ', + ].forEach(val => { it(`invalid attribute: ${val}`, () => { element.setAttribute('data-force-safeframe', val); - return expect(new AmpAdNetworkDoubleclickImpl(element).getAdUrl()) - .to.eventually.not.match(fsfRegexp); + return expect( + new AmpAdNetworkDoubleclickImpl(element).getAdUrl() + ).to.eventually.not.match(fsfRegexp); }); }); }); @@ -640,54 +681,53 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { return impl.getAdUrl().then(url => // With exp dc-use-attr-for-format off, we can't test for specific // numbers, but we know that the values should be numeric. - expect(url).to.match(/sz=[0-9]+x[0-9]+/)); + expect(url).to.match(/sz=[0-9]+x[0-9]+/) + ); + }); + it('has correct format when width == "auto"', () => { + element.setAttribute('width', 'auto'); + new AmpAd(element).upgradeCallback(); + expect(impl.element.getAttribute('width')).to.equal('auto'); + impl.buildCallback(); + impl.onLayoutMeasure(); + return impl.getAdUrl().then(url => + // Ensure that "auto" doesn't appear anywhere here: + expect(url).to.match(/sz=[0-9]+x[0-9]+/) + ); + }); + it('has correct format with height/width override', () => { + element.setAttribute('data-override-width', '123'); + element.setAttribute('data-override-height', '456'); + new AmpAd(element).upgradeCallback(); + impl.buildCallback(); + impl.onLayoutMeasure(); + return impl.getAdUrl().then(url => expect(url).to.contain('sz=123x456&')); + }); + it('has correct format with height/width override and multiSize', () => { + element.setAttribute('data-override-width', '123'); + element.setAttribute('data-override-height', '456'); + element.setAttribute('data-multi-size', '1x2,3x4'); + element.setAttribute('data-multi-size-validation', 'false'); + new AmpAd(element).upgradeCallback(); + impl.buildCallback(); + impl.onLayoutMeasure(); + return impl + .getAdUrl() + .then(url => expect(url).to.contain('sz=123x456%7C1x2%7C3x4&')); + }); + it('has correct format with auto height/width and multiSize', () => { + element.setAttribute('data-override-width', '123'); + element.setAttribute('data-override-height', '456'); + element.setAttribute('data-multi-size', '1x2,3x4'); + element.setAttribute('data-multi-size-validation', 'false'); + new AmpAd(element).upgradeCallback(); + impl.buildCallback(); + impl.onLayoutMeasure(); + return impl.getAdUrl().then(url => + // Ensure that "auto" doesn't appear anywhere here: + expect(url).to.match(/sz=[0-9]+x[0-9]+%7C1x2%7C3x4&/) + ); }); - it('has correct format when width == "auto"', - () => { - element.setAttribute('width', 'auto'); - new AmpAd(element).upgradeCallback(); - expect(impl.element.getAttribute('width')).to.equal('auto'); - impl.buildCallback(); - impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - // Ensure that "auto" doesn't appear anywhere here: - expect(url).to.match(/sz=[0-9]+x[0-9]+/)); - }); - it('has correct format with height/width override', - () => { - element.setAttribute('data-override-width', '123'); - element.setAttribute('data-override-height', '456'); - new AmpAd(element).upgradeCallback(); - impl.buildCallback(); - impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - expect(url).to.contain('sz=123x456&')); - }); - it('has correct format with height/width override and multiSize', - () => { - element.setAttribute('data-override-width', '123'); - element.setAttribute('data-override-height', '456'); - element.setAttribute('data-multi-size', '1x2,3x4'); - element.setAttribute('data-multi-size-validation', 'false'); - new AmpAd(element).upgradeCallback(); - impl.buildCallback(); - impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - expect(url).to.contain('sz=123x456%7C1x2%7C3x4&')); - }); - it('has correct format with auto height/width and multiSize', - () => { - element.setAttribute('data-override-width', '123'); - element.setAttribute('data-override-height', '456'); - element.setAttribute('data-multi-size', '1x2,3x4'); - element.setAttribute('data-multi-size-validation', 'false'); - new AmpAd(element).upgradeCallback(); - impl.buildCallback(); - impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - // Ensure that "auto" doesn't appear anywhere here: - expect(url).to.match(/sz=[0-9]+x[0-9]+%7C1x2%7C3x4&/)); - }); it('has correct sz with fluid as multi-size', () => { element.setAttribute('width', '300'); element.setAttribute('height', '250'); @@ -695,8 +735,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { new AmpAd(element).upgradeCallback(); impl.buildCallback(); impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - expect(url).to.match(/sz=300x250%7C320x50&/)); + return impl + .getAdUrl() + .then(url => expect(url).to.match(/sz=300x250%7C320x50&/)); }); it('should have the correct ifi numbers - no refresh', function() { // When ran locally, this test tends to exceed 2000ms timeout. @@ -718,8 +759,10 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); }); it('should have google_preview parameter', () => { - sandbox.stub(impl, 'getLocationQueryParameterValue') - .withArgs('google_preview').returns('abcdef'); + sandbox + .stub(impl, 'getLocationQueryParameterValue') + .withArgs('google_preview') + .returns('abcdef'); new AmpAd(element).upgradeCallback(); expect(impl.getAdUrl()).to.eventually.contain('&gct=abcdef'); }); @@ -734,8 +777,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { // We don't really care about the behavior of the following methods, so // we'll just stub them out so that refresh() can run without tripping any // unrelated errors. - sandbox.stub(AmpA4A.prototype, 'initiateAdRequest').callsFake( - () => impl.adPromise_ = Promise.resolve()); + sandbox + .stub(AmpA4A.prototype, 'initiateAdRequest') + .callsFake(() => (impl.adPromise_ = Promise.resolve())); const tearDownSlotMock = sandbox.stub(AmpA4A.prototype, 'tearDownSlot'); tearDownSlotMock.returns(undefined); const destroyFrameMock = sandbox.stub(AmpA4A.prototype, 'destroyFrame'); @@ -753,13 +797,15 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { return impl.getAdUrl().then(url1 => { expect(url1).to.not.match(/(\?|&)rc=[0-9]+(&|$)/); expect(url1).to.match(/(\?|&)ifi=1(&|$)/); - return impl.refresh(() => {}).then(() => { - return impl.getAdUrl().then(url2 => { - expect(url2).to.match(/(\?|&)rc=1(&|$)/); - expect(url1).to.match(/(\?|&)ifi=1(&|$)/); - expect(url2).to.not.match(/(\?|&)frc=1(&|$)/); + return impl + .refresh(() => {}) + .then(() => { + return impl.getAdUrl().then(url2 => { + expect(url2).to.match(/(\?|&)rc=1(&|$)/); + expect(url1).to.match(/(\?|&)ifi=1(&|$)/); + expect(url2).to.not.match(/(\?|&)frc=1(&|$)/); + }); }); - }); }); }); it('has correct frc value', () => { @@ -770,16 +816,19 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); it('should include identity', () => { // Force get identity result by overloading window variable. - const token = /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/({ - token: 'abcdef', jar: 'some_jar', pucrd: 'some_pucrd', + const token = /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/ ({ + token: 'abcdef', + jar: 'some_jar', + pucrd: 'some_pucrd', }); impl.win['goog_identity_prom'] = Promise.resolve(token); impl.buildCallback(); return impl.getAdUrl().then(url => { - [/(\?|&)adsid=abcdef(&|$)/, + [ + /(\?|&)adsid=abcdef(&|$)/, /(\?|&)jar=some_jar(&|$)/, - /(\?|&)pucrd=some_pucrd(&|$)/].forEach( - regexp => expect(url).to.match(regexp)); + /(\?|&)pucrd=some_pucrd(&|$)/, + ].forEach(regexp => expect(url).to.match(regexp)); }); }); @@ -812,8 +861,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { })); it('should include msz/psz if in experiment', () => { - sandbox.stub(impl, 'randomlySelectUnsetExperiments_').returns( - {flexAdSlots: '21063174'}); + sandbox + .stub(impl, 'randomlySelectUnsetExperiments_') + .returns({flexAdSlots: '21063174'}); impl.setPageLevelExperiments(); return impl.getAdUrl().then(url => { expect(url).to.match(/(\?|&)msz=[0-9]+x-1(&|$)/); @@ -823,8 +873,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); it('should not include msz/psz if not in flexAdSlots control', () => { - sandbox.stub(impl, 'randomlySelectUnsetExperiments_').returns( - {flexAdSlots: '21063173'}); + sandbox + .stub(impl, 'randomlySelectUnsetExperiments_') + .returns({flexAdSlots: '21063173'}); impl.setPageLevelExperiments(); return impl.getAdUrl().then(url => { expect(url).to.not.match(/(\?|&)msz=/); @@ -851,8 +902,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { 'data-slot': '/1234/abc/def', }); const impl = new AmpAdNetworkDoubleclickImpl(element); - expect(impl.getPageParameters( - CONSENT_POLICY_STATE.INSUFFICIENT).npa).to.equal(1); + expect( + impl.getPageParameters(CONSENT_POLICY_STATE.INSUFFICIENT).npa + ).to.equal(1); }); }); @@ -860,10 +912,15 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { let sandbox; beforeEach(() => { - const setup = createImplTag({ - width: '300', - height: '150', - }, element, impl, env); + const setup = createImplTag( + { + width: '300', + height: '150', + }, + element, + impl, + env + ); element = setup[0]; impl = setup[1]; env = setup[2]; @@ -896,28 +953,27 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { expect(impl.iframe).is.ok; }); - it('should call #resetSlot, remove child iframe, but keep other children', - () => { - impl.ampAnalyticsConfig_ = {}; - impl.ampAnalyticsElement_ = - doc.createElement('amp-analytics'); - impl.element.appendChild(impl.ampAnalyticsElement_); - expect(impl.iframe).to.be.ok; - expect(impl.element.querySelector('iframe')).to.be.ok; - impl.unlayoutCallback(); - expect(impl.element.querySelector('div[placeholder]')).to.be.ok; - expect(impl.element.querySelector('div[fallback]')).to.be.ok; - expect(impl.element.querySelector('iframe')).to.be.null; - expect(impl.element.querySelectorAll('amp-analytics')) - .to.have.lengthOf(1); - expect(impl.element.querySelector('amp-analytics')).to.equal( - impl.a4aAnalyticsElement_); - expect(impl.iframe).to.be.null; - expect(impl.ampAnalyticsConfig_).to.be.null; - expect(impl.ampAnalyticsElement_).to.be.null; - expect(impl.element.getAttribute('data-amp-slot-index')) - .to.equal('1'); - }); + it('should call #resetSlot, remove child iframe, but keep other children', () => { + impl.ampAnalyticsConfig_ = {}; + impl.ampAnalyticsElement_ = doc.createElement('amp-analytics'); + impl.element.appendChild(impl.ampAnalyticsElement_); + expect(impl.iframe).to.be.ok; + expect(impl.element.querySelector('iframe')).to.be.ok; + impl.unlayoutCallback(); + expect(impl.element.querySelector('div[placeholder]')).to.be.ok; + expect(impl.element.querySelector('div[fallback]')).to.be.ok; + expect(impl.element.querySelector('iframe')).to.be.null; + expect(impl.element.querySelectorAll('amp-analytics')).to.have.lengthOf( + 1 + ); + expect(impl.element.querySelector('amp-analytics')).to.equal( + impl.a4aAnalyticsElement_ + ); + expect(impl.iframe).to.be.null; + expect(impl.ampAnalyticsConfig_).to.be.null; + expect(impl.ampAnalyticsElement_).to.be.null; + expect(impl.element.getAttribute('data-amp-slot-index')).to.equal('1'); + }); it('should call #unobserve on refreshManager', () => { impl.refreshManager_ = { @@ -967,9 +1023,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { * it has an AMP creative. */ function stubForAmpCreative() { - sandbox.stub( - signatureVerifierFor(impl.win), 'verify').callsFake( - () => Promise.resolve(VerificationStatus.OK)); + sandbox + .stub(signatureVerifierFor(impl.win), 'verify') + .callsFake(() => Promise.resolve(VerificationStatus.OK)); } /** @@ -978,8 +1034,10 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { */ function mockSendXhrRequest(size, isAmpCreative) { return { - arrayBuffer: () => Promise.resolve(utf8Encode( - 'Hello, World!')), + arrayBuffer: () => + Promise.resolve( + utf8Encode('Hello, World!') + ), headers: { get(prop) { switch (prop) { @@ -1042,20 +1100,24 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { headers: {'Content-Type': 'application/jwk-set+json'}, }; env.expectFetch( - 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', - keyResponse); + 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', + keyResponse + ); env.expectFetch( - 'https://cdn.ampproject.org/amp-ad-verifying-keyset-dev.json', - keyResponse); + 'https://cdn.ampproject.org/amp-ad-verifying-keyset-dev.json', + keyResponse + ); }); it('amp creative - should force iframe to match size of creative', () => { stubForAmpCreative(); - sandbox.stub(impl, 'sendXhrRequest').returns( - mockSendXhrRequest('150x50', true)); + sandbox + .stub(impl, 'sendXhrRequest') + .returns(mockSendXhrRequest('150x50', true)); // Stub ini load otherwise FIE could delay test - sandbox./*OK*/stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') - .returns(Promise.resolve()); + sandbox + ./*OK*/ stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .returns(Promise.resolve()); impl.buildCallback(); impl.onLayoutMeasure(); return impl.layoutCallback().then(() => { @@ -1067,8 +1129,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); it('should force iframe to match size of creative', () => { - sandbox.stub(impl, 'sendXhrRequest').returns( - mockSendXhrRequest('150x50', false)); + sandbox + .stub(impl, 'sendXhrRequest') + .returns(mockSendXhrRequest('150x50', false)); impl.buildCallback(); impl.onLayoutMeasure(); return impl.layoutCallback().then(() => { @@ -1081,13 +1144,18 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { it('amp creative - should force iframe to match size of slot', () => { stubForAmpCreative(); - sandbox.stub(impl, 'sendXhrRequest').callsFake( - () => mockSendXhrRequest(undefined, true)); - sandbox.stub(impl, 'renderViaIframeGet_').callsFake( - () => impl.iframeRenderHelper_({src: impl.adUrl_, name: 'name'})); + sandbox + .stub(impl, 'sendXhrRequest') + .callsFake(() => mockSendXhrRequest(undefined, true)); + sandbox + .stub(impl, 'renderViaIframeGet_') + .callsFake(() => + impl.iframeRenderHelper_({src: impl.adUrl_, name: 'name'}) + ); // Stub ini load otherwise FIE could delay test - sandbox./*OK*/stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') - .returns(Promise.resolve()); + sandbox + ./*OK*/ stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .returns(Promise.resolve()); // This would normally be set in AmpA4a#buildCallback. impl.creativeSize_ = {width: 200, height: 50}; impl.buildCallback(); @@ -1101,10 +1169,14 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); it('should force iframe to match size of slot', () => { - sandbox.stub(impl, 'sendXhrRequest').callsFake( - () => mockSendXhrRequest(undefined, false)); - sandbox.stub(impl, 'renderViaIframeGet_').callsFake( - () => impl.iframeRenderHelper_({src: impl.adUrl_, name: 'name'})); + sandbox + .stub(impl, 'sendXhrRequest') + .callsFake(() => mockSendXhrRequest(undefined, false)); + sandbox + .stub(impl, 'renderViaIframeGet_') + .callsFake(() => + impl.iframeRenderHelper_({src: impl.adUrl_, name: 'name'}) + ); // This would normally be set in AmpA4a#buildCallback. impl.creativeSize_ = {width: 200, height: 50}; impl.buildCallback(); @@ -1119,12 +1191,14 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { it('should issue an ad request even with bad multi-size data attr', () => { stubForAmpCreative(); - sandbox.stub(impl, 'sendXhrRequest').callsFake( - () => mockSendXhrRequest(undefined, true)); + sandbox + .stub(impl, 'sendXhrRequest') + .callsFake(() => mockSendXhrRequest(undefined, true)); impl.element.setAttribute('data-multi-size', '201x50'); // Stub ini load otherwise FIE could delay test - sandbox./*OK*/stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') - .returns(Promise.resolve()); + sandbox + ./*OK*/ stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .returns(Promise.resolve()); impl.buildCallback(); impl.onLayoutMeasure(); return impl.layoutCallback().then(() => { @@ -1173,8 +1247,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { expect(gutData.events[0].slotid).to.equal(slotId + '_0'); expect(gutData.events[0].messageId).to.equal(4); - expect(gutData.slots[0].contentUrl).to - .equal('http://www.getmesomeads.com'); + expect(gutData.slots[0].contentUrl).to.equal( + 'http://www.getmesomeads.com' + ); expect(gutData.slots[0].id).to.equal(slotId + '_0'); expect(gutData.slots[0].leafAdUnitName).to.equal(slotId); expect(gutData.slots[0].domId).to.equal(slotId + '_0'); @@ -1185,8 +1260,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }; const postMessageSpy = sandbox.spy(env.win.opener, 'postMessage'); impl.win = env.win; - return impl.postTroubleshootMessage().then(() => - expect(postMessageSpy).to.be.calledOnce); + return impl + .postTroubleshootMessage() + .then(() => expect(postMessageSpy).to.be.calledOnce); }); it('should not emit post message', () => { @@ -1221,375 +1297,431 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { it('should return safeframe if fluid', () => { impl.isLayoutSupported(Layout.FLUID); - expect(impl.getNonAmpCreativeRenderingMethod()).to - .equal(XORIGIN_MODE.SAFEFRAME); + expect(impl.getNonAmpCreativeRenderingMethod()).to.equal( + XORIGIN_MODE.SAFEFRAME + ); }); it('should return safeframe if force safeframe', () => { element.setAttribute('data-force-safeframe', '1'); - expect(new AmpAdNetworkDoubleclickImpl(element) - .getNonAmpCreativeRenderingMethod()).to.equal(XORIGIN_MODE.SAFEFRAME); + expect( + new AmpAdNetworkDoubleclickImpl( + element + ).getNonAmpCreativeRenderingMethod() + ).to.equal(XORIGIN_MODE.SAFEFRAME); }); }); }); +describes.realWin( + 'additional amp-ad-network-doubleclick-impl', + realWinConfigAmpAd, + env => { + let doc; + let impl; + let element; -describes.realWin('additional amp-ad-network-doubleclick-impl', - realWinConfigAmpAd, env => { - let doc; - let impl; - let element; + beforeEach(() => { + doc = env.win.document; + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', + }); + doc.body.appendChild(element); + impl = new AmpAdNetworkDoubleclickImpl(element); + }); + describe('#onNetworkFailure', () => { beforeEach(() => { - doc = env.win.document; element = createElementWithAttributes(doc, 'amp-ad', { 'width': '200', 'height': '50', 'type': 'doubleclick', }); - doc.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl(element); }); - describe('#onNetworkFailure', () => { - - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - impl = new AmpAdNetworkDoubleclickImpl(element); - }); - - it('should append error parameter', () => { - const TEST_URL = 'https://somenetwork.com/foo?hello=world&a=b'; - expect(impl.onNetworkFailure(new Error('xhr failure'), TEST_URL)) - .to.jsonEqual({adUrl: TEST_URL + '&aet=n'}); - }); + it('should append error parameter', () => { + const TEST_URL = 'https://somenetwork.com/foo?hello=world&a=b'; + expect( + impl.onNetworkFailure(new Error('xhr failure'), TEST_URL) + ).to.jsonEqual({adUrl: TEST_URL + '&aet=n'}); }); + }); - describe('centering', () => { - const size = {width: '300px', height: '150px'}; - - /** - * @param {!Element} iframe - * @param {{width: number, height: number}} expectedSize - */ - function verifyCss(iframe, expectedSize) { - expect(iframe).to.be.ok; - const style = env.win.getComputedStyle(iframe); - expect(style.top).to.equal('50%'); - expect(style.left).to.equal('50%'); - expect(style.width).to.equal(expectedSize.width); - expect(style.height).to.equal(expectedSize.height); - // We don't know the exact values by which the frame will be - // translated, as this can vary depending on whether we use the - // height/width attributes, or the actual size of the frame. To make - // this less of a hassle, we'll just match against regexp. - expect(style.transform).to.match(new RegExp( - 'matrix\\(1, 0, 0, 1, -[0-9]+, -[0-9]+\\)')); - } - - afterEach(() => env.win.document.body.removeChild(impl.element)); + describe('centering', () => { + const size = {width: '300px', height: '150px'}; - it('centers iframe in slot when height && width', () => { - const setup = createImplTag({ + /** + * @param {!Element} iframe + * @param {{width: number, height: number}} expectedSize + */ + function verifyCss(iframe, expectedSize) { + expect(iframe).to.be.ok; + const style = env.win.getComputedStyle(iframe); + expect(style.top).to.equal('50%'); + expect(style.left).to.equal('50%'); + expect(style.width).to.equal(expectedSize.width); + expect(style.height).to.equal(expectedSize.height); + // We don't know the exact values by which the frame will be + // translated, as this can vary depending on whether we use the + // height/width attributes, or the actual size of the frame. To make + // this less of a hassle, we'll just match against regexp. + expect(style.transform).to.match( + new RegExp('matrix\\(1, 0, 0, 1, -[0-9]+, -[0-9]+\\)') + ); + } + + afterEach(() => env.win.document.body.removeChild(impl.element)); + + it('centers iframe in slot when height && width', () => { + const setup = createImplTag( + { width: '300', height: '150', - }, element, impl, env); - element = setup[0]; - impl = setup[1]; - env = setup[2]; - expect(impl.element.getAttribute('width')).to.equal('300'); - expect(impl.element.getAttribute('height')).to.equal('150'); - verifyCss(impl.iframe, size); - }); - it('centers iframe in slot when !height && !width', () => { - const setup = createImplTag({ + }, + element, + impl, + env + ); + element = setup[0]; + impl = setup[1]; + env = setup[2]; + expect(impl.element.getAttribute('width')).to.equal('300'); + expect(impl.element.getAttribute('height')).to.equal('150'); + verifyCss(impl.iframe, size); + }); + it('centers iframe in slot when !height && !width', () => { + const setup = createImplTag( + { layout: 'fixed', - }, element, impl, env); - element = setup[0]; - impl = setup[1]; - env = setup[2]; - expect(impl.element.getAttribute('width')).to.be.null; - expect(impl.element.getAttribute('height')).to.be.null; - verifyCss(impl.iframe, size); - }); - it('centers iframe in slot when !height && width', () => { - const setup = createImplTag({ + }, + element, + impl, + env + ); + element = setup[0]; + impl = setup[1]; + env = setup[2]; + expect(impl.element.getAttribute('width')).to.be.null; + expect(impl.element.getAttribute('height')).to.be.null; + verifyCss(impl.iframe, size); + }); + it('centers iframe in slot when !height && width', () => { + const setup = createImplTag( + { width: '300', layout: 'fixed', - }, element, impl, env); - element = setup[0]; - impl = setup[1]; - env = setup[2]; - expect(impl.element.getAttribute('width')).to.equal('300'); - expect(impl.element.getAttribute('height')).to.be.null; - verifyCss(impl.iframe, size); - }); - it('centers iframe in slot when height && !width', () => { - const setup = createImplTag({ + }, + element, + impl, + env + ); + element = setup[0]; + impl = setup[1]; + env = setup[2]; + expect(impl.element.getAttribute('width')).to.equal('300'); + expect(impl.element.getAttribute('height')).to.be.null; + verifyCss(impl.iframe, size); + }); + it('centers iframe in slot when height && !width', () => { + const setup = createImplTag( + { height: '150', layout: 'fixed', - }, element, impl, env); - element = setup[0]; - impl = setup[1]; - env = setup[2]; - expect(impl.element.getAttribute('width')).to.be.null; - expect(impl.element.getAttribute('height')).to.equal('150'); - verifyCss(impl.iframe, size); - }); + }, + element, + impl, + env + ); + element = setup[0]; + impl = setup[1]; + env = setup[2]; + expect(impl.element.getAttribute('width')).to.be.null; + expect(impl.element.getAttribute('height')).to.equal('150'); + verifyCss(impl.iframe, size); }); + }); - describe('#fireDelayedImpressions', () => { - let isSecureStub; - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - impl = new AmpAdNetworkDoubleclickImpl(element); - impl.getAmpDoc = () => {}; - isSecureStub = sandbox.stub(); - sandbox.stub(Services, 'urlForDoc').returns({isSecure: isSecureStub}); - }); - - it('should handle null impressions', () => { - impl.fireDelayedImpressions(null); - expect(env.win.document.querySelectorAll('amp-pixel').length) - .to.equal(0); + describe('#fireDelayedImpressions', () => { + let isSecureStub; + beforeEach(() => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', }); + impl = new AmpAdNetworkDoubleclickImpl(element); + impl.getAmpDoc = () => {}; + isSecureStub = sandbox.stub(); + sandbox.stub(Services, 'urlForDoc').returns({isSecure: isSecureStub}); + }); - it('should not include non-https', () => { - const urls = ['http://f.com?a=b', 'https://b.net?c=d']; - isSecureStub.withArgs(urls[0]).returns(false); - isSecureStub.withArgs(urls[1]).returns(true); - impl.fireDelayedImpressions(urls.join()); - expect(env.win.document.querySelectorAll('amp-pixel').length) - .to.equal(1); - expect(env.win.document.querySelector( - `amp-pixel[src="${urls[1]}"][referrerpolicy=""]`)) - .to.be.ok; - }); + it('should handle null impressions', () => { + impl.fireDelayedImpressions(null); + expect(env.win.document.querySelectorAll('amp-pixel').length).to.equal( + 0 + ); + }); - it('should append amp-pixel w/o scrubReferer', () => { - const urls = ['https://f.com?a=b', 'https://b.net?c=d']; - isSecureStub.returns(true); - impl.fireDelayedImpressions(urls.join()); - urls.forEach(url => expect(env.win.document.querySelector( - `amp-pixel[src="${url}"][referrerpolicy=""]`)).to.be.ok); - }); + it('should not include non-https', () => { + const urls = ['http://f.com?a=b', 'https://b.net?c=d']; + isSecureStub.withArgs(urls[0]).returns(false); + isSecureStub.withArgs(urls[1]).returns(true); + impl.fireDelayedImpressions(urls.join()); + expect(env.win.document.querySelectorAll('amp-pixel').length).to.equal( + 1 + ); + expect( + env.win.document.querySelector( + `amp-pixel[src="${urls[1]}"][referrerpolicy=""]` + ) + ).to.be.ok; + }); - it('should append amp-pixel with scrubReferer', () => { - const urls = ['https://f.com?a=b', 'https://b.net?c=d']; - isSecureStub.returns(true); - impl.fireDelayedImpressions(urls.join(), true); - urls.forEach(url => expect(env.win.document.querySelector( - `amp-pixel[src="${url}"][referrerpolicy="no-referrer"]`)) - .to.be.ok); - }); + it('should append amp-pixel w/o scrubReferer', () => { + const urls = ['https://f.com?a=b', 'https://b.net?c=d']; + isSecureStub.returns(true); + impl.fireDelayedImpressions(urls.join()); + urls.forEach( + url => + expect( + env.win.document.querySelector( + `amp-pixel[src="${url}"][referrerpolicy=""]` + ) + ).to.be.ok + ); }); - describe('#idleRenderOutsideViewport', () => { - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - impl = new AmpAdNetworkDoubleclickImpl(element); - sandbox.stub(impl, 'getResource').returns( - {whenWithinViewport: () => Promise.resolve()}); - }); + it('should append amp-pixel with scrubReferer', () => { + const urls = ['https://f.com?a=b', 'https://b.net?c=d']; + isSecureStub.returns(true); + impl.fireDelayedImpressions(urls.join(), true); + urls.forEach( + url => + expect( + env.win.document.querySelector( + `amp-pixel[src="${url}"][referrerpolicy="no-referrer"]` + ) + ).to.be.ok + ); + }); + }); - it('should use experiment value', () => { - impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; - expect(impl.idleRenderOutsideViewport()).to.equal(4); - expect(impl.isIdleRender_).to.be.true; + describe('#idleRenderOutsideViewport', () => { + beforeEach(() => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', }); + impl = new AmpAdNetworkDoubleclickImpl(element); + sandbox + .stub(impl, 'getResource') + .returns({whenWithinViewport: () => Promise.resolve()}); + }); - it('should return false if using loading strategy', () => { - impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; - impl.element.setAttribute('data-loading-strategy', - 'prefer-viewability-over-views'); - expect(impl.idleRenderOutsideViewport()).to.be.false; - expect(impl.isIdleRender_).to.be.false; - }); + it('should use experiment value', () => { + impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; + expect(impl.idleRenderOutsideViewport()).to.equal(4); + expect(impl.isIdleRender_).to.be.true; + }); - it('should return false if invalid experiment value', () => { - impl.postAdResponseExperimentFeatures['render-idle-vp'] = 'abc'; - expect(impl.idleRenderOutsideViewport()).to.be.false; - }); + it('should return false if using loading strategy', () => { + impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; + impl.element.setAttribute( + 'data-loading-strategy', + 'prefer-viewability-over-views' + ); + expect(impl.idleRenderOutsideViewport()).to.be.false; + expect(impl.isIdleRender_).to.be.false; + }); - it('should return 12 if no experiment header', () => { - expect(impl.idleRenderOutsideViewport()).to.equal(12); - }); + it('should return false if invalid experiment value', () => { + impl.postAdResponseExperimentFeatures['render-idle-vp'] = 'abc'; + expect(impl.idleRenderOutsideViewport()).to.be.false; + }); - it('should return renderOutsideViewport boolean', () => { - sandbox.stub(impl, 'renderOutsideViewport').returns(false); - expect(impl.idleRenderOutsideViewport()).to.be.false; - }); + it('should return 12 if no experiment header', () => { + expect(impl.idleRenderOutsideViewport()).to.equal(12); }); - describe('idle renderNonAmpCreative', () => { + it('should return renderOutsideViewport boolean', () => { + sandbox.stub(impl, 'renderOutsideViewport').returns(false); + expect(impl.idleRenderOutsideViewport()).to.be.false; + }); + }); - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - impl = new AmpAdNetworkDoubleclickImpl(element); - impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; - impl.postAdResponseExperimentFeatures['render-idle-throttle'] = - 'true'; - sandbox.stub(AmpA4A.prototype, 'renderNonAmpCreative') - .returns(Promise.resolve()); + describe('idle renderNonAmpCreative', () => { + beforeEach(() => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', }); + impl = new AmpAdNetworkDoubleclickImpl(element); + impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; + impl.postAdResponseExperimentFeatures['render-idle-throttle'] = 'true'; + sandbox + .stub(AmpA4A.prototype, 'renderNonAmpCreative') + .returns(Promise.resolve()); + }); - // TODO(jeffkaufman, #13422): this test was silently failing - it.skip('should throttle if idle render and non-AMP creative', () => { - impl.win['3pla'] = 1; - const startTime = Date.now(); - return impl.renderNonAmpCreative().then(() => { - expect(Date.now() - startTime).to.be.at.least(1000); - }); + // TODO(jeffkaufman, #13422): this test was silently failing + it.skip('should throttle if idle render and non-AMP creative', () => { + impl.win['3pla'] = 1; + const startTime = Date.now(); + return impl.renderNonAmpCreative().then(() => { + expect(Date.now() - startTime).to.be.at.least(1000); }); + }); - it('should NOT throttle if idle experiment not enabled', () => { - impl.win['3pla'] = 1; - delete impl.postAdResponseExperimentFeatures['render-idle-vp']; - const startTime = Date.now(); - return impl.renderNonAmpCreative().then(() => { - expect(Date.now() - startTime).to.be.at.most(50); - }); + it('should NOT throttle if idle experiment not enabled', () => { + impl.win['3pla'] = 1; + delete impl.postAdResponseExperimentFeatures['render-idle-vp']; + const startTime = Date.now(); + return impl.renderNonAmpCreative().then(() => { + expect(Date.now() - startTime).to.be.at.most(50); }); + }); - it('should NOT throttle if experiment throttle not enabled', () => { - impl.win['3pla'] = 1; - const startTime = Date.now(); - return impl.renderNonAmpCreative().then(() => { - expect(Date.now() - startTime).to.be.at.most(50); - }); + it('should NOT throttle if experiment throttle not enabled', () => { + impl.win['3pla'] = 1; + const startTime = Date.now(); + return impl.renderNonAmpCreative().then(() => { + expect(Date.now() - startTime).to.be.at.most(50); }); + }); - it('should NOT throttle if idle render and no previous', () => { - impl.win['3pla'] = 0; - const startTime = Date.now(); - return impl.renderNonAmpCreative().then(() => { - expect(Date.now() - startTime).to.be.at.most(50); - }); + it('should NOT throttle if idle render and no previous', () => { + impl.win['3pla'] = 0; + const startTime = Date.now(); + return impl.renderNonAmpCreative().then(() => { + expect(Date.now() - startTime).to.be.at.most(50); }); }); + }); - describe('#preconnect', () => { - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - doc.body.appendChild(element); - impl = new AmpAdNetworkDoubleclickImpl(element); + describe('#preconnect', () => { + beforeEach(() => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', }); + doc.body.appendChild(element); + impl = new AmpAdNetworkDoubleclickImpl(element); }); + }); - describe('#getConsentPolicy', () => { - it('should return null', () => expect( - AmpAdNetworkDoubleclickImpl.prototype.getConsentPolicy()) - .to.be.null); + describe('#getConsentPolicy', () => { + it('should return null', () => + expect(AmpAdNetworkDoubleclickImpl.prototype.getConsentPolicy()).to.be + .null); + }); + + describe('#setPageLevelExperiments', () => { + let randomlySelectUnsetExperimentsStub; + let extractUrlExperimentIdStub; + beforeEach(() => { + randomlySelectUnsetExperimentsStub = sandbox.stub( + impl, + 'randomlySelectUnsetExperiments_' + ); + extractUrlExperimentIdStub = sandbox.stub( + impl, + 'extractUrlExperimentId_' + ); + sandbox.stub(AmpA4A.prototype, 'buildCallback').callsFake(() => {}); + sandbox.stub(impl, 'getAmpDoc').returns({}); + sandbox + .stub(Services, 'viewerForDoc') + .returns({whenFirstVisible: () => new Deferred().promise}); + }); + afterEach(() => { + toggleExperiment(env.win, 'envDfpInvOrigDeprecated', false); }); - describe('#setPageLevelExperiments', () => { - let randomlySelectUnsetExperimentsStub; - let extractUrlExperimentIdStub; - beforeEach(() => { - randomlySelectUnsetExperimentsStub = - sandbox.stub(impl, 'randomlySelectUnsetExperiments_'); - extractUrlExperimentIdStub = - sandbox.stub(impl, 'extractUrlExperimentId_'); - sandbox.stub(AmpA4A.prototype, 'buildCallback').callsFake(() => {}); - sandbox.stub(impl, 'getAmpDoc').returns({}); - sandbox.stub(Services, 'viewerForDoc').returns( - {whenFirstVisible: () => new Deferred().promise}); - }); - afterEach(() => { - toggleExperiment(env.win, 'envDfpInvOrigDeprecated', false); - }); + it('should set invalid origin fix experiment if on canonical', () => { + randomlySelectUnsetExperimentsStub.returns({}); + impl.setPageLevelExperiments(); + expect(impl.experimentIds.includes('21060933')).to.be.true; + }); - it('should set invalid origin fix experiment if on canonical', () => { - randomlySelectUnsetExperimentsStub.returns({}); - impl.setPageLevelExperiments(); - expect(impl.experimentIds.includes('21060933')).to.be.true; - }); + it('should not set invalid origin fix if exp on', () => { + toggleExperiment(env.win, 'envDfpInvOrigDeprecated', true); + randomlySelectUnsetExperimentsStub.returns({}); + impl.setPageLevelExperiments(); + expect(impl.experimentIds.includes('21060933')).to.be.true; + }); - it('should not set invalid origin fix if exp on', () => { - toggleExperiment(env.win, 'envDfpInvOrigDeprecated', true); - randomlySelectUnsetExperimentsStub.returns({}); - impl.setPageLevelExperiments(); - expect(impl.experimentIds.includes('21060933')).to.be.true; + it('should select SRA experiments', () => { + randomlySelectUnsetExperimentsStub.returns({ + doubleclickSraExp: '117152667', }); + extractUrlExperimentIdStub.returns(undefined); + impl.buildCallback(); + expect(impl.experimentIds.includes('117152667')).to.be.true; + expect(impl.useSra).to.be.true; + }); - it('should select SRA experiments', () => { - randomlySelectUnsetExperimentsStub.returns( - {doubleclickSraExp: '117152667'}); - extractUrlExperimentIdStub.returns(undefined); - impl.buildCallback(); - expect(impl.experimentIds.includes('117152667')).to.be.true; - expect(impl.useSra).to.be.true; - }); + it('should force-select SRA experiment from URL experiment ID', () => { + randomlySelectUnsetExperimentsStub.returns({}); + impl.setPageLevelExperiments('8'); + expect(impl.experimentIds.includes('117152667')).to.be.true; + }); - it('should force-select SRA experiment from URL experiment ID', () => { + describe('should properly limit SRA traffic', () => { + let experimentInfoMap; + beforeEach(() => { randomlySelectUnsetExperimentsStub.returns({}); - impl.setPageLevelExperiments('8'); - expect(impl.experimentIds.includes('117152667')).to.be.true; + impl.setPageLevelExperiments(); + // args format is call array followed by parameter array so expect + // first call, first param. + experimentInfoMap = + randomlySelectUnsetExperimentsStub.args[0][0]['doubleclickSraExp']; + expect(experimentInfoMap).to.be.ok; + expect(impl.useSra).to.be.false; }); - describe('should properly limit SRA traffic', () => { - let experimentInfoMap; - beforeEach(() => { - randomlySelectUnsetExperimentsStub.returns({}); - impl.setPageLevelExperiments(); - // args format is call array followed by parameter array so expect - // first call, first param. - experimentInfoMap = randomlySelectUnsetExperimentsStub - .args[0][0]['doubleclickSraExp']; - expect(experimentInfoMap).to.be.ok; - expect(impl.useSra).to.be.false; - }); - - it('should allow by default', () => - expect(experimentInfoMap.isTrafficEligible()).to.be.true); + it('should allow by default', () => + expect(experimentInfoMap.isTrafficEligible()).to.be.true); - it('should not allow if refresh meta', () => { - doc.head.appendChild(createElementWithAttributes( - doc, 'meta', {name: 'amp-ad-enable-refresh'})); - expect(experimentInfoMap.isTrafficEligible()).to.be.false; - }); + it('should not allow if refresh meta', () => { + doc.head.appendChild( + createElementWithAttributes(doc, 'meta', { + name: 'amp-ad-enable-refresh', + }) + ); + expect(experimentInfoMap.isTrafficEligible()).to.be.false; + }); - it('should not allow if sra meta', () => { - doc.head.appendChild(createElementWithAttributes( - doc, 'meta', {name: 'amp-ad-doubleclick-sra'})); - expect(experimentInfoMap.isTrafficEligible()).to.be.false; - }); + it('should not allow if sra meta', () => { + doc.head.appendChild( + createElementWithAttributes(doc, 'meta', { + name: 'amp-ad-doubleclick-sra', + }) + ); + expect(experimentInfoMap.isTrafficEligible()).to.be.false; + }); - it('should not allow if block level refresh', () => { - impl.element.setAttribute('data-enable-refresh', ''); - expect(experimentInfoMap.isTrafficEligible()).to.be.false; - }); + it('should not allow if block level refresh', () => { + impl.element.setAttribute('data-enable-refresh', ''); + expect(experimentInfoMap.isTrafficEligible()).to.be.false; }); }); + }); - describe('#getPageviewStateTokensForAdRequest', () => { - - beforeEach(() => { - resetTokensToInstancesMap(); - }); + describe('#getPageviewStateTokensForAdRequest', () => { + beforeEach(() => { + resetTokensToInstancesMap(); + }); - it('should return the tokens associated with instances that are not ' + - 'passed to it as an argument', () => { + it( + 'should return the tokens associated with instances that are not ' + + 'passed to it as an argument', + () => { const element1 = doc.createElement('amp-ad'); element1.setAttribute('type', 'doubleclick'); element1.setAttribute('data-ad-client', 'doubleclick'); @@ -1601,69 +1733,82 @@ describes.realWin('additional amp-ad-network-doubleclick-impl', const impl2 = new AmpAdNetworkDoubleclickImpl(element2); impl2.setPageviewStateToken('DUMMY_TOKEN_2'); const instances = [impl1]; - expect(getPageviewStateTokensForAdRequest(instances)).to.deep.equal( - ['DUMMY_TOKEN_2']); - }); - }); + expect(getPageviewStateTokensForAdRequest(instances)).to.deep.equal([ + 'DUMMY_TOKEN_2', + ]); + } + ); + }); - describe('#checksumVerification', () => { - it('should call super if missing Algorithm header', () => { - sandbox.stub(AmpA4A.prototype, 'maybeValidateAmpCreative') - .returns(Promise.resolve('foo')); - const creative = 'This is some text'; - const mockHeaders = { - get: key => { - switch (key) { - case 'AMP-Verification-Checksum-Algorithm': - return 'unknown'; - case 'AMP-Verification-Checksum': - return '2569076912'; - default: - throw new Error(`unexpected header: ${key}`); - } - }, - }; - expect(AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( - utf8Encode(creative), mockHeaders)).to.eventually.equal('foo'); - }); + describe('#checksumVerification', () => { + it('should call super if missing Algorithm header', () => { + sandbox + .stub(AmpA4A.prototype, 'maybeValidateAmpCreative') + .returns(Promise.resolve('foo')); + const creative = 'This is some text'; + const mockHeaders = { + get: key => { + switch (key) { + case 'AMP-Verification-Checksum-Algorithm': + return 'unknown'; + case 'AMP-Verification-Checksum': + return '2569076912'; + default: + throw new Error(`unexpected header: ${key}`); + } + }, + }; + expect( + AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( + utf8Encode(creative), + mockHeaders + ) + ).to.eventually.equal('foo'); + }); - it('should properly validate checksum', () => { - const creative = 'This is some text'; - const mockHeaders = { - get: key => { - switch (key) { - case 'AMP-Verification-Checksum-Algorithm': - return 'djb2a-32'; - case 'AMP-Verification-Checksum': - return '2569076912'; - default: - throw new Error(`unexpected header: ${key}`); - } - }, - }; - return AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( - utf8Encode(creative), mockHeaders).then(result => { + it('should properly validate checksum', () => { + const creative = 'This is some text'; + const mockHeaders = { + get: key => { + switch (key) { + case 'AMP-Verification-Checksum-Algorithm': + return 'djb2a-32'; + case 'AMP-Verification-Checksum': + return '2569076912'; + default: + throw new Error(`unexpected header: ${key}`); + } + }, + }; + return AmpAdNetworkDoubleclickImpl.prototype + .maybeValidateAmpCreative(utf8Encode(creative), mockHeaders) + .then(result => { expect(result).to.be.ok; expect(utf8Decode(result)).to.equal(creative); }); - }); + }); - it('should fail validation if invalid checksum', () => { - const creative = 'This is some text'; - const mockHeaders = { - get: key => { - switch (key) { - case 'AMP-Verification-Checksum-Algorithm': - return 'djb2a-32'; - case 'AMP-Verification-Checksum': - return '12345'; - default: - throw new Error(`unexpected header: ${key}`); - } - }, - }; - expect(AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( - utf8Encode(creative), mockHeaders)).to.eventually.not.be.ok; - }); + it('should fail validation if invalid checksum', () => { + const creative = 'This is some text'; + const mockHeaders = { + get: key => { + switch (key) { + case 'AMP-Verification-Checksum-Algorithm': + return 'djb2a-32'; + case 'AMP-Verification-Checksum': + return '12345'; + default: + throw new Error(`unexpected header: ${key}`); + } + }, + }; + expect( + AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( + utf8Encode(creative), + mockHeaders + ) + ).to.eventually.not.be.ok; }); }); + } +); diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-fluid.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-fluid.js index 544d1b264c55..97ac919d3c0d 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-fluid.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-fluid.js @@ -78,15 +78,16 @@ function createScaffoldingForFluidRendering(impl, sandbox) { }; impl.buildCallback(); impl.attemptChangeHeight = () => Promise.resolve(); - sandbox.stub(impl, 'sendXhrRequest').returns(Promise.resolve({ - arrayBuffer: () => Promise.resolve(utf8Encode(rawCreative)), - headers: {has: () => false, get: () => undefined}, - })); + sandbox.stub(impl, 'sendXhrRequest').returns( + Promise.resolve({ + arrayBuffer: () => Promise.resolve(utf8Encode(rawCreative)), + headers: {has: () => false, get: () => undefined}, + }) + ); impl.sentinel = 'sentinel'; impl.initiateAdRequest(); - impl.safeframeApi_ = new SafeframeHostApi( - impl, true, impl.creativeSize_); - sandbox./*OK*/stub(impl.safeframeApi_, 'setupGeom_'); + impl.safeframeApi_ = new SafeframeHostApi(impl, true, impl.creativeSize_); + sandbox./*OK*/ stub(impl.safeframeApi_, 'setupGeom_'); } describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { @@ -109,10 +110,11 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { const ampStyle = doc.createElement('style'); ampStyle.setAttribute('amp-runtime', 'scratch-fortesting'); doc.head.appendChild(ampStyle); - doc.body.appendChild(createElementWithAttributes( - env.win.document, 'div', { - 'style': 'width: 1px; height: 1000px;', - })); + doc.body.appendChild( + createElementWithAttributes(env.win.document, 'div', { + 'style': 'width: 1px; height: 1000px;', + }) + ); element = createElementWithAttributes(env.win.document, 'amp-ad', { 'height': 'fluid', 'type': 'doubleclick', @@ -126,7 +128,10 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { }); doc.body.appendChild(multiSizeElement); multiSizeImpl = new AmpAdNetworkDoubleclickImpl( - multiSizeElement, env.win.document, env.win); + multiSizeElement, + env.win.document, + env.win + ); const getLayout = () => 'fluid'; impl.getLayout = getLayout; @@ -158,8 +163,10 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { }); it('should NOT load delayed impression amp-pixels', () => { - const fireDelayedImpressionsSpy = - sandbox.spy(impl, 'fireDelayedImpressions'); + const fireDelayedImpressionsSpy = sandbox.spy( + impl, + 'fireDelayedImpressions' + ); const size = impl.extractSize({ get(name) { switch (name) { @@ -192,8 +199,7 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { multiSizeImpl.initiateAdRequest(); return multiSizeImpl.adPromise_.then(() => { expect(multiSizeImpl.adUrl_).to.be.ok; - expect(multiSizeImpl.adUrl_).to.match( - /[&?]sz=320x50%7C300x200%7C150x50/); + expect(multiSizeImpl.adUrl_).to.match(/[&?]sz=320x50%7C300x200%7C150x50/); }); }); @@ -226,11 +232,14 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { it('should fire delayed impression ping', () => { createScaffoldingForFluidRendering(impl, sandbox); - const connectMessagingChannelSpy = - sandbox./*OK*/spy(impl.safeframeApi_, - 'connectMessagingChannel'); - const onFluidResizeSpy = sandbox./*OK*/spy(impl.safeframeApi_, - 'onFluidResize_'); + const connectMessagingChannelSpy = sandbox./*OK*/ spy( + impl.safeframeApi_, + 'connectMessagingChannel' + ); + const onFluidResizeSpy = sandbox./*OK*/ spy( + impl.safeframeApi_, + 'onFluidResize_' + ); return impl.adPromise_.then(() => { return impl.layoutCallback().then(() => { expect(connectMessagingChannelSpy).to.be.calledOnce; @@ -259,8 +268,8 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { impl.isVerifiedAmpCreative_ = true; impl.fluidImpressionUrl_ = 'http://www.foo.co.uk'; impl.onCreativeRender(null, mockPromise); - expect(delayedImpressionSpy.withArgs('http://www.foo.co.uk')) - .to.be.calledOnce; + expect(delayedImpressionSpy.withArgs('http://www.foo.co.uk')).to.be + .calledOnce; }); it('should set expansion re-attempt flag after initial failure', () => { 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 08bc383caea4..70e51622482b 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 @@ -19,9 +19,7 @@ // always available for them. However, when we test an impl in isolation, // AmpAd is not loaded already, so we need to load it separately. import '../../../amp-ad/0.1/amp-ad'; -import { - AmpAdNetworkDoubleclickImpl, -} from '../amp-ad-network-doubleclick-impl'; +import {AmpAdNetworkDoubleclickImpl} from '../amp-ad-network-doubleclick-impl'; import {RTC_ERROR_ENUM} from '../../../amp-a4a/0.1/real-time-config-manager'; import {RTC_VENDORS} from '../../../amp-a4a/0.1/callout-vendors'; import {Services} from '../../../../src/services'; @@ -60,7 +58,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { describe('#mergeRtcResponses_', () => { function testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting) { + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ) { const rtcUrlParams = impl.mergeRtcResponses_(rtcResponseArray); expect(rtcUrlParams).to.deep.equal(expectedParams); expect(impl.jsonTargeting).to.deep.equal(expectedJsonTargeting); @@ -71,17 +72,29 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { const expectedParams = {'artc': null, 'ati': '', 'ard': ''}; const expectedJsonTargeting = {}; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should properly merge RTC responses into jsonTargeting on impl', () => { const rtcResponseArray = [ - {response: {targeting: {'a': [1,2,3], 'b': {c: 'd'}}}, - callout: 'www.exampleA.com', rtcTime: 100}, - {response: {targeting: {'a': 'foo', 'b': {e: 'f'}}}, - callout: 'www.exampleB.com', rtcTime: 500}, - {response: {targeting: {'z': [{a: 'b'}, {c: 'd'}], 'b': {c: 'd'}}}, - callout: 'www.exampleC.com', rtcTime: 100}, + { + response: {targeting: {'a': [1, 2, 3], 'b': {c: 'd'}}}, + callout: 'www.exampleA.com', + rtcTime: 100, + }, + { + response: {targeting: {'a': 'foo', 'b': {e: 'f'}}}, + callout: 'www.exampleB.com', + rtcTime: 500, + }, + { + response: {targeting: {'z': [{a: 'b'}, {c: 'd'}], 'b': {c: 'd'}}}, + callout: 'www.exampleC.com', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2,2,2', @@ -90,10 +103,16 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }; const expectedJsonTargeting = { targeting: { - 'a': 'foo', 'b': {c: 'd', e: 'f'}, 'z': [{a: 'b'}, {c: 'd'}]}, + 'a': 'foo', + 'b': {c: 'd', e: 'f'}, + 'z': [{a: 'b'}, {c: 'd'}], + }, }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should properly merge RTC responses from vendors', () => { @@ -101,12 +120,21 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'url': 'https://fakevendor2.biz', }; const rtcResponseArray = [ - {response: {targeting: {'a': [1,2,3], 'b': {c: 'd'}}}, - callout: 'fakevendor', rtcTime: 100}, - {response: {targeting: {'a': 'foo', 'b': {e: 'f'}}}, - callout: 'www.exampleB.com', rtcTime: 500}, - {response: {targeting: {'a': 'bar'}}, - callout: 'TEMP_VENDOR', rtcTime: 100}, + { + response: {targeting: {'a': [1, 2, 3], 'b': {c: 'd'}}}, + callout: 'fakevendor', + rtcTime: 100, + }, + { + response: {targeting: {'a': 'foo', 'b': {e: 'f'}}}, + callout: 'www.exampleB.com', + rtcTime: 500, + }, + { + response: {targeting: {'a': 'bar'}}, + callout: 'TEMP_VENDOR', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2,2,2', @@ -115,21 +143,34 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }; const expectedJsonTargeting = { targeting: { - 'a': 'foo', 'b': {e: 'f'}, 'a_fakevendor': [1,2,3], - 'b_fakevendor': {c: 'd'}, 'a_TEMP_VENDOR': 'bar'}, + 'a': 'foo', + 'b': {e: 'f'}, + 'a_fakevendor': [1, 2, 3], + 'b_fakevendor': {c: 'd'}, + 'a_TEMP_VENDOR': 'bar', + }, }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should properly merge into existing json', () => { element.setAttribute('json', '{"targeting":{"a":"foo"}}'); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const rtcResponseArray = [ - {response: {targeting: {'a': [1,2,3]}}, - callout: 'fakevendor', rtcTime: 100}, + { + response: {targeting: {'a': [1, 2, 3]}}, + callout: 'fakevendor', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2', @@ -137,19 +178,32 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { ard: 'fakevendor', }; const expectedJsonTargeting = { - targeting: {'a': 'foo', 'a_fakevendor': [1,2,3]}}; + targeting: {'a': 'foo', 'a_fakevendor': [1, 2, 3]}, + }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should properly merge into existing categoryExclusions', () => { element.setAttribute('json', '{"categoryExclusions": ["sports"]}'); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const rtcResponseArray = [ - {response: {targeting: {'a': [1,2,3]}, categoryExclusions: ['health']}, - callout: 'fakevendor', rtcTime: 100}, + { + response: { + targeting: {'a': [1, 2, 3]}, + categoryExclusions: ['health'], + }, + callout: 'fakevendor', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2', @@ -157,21 +211,30 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { ard: 'fakevendor', }; const expectedJsonTargeting = { - targeting: {'a_fakevendor': [1,2,3]}, + targeting: {'a_fakevendor': [1, 2, 3]}, categoryExclusions: ['sports', 'health'], }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should not allow duplicate categoryExclusions', () => { element.setAttribute('json', '{"categoryExclusions": ["health"]}'); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const rtcResponseArray = [ - {response: {categoryExclusions: ['health']}, - callout: 'fakevendor', rtcTime: 100}, + { + response: {categoryExclusions: ['health']}, + callout: 'fakevendor', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2', @@ -182,14 +245,20 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { categoryExclusions: ['health'], }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); Object.keys(RTC_ERROR_ENUM).forEach(errorName => { it(`should send correct error value for ${errorName}`, () => { const rtcResponseArray = [ - {error: RTC_ERROR_ENUM[errorName], - callout: 'www.exampleA.com', rtcTime: 100}, + { + error: RTC_ERROR_ENUM[errorName], + callout: 'www.exampleA.com', + rtcTime: 100, + }, ]; const expectedParams = { ati: `${RTC_ERROR_ENUM[errorName]}`, @@ -198,50 +267,78 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }; const expectedJsonTargeting = {}; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); }); - it('should properly merge mix of success and errors', () => { - impl.jsonTargeting = {targeting: - {'abc': [1,2,3], 'b': {n: 'm'}, 'a': 'TEST'}, - categoryExclusions: ['sports']}; + impl.jsonTargeting = { + targeting: {'abc': [1, 2, 3], 'b': {n: 'm'}, 'a': 'TEST'}, + categoryExclusions: ['sports'], + }; const rtcResponseArray = [ - {error: RTC_ERROR_ENUM.TIMEOUT, - callout: 'www.exampleA.com', rtcTime: 1500}, - {response: {targeting: {'a': 'foo', 'b': {e: 'f'}}, - categoryExclusions: ['health']}, - callout: 'VendorFoo', rtcTime: 500}, - {response: {targeting: {'a': [1,2,3], 'b': {c: 'd'}}}, - callout: 'www.exampleB.com', rtcTime: 100}, - {response: {targeting: {'a': [4,5,6], 'b': {x: [1,2]}}}, - callout: 'VendCom', rtcTime: 500}, - {error: RTC_ERROR_ENUM.DUPLICATE_URL, - callout: 'www.exampleB.com', rtcTime: 0}, - {error: RTC_ERROR_ENUM.NETWORK_FAILURE, - callout: '3PVend', rtcTime: 100}, + { + error: RTC_ERROR_ENUM.TIMEOUT, + callout: 'www.exampleA.com', + rtcTime: 1500, + }, + { + response: { + targeting: {'a': 'foo', 'b': {e: 'f'}}, + categoryExclusions: ['health'], + }, + callout: 'VendorFoo', + rtcTime: 500, + }, + { + response: {targeting: {'a': [1, 2, 3], 'b': {c: 'd'}}}, + callout: 'www.exampleB.com', + rtcTime: 100, + }, + { + response: {targeting: {'a': [4, 5, 6], 'b': {x: [1, 2]}}}, + callout: 'VendCom', + rtcTime: 500, + }, + { + error: RTC_ERROR_ENUM.DUPLICATE_URL, + callout: 'www.exampleB.com', + rtcTime: 0, + }, + { + error: RTC_ERROR_ENUM.NETWORK_FAILURE, + callout: '3PVend', + rtcTime: 100, + }, ]; const expectedParams = { ati: '10,2,2,2,5,8', artc: '1500,500,100,500,0,100', - ard: 'www.exampleA.com,VendorFoo,www.exampleB.com,' + - 'VendCom,www.exampleB.com,3PVend', + ard: + 'www.exampleA.com,VendorFoo,www.exampleB.com,' + + 'VendCom,www.exampleB.com,3PVend', }; const expectedJsonTargeting = { targeting: { - 'a': [4,5,6], 'b': {n: 'm', e: 'f', c: 'd', x: [1,2]}, - abc: [1,2,3]}, + 'a': [4, 5, 6], + 'b': {n: 'm', e: 'f', c: 'd', x: [1, 2]}, + abc: [1, 2, 3], + }, categoryExclusions: ['sports', 'health'], }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should return null for empty array', () => { expect(impl.mergeRtcResponses_()).to.be.null; }); - }); describe('rewriteRtcKeys', () => { @@ -254,8 +351,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'a_fakevendor': '1', 'b_fakevendor': '2', }; - expect(impl.rewriteRtcKeys_(response, 'fakevendor')) - .to.deep.equal(rewrittenResponse); + expect(impl.rewriteRtcKeys_(response, 'fakevendor')).to.deep.equal( + rewrittenResponse + ); }); it('should not rewrite key names if vendor has disableKeyAppend', () => { @@ -264,8 +362,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'b': '2', }; // fakevendor2 has disableKeyAppend set to true, see callout-vendors.js - expect(impl.rewriteRtcKeys_(response, 'fakevendor2')) - .to.deep.equal(response); + expect(impl.rewriteRtcKeys_(response, 'fakevendor2')).to.deep.equal( + response + ); }); it('should not rewrite key names if custom url callout', () => { @@ -273,8 +372,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'a': '1', 'b': '2', }; - expect(impl.rewriteRtcKeys_(response, 'www.customurl.biz')) - .to.deep.equal(response); + expect(impl.rewriteRtcKeys_(response, 'www.customurl.biz')).to.deep.equal( + response + ); }); }); @@ -306,11 +406,15 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'json': JSON.stringify(json), }); env.win.document.body.appendChild(element); - Object.defineProperty( - env.win.document, 'referrer', {value: 'https://www.google.com/'}); + Object.defineProperty(env.win.document, 'referrer', { + value: 'https://www.google.com/', + }); const docInfo = Services.documentInfoForDoc(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const customMacros = impl.getCustomRealTimeConfigMacros_(); expect(customMacros.PAGEVIEWID()).to.equal(docInfo.pageViewId); @@ -335,7 +439,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }); env.win.document.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const customMacros = impl.getCustomRealTimeConfigMacros_(); let adcid; @@ -354,7 +461,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }); env.win.document.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const customMacros = impl.getCustomRealTimeConfigMacros_(); return customMacros.ADCID(0).then(adcid => { @@ -368,7 +478,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }); env.win.document.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const viewer = Services.viewerForDoc(impl.getAmpDoc()); sandbox.stub(viewer, 'getReferrerUrl').returns(new Promise(() => {})); @@ -385,7 +498,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }); env.win.document.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const customMacros = impl.getCustomRealTimeConfigMacros_(); expect(customMacros.TGT()).to.equal(JSON.stringify(json['targeting'])); diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-safeframe.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-safeframe.js index 33ba2c7c3620..7a7ba108b5e6 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-safeframe.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-safeframe.js @@ -46,7 +46,6 @@ const realWinConfig = { allowExternalResources: true, }; - describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { let doubleclickImpl; let ampAd; @@ -80,8 +79,7 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { width: creativeWidth, height: creativeHeight, }; - safeframeHost = new SafeframeHostApi( - doubleclickImpl, false, creativeSize); + safeframeHost = new SafeframeHostApi(doubleclickImpl, false, creativeSize); doubleclickImpl.upgradeCallback(); doubleclickImpl.layoutCallback(); } @@ -124,10 +122,14 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { const safeframeMock = createElementWithAttributes(doc, 'iframe', {}); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe = safeframeMock; - const connectMessagingChannelSpy = sandbox.spy( - safeframeHost, 'connectMessagingChannel'); - const postMessageStub = sandbox./*OK*/stub( - safeframeMock.contentWindow, 'postMessage'); + const connectMessagingChannelSpy = sandbox./*OK*/ spy( + safeframeHost, + 'connectMessagingChannel' + ); + const postMessageStub = sandbox./*OK*/ stub( + safeframeMock.contentWindow, + 'postMessage' + ); sendSetupMessage(); // Verify that the channel was set up @@ -138,36 +140,59 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { const firstPostMessageArgs = postMessageStub.firstCall.args; let connectMessage = JSON.parse(firstPostMessageArgs[0]); let payload = JSON.parse(connectMessage[MESSAGE_FIELDS.PAYLOAD]); - expect(payload).to.deep.equal({'c': safeframeChannel, - 'message': 'connect'}); + expect(payload).to.deep.equal({ + 'c': safeframeChannel, + 'message': 'connect', + }); expect(connectMessage[MESSAGE_FIELDS.CHANNEL]).to.equal(safeframeChannel); expect(connectMessage[MESSAGE_FIELDS.SENTINEL]).to.equal( - doubleclickImpl.sentinel); + doubleclickImpl.sentinel + ); expect(connectMessage[MESSAGE_FIELDS.ENDPOINT_IDENTITY]).to.equal( - safeframeHost.endpointIdentity_); + safeframeHost.endpointIdentity_ + ); // Verify that the initial geometry update was sent - return Services.timerFor(env.win).promise(500).then(() => { - const secondPostMessageArgs = postMessageStub.secondCall.args; - connectMessage = JSON.parse(secondPostMessageArgs[0]); - expect(connectMessage[MESSAGE_FIELDS.CHANNEL]).to.equal( - safeframeChannel); - expect(connectMessage[MESSAGE_FIELDS.SENTINEL]).to.equal( - doubleclickImpl.sentinel); - expect(connectMessage[MESSAGE_FIELDS.ENDPOINT_IDENTITY]).to.equal( - safeframeHost.endpointIdentity_); - payload = JSON.parse(connectMessage[MESSAGE_FIELDS.PAYLOAD]); - expect(Object.keys(payload)).to.deep.equal(['newGeometry', 'uid']); - expect(Object.keys(JSON.parse(payload['newGeometry']))).to.deep.equal([ - 'windowCoords_t', 'windowCoords_r', 'windowCoords_b', - 'windowCoords_l', 'frameCoords_t', 'frameCoords_r', - 'frameCoords_b', 'frameCoords_l', - 'posCoords_t', 'posCoords_b', 'posCoords_r', 'posCoords_l', - 'styleZIndex', - 'allowedExpansion_r', 'allowedExpansion_b', 'allowedExpansion_t', - 'allowedExpansion_l', 'yInView', 'xInView', - ]); - }); + return Services.timerFor(env.win) + .promise(500) + .then(() => { + const secondPostMessageArgs = postMessageStub.secondCall.args; + connectMessage = JSON.parse(secondPostMessageArgs[0]); + expect(connectMessage[MESSAGE_FIELDS.CHANNEL]).to.equal( + safeframeChannel + ); + expect(connectMessage[MESSAGE_FIELDS.SENTINEL]).to.equal( + doubleclickImpl.sentinel + ); + expect(connectMessage[MESSAGE_FIELDS.ENDPOINT_IDENTITY]).to.equal( + safeframeHost.endpointIdentity_ + ); + payload = JSON.parse(connectMessage[MESSAGE_FIELDS.PAYLOAD]); + expect(Object.keys(payload)).to.deep.equal(['newGeometry', 'uid']); + expect(Object.keys(JSON.parse(payload['newGeometry']))).to.deep.equal( + [ + 'windowCoords_t', + 'windowCoords_r', + 'windowCoords_b', + 'windowCoords_l', + 'frameCoords_t', + 'frameCoords_r', + 'frameCoords_b', + 'frameCoords_l', + 'posCoords_t', + 'posCoords_b', + 'posCoords_r', + 'posCoords_l', + 'styleZIndex', + 'allowedExpansion_r', + 'allowedExpansion_b', + 'allowedExpansion_t', + 'allowedExpansion_l', + 'yInView', + 'xInView', + ] + ); + }); }); }); @@ -177,20 +202,40 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { canonicalUrl: 'http://example.org/canonical', }); const attrs = safeframeHost.getSafeframeNameAttr(); - expect(Object.keys(attrs)).to.deep.equal( - ['uid', 'hostPeerName', 'initialGeometry', 'permissions', - 'metadata', 'reportCreativeGeometry', 'isDifferentSourceWindow', - 'sentinel']); + expect(Object.keys(attrs)).to.deep.equal([ + 'uid', + 'hostPeerName', + 'initialGeometry', + 'permissions', + 'metadata', + 'reportCreativeGeometry', + 'isDifferentSourceWindow', + 'sentinel', + ]); // Check the geometry const initialGeometry = JSON.parse(attrs['initialGeometry']); - expect(Object.keys(initialGeometry)).to.deep.equal( - ['windowCoords_t', 'windowCoords_r', 'windowCoords_b', - 'windowCoords_l', 'frameCoords_t', 'frameCoords_r', - 'frameCoords_b', 'frameCoords_l', 'posCoords_t', - 'posCoords_b', 'posCoords_r', 'posCoords_l', 'styleZIndex', - 'allowedExpansion_r', 'allowedExpansion_b', 'allowedExpansion_t', - 'allowedExpansion_l', 'yInView', 'xInView']); + expect(Object.keys(initialGeometry)).to.deep.equal([ + 'windowCoords_t', + 'windowCoords_r', + 'windowCoords_b', + 'windowCoords_l', + 'frameCoords_t', + 'frameCoords_r', + 'frameCoords_b', + 'frameCoords_l', + 'posCoords_t', + 'posCoords_b', + 'posCoords_r', + 'posCoords_l', + 'styleZIndex', + 'allowedExpansion_r', + 'allowedExpansion_b', + 'allowedExpansion_t', + 'allowedExpansion_l', + 'yInView', + 'xInView', + ]); Object.keys(initialGeometry).forEach(key => { if (key != 'styleZIndex') { expect(typeof initialGeometry[key]).to.equal('number'); @@ -283,44 +328,50 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { describe('getCurrentGeometry', () => { beforeEach(() => { - sandbox./*OK*/stub(safeframeHost.viewport_, 'getSize').returns({ + sandbox./*OK*/ stub(safeframeHost.viewport_, 'getSize').returns({ height: 1000, width: 500, }); }); it('should get current geometry when safeframe fills amp-ad', () => { - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 0, - left: 0, - right: 300, - bottom: 250, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 0, + left: 0, + right: 300, + bottom: 250, + }); const safeframeMock = createElementWithAttributes(doc, 'iframe', { 'class': 'safeframe', }); // Set the size of the safeframe to be the same as its containing // amp-ad element const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + - '{height:250px!important;' + - 'width:300px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + css.innerHTML = + '.safeframe' + + '{height:250px!important;' + + 'width:300px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe_ = safeframeMock; safeframeHost.iframe_ = safeframeMock; - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.updateGeometry_(); - return Services.timerFor(env.win).promise(100).then(() => { - const payload = sendMessageStub.firstCall.args[0]; - const messageType = sendMessageStub.firstCall.args[1]; - expect(payload['newGeometry']).to.equal( + return Services.timerFor(env.win) + .promise(100) + .then(() => { + const payload = sendMessageStub.firstCall.args[0]; + const messageType = sendMessageStub.firstCall.args[1]; + expect(payload['newGeometry']).to.equal( '{"windowCoords_t":0,"windowCoords_r":500,"windowCoords_b":1000,' + '"windowCoords_l":0,"frameCoords_t":0,"frameCoords_r":300,' + '"frameCoords_b":250,"frameCoords_l":0,' + @@ -328,44 +379,51 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { '"posCoords_l":0,"styleZIndex":"",' + '"allowedExpansion_r":200,"allowedExpansion_b":750,' + '"allowedExpansion_t":0,"allowedExpansion_l":0,"yInView":1,' + - '"xInView":1}'); - expect(payload['uid']).to.equal(safeframeHost.uid_); - expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); - }); + '"xInView":1}' + ); + expect(payload['uid']).to.equal(safeframeHost.uid_); + expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + }); }); it('should get geometry when safeframe does not fill amp-ad', () => { - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 0, - left: 0, - right: 50, - bottom: 50, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 0, + left: 0, + right: 50, + bottom: 50, + }); // In this case, the safeframe is smaller than its containing // amp-ad element. const safeframeMock = createElementWithAttributes(doc, 'iframe', { 'class': 'safeframe', }); const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + - '{height:10px!important;' + - 'width:10px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + css.innerHTML = + '.safeframe' + + '{height:10px!important;' + + 'width:10px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe_ = safeframeMock; safeframeHost.iframe_ = safeframeMock; - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.updateGeometry_(); - return Services.timerFor(env.win).promise(100).then(() => { - const payload = sendMessageStub.firstCall.args[0]; - const messageType = sendMessageStub.firstCall.args[1]; - expect(payload['newGeometry']).to.equal( + return Services.timerFor(env.win) + .promise(100) + .then(() => { + const payload = sendMessageStub.firstCall.args[0]; + const messageType = sendMessageStub.firstCall.args[1]; + expect(payload['newGeometry']).to.equal( '{"windowCoords_t":0,"windowCoords_r":500,"windowCoords_b":1000,' + '"windowCoords_l":0,"frameCoords_t":0,"frameCoords_r":10,' + '"frameCoords_b":10,"frameCoords_l":0,' + @@ -373,65 +431,72 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { '"posCoords_l":0,"styleZIndex":"",' + '"allowedExpansion_r":490,"allowedExpansion_b":990,' + '"allowedExpansion_t":0,"allowedExpansion_l":0,"yInView":1,' + - '"xInView":1}'); - expect(payload['uid']).to.equal(safeframeHost.uid_); - expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); - }); + '"xInView":1}' + ); + expect(payload['uid']).to.equal(safeframeHost.uid_); + expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + }); }); - it('should handle cancellation', () => { - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 0, - left: 0, - right: 50, - bottom: 50, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 0, + left: 0, + right: 50, + bottom: 50, + }); // In this case, the safeframe is smaller than its containing // amp-ad element. const safeframeMock = createElementWithAttributes(doc, 'iframe', { 'class': 'safeframe', }); const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + + css.innerHTML = + '.safeframe' + '{height:10px!important;' + - 'width:10px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + 'width:10px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe_ = safeframeMock; safeframeHost.iframe_ = safeframeMock; - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.updateGeometry_(); safeframeHost.baseInstance_.promiseId_++; - return Services.timerFor(env.win).promise(1000).then(() => { - expect(sendMessageStub).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(1000) + .then(() => { + expect(sendMessageStub).to.not.be.called; + }); }); it('should get geometry when scrolled', () => { - - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 0, - left: 0, - right: 50, - bottom: 50, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 0, + left: 0, + right: 50, + bottom: 50, + }); // In this case, the safeframe is smaller than its containing // amp-ad element. const safeframeMock = createElementWithAttributes(doc, 'iframe', { 'class': 'safeframe', }); const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + - '{height:100px!important;' + - 'width:100px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + css.innerHTML = + '.safeframe' + + '{height:100px!important;' + + 'width:100px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe_ = safeframeMock; @@ -440,24 +505,29 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { // Scroll 100 px safeframeHost.viewport_.setScrollTop(50); - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.updateGeometry_(); - return Services.timerFor(env.win).promise(100).then(() => { - const payload = sendMessageStub.firstCall.args[0]; - const messageType = sendMessageStub.firstCall.args[1]; - expect(payload['newGeometry']).to.equal( + return Services.timerFor(env.win) + .promise(100) + .then(() => { + const payload = sendMessageStub.firstCall.args[0]; + const messageType = sendMessageStub.firstCall.args[1]; + expect(payload['newGeometry']).to.equal( '{"windowCoords_t":0,"windowCoords_r":500,"windowCoords_b":1000,' + '"windowCoords_l":0,"frameCoords_t":0,"frameCoords_r":100,' + '"frameCoords_b":100,"frameCoords_l":0,"posCoords_t":-50,' + '"posCoords_b":50,"posCoords_r":100,"posCoords_l":0,' + '"styleZIndex":"","allowedExpansion_r":400,' + '"allowedExpansion_b":900,"allowedExpansion_t":0,' + - '"allowedExpansion_l":0,"yInView":0.5,"xInView":1}'); - expect(payload['uid']).to.equal(safeframeHost.uid_); - expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); - }); + '"allowedExpansion_l":0,"yInView":0.5,"xInView":1}' + ); + expect(payload['uid']).to.equal(safeframeHost.uid_); + expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + }); }); }); @@ -467,51 +537,60 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { ampAd.appendChild(safeframeMock); doubleclickImpl.iframe = safeframeMock; - const onScrollStub = sandbox./*OK*/stub( - safeframeHost.viewport_, 'onScroll'); - const onChangedStub = sandbox./*OK*/stub( - safeframeHost.viewport_, 'onChanged'); + const onScrollStub = sandbox./*OK*/ stub( + safeframeHost.viewport_, + 'onScroll' + ); + const onChangedStub = sandbox./*OK*/ stub( + safeframeHost.viewport_, + 'onChanged' + ); safeframeMock.contentWindow.postMessage = () => {}; sendSetupMessage(); const maybeUpdateGeometry1 = onScrollStub.firstCall.args[0]; const maybeUpdateGeometry2 = onChangedStub.firstCall.args[0]; - const sendMessageStub = sandbox./*OK*/spy(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ spy(safeframeHost, 'sendMessage_'); maybeUpdateGeometry1(); maybeUpdateGeometry2(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendMessageStub).to.be.calledTwice; - const payload = sendMessageStub.secondCall.args[0]; - const messageType = sendMessageStub.secondCall.args[1]; - expect(JSON.parse(payload['newGeometry'])).to.deep.equal( - safeframeHost.currentGeometry_); - expect(payload['uid']).to.equal(safeframeHost.uid_); - expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); - return Services.timerFor(env.win).promise(1000).then(() => { - expect(sendMessageStub).to.be.calledThrice; - const payload = sendMessageStub.thirdCall.args[0]; - const messageType = sendMessageStub.thirdCall.args[1]; + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendMessageStub).to.be.calledTwice; + const payload = sendMessageStub.secondCall.args[0]; + const messageType = sendMessageStub.secondCall.args[1]; expect(JSON.parse(payload['newGeometry'])).to.deep.equal( - safeframeHost.currentGeometry_); + safeframeHost.currentGeometry_ + ); expect(payload['uid']).to.equal(safeframeHost.uid_); expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + return Services.timerFor(env.win) + .promise(1000) + .then(() => { + expect(sendMessageStub).to.be.calledThrice; + const payload = sendMessageStub.thirdCall.args[0]; + const messageType = sendMessageStub.thirdCall.args[1]; + expect(JSON.parse(payload['newGeometry'])).to.deep.equal( + safeframeHost.currentGeometry_ + ); + expect(payload['uid']).to.equal(safeframeHost.uid_); + expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + }); }); - }); - }); }); describe('formatGeom', () => { it('should build proper geometry update', () => { - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 200, - left: 100, - right: 400, - bottom: 800, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 200, + left: 100, + right: 400, + bottom: 800, + }); const iframeBox = { top: 300, left: 200, @@ -520,63 +599,83 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { width: 300, height: 700, }; - sandbox./*OK*/stub(safeframeHost.viewport_, 'getSize').returns({ + sandbox./*OK*/ stub(safeframeHost.viewport_, 'getSize').returns({ width: 500, height: 1000, }); const expectedParsedSfGU = { - 'windowCoords_t': 0, 'windowCoords_r': 500, 'windowCoords_b': 1000, - 'windowCoords_l': 0, 'frameCoords_t': 300, 'frameCoords_r': 500, - 'frameCoords_b': 1000, 'frameCoords_l': 200, - 'posCoords_b': 1000, 'posCoords_l': 200, 'posCoords_r': 500, - 'posCoords_t': 300, 'styleZIndex': '', - 'allowedExpansion_r': 200, 'allowedExpansion_b': 300, - 'allowedExpansion_t': 0, 'allowedExpansion_l': 0, 'yInView': 1, + 'windowCoords_t': 0, + 'windowCoords_r': 500, + 'windowCoords_b': 1000, + 'windowCoords_l': 0, + 'frameCoords_t': 300, + 'frameCoords_r': 500, + 'frameCoords_b': 1000, + 'frameCoords_l': 200, + 'posCoords_b': 1000, + 'posCoords_l': 200, + 'posCoords_r': 500, + 'posCoords_t': 300, + 'styleZIndex': '', + 'allowedExpansion_r': 200, + 'allowedExpansion_b': 300, + 'allowedExpansion_t': 0, + 'allowedExpansion_l': 0, + 'yInView': 1, 'xInView': 1, }; - const safeframeGeometryUpdate = safeframeHost.formatGeom_( - iframeBox); + const safeframeGeometryUpdate = safeframeHost.formatGeom_(iframeBox); const parsedSfGU = JSON.parse(safeframeGeometryUpdate); expect(parsedSfGU).to.deep.equal(expectedParsedSfGU); expect(safeframeHost.currentGeometry_).to.deep.equal(expectedParsedSfGU); - }); }); describe('sendResizeResponse', () => { it('should handle cancellation', () => { - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.sendResizeResponse(true, SERVICE.COLLAPSE_REQUEST); safeframeHost.baseInstance_.promiseId_++; - return Services.timerFor(env.win).promise(0).then(() => { - expect(sendMessageStub).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(0) + .then(() => { + expect(sendMessageStub).to.not.be.called; + }); }); }); describe('resizeAmpAdAndSafeframe', () => { it('should handle cancellation', () => { - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); - safeframeHost.resizeAmpAdAndSafeframe( - 100, 100, SERVICE.COLLAPSE_REQUEST); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); + safeframeHost.resizeAmpAdAndSafeframe(100, 100, SERVICE.COLLAPSE_REQUEST); safeframeHost.baseInstance_.promiseId_++; - return Services.timerFor(env.win).promise(0).then(() => { - expect(sendMessageStub).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(0) + .then(() => { + expect(sendMessageStub).to.not.be.called; + }); }); }); describe('handleFluidMessage', () => { it('should handle cancellation', () => { - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.handleFluidMessage_({height: 10}); safeframeHost.baseInstance_.promiseId_++; - return Services.timerFor(env.win).promise(0).then(() => { - expect(sendMessageStub).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(0) + .then(() => { + expect(sendMessageStub).to.not.be.called; + }); }); }); @@ -593,11 +692,12 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { function setupForResize() { sandbox.restore(); const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + - '{height:50px!important;' + - 'width:50px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + css.innerHTML = + '.safeframe' + + '{height:50px!important;' + + 'width:50px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); safeframeMock = createElementWithAttributes(doc, 'iframe', { height: 50, @@ -605,19 +705,24 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { }); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe = safeframeMock; - resizeSafeframeSpy = sandbox.spy( - safeframeHost, 'resizeSafeframe'); - sendResizeResponseSpy = sandbox.spy( - safeframeHost, 'sendResizeResponse'); - resizeAmpAdAndSafeframeSpy = sandbox.spy( - safeframeHost, 'resizeAmpAdAndSafeframe'); + resizeSafeframeSpy = sandbox./*OK*/ spy(safeframeHost, 'resizeSafeframe'); + sendResizeResponseSpy = sandbox./*OK*/ spy( + safeframeHost, + 'sendResizeResponse' + ); + resizeAmpAdAndSafeframeSpy = sandbox./*OK*/ spy( + safeframeHost, + 'resizeAmpAdAndSafeframe' + ); safeframeHost.initialHeight_ = ampAdHeight; safeframeHost.initialWidth_ = ampAdWidth; sendSetupMessage(); sendRegisterDoneMessage(); attemptChangeSizeStub = sandbox.stub( - doubleclickImpl, 'attemptChangeSize'); - sandbox./*OK*/stub(safeframeHost.viewport_, 'getSize').returns({ + doubleclickImpl, + 'attemptChangeSize' + ); + sandbox./*OK*/ stub(safeframeHost.viewport_, 'getSize').returns({ height: 1000, width: 1000, }); @@ -658,15 +763,19 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { sendExpandMessage(50, 50); // Verify that we can immediately resize the safeframe, and don't // need to call any of the fancy AMP element resize things. - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(300, 350); - expect(safeframeMock.style.height).to.equal('300px'); - expect(safeframeMock.style.width).to.equal('350px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.EXPAND_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(300, 350); + expect(safeframeMock.style.height).to.equal('300px'); + expect(safeframeMock.style.width).to.equal('350px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.EXPAND_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.not.be.called; + }); }); /** @@ -688,15 +797,19 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { sendExpandMessage(50, 50); // Verify that we can immediately resize the safeframe, and don't // need to call any of the fancy AMP element resize things. - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(300, 350); - expect(safeframeMock.style.height).to.equal('300px'); - expect(safeframeMock.style.width).to.equal('350px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.EXPAND_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(300, 350); + expect(safeframeMock.style.height).to.equal('300px'); + expect(safeframeMock.style.width).to.equal('350px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.EXPAND_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.not.be.called; + }); }); /** @@ -705,31 +818,38 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { * using element.attemptChangeSize. If that succeeds, then we also * resize the safeframe. */ - it('expand_request should succeed if expanding past amp-ad bounds and' + - ' does not create reflow', () => { - const expandWidthBy = 550; - const expandHeightBy = 600; - // Sneaky hack to do a synchronous mock of attemptChangeSize - // Resize the ampAd to simulate a success. - const then = f => { - ampAd.style.height = '850px'; - ampAd.style.width = '850px'; - f(); - return {'catch': () => {}}; - }; - attemptChangeSizeStub.returns({then}); - sendExpandMessage(expandHeightBy, expandWidthBy); - - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(850, 850); - expect(safeframeMock.style.height).to.equal('850px'); - expect(safeframeMock.style.width).to.equal('850px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.EXPAND_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); - }); + it( + 'expand_request should succeed if expanding past amp-ad bounds and' + + ' does not create reflow', + () => { + const expandWidthBy = 550; + const expandHeightBy = 600; + // Sneaky hack to do a synchronous mock of attemptChangeSize + // Resize the ampAd to simulate a success. + const then = f => { + ampAd.style.height = '850px'; + ampAd.style.width = '850px'; + f(); + return {'catch': () => {}}; + }; + attemptChangeSizeStub.returns({then}); + sendExpandMessage(expandHeightBy, expandWidthBy); + + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(850, 850); + expect(safeframeMock.style.height).to.equal('850px'); + expect(safeframeMock.style.width).to.equal('850px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.EXPAND_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); + } + ); /** * If the safeframed creative asks to resize outside the bounds of @@ -740,10 +860,14 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { it('resizeAmpAdAndSafeframe should send error on rejection', () => { attemptChangeSizeStub.rejects(); safeframeHost.resizeAmpAdAndSafeframe(550, 550, SERVICE.EXPAND_RESPONSE); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.EXPAND_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.EXPAND_RESPONSE + ); + }); }); /** @@ -752,10 +876,14 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { */ it('expand_request fails if expanding larger than viewport', () => { sendExpandMessage(5000, 5000); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.EXPAND_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.EXPAND_RESPONSE + ); + }); }); /** @@ -779,10 +907,14 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { allowConsoleError(() => { receiveMessage(expandMessage); }); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.EXPAND_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.EXPAND_RESPONSE + ); + }); }); /** @@ -790,21 +922,28 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { * try to expand that element, and in this test it fails. Thus, we also * fail resizing the safeframe. */ - it('expand_request should fail if expanding past amp-ad bounds and would ' + - 'create reflow', () => { - attemptChangeSizeStub.rejects(); - - sendExpandMessage(550, 550); - - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.not.be.called; - expect(safeframeMock.height).to.equal('50'); - expect(safeframeMock.width).to.equal('50'); - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.EXPAND_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); - }); + it( + 'expand_request should fail if expanding past amp-ad bounds and would ' + + 'create reflow', + () => { + attemptChangeSizeStub.rejects(); + + sendExpandMessage(550, 550); + + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.not.be.called; + expect(safeframeMock.height).to.equal('50'); + expect(safeframeMock.width).to.equal('50'); + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.EXPAND_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); + } + ); function sendCollapseMessage() { const collapseMessage = {}; @@ -828,15 +967,19 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { attemptChangeSizeStub.rejects(); sendCollapseMessage(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(250, 300); - expect(safeframeMock.style.height).to.equal('250px'); - expect(safeframeMock.style.width).to.equal('300px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.COLLAPSE_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(250, 300); + expect(safeframeMock.style.height).to.equal('250px'); + expect(safeframeMock.style.width).to.equal('300px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.COLLAPSE_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); }); it('should collapse safeframe on amp-ad resize success', () => { @@ -856,34 +999,46 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { attemptChangeSizeStub.returns({then}); sendCollapseMessage(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(250, 300); - expect(safeframeMock.style.height).to.equal('250px'); - expect(safeframeMock.style.width).to.equal('300px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.COLLAPSE_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(250, 300); + expect(safeframeMock.style.height).to.equal('250px'); + expect(safeframeMock.style.width).to.equal('300px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.COLLAPSE_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); }); it('should send collapse failure message if already collapsed', () => { safeframeHost.isCollapsed_ = true; sendCollapseMessage(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.COLLAPSE_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.COLLAPSE_RESPONSE + ); + }); }); it('should send collapse failure message if not registered', () => { safeframeHost.isCollapsed_ = false; safeframeHost.isRegistered_ = false; sendCollapseMessage(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.COLLAPSE_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.COLLAPSE_RESPONSE + ); + }); }); /** @@ -922,15 +1077,19 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { attemptChangeSizeStub.returns({then}); sendResizeMessage(-5, -5, -5, -5); - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(240, 290); - expect(safeframeMock.style.height).to.equal('240px'); - expect(safeframeMock.style.width).to.equal('290px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.RESIZE_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(240, 290); + expect(safeframeMock.style.height).to.equal('240px'); + expect(safeframeMock.style.width).to.equal('290px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.RESIZE_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); }); }); }); diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-sra.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-sra.js index 537d9aeedc37..60f6cf134df3 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-sra.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-sra.js @@ -27,12 +27,8 @@ import { } from '../amp-ad-network-doubleclick-impl'; import {BaseElement} from '../../../../src/base-element'; import {Deferred} from '../../../../src/utils/promise'; -import { - EXPERIMENT_ATTRIBUTE, -} from '../../../../ads/google/a4a/utils'; -import { - MANUAL_EXPERIMENT_ID, -} from '../../../../ads/google/a4a/traffic-experiments'; +import {EXPERIMENT_ATTRIBUTE} from '../../../../ads/google/a4a/utils'; +import {MANUAL_EXPERIMENT_ID} from '../../../../ads/google/a4a/traffic-experiments'; import {SignatureVerifier} from '../../../amp-a4a/0.1/signature-verifier'; import { TFCD, @@ -67,7 +63,7 @@ const config = {amp: true, allowExternalResources: true}; calling setAttribute('src', 'foo') on the iframe, which will cause all these tests to fail. */ -describes.realWin('Doubleclick SRA', config , env => { +describes.realWin('Doubleclick SRA', config, env => { let sandbox; let doc; @@ -80,9 +76,13 @@ describes.realWin('Doubleclick SRA', config , env => { function createAndAppendAdElement(opt_attributes, opt_type, opt_domElement) { const element = createElementWithAttributes( - doc, opt_type || 'amp-ad', - Object.assign( - {type: 'doubleclick', height: 320, width: 50}, opt_attributes)); + doc, + opt_type || 'amp-ad', + Object.assign( + {type: 'doubleclick', height: 320, width: 50}, + opt_attributes + ) + ); (opt_domElement || doc.body).appendChild(element); return element; } @@ -99,7 +99,10 @@ describes.realWin('Doubleclick SRA', config , env => { // layoutCallback is executed. it('should be enabled if meta tag present, and force refresh off', () => { createAndAppendAdElement( - {name: 'amp-ad-doubleclick-sra'}, 'meta', doc.head); + {name: 'amp-ad-doubleclick-sra'}, + 'meta', + doc.head + ); const element = createAndAppendAdElement({'data-enable-refresh': 30}); const impl = new AmpAdNetworkDoubleclickImpl(element); impl.buildCallback(); @@ -111,7 +114,7 @@ describes.realWin('Doubleclick SRA', config , env => { describe('block parameter joining', () => { let impls; - beforeEach(() => impls = []); + beforeEach(() => (impls = [])); it('should join IUs', () => { for (let i = 0; i < 2; i++) { @@ -154,8 +157,7 @@ describes.realWin('Doubleclick SRA', config , env => { impls.push({parameterSize: i}); expected.push(i); } - expect(getSizes(impls)).to.jsonEqual( - {'prev_iu_szs': expected.join()}); + expect(getSizes(impls)).to.jsonEqual({'prev_iu_szs': expected.join()}); }); it('should determine tagForChildDirectedTreatment', () => { expect(getTfcd(impls)).to.be.null; @@ -192,35 +194,49 @@ describes.realWin('Doubleclick SRA', config , env => { impls[0] = {jsonTargeting: {}}; expect(getTargetingAndExclusions(impls)).to.be.null; impls[1] = {jsonTargeting: {targeting: {a: 1, b: 2}}}; - expect(getTargetingAndExclusions(impls)).to.jsonEqual( - {'prev_scp': '|a=1&b=2'}); - impls[2] = {jsonTargeting: {targeting: {c: 1, d: 'l=d'}, - categoryExclusions: ['a','b']}}; + expect(getTargetingAndExclusions(impls)).to.jsonEqual({ + 'prev_scp': '|a=1&b=2', + }); + impls[2] = { + jsonTargeting: { + targeting: {c: 1, d: 'l=d'}, + categoryExclusions: ['a', 'b'], + }, + }; impls[3] = {}; - expect(getTargetingAndExclusions(impls)).to.jsonEqual( - {'prev_scp': '|a=1&b=2|c=1&d=l%3Dd&excl_cat=a,b|'}); + expect(getTargetingAndExclusions(impls)).to.jsonEqual({ + 'prev_scp': '|a=1&b=2|c=1&d=l%3Dd&excl_cat=a,b|', + }); }); it('should determine experiment ids', () => { expect(getExperimentIds(impls)).to.be.null; - impls[0] = {win: {location: {hash: '#deid=123,456,7'}}, - experimentIds: []}; + impls[0] = { + win: {location: {hash: '#deid=123,456,7'}}, + experimentIds: [], + }; // NOTE(keithwrightbos): let's hope this doesn't flake given eids // are stored in object. expect(getExperimentIds(impls)).to.jsonEqual({'eid': '7,123,456'}); impls[0].experimentIds = ['901', '902']; - expect(getExperimentIds(impls)).to.jsonEqual( - {'eid': '7,123,456,901,902'}); + expect(getExperimentIds(impls)).to.jsonEqual({ + 'eid': '7,123,456,901,902', + }); impls[1] = {experimentIds: ['902', '903']}; - expect(getExperimentIds(impls)).to.jsonEqual( - {'eid': '7,123,456,901,902,903'}); + expect(getExperimentIds(impls)).to.jsonEqual({ + 'eid': '7,123,456,901,902,903', + }); }); it('should determine identity', () => { impls[0] = new AmpAdNetworkDoubleclickImpl( - env.win.document.createElement('span')); + env.win.document.createElement('span') + ); impls[0].identityToken = {token: 'foo', jar: 'bar', pucrd: 'oof'}; impls[1] = {}; - expect(getIdentity(impls)).to.jsonEqual( - {adsid: 'foo', jar: 'bar', pucrd: 'oof'}); + expect(getIdentity(impls)).to.jsonEqual({ + adsid: 'foo', + jar: 'bar', + pucrd: 'oof', + }); }); it('should combine force safeframe', () => { expect(getForceSafeframe(impls)).to.be.null; @@ -233,11 +249,15 @@ describes.realWin('Doubleclick SRA', config , env => { }); it('should combine page offsets', () => { impls[0] = {getPageLayoutBox: () => ({left: 123, top: 456})}; - expect(getPageOffsets(impls)).to.jsonEqual( - {'adxs': '123', 'adys': '456'}); + expect(getPageOffsets(impls)).to.jsonEqual({ + 'adxs': '123', + 'adys': '456', + }); impls[1] = {getPageLayoutBox: () => ({left: 123, top: 789})}; - expect(getPageOffsets(impls)).to.jsonEqual( - {'adxs': '123,123', 'adys': '456,789'}); + expect(getPageOffsets(impls)).to.jsonEqual({ + 'adxs': '123,123', + 'adys': '456,789', + }); }); it('should combine contained state', () => { expect(getContainers(impls)).to.be.null; @@ -245,8 +265,14 @@ describes.realWin('Doubleclick SRA', config , env => { expect(getContainers(impls)).to.be.null; impls[1] = {element: {parentElement: {tagName: 'AMP-CAROUSEL'}}}; expect(getContainers(impls)).to.jsonEqual({'acts': '|ac'}); - impls[2] = {element: {parentElement: {tagName: 'AMP-CAROUSEL', - parentElement: {tagName: 'AMP-STICKY-AD'}}}}; + impls[2] = { + element: { + parentElement: { + tagName: 'AMP-CAROUSEL', + parentElement: {tagName: 'AMP-STICKY-AD'}, + }, + }, + }; expect(getContainers(impls)).to.jsonEqual({'acts': '|ac|ac,sa'}); }); it('should combine fluid state', () => { @@ -261,17 +287,21 @@ describes.realWin('Doubleclick SRA', config , env => { let impl; beforeEach(() => { - const element = createAndAppendAdElement( - {'data-a4a-upgrade-type': 'amp-ad-network-doubleclick-impl'}); + const element = createAndAppendAdElement({ + 'data-a4a-upgrade-type': 'amp-ad-network-doubleclick-impl', + }); // Testing competitive exclusion when we have an AMP ad and a non-AMP ad // on the same page. Need to add the child frame of the element to stand // in as the non-AMP ad. createAndAppendAdElement( - { - src: 'https://foo.com', - height: 320, - width: 50, - }, 'iframe', element); + { + src: 'https://foo.com', + height: 320, + width: 50, + }, + 'iframe', + element + ); impl = new AmpAdNetworkDoubleclickImpl(element); impl.buildCallback(); impl.isAmpCreative_ = true; @@ -305,18 +335,20 @@ describes.realWin('Doubleclick SRA', config , env => { 'data-force-safeframe': forceSafeFrame ? '1' : '0', 'data-multi-size': '9999x9999', }; - const element1 = - createElementWithAttributes(doc, 'amp-ad', config1); + const element1 = createElementWithAttributes(doc, 'amp-ad', config1); const impl1 = new AmpAdNetworkDoubleclickImpl(element1); sandbox.stub(impl1, 'getPageLayoutBox').returns({top: 123, left: 456}); impl1.experimentIds = [MANUAL_EXPERIMENT_ID]; - sandbox.stub(impl1, 'generateAdKey_').withArgs('50x320') - .returns('13579'); + sandbox + .stub(impl1, 'generateAdKey_') + .withArgs('50x320') + .returns('13579'); impl1.populateAdUrlState(); - impl1.identityToken = - /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/({ - token: 'abcdef', jar: 'some_jar', pucrd: 'some_pucrd', - }); + impl1.identityToken = /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/ ({ + token: 'abcdef', + jar: 'some_jar', + pucrd: 'some_pucrd', + }); const targeting2 = { cookieOptOut: 1, categoryExclusions: 'food', @@ -332,12 +364,13 @@ describes.realWin('Doubleclick SRA', config , env => { 'data-multi-size-validation': 'false', 'data-multi-size': '1x2,3x4', }; - const element2 = - createElementWithAttributes(doc, 'amp-ad', config2); + const element2 = createElementWithAttributes(doc, 'amp-ad', config2); const impl2 = new AmpAdNetworkDoubleclickImpl(element2); sandbox.stub(impl2, 'getPageLayoutBox').returns({top: 789, left: 101}); - sandbox.stub(impl2, 'generateAdKey_').withArgs('250x300') - .returns('2468'); + sandbox + .stub(impl2, 'generateAdKey_') + .withArgs('250x300') + .returns('2468'); element2.setAttribute(EXPERIMENT_ATTRIBUTE, MANUAL_EXPERIMENT_ID); impl2.populateAdUrlState(); const exp = { @@ -386,7 +419,12 @@ describes.realWin('Doubleclick SRA', config , env => { } function generateSraXhrMockCall( - validInstances, networkId, responses, opt_xhrFail, opt_allInvalid) { + validInstances, + networkId, + responses, + opt_xhrFail, + opt_allInvalid + ) { devAssert(validInstances.length > 1); devAssert(!(opt_xhrFail && opt_allInvalid)); // Start with nameframe method, SRA will override to use safeframe. @@ -394,39 +432,55 @@ describes.realWin('Doubleclick SRA', config , env => { headers[RENDERING_TYPE_HEADER] = XORIGIN_MODE.NAMEFRAME; // Assume all implementations have same data slot. const iuParts = encodeURIComponent( - validInstances[0].element.getAttribute('data-slot').split(/\//) - .splice(1).join()); - sandbox.stub(validInstances[0], 'getLocationQueryParameterValue') - .withArgs('google_preview').returns('abcdef'); + validInstances[0].element + .getAttribute('data-slot') + .split(/\//) + .splice(1) + .join() + ); + sandbox + .stub(validInstances[0], 'getLocationQueryParameterValue') + .withArgs('google_preview') + .returns('abcdef'); const xhrWithArgs = xhrMock.withArgs( - sinon.match(new RegExp( - '^https:\/\/securepubads\\.g\\.doubleclick\\.net' + - '\/gampad\/ads\\?output=ldjh&impl=fifs&iu_parts=' + - `${iuParts}&enc_prev_ius=.*&gct=abcdef`)), - { - mode: 'cors', - method: 'GET', - credentials: 'include', - }); + sinon.match( + new RegExp( + '^https://securepubads\\.g\\.doubleclick\\.net' + + '/gampad/ads\\?output=ldjh&impl=fifs&iu_parts=' + + `${iuParts}&enc_prev_ius=.*&gct=abcdef` + ) + ), + { + mode: 'cors', + method: 'GET', + credentials: 'include', + } + ); if (opt_xhrFail) { - xhrWithArgs.returns(Promise.reject( - new TypeError('some random network error'))); + xhrWithArgs.returns( + Promise.reject(new TypeError('some random network error')) + ); } else if (opt_allInvalid) { xhrWithArgs.throws(new Error('invalid should not make xhr!')); } else { - xhrWithArgs.returns(Promise.resolve({ - arrayBuffer: () => { throw new Error('Expected SRA!'); }, - bodyUsed: false, - text: () => { - let slotDataString = ''; - responses.forEach(slot => { - slotDataString += - `${JSON.stringify(slot.headers)}\n${slot.creative}\n`; - }); - return Promise.resolve(slotDataString); - }, - headers, - })); + xhrWithArgs.returns( + Promise.resolve({ + arrayBuffer: () => { + throw new Error('Expected SRA!'); + }, + bodyUsed: false, + text: () => { + let slotDataString = ''; + responses.forEach(slot => { + slotDataString += `${JSON.stringify(slot.headers)}\n${ + slot.creative + }\n`; + }); + return Promise.resolve(slotDataString); + }, + headers, + }) + ); } } @@ -438,25 +492,28 @@ describes.realWin('Doubleclick SRA', config , env => { }; const iu = encodeURIComponent(impl.element.getAttribute('data-slot')); const urlRegexp = new RegExp( - '^https:\/\/securepubads\\.g\\.doubleclick\\.net' + - `\/gampad\/ads\\?iu=${iu}&`); - xhrMock.withArgs( - sinon.match(urlRegexp), - { - mode: 'cors', - method: 'GET', - credentials: 'include', - }).returns(Promise.resolve({ - arrayBuffer: () => Promise.resolve(utf8Encode(creative)), - bodyUsed: false, - headers: { - get: header => headers[header], - has: header => header in headers, - }, - text: () => { - throw new Error('should not be SRA!'); - }, - })); + '^https://securepubads\\.g\\.doubleclick\\.net' + + `\/gampad\/ads\\?iu=${iu}&` + ); + xhrMock + .withArgs(sinon.match(urlRegexp), { + mode: 'cors', + method: 'GET', + credentials: 'include', + }) + .returns( + Promise.resolve({ + arrayBuffer: () => Promise.resolve(utf8Encode(creative)), + bodyUsed: false, + headers: { + get: header => headers[header], + has: header => header in headers, + }, + text: () => { + throw new Error('should not be SRA!'); + }, + }) + ); } /** @@ -479,7 +536,10 @@ describes.realWin('Doubleclick SRA', config , env => { function executeTest(items, opt_implicitSra) { if (!opt_implicitSra) { createAndAppendAdElement( - {name: 'amp-ad-doubleclick-sra'}, 'meta', doc.head); + {name: 'amp-ad-doubleclick-sra'}, + 'meta', + doc.head + ); } // Store if XHR will fail by networkId. const networkXhrFailure = {}; @@ -487,8 +547,10 @@ describes.realWin('Doubleclick SRA', config , env => { const networkValidity = {}; const doubleclickInstances = []; const networkNestHeaders = []; - const attemptCollapseSpy = - sandbox.spy(BaseElement.prototype, 'attemptCollapse'); + const attemptCollapseSpy = sandbox.spy( + BaseElement.prototype, + 'attemptCollapse' + ); const expIds = []; let expectedAttemptCollapseCalls = 0; items.forEach(network => { @@ -515,20 +577,21 @@ describes.realWin('Doubleclick SRA', config , env => { networkXhrFailure[network.networkId] = !!network.xhrFail; networkNestHeaders[network.networkId] = network.nestHeaders; expectedAttemptCollapseCalls += - network.xhrFail && !opt_implicitSra ? network.instances : 0; + network.xhrFail && !opt_implicitSra ? network.instances : 0; expIds[network.networkId] = network.expIds || []; }); const grouping = {}; const groupingPromises = {}; doubleclickInstances.forEach(impl => { const networkId = getNetworkId(impl.element); - (grouping[networkId] || (grouping[networkId] = [])) - .push(impl); - (groupingPromises[networkId] || (groupingPromises[networkId] = [])) - .push(Promise.resolve(impl)); + (grouping[networkId] || (grouping[networkId] = [])).push(impl); + ( + groupingPromises[networkId] || (groupingPromises[networkId] = []) + ).push(Promise.resolve(impl)); }); - sandbox.stub(AmpAdNetworkDoubleclickImpl.prototype, 'groupSlotsForSra') - .returns(Promise.resolve(groupingPromises)); + sandbox + .stub(AmpAdNetworkDoubleclickImpl.prototype, 'groupSlotsForSra') + .returns(Promise.resolve(groupingPromises)); let idx = 0; const layoutCallbacks = []; const getLayoutCallback = (impl, creative, isSra, noRender) => { @@ -543,7 +606,8 @@ describes.realWin('Doubleclick SRA', config , env => { if (opt_implicitSra) { expect(impl.iframe).to.be.ok; expect(impl.iframe.src).to.match( - /securepubads\.g\.doubleclick\.net/); + /securepubads\.g\.doubleclick\.net/ + ); return; } expect(impl.postAdResponseExperimentFeatures['foo']).to.equal('bar'); @@ -552,7 +616,8 @@ describes.realWin('Doubleclick SRA', config , env => { if (isSra) { // Expect safeframe. expect(name).to.match( - new RegExp(`^\\d+-\\d+-\\d+;\\d+;${creative}`)); + new RegExp(`^\\d+-\\d+-\\d+;\\d+;${creative}`) + ); } else { // Expect nameframe render. expect(JSON.parse(name).creative).to.equal(creative); @@ -560,10 +625,12 @@ describes.realWin('Doubleclick SRA', config , env => { }); }; Object.keys(grouping).forEach(networkId => { - const validInstances = grouping[networkId].filter(impl => - impl.element.getAttribute('data-test-invalid') != 'true'); - const isSra = validInstances.length > 1 && - !validInstances[0].experimentIds.includes('21062235'); + const validInstances = grouping[networkId].filter( + impl => impl.element.getAttribute('data-test-invalid') != 'true' + ); + const isSra = + validInstances.length > 1 && + !validInstances[0].experimentIds.includes('21062235'); const sraResponses = []; validInstances.forEach(impl => { const creative = `slot${idx++}`; @@ -579,26 +646,41 @@ describes.realWin('Doubleclick SRA', config , env => { } else { generateNonSraXhrMockCall(impl, creative); } - layoutCallbacks.push(getLayoutCallback( - impl, creative, isSra, + layoutCallbacks.push( + getLayoutCallback( + impl, + creative, + isSra, (!opt_implicitSra && networkXhrFailure[networkId]) || - impl.element.getAttribute('data-test-invalid') == 'true')); + impl.element.getAttribute('data-test-invalid') == 'true' + ) + ); }); if (isSra) { - generateSraXhrMockCall(validInstances, networkId, sraResponses, - networkXhrFailure[networkId], networkValidity[networkId]); + generateSraXhrMockCall( + validInstances, + networkId, + sraResponses, + networkXhrFailure[networkId], + networkValidity[networkId] + ); } }); - return Promise.all(layoutCallbacks).then(() => expect( - attemptCollapseSpy.callCount).to.equal(expectedAttemptCollapseCalls)); + return Promise.all(layoutCallbacks).then(() => + expect(attemptCollapseSpy.callCount).to.equal( + expectedAttemptCollapseCalls + ) + ); } beforeEach(() => { xhrMock = sandbox.stub(Xhr.prototype, 'fetch'); - sandbox.stub(AmpA4A.prototype, - 'getSigningServiceNames').returns(['google']); - sandbox.stub(SignatureVerifier.prototype, 'loadKeyset') - .callsFake(() => {}); + sandbox + .stub(AmpA4A.prototype, 'getSigningServiceNames') + .returns(['google']); + sandbox + .stub(SignatureVerifier.prototype, 'loadKeyset') + .callsFake(() => {}); }); afterEach(() => { @@ -607,17 +689,17 @@ describes.realWin('Doubleclick SRA', config , env => { it('should not use SRA if single slot', () => executeTest([1234])); - it('should not use SRA if single slot, multiple networks', - () => executeTest([1234, 4567])); + it('should not use SRA if single slot, multiple networks', () => + executeTest([1234, 4567])); - it('should correctly use SRA for multiple slots', - () => executeTest([1234, 1234])); + it('should correctly use SRA for multiple slots', () => + executeTest([1234, 1234])); it('should correctly handle SRA response with nested headers', () => executeTest([{networkId: 1234, instances: 2, nestHeaders: true}])); - it('should not send SRA request if slots are invalid', - () => executeTest([{networkId: 1234, invalidInstances: 2}])); + it('should not send SRA request if slots are invalid', () => + executeTest([{networkId: 1234, invalidInstances: 2}])); it('should send SRA request if more than 1 slot is valid', () => executeTest([{networkId: 1234, instances: 2, invalidInstances: 2}])); @@ -628,22 +710,31 @@ describes.realWin('Doubleclick SRA', config , env => { it('should send SRA request if only 1 slot and no recovery exp', () => executeTest([{networkId: 1234, instances: 1, expIds: ['21062235']}])); - it('should handle xhr failure by not sending subsequent request', - () => executeTest([{networkId: 1234, instances: 2, xhrFail: true}])); - - it('should handle xhr failure by via subsequent request if implicit', - () => executeTest([{networkId: 1234, instances: 2, xhrFail: true}], - true)); - - it('should handle mixture of xhr and non xhr failures', () => executeTest( - [{networkId: 1234, instances: 2, xhrFail: true}, 4567, 4567])); - - it('should correctly use SRA for multiple slots. multiple networks', - () => executeTest([1234, 4567, 1234, 4567])); - - it('should handle mixture of all possible scenarios', () => executeTest( - [1234, 1234, 101, {networkId: 4567, instances: 2, xhrFail: true}, 202, - {networkId: 8901, instances: 3, invalidInstances: 1}])); + it('should handle xhr failure by not sending subsequent request', () => + executeTest([{networkId: 1234, instances: 2, xhrFail: true}])); + + it('should handle xhr failure by via subsequent request if implicit', () => + executeTest([{networkId: 1234, instances: 2, xhrFail: true}], true)); + + it('should handle mixture of xhr and non xhr failures', () => + executeTest([ + {networkId: 1234, instances: 2, xhrFail: true}, + 4567, + 4567, + ])); + + it('should correctly use SRA for multiple slots. multiple networks', () => + executeTest([1234, 4567, 1234, 4567])); + + it('should handle mixture of all possible scenarios', () => + executeTest([ + 1234, + 1234, + 101, + {networkId: 4567, instances: 2, xhrFail: true}, + 202, + {networkId: 8901, instances: 3, invalidInstances: 1}, + ])); }); describe('#sraBlockCallbackHandler', () => { @@ -652,18 +743,30 @@ describes.realWin('Doubleclick SRA', config , env => { const headerObj = {a: 'b', c: 123}; const slotDeferred = new Deferred(); const sraRequestAdUrlResolvers = [ - slotDeferred.resolve, {resolve: () => {throw new Error();}}]; + slotDeferred.resolve, + { + resolve: () => { + throw new Error(); + }, + }, + ]; sraBlockCallbackHandler( - creative, headerObj, /* done */false, sraRequestAdUrlResolvers); + creative, + headerObj, + /* done */ false, + sraRequestAdUrlResolvers + ); expect(sraRequestAdUrlResolvers.length).to.equal(1); return slotDeferred.promise.then(fetchResponse => { expect(fetchResponse.headers.get('a')).to.equal('b'); expect(fetchResponse.headers.get('c')).to.equal('123'); - expect(fetchResponse.headers.get(RENDERING_TYPE_HEADER.toLowerCase())) - .to.equal(XORIGIN_MODE.SAFEFRAME); + expect( + fetchResponse.headers.get(RENDERING_TYPE_HEADER.toLowerCase()) + ).to.equal(XORIGIN_MODE.SAFEFRAME); expect(fetchResponse.headers.has('unknown')).to.be.false; - return fetchResponse.arrayBuffer().then(buffer => - expect(utf8Decode(buffer)).to.equal(creative)); + return fetchResponse + .arrayBuffer() + .then(buffer => expect(utf8Decode(buffer)).to.equal(creative)); }); }); @@ -690,17 +793,28 @@ describes.realWin('Doubleclick SRA', config , env => { for (let i = 1; i <= blocks.length; i++) { const {creative, headers, deferred} = blocks[i - 1]; sraBlockCallbackHandler( - creative, headers, resolvers.length == 1, resolvers); + creative, + headers, + resolvers.length == 1, + resolvers + ); expect(resolvers.length).to.equal(blocks.length - i); - promises.push(deferred.promise.then(fetchResponse => { - Object.keys(headers).forEach(name => expect( - fetchResponse.header.get(name)).to.equal(String(headers[name]))); - expect(fetchResponse.headers.get(RENDERING_TYPE_HEADER.toLowerCase())) - .to.equal(XORIGIN_MODE.SAFEFRAME); - expect(fetchResponse.headers.has('unknown')).to.be.false; - return fetchResponse.arrayBuffer().then(buffer => - expect(utf8Decode(buffer)).to.equal(creative)); - })); + promises.push( + deferred.promise.then(fetchResponse => { + Object.keys(headers).forEach(name => + expect(fetchResponse.header.get(name)).to.equal( + String(headers[name]) + ) + ); + expect( + fetchResponse.headers.get(RENDERING_TYPE_HEADER.toLowerCase()) + ).to.equal(XORIGIN_MODE.SAFEFRAME); + expect(fetchResponse.headers.has('unknown')).to.be.false; + return fetchResponse + .arrayBuffer() + .then(buffer => expect(utf8Decode(buffer)).to.equal(creative)); + }) + ); } return promises; }); diff --git a/extensions/amp-ad-network-fake-impl/0.1/amp-ad-network-fake-impl.js b/extensions/amp-ad-network-fake-impl/0.1/amp-ad-network-fake-impl.js index 35df58b2948b..8d3b47dfaaf3 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/amp-ad-network-fake-impl.js +++ b/extensions/amp-ad-network-fake-impl/0.1/amp-ad-network-fake-impl.js @@ -21,7 +21,6 @@ import {user, userAssert} from '../../../src/log'; const TAG = 'AMP-AD-NETWORK-FAKE-IMPL'; export class AmpAdNetworkFakeImpl extends AmpA4A { - /** * @param {!Element} element */ @@ -31,8 +30,11 @@ export class AmpAdNetworkFakeImpl extends AmpA4A { /** @override */ buildCallback() { - userAssert(this.element.hasAttribute('src'), - 'Attribute src required for : %s', this.element); + userAssert( + this.element.hasAttribute('src'), + 'Attribute src required for : %s', + this.element + ); super.buildCallback(); } @@ -60,17 +62,22 @@ export class AmpAdNetworkFakeImpl extends AmpA4A { if (!response) { return null; } - const {status, headers} = - /** @type {{status: number, headers: !Headers}} */ (response); + const { + status, + headers, + } = /** @type {{status: number, headers: !Headers}} */ (response); // In the convert creative mode the content is the plain AMP HTML. // This mode is primarily used for A4A Envelop for testing. // See DEVELOPING.md for more info. if (this.element.getAttribute('a4a-conversion') == 'true') { return response.text().then( - responseText => new Response( - this.transformCreative_(responseText), - {status, headers})); + responseText => + new Response(this.transformCreative_(responseText), { + status, + headers, + }) + ); } // Normal mode: Expect the creative is written in AMP4ADS doc. @@ -119,7 +126,7 @@ export class AmpAdNetworkFakeImpl extends AmpA4A { style.parentNode.removeChild(style); } - let creative = root./*OK*/outerHTML; + let creative = root./*OK*/ outerHTML; // Metadata creative += ' ' + - '' + - 'Non-Performant Fake Iframe' + - '' + - ''; - const frameUrl2 = addParamsToUrl('http://ads.localhost:' + - document.location.port + '/amp4test/compose-doc', {body}); - sandbox.stub(env.ampdoc.win.document.body, 'appendChild'); - new IframeTransport(env.ampdoc.win, 'some_other_vendor_type', - {iframe: frameUrl2}, frameUrl2 + '-3'); - sandbox.restore(); - const errorSpy = sandbox.spy(user(), 'error'); - const {frame} = IframeTransport.getFrameData('some_other_vendor_type'); - frame.setAttribute('style', ''); - env.ampdoc.win.document.body.appendChild(frame); - return new Promise((resolve,unused) => { - expectPostMessage(frame.contentWindow, env.ampdoc.win, 'doneSleeping') - .then(() => { - expect(errorSpy).to.be.called; - expect(errorSpy.args[0][1]).to.match( - /Long Task: Vendor: "some_other_vendor_type"/); - resolve(); - }); +describes.realWin( + 'amp-analytics.iframe-transport', + {amp: true, allowExternalResources: true}, + env => { + it('logs poor performance of vendor iframe', () => { + const body = + ' ' + + '' + + 'Non-Performant Fake Iframe' + + '' + + ''; + const frameUrl2 = addParamsToUrl( + 'http://ads.localhost:' + + document.location.port + + '/amp4test/compose-doc', + {body} + ); + sandbox.stub(env.ampdoc.win.document.body, 'appendChild'); + new IframeTransport( + env.ampdoc.win, + 'some_other_vendor_type', + {iframe: frameUrl2}, + frameUrl2 + '-3' + ); + sandbox.restore(); + const errorSpy = sandbox.spy(user(), 'error'); + const {frame} = IframeTransport.getFrameData('some_other_vendor_type'); + frame.setAttribute('style', ''); + env.ampdoc.win.document.body.appendChild(frame); + return new Promise((resolve, unused) => { + expectPostMessage( + frame.contentWindow, + env.ampdoc.win, + 'doneSleeping' + ).then(() => { + expect(errorSpy).to.be.called; + expect(errorSpy.args[0][1]).to.match( + /Long Task: Vendor: "some_other_vendor_type"/ + ); + resolve(); }); - }).timeout(10000); - }); + }); + }).timeout(10000); + } +); diff --git a/extensions/amp-analytics/0.1/test/test-instrumentation.js b/extensions/amp-analytics/0.1/test/test-instrumentation.js index 9ac05daf3fb5..42e5b6a56818 100644 --- a/extensions/amp-analytics/0.1/test/test-instrumentation.js +++ b/extensions/amp-analytics/0.1/test/test-instrumentation.js @@ -16,9 +16,7 @@ import {CustomEventTracker} from '../events'; -import { - InstrumentationService, -} from '../instrumentation.js'; +import {InstrumentationService} from '../instrumentation.js'; describes.realWin('InstrumentationService', {amp: 1}, env => { let win; @@ -77,48 +75,50 @@ describes.realWin('InstrumentationService', {amp: 1}, env => { }); }); - -describes.realWin('InstrumentationService in FIE', { - amp: {ampdoc: 'fie'}, -}, env => { - let win; - let embed; - let ampdoc; - let service; - let root; - let analyticsElement; - let target; - - beforeEach(() => { - win = env.win; - embed = env.embed; - ampdoc = env.ampdoc; - service = new InstrumentationService(ampdoc); - root = service.ampdocRoot_; - - analyticsElement = win.document.createElement('amp-analytics'); - win.document.body.appendChild(analyticsElement); - - target = win.document.createElement('div'); - win.document.body.appendChild(target); - }); - - it('should create and reuse embed root', () => { - expect(root.ampdoc).to.equal(ampdoc); - expect(root.parent).to.be.null; - - const group1 = service.createAnalyticsGroup(analyticsElement); - const embedRoot = group1.root_; - expect(embedRoot).to.not.equal(root); - expect(embedRoot.parent).to.equal(root); - expect(embedRoot.ampdoc).to.equal(ampdoc); - expect(embedRoot.embed).to.equal(embed); - - // Reuse the previously created instance. - const analyticsElement2 = win.document.createElement('amp-analytics'); - win.document.body.appendChild(analyticsElement2); - const group2 = service.createAnalyticsGroup(analyticsElement2); - expect(group2.root_).to.equal(embedRoot); - }); -}); - +describes.realWin( + 'InstrumentationService in FIE', + { + amp: {ampdoc: 'fie'}, + }, + env => { + let win; + let embed; + let ampdoc; + let service; + let root; + let analyticsElement; + let target; + + beforeEach(() => { + win = env.win; + embed = env.embed; + ampdoc = env.ampdoc; + service = new InstrumentationService(ampdoc); + root = service.ampdocRoot_; + + analyticsElement = win.document.createElement('amp-analytics'); + win.document.body.appendChild(analyticsElement); + + target = win.document.createElement('div'); + win.document.body.appendChild(target); + }); + + it('should create and reuse embed root', () => { + expect(root.ampdoc).to.equal(ampdoc); + expect(root.parent).to.be.null; + + const group1 = service.createAnalyticsGroup(analyticsElement); + const embedRoot = group1.root_; + expect(embedRoot).to.not.equal(root); + expect(embedRoot.parent).to.equal(root); + expect(embedRoot.ampdoc).to.equal(ampdoc); + expect(embedRoot.embed).to.equal(embed); + + // Reuse the previously created instance. + const analyticsElement2 = win.document.createElement('amp-analytics'); + win.document.body.appendChild(analyticsElement2); + const group2 = service.createAnalyticsGroup(analyticsElement2); + expect(group2.root_).to.equal(embedRoot); + }); + } +); diff --git a/extensions/amp-analytics/0.1/test/test-linker-manager.js b/extensions/amp-analytics/0.1/test/test-linker-manager.js index 5fee17cfc82d..8429cdb54b7b 100644 --- a/extensions/amp-analytics/0.1/test/test-linker-manager.js +++ b/extensions/amp-analytics/0.1/test/test-linker-manager.js @@ -1,4 +1,3 @@ - /** * Copyright 2018 The AMP HTML Authors. All Rights Reserved. * @@ -52,20 +51,24 @@ describes.realWin('Linker Manager', {amp: true}, env => { beforeSubmit: beforeSubmitStub, }); - sandbox.stub(Services, 'documentInfoForDoc') - .returns({ - sourceUrl: 'https://amp.source.com/some/path?q=123', - canonicalUrl: 'https://www.canonical.com/some/path?q=123', - }); + sandbox.stub(Services, 'documentInfoForDoc').returns({ + sourceUrl: 'https://amp.source.com/some/path?q=123', + canonicalUrl: 'https://www.canonical.com/some/path?q=123', + }); // LinkerManager uses Url/UrlReplacements services scoped to the element, // but for testing stub in the top-level ampdoc service for simplicity. element = {}; const urlReplacements = Services.urlReplacementsForDoc(doc.documentElement); - sandbox.stub(Services, 'urlReplacementsForDoc') - .withArgs(element).returns(urlReplacements); + sandbox + .stub(Services, 'urlReplacementsForDoc') + .withArgs(element) + .returns(urlReplacements); const url = Services.urlForDoc(doc.documentElement); - sandbox.stub(Services, 'urlForDoc').withArgs(element).returns(url); + sandbox + .stub(Services, 'urlForDoc') + .withArgs(element) + .returns(url); handlers = []; sandbox.stub(Services, 'navigationForDoc').returns({ @@ -84,16 +87,21 @@ describes.realWin('Linker Manager', {amp: true}, env => { }); it('registers anchor mutator if given valid linkers config', () => { - new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, }, }, - }, /* type */ null, element).init(); + /* type */ null, + element + ).init(); expect(handlers.length).to.equal(1); }); @@ -109,20 +117,25 @@ describes.realWin('Linker Manager', {amp: true}, env => { }); it('does not register anchor mutator if no linkers enabled', () => { - new LinkerManager(ampdoc, { - linkers: { - testLinker1: { - ids: { - bar: 'foo', + new LinkerManager( + ampdoc, + { + linkers: { + testLinker1: { + ids: { + bar: 'foo', + }, }, - }, - testLinker2: { - ids: { - foo: 'bar', + testLinker2: { + ids: { + foo: 'bar', + }, }, }, }, - }, /* type */ null, element).init(); + /* type */ null, + element + ).init(); expect(handlers.length).to.equal(0); }); @@ -130,16 +143,21 @@ describes.realWin('Linker Manager', {amp: true}, env => { windowInterface.getLocation.returns({ origin: 'https://amp.source.com', }); - new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - bar: 'foo', + new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + bar: 'foo', + }, }, }, }, - }, /* type */ null, element).init(); + /* type */ null, + element + ).init(); expect(handlers.length).to.equal(0); }); @@ -147,24 +165,31 @@ describes.realWin('Linker Manager', {amp: true}, env => { windowInterface.getLocation.returns({ origin: 'https://amp.source.com', }); - new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - proxyOnly: false, - ids: { - bar: 'foo', + new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + proxyOnly: false, + ids: { + bar: 'foo', + }, }, }, }, - }, /* type */ null, element).init(); + /* type */ null, + element + ).init(); expect(handlers.length).to.equal(1); }); it('should resolve vars and append to matching anchor', () => { - windowInterface.getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) ' + + windowInterface.getUserAgent.returns( + 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 ' + - 'Safari/537.36'); + 'Safari/537.36' + ); windowInterface.getUserLanguage.returns('en-US'); sandbox.useFakeTimers(1533329483292); sandbox.stub(Date.prototype, 'getTimezoneOffset').returns(420); @@ -193,10 +218,11 @@ describes.realWin('Linker Manager', {amp: true}, env => { return lm.init().then(() => { expect(handlers.length).to.equal(1); expect(clickAnchor('https://www.source.com/dest?a=1')).to.equal( - 'https://www.source.com/dest' + + 'https://www.source.com/dest' + '?a=1' + '&testLinker1=1*1pgvkob*_key*VEVTVCUyMFRJVExF*gclid*MjM0' + - '&testLinker2=1*1u4ugj3*foo*YmFy'); + '&testLinker2=1*1u4ugj3*foo*YmFy' + ); }); }); @@ -217,7 +243,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { return lm.init().then(() => { expect(handlers.length).to.equal(1); expect(clickAnchor('https://www.source.com/dest?a=1')).to.equal( - 'https://www.source.com/dest?a=1' + 'https://www.source.com/dest?a=1' ); }); }); @@ -611,10 +637,18 @@ describes.realWin('Linker Manager', {amp: true}, env => { }, }, }; - const p1 = - new LinkerManager(ampdoc, config, 'googleanalytics', element).init(); - const p2 = - new LinkerManager(ampdoc, config, 'googleanalytics', element).init(); + const p1 = new LinkerManager( + ampdoc, + config, + 'googleanalytics', + element + ).init(); + const p2 = new LinkerManager( + ampdoc, + config, + 'googleanalytics', + element + ).init(); return Promise.all([p1, p2]).then(() => { const a = clickAnchor('https://www.source.com/path'); expect(a).to.not.match(/(testLinker1=.*){2}/); @@ -714,16 +748,21 @@ describes.realWin('Linker Manager', {amp: true}, env => { describe('form support', () => { it('should register the `beforeSubmit` callback', () => { toggleExperiment(win, 'linker-form', true); - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, }, }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { expect(beforeSubmitStub.calledOnce).to.be.true; @@ -733,17 +772,22 @@ describes.realWin('Linker Manager', {amp: true}, env => { }); it('should add hidden elements to form if not action-xhr', () => { - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, + destinationDomains: ['www.ampproject.com'], }, - destinationDomains: ['www.ampproject.com'], }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { const form = createForm(); @@ -763,17 +807,22 @@ describes.realWin('Linker Manager', {amp: true}, env => { }); it('if action-xhr and method=GET it should add linker-xhr attr', () => { - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, + destinationDomains: ['www.ampproject.com'], }, - destinationDomains: ['www.ampproject.com'], }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { const form = createForm(); @@ -785,24 +834,30 @@ describes.realWin('Linker Manager', {amp: true}, env => { expect(setterSpy.calledOnce).to.be.true; - const calledWithLinkerUrl = setterSpy - .calledWith(sinon.match(/testLinker=1\*\w{5,7}\*foo*\w+/)); + const calledWithLinkerUrl = setterSpy.calledWith( + sinon.match(/testLinker=1\*\w{5,7}\*foo*\w+/) + ); return expect(calledWithLinkerUrl).to.be.true; }); }); it('if action-xhr and method=POST it should add linker-xhr attr', () => { - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, + destinationDomains: ['www.ampproject.com'], }, - destinationDomains: ['www.ampproject.com'], }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { const form = createForm(); @@ -814,25 +869,30 @@ describes.realWin('Linker Manager', {amp: true}, env => { expect(setterSpy.calledOnce).to.be.true; - const calledWithLinkerUrl = setterSpy - .calledWith(sinon.match(/testLinker=1\*\w{5,7}\*foo*\w+/)); + const calledWithLinkerUrl = setterSpy.calledWith( + sinon.match(/testLinker=1\*\w{5,7}\*foo*\w+/) + ); return expect(calledWithLinkerUrl).to.be.true; }); }); - it('should not add linker if no domain match', () => { - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, + destinationDomains: ['www.ampproject.com'], }, - destinationDomains: ['www.ampproject.com'], }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { const form = createForm(); @@ -849,29 +909,39 @@ describes.realWin('Linker Manager', {amp: true}, env => { origin: 'https://www.ampbyexample.com', }); - const manager1 = new LinkerManager(ampdoc, { - linkers: { - proxyOnly: false, - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const manager1 = new LinkerManager( + ampdoc, + { + linkers: { + proxyOnly: false, + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, }, }, - }, /* type */ null, element); + /* type */ null, + element + ); - const manager2 = new LinkerManager(ampdoc, { - linkers: { - proxyOnly: false, - testLinker2: { - enabled: true, - ids: { - hello: 'world', + const manager2 = new LinkerManager( + ampdoc, + { + linkers: { + proxyOnly: false, + testLinker2: { + enabled: true, + ids: { + hello: 'world', + }, }, }, }, - }, /* type */ null, element); + /* type */ null, + element + ); const p1 = manager1.init(); const p2 = manager2.init(); @@ -915,12 +985,11 @@ describe('areFriendlyDomains', () => { expect(areFriendlyDomains('amp.source.com', 'www.source.com')).to.be.true; expect(areFriendlyDomains('m.source.com', 'www.source.com')).to.be.true; expect(areFriendlyDomains('amp.www.source.com', 'source.com')).to.be.true; - expect(areFriendlyDomains('amp.source.com', 'm.www.source.com')) - .to.be.true; + expect(areFriendlyDomains('amp.source.com', 'm.www.source.com')).to.be.true; expect(areFriendlyDomains('amp.source.com', 'amp.google.com')).to.be.false; - expect(areFriendlyDomains('web.amp.source.com', 'web.m.source.com')) - .to.be.false; + expect(areFriendlyDomains('web.amp.source.com', 'web.m.source.com')).to.be + .false; }); }); diff --git a/extensions/amp-analytics/0.1/test/test-linker-reader.js b/extensions/amp-analytics/0.1/test/test-linker-reader.js index 3ee61515b546..5abb22a182b6 100644 --- a/extensions/amp-analytics/0.1/test/test-linker-reader.js +++ b/extensions/amp-analytics/0.1/test/test-linker-reader.js @@ -14,7 +14,6 @@ * limitations under the License. */ - import { installLinkerReaderService, linkerReaderServiceFor, @@ -33,9 +32,11 @@ describe('LinkerReader', () => { sandbox.useFakeTimers(1533329483292); sandbox.stub(Date.prototype, 'getTimezoneOffset').returns(420); mockWin = mockWindowInterface(sandbox); - mockWin.getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) ' + + mockWin.getUserAgent.returns( + 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 ' + - 'Safari/537.36'); + 'Safari/537.36' + ); mockWin.getUserLanguage.returns('en-US'); mockWin.location = { href: 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx', @@ -58,13 +59,15 @@ describe('LinkerReader', () => { expectAsyncConsoleError(/LINKER_PARAM requires two params, name and id/); expect(linkerReader.get('testlinker')).to.be.null; expect(mockWin.location.href).to.equal( - 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx'); + 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx' + ); }); it('return null when no linker name', () => { expect(linkerReader.get('nolinker', 'id')).to.be.null; expect(mockWin.location.href).to.equal( - 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx'); + 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx' + ); }); it('return null when linker name value is invalid', () => { @@ -76,37 +79,43 @@ describe('LinkerReader', () => { it('return null when no linker id value', () => { mockWin.location.href = - 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx'; + 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx'; expect(linkerReader.get('testlinker', 'key2')).to.be.null; expect(mockWin.location.href).to.equal('https://example.com/'); }); it('remove linker_param from url', () => { - mockWin.location.href = 'https://example.com?a=1&b=2&' + - 'testlinker=1*1f66u1p*key1*dmFsdWUx&c&' + - 'testlinker2=1*1f66u1p*key1*dmFsdWUx&d=2#hash'; + mockWin.location.href = + 'https://example.com?a=1&b=2&' + + 'testlinker=1*1f66u1p*key1*dmFsdWUx&c&' + + 'testlinker2=1*1f66u1p*key1*dmFsdWUx&d=2#hash'; linkerReader.get('testlinker', 'id'); - expect(mockWin.location.href).to.equal('https://example.com/?a=1&b=2&c&' + - 'testlinker2=1*1f66u1p*key1*dmFsdWUx&d=2#hash'); + expect(mockWin.location.href).to.equal( + 'https://example.com/?a=1&b=2&c&' + + 'testlinker2=1*1f66u1p*key1*dmFsdWUx&d=2#hash' + ); linkerReader.get('testlinker2', 'key1'); - expect(mockWin.location.href).to.equal('https://example.com/?a=1&b=2&c&' + - 'd=2#hash'); + expect(mockWin.location.href).to.equal( + 'https://example.com/?a=1&b=2&c&d=2#hash' + ); }); it('return correct id value', () => { - mockWin.location.href = 'https://example.com?' + + mockWin.location.href = + 'https://example.com?' + 'test=1*1f66u1p*key1*dmFsdWUx&var=foo&' + 'test2=1*1m48hbv*cid*MTIzNDU.*ref*aHR0cHM6Ly93d3cuZXhhbXBsZS5jb20.'; expect(linkerReader.get('test', 'key1')).to.equal('value1'); expect(linkerReader.get('test2', 'cid')).to.equal('12345'); expect(linkerReader.get('test2', 'ref')).to.equal( - 'https://www.example.com'); + 'https://www.example.com' + ); expect(mockWin.location.href).to.equal('https://example.com/?var=foo'); }); it('returns same value when reading the same id', () => { - mockWin.location.href = 'https://example.com?' + - 'test=1*1f66u1p*key1*dmFsdWUx&var=foo'; + mockWin.location.href = + 'https://example.com?test=1*1f66u1p*key1*dmFsdWUx&var=foo'; expect(linkerReader.get('test', 'key1')).to.equal('value1'); expect(linkerReader.get('test', 'key1')).to.equal('value1'); }); diff --git a/extensions/amp-analytics/0.1/test/test-linker.js b/extensions/amp-analytics/0.1/test/test-linker.js index 8c6c4c9641f2..d1a933399ef6 100644 --- a/extensions/amp-analytics/0.1/test/test-linker.js +++ b/extensions/amp-analytics/0.1/test/test-linker.js @@ -223,10 +223,12 @@ const createLinkerTests = [ description: 'works for AMP CID API generated Client ID', version: '1', pairs: { - '_ga': 'amp-' + + '_ga': + 'amp-' + 'oRg8vByriPdstwLgkz-UNWbp2P13vNFsnhES5vW8s5WodTOoea0mTiY7X62utLyz', }, - output: '1*1fkd1zz*_ga*' + + output: + '1*1fkd1zz*_ga*' + 'YW1wLW9SZzh2QnlyaVBkc3R3TGdrei1VTldicDJQMT' + 'N2TkZzbmhFUzV2VzhzNVdvZFRPb2VhMG1UaVk3WDYydXRMeXo.', }, @@ -234,10 +236,10 @@ const createLinkerTests = [ description: 'works for AMP Viewer generated Client ID', version: '1', pairs: { - '_ga': - 'WgcaAD4XN2lydhQVNFruk6X8zwoUg6K2RnaRlhjs6CXvTv4aJV-3oVLdI1WxxvJb', + '_ga': 'WgcaAD4XN2lydhQVNFruk6X8zwoUg6K2RnaRlhjs6CXvTv4aJV-3oVLdI1WxxvJb', }, - output: '1*19eaxqc*_ga*' + + output: + '1*19eaxqc*_ga*' + 'V2djYUFENFhOMmx5ZGhRVk5GcnVrNlg4endvVWc2Sz' + 'JSbmFSbGhqczZDWHZUdjRhSlYtM29WTGRJMVd4eHZKYg..', }, @@ -252,9 +254,11 @@ describe('Linker', () => { sandbox.useFakeTimers(BASE_TIME); sandbox.stub(Date.prototype, 'getTimezoneOffset').returns(420); mockWin = mockWindowInterface(sandbox); - mockWin.getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) ' + + mockWin.getUserAgent.returns( + 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 ' + - 'Safari/537.36'); + 'Safari/537.36' + ); mockWin.getUserLanguage.returns('en-US'); }); diff --git a/extensions/amp-analytics/0.1/test/test-opacity.js b/extensions/amp-analytics/0.1/test/test-opacity.js index 95f20e92d244..4cc17720992d 100644 --- a/extensions/amp-analytics/0.1/test/test-opacity.js +++ b/extensions/amp-analytics/0.1/test/test-opacity.js @@ -27,7 +27,7 @@ describes.realWin('getMinOpacity', {amp: true}, env => { win = env.win; doc = win.document; style = doc.createElement('style'); - style.setAttribute('amp-custom',''); + style.setAttribute('amp-custom', ''); style.innerHTML = ` #img { opacity: 0.5; @@ -67,7 +67,7 @@ describes.realWin('getMinOpacity', {amp: true}, env => { expect(getMinOpacity(ampElement)).to.equal(0); }); - it('amp element\'s parent opacity value lower than amp element', () => { + it("amp element's parent opacity value lower than amp element", () => { parent.style.opacity = 0; expect(getMinOpacity(ampElement)).to.equal(0); diff --git a/extensions/amp-analytics/0.1/test/test-requests.js b/extensions/amp-analytics/0.1/test/test-requests.js index 4cbc2bd0fe27..7791b5e631a3 100644 --- a/extensions/amp-analytics/0.1/test/test-requests.js +++ b/extensions/amp-analytics/0.1/test/test-requests.js @@ -49,12 +49,17 @@ describes.realWin('Requests', {amp: 1}, env => { function createRequestHandler(request, spy) { return new RequestHandler( - analyticsElement, request, preconnect, {sendRequest: spy}, false); + analyticsElement, + request, + preconnect, + {sendRequest: spy}, + false + ); } describe('RequestHandler', () => { describe('batch', () => { - it('should batch multiple send', function* () { + it('should batch multiple send', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r2', 'batchInterval': 1}; const handler = createRequestHandler(r, spy); @@ -68,7 +73,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.be.calledOnce; }); - it('should work properly with no batch', function* () { + it('should work properly with no batch', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1'}; const handler = createRequestHandler(r, spy); @@ -79,15 +84,15 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.be.calledTwice; }); - - it('should preconnect', function* () { + it('should preconnect', function*() { const r = {'baseUrl': 'r2?cid=CLIENT_ID(scope)&var=${test}'}; const handler = createRequestHandler(r, sandbox.spy()); const expansionOptions = new ExpansionOptions({'test': 'expanded'}); handler.send({}, {}, expansionOptions, {}); yield macroTask(); expect(preconnectSpy).to.be.calledWith( - 'r2?cid=CLIENT_ID(scope)&var=expanded'); + 'r2?cid=CLIENT_ID(scope)&var=expanded' + ); }); }); @@ -154,7 +159,7 @@ describes.realWin('Requests', {amp: 1}, env => { } }); - it('should schedule send request with interval array', function* () { + it('should schedule send request with interval array', function*() { const r = {'baseUrl': 'r', 'batchInterval': [1, 2]}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); @@ -182,7 +187,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.be.calledOnce; }); - it('should not schedule send request w/o trigger', function* () { + it('should not schedule send request w/o trigger', function*() { const r = {'baseUrl': 'r', 'batchInterval': [1]}; createRequestHandler(r, spy); clock.tick(1000); @@ -190,7 +195,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.not.be.called; }); - it('should schedule send independent of trigger immediate', function* () { + it('should schedule send independent of trigger immediate', function*() { const r = {'baseUrl': 'r', 'batchInterval': [1, 2]}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); @@ -228,7 +233,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(handler3.reportWindow_).to.be.null; }); - it('should stop bathInterval outside batch report window', function* () { + it('should stop bathInterval outside batch report window', function*() { const r = {'baseUrl': 'r', 'batchInterval': 0.5, 'reportWindow': 1}; const handler = createRequestHandler(r, spy); @@ -246,7 +251,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.not.be.called; }); - it('should stop send request outside batch report window', function* () { + it('should stop send request outside batch report window', function*() { const r = {'baseUrl': 'r', 'reportWindow': 1}; const handler = createRequestHandler(r, spy); @@ -261,7 +266,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.not.be.called; }); - it('should flush batch queue after batch report window', function* () { + it('should flush batch queue after batch report window', function*() { const r = {'baseUrl': 'r', 'batchInterval': 5, 'reportWindow': 1}; const handler = createRequestHandler(r, spy); @@ -272,7 +277,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.be.calledOnce; }); - it('should respect immediate trigger', function* () { + it('should respect immediate trigger', function*() { const r = {'baseUrl': 'r', 'batchInterval': 0.2, 'reportWindow': 0.5}; const handler = createRequestHandler(r, spy); @@ -287,7 +292,7 @@ describes.realWin('Requests', {amp: 1}, env => { }); describe('batch segments', () => { - it('should respect config extraUrlParam', function* () { + it('should respect config extraUrlParam', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1', 'batchInterval': 1}; const handler = createRequestHandler(r, spy); @@ -303,39 +308,59 @@ describes.realWin('Requests', {amp: 1}, env => { ]); }); - it('should respect trigger extraUrlParam', function* () { + it('should respect trigger extraUrlParam', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1', 'batchInterval': 1}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({'v2': '中'}); - handler.send({}, { - 'extraUrlParams': { - 'e1': 'e1', - 'e2': '${v2}', // check vars are used and not double encoded + handler.send( + {}, + { + 'extraUrlParams': { + 'e1': 'e1', + 'e2': '${v2}', // check vars are used and not double encoded + }, }, - }, expansionOptions, {}); + expansionOptions, + {} + ); handler.send( - {}, {'extraUrlParams': {'e1': 'e1'}}, expansionOptions, {}); + {}, + {'extraUrlParams': {'e1': 'e1'}}, + expansionOptions, + {} + ); clock.tick(1000); yield macroTask(); expect(spy).to.be.calledWith('r1', [ - {extraUrlParams: {e1: 'e1', e2: '中'}, - timestamp: 0, trigger: undefined}, + { + extraUrlParams: {e1: 'e1', e2: '中'}, + timestamp: 0, + trigger: undefined, + }, {extraUrlParams: {e1: 'e1'}, timestamp: 0, trigger: undefined}, ]); }); - it('should keep extraUrlParam', function* () { + it('should keep extraUrlParam', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1&${extraUrlParams}&r2', 'batchInterval': 1}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); handler.send( - {}, {'extraUrlParams': {'e1': 'e1'}}, expansionOptions, {}); + {}, + {'extraUrlParams': {'e1': 'e1'}}, + expansionOptions, + {} + ); handler.send( - {}, {'extraUrlParams': {'e2': 'e2'}}, expansionOptions, {}); + {}, + {'extraUrlParams': {'e2': 'e2'}}, + expansionOptions, + {} + ); clock.tick(1000); yield macroTask(); expect(spy).to.be.calledWith('r1&${extraUrlParams}&r2', [ @@ -353,14 +378,18 @@ describes.realWin('Requests', {amp: 1}, env => { createRequestHandler(r, spy); } catch (e) { expect(e).to.match( - /batchPlugin cannot be set on non-batched request/); + /batchPlugin cannot be set on non-batched request/ + ); } }); it('should throw error with unsupported batchPlugin', () => { const spy = sandbox.spy(); - const r = - {'baseUrl': 'r', 'batchInterval': 1, 'batchPlugin': 'invalid'}; + const r = { + 'baseUrl': 'r', + 'batchInterval': 1, + 'batchPlugin': 'invalid', + }; try { createRequestHandler(r, spy); } catch (e) { @@ -370,21 +399,22 @@ describes.realWin('Requests', {amp: 1}, env => { }); }); - it('should replace dynamic bindings RESOURCE_TIMING', function* () { + it('should replace dynamic bindings RESOURCE_TIMING', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1&${resourceTiming}'}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({ 'resourceTiming': 'RESOURCE_TIMING', }); - sandbox.stub(ResourceTiming, 'getResourceTiming') - .returns(Promise.resolve('resource-timing')); + sandbox + .stub(ResourceTiming, 'getResourceTiming') + .returns(Promise.resolve('resource-timing')); handler.send({}, {}, expansionOptions); yield macroTask(); expect(spy).to.be.calledWith('r1&resource-timing'); }); - it('should replace dynamic bindings CONSENT_STATE', function* () { + it('should replace dynamic bindings CONSENT_STATE', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1&$CONSENT_STATEtest&${consentState}test2'}; const handler = createRequestHandler(r, spy); @@ -416,31 +446,34 @@ describes.realWin('Requests', {amp: 1}, env => { it('should expand', () => { return expandPostMessage( - ampdoc, - 'test foo 123 ... ${teste1}', - undefined, - {}, - expansionOptions, - element).then(msg => { + ampdoc, + 'test foo 123 ... ${teste1}', + undefined, + {}, + expansionOptions, + element + ).then(msg => { expect(msg).to.equal('test foo 123 ... TESTE1'); }); }); it('should replace not append ${extraUrlParams}', () => { const replacePromise = expandPostMessage( - ampdoc, - 'test ${extraUrlParams} foo', - params, /* configParams */ - {}, /* trigger */ - expansionOptions, - element); + ampdoc, + 'test ${extraUrlParams} foo', + params /* configParams */, + {} /* trigger */, + expansionOptions, + element + ); const appendPromise = expandPostMessage( - ampdoc, - 'test foo', - params, /* configParams */ - {}, /* trigger */ - expansionOptions, - element); + ampdoc, + 'test foo', + params /* configParams */, + {} /* trigger */, + expansionOptions, + element + ); return replacePromise.then(replace => { expect(replace).to.equal('test e1=TESTE1&e2=teste2 foo'); expect(appendPromise).to.eventually.equal('test foo'); diff --git a/extensions/amp-analytics/0.1/test/test-resource-timing.js b/extensions/amp-analytics/0.1/test/test-resource-timing.js index 14f45ff2c771..9152136e7efe 100644 --- a/extensions/amp-analytics/0.1/test/test-resource-timing.js +++ b/extensions/amp-analytics/0.1/test/test-resource-timing.js @@ -36,7 +36,7 @@ export function newResourceTimingSpec() { }, 'encoding': { 'entry': - '${key}-${initiatorType}-${startTime}-${duration}-${transferSize}', + '${key}-${initiatorType}-${startTime}-${duration}-${transferSize}', 'delim': '~', }, }; @@ -55,7 +55,13 @@ export function newResourceTimingSpec() { * @return {!JsonObject} */ export function newPerformanceResourceTiming( - url, initiatorType, startTime, duration, bodySize, cached) { + url, + initiatorType, + startTime, + duration, + bodySize, + cached +) { const dnsTime = cached ? 0 : duration * 0.1; const tcpTime = cached ? 0 : duration * 0.2; const serverTime = cached ? duration : duration * 0.4; @@ -90,12 +96,16 @@ describes.realWin('resourceTiming', {amp: true}, env => { * @return {!Promise} */ const runSerializeTest = function( - fakeEntries, resourceTimingSpec, expectedResult) { + fakeEntries, + resourceTimingSpec, + expectedResult + ) { sandbox.stub(win.performance, 'getEntriesByType').returns(fakeEntries); - return getResourceTiming(win, resourceTimingSpec, Date.now()) - .then(result => { - expect(result).to.equal(expectedResult); - }); + return getResourceTiming(win, resourceTimingSpec, Date.now()).then( + result => { + expect(result).to.equal(expectedResult); + } + ); }; beforeEach(() => { @@ -106,44 +116,60 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should return empty if the performance API is not supported', () => { const fakeWin = {}; - return getResourceTiming(fakeWin, newResourceTimingSpec(), Date.now()) - .then(result => { - expect(result).to.equal(''); - }); + return getResourceTiming(fakeWin, newResourceTimingSpec(), Date.now()).then( + result => { + expect(result).to.equal(''); + } + ); }); it('should return empty when resource timing is not supported', () => { // Performance API (fakeWin.performance) doesn't support resource timing. const fakeWin = {performance: {}}; - return getResourceTiming(fakeWin, newResourceTimingSpec(), Date.now()) - .then(result => { - expect(result).to.equal(''); - }); + return getResourceTiming(fakeWin, newResourceTimingSpec(), Date.now()).then( + result => { + expect(result).to.equal(''); + } + ); }); it('should return empty when start time has passed 1s', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); sandbox.stub(win.performance, 'getEntriesByType').returns([entry]); - return getResourceTiming(win, spec, Date.now() - 60 * 1000) - .then(result => { - expect(result).to.equal(''); - }); + return getResourceTiming(win, spec, Date.now() - 60 * 1000).then(result => { + expect(result).to.equal(''); + }); }); it('should return empty if resourceTimingSpec is empty', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); return runSerializeTest([entry], {}, ''); }); it('should return empty if encoding spec is empty', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); delete spec['encoding']; return runSerializeTest([entry], spec, ''); @@ -151,8 +177,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should return empty if encoding spec is missing delim', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); delete spec['encoding']['delim']; return runSerializeTest([entry], spec, ''); @@ -160,8 +191,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should return empty if encoding spec is missing entry', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); delete spec['encoding']['entry']; return runSerializeTest([entry], spec, ''); @@ -169,8 +205,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should serialize matching entries', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); const expect = 'foo_bar-script-100-500-7200'; return runSerializeTest([entry], spec, expect); @@ -178,19 +219,37 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should serialize multiple matching entries', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 700, 100, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); return runSerializeTest( - [entry1, entry2], newResourceTimingSpec(), - 'foo_bar-script-100-500-7200~foo_bar-script-700-100-0'); + [entry1, entry2], + newResourceTimingSpec(), + 'foo_bar-script-100-500-7200~foo_bar-script-700-100-0' + ); }); it('should match against the first spec', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); // Note that both spec'd resources match. @@ -207,8 +266,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should accept empty per-resource specs', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); // Note that both spec'd resources match. @@ -225,9 +289,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should should only report resources if the host matches', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js', 'script', 100, 500, 10 * 1000, false); + 'http://foo.example.com/lib.js', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://baz.example.com/lib.js', 'script', 700, 100, 80 * 1000, true); + 'http://baz.example.com/lib.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec.resources = {'foo': {'host': 'foo.example.com'}}; @@ -236,9 +312,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should should only report resources if the path matches', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js', 'script', 100, 500, 10 * 1000, false); + 'http://foo.example.com/lib.js', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://foo.example.com/extra.js', 'script', 700, 100, 80 * 1000, true); + 'http://foo.example.com/extra.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec.resources = { @@ -249,11 +337,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should should only report resources if the query matches', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=200', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=200', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=test', 'script', 700, 100, 80 * 1000, - true); + 'http://foo.example.com/lib.js?v=test', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec.resources = { @@ -268,8 +366,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${key} and ${initiatorType}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${key}.${initiatorType}'; return runSerializeTest([entry], spec, 'foo_style.link'); @@ -277,8 +380,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${startTime} and ${duration}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${startTime}.${duration}'; return runSerializeTest([entry], spec, '100.500'); @@ -286,8 +394,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${domainLookupTime} and ${tcpConnectTime}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${domainLookupTime}.${tcpConnectTime}'; return runSerializeTest([entry], spec, '50.100'); @@ -295,28 +408,42 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${serverResponseTime} and ${networkTransferTime}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${serverResponseTime}.${networkTransferTime}'; return runSerializeTest([entry], spec, '200.150'); }); - it('should replace ${transferSize}, ${encodedBodySize}, ${decodedBodySize}', - () => { - const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, - 10 * 1000, false); - const spec = newResourceTimingSpec(); - spec['encoding']['entry'] = - '${transferSize}.${encodedBodySize}.${decodedBodySize}'; - return runSerializeTest([entry], spec, '7200.7000.10000'); - }); + it('should replace ${transferSize}, ${encodedBodySize}, ${decodedBodySize}', () => { + const entry = newPerformanceResourceTiming( + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); + const spec = newResourceTimingSpec(); + spec['encoding']['entry'] = + '${transferSize}.${encodedBodySize}.${decodedBodySize}'; + return runSerializeTest([entry], spec, '7200.7000.10000'); + }); it('should use the base specified in encoding', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${decodedBodySize}'; spec['encoding']['base'] = 36; @@ -326,8 +453,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should reject invalid bases (over 36)', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${decodedBodySize}'; spec['encoding']['base'] = 40; @@ -338,10 +470,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should not replace other analytics variables', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 700, 100, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${startTime}.${random}.${undefinedVariable}'; // The counter is incremented for each entry. @@ -350,37 +493,76 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should URL-encode the results', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 700, 100, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${key}?${startTime},${duration}'; spec['encoding']['delim'] = ':'; return runSerializeTest( - [entry1, entry2], spec, 'foo_bar?100,500:foo_bar?700,100'); + [entry1, entry2], + spec, + 'foo_bar?100,500:foo_bar?700,100' + ); }); it('should only include resources downloaded after `responseAfter`', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 200, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 200, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 200, 200, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 200, + 200, + 80 * 1000, + true + ); const entry3 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 300, 200, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 300, + 200, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${key}.${startTime}'; spec['encoding']['delim'] = '-'; spec['responseAfter'] = 350; return runSerializeTest( - [entry1, entry2, entry3], spec, 'foo_bar.200-foo_bar.300'); + [entry1, entry2, entry3], + spec, + 'foo_bar.200-foo_bar.300' + ); }); it('should reject invalid (non-numeric) responseAfter fields', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['responseAfter'] = '100'; return runSerializeTest([entry], spec, '').then(() => { @@ -390,9 +572,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should update responseAfter', () => { const initialEntry = newPerformanceResourceTiming( - 'https://example.com/lib.css', 'link', 100, 400, 5 * 1000, false); + 'https://example.com/lib.css', + 'link', + 100, + 400, + 5 * 1000, + false + ); const laterEntry = newPerformanceResourceTiming( - 'https://bar.example.com/lib.js', 'script', 200, 500, 10 * 1000, false); + 'https://bar.example.com/lib.js', + 'script', + 200, + 500, + 10 * 1000, + false + ); // Stub performance.now so that it returns a timestamp after the resource // timing entry. @@ -407,16 +601,18 @@ describes.realWin('resourceTiming', {amp: true}, env => { const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${initiatorType}.${startTime}.${duration}'; - return getResourceTiming(win, spec, Date.now()).then(result => { - expect(result).to.equal('link.100.400'); - expect(spec['responseAfter']).to.equal(600); - - // Check resource timings a second time. - return getResourceTiming(win, spec, Date.now()); - }).then(result => { - expect(result).to.equal('script.200.500'); - expect(spec['responseAfter']).to.equal(800); - }); + return getResourceTiming(win, spec, Date.now()) + .then(result => { + expect(result).to.equal('link.100.400'); + expect(spec['responseAfter']).to.equal(600); + + // Check resource timings a second time. + return getResourceTiming(win, spec, Date.now()); + }) + .then(result => { + expect(result).to.equal('script.200.500'); + expect(spec['responseAfter']).to.equal(800); + }); }); it('should not update responseAfter if greater', () => { @@ -432,8 +628,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should stop reporting after reaching the buffer limit', () => { const entry = newPerformanceResourceTiming( - 'http://does_not_match.com/lib.js', 'script', 100, 500, 10 * 1000, - false); + 'http://does_not_match.com/lib.js', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entries = new Array(150).fill(entry); // Stub performance.now so that it returns a timestamp after the resource // timing entry. @@ -446,8 +647,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should not report if resourceTimingSpec is done', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['done'] = true; return runSerializeTest([entry], spec, ''); diff --git a/extensions/amp-analytics/0.1/test/test-scroll-manager.js b/extensions/amp-analytics/0.1/test/test-scroll-manager.js index 3fb60d220463..7b0f67c4bd16 100644 --- a/extensions/amp-analytics/0.1/test/test-scroll-manager.js +++ b/extensions/amp-analytics/0.1/test/test-scroll-manager.js @@ -17,7 +17,6 @@ import {AmpdocAnalyticsRoot} from '../analytics-root'; import {ScrollManager} from '../scroll-manager'; - describes.realWin('ScrollManager', {amp: 1}, env => { let win; let ampdoc; @@ -50,8 +49,9 @@ describes.realWin('ScrollManager', {amp: 1}, env => { scrollManager = new ScrollManager(ampdoc); root.scrollManager_ = scrollManager; fakeViewport = { - 'getSize': sandbox.stub().returns( - {top: 0, left: 0, height: 200, width: 200}), + 'getSize': sandbox + .stub() + .returns({top: 0, left: 0, height: 200, width: 200}), 'getScrollTop': sandbox.stub().returns(0), 'getScrollLeft': sandbox.stub().returns(0), 'getScrollHeight': sandbox.stub().returns(500), @@ -61,7 +61,6 @@ describes.realWin('ScrollManager', {amp: 1}, env => { }, }; scrollManager.viewport_ = fakeViewport; - }); it('should initalize, add listeners and dispose', () => { @@ -74,21 +73,24 @@ describes.realWin('ScrollManager', {amp: 1}, env => { expect(scrollManager.scrollObservable_.getHandlerCount()).to.equal(0); }); - it('should add a viewport onChanged listener with scroll handlers, ' - + 'and dispose when there are none', () => { - expect(scrollManager.viewportOnChangedUnlistener_).to.not.be.ok; + it( + 'should add a viewport onChanged listener with scroll handlers, ' + + 'and dispose when there are none', + () => { + expect(scrollManager.viewportOnChangedUnlistener_).to.not.be.ok; - const fn1 = sandbox.stub(); - scrollManager.addScrollHandler(fn1); + const fn1 = sandbox.stub(); + scrollManager.addScrollHandler(fn1); - expect(scrollManager.viewportOnChangedUnlistener_).to.be.ok; - const unlistenStub = scrollManager.viewportOnChangedUnlistener_; + expect(scrollManager.viewportOnChangedUnlistener_).to.be.ok; + const unlistenStub = scrollManager.viewportOnChangedUnlistener_; - scrollManager.removeScrollHandler(fn1); + scrollManager.removeScrollHandler(fn1); - expect(scrollManager.viewportOnChangedUnlistener_).to.not.be.ok; - expect(unlistenStub).to.have.callCount(1); - }); + expect(scrollManager.viewportOnChangedUnlistener_).to.not.be.ok; + expect(unlistenStub).to.have.callCount(1); + } + ); it('fires on scroll', () => { const fn1 = sandbox.stub(); @@ -124,16 +126,13 @@ describes.realWin('ScrollManager', {amp: 1}, env => { expect(fn1).to.have.callCount(2); expect( - fn1.getCall(1) - .calledWithMatch(sinon.match(matcher(expectedScrollEvent))) + fn1.getCall(1).calledWithMatch(sinon.match(matcher(expectedScrollEvent))) ).to.be.true; expect(fn2).to.have.callCount(2); expect( - fn2.getCall(1) - .calledWithMatch(sinon.match(matcher(expectedScrollEvent))) + fn2.getCall(1).calledWithMatch(sinon.match(matcher(expectedScrollEvent))) ).to.be.true; - }); it('can remove specifc handlers', () => { @@ -152,7 +151,6 @@ describes.realWin('ScrollManager', {amp: 1}, env => { fakeViewport.getScrollLeft.returns(500); scrollManager.onScroll_({top: 500, left: 500, height: 250, width: 250}); - expect(fn1).to.have.callCount(2); expect(fn2).to.have.callCount(1); }); diff --git a/extensions/amp-analytics/0.1/test/test-transport-serializers.js b/extensions/amp-analytics/0.1/test/test-transport-serializers.js index 10c1234e3a21..257ce4fd8f49 100644 --- a/extensions/amp-analytics/0.1/test/test-transport-serializers.js +++ b/extensions/amp-analytics/0.1/test/test-transport-serializers.js @@ -14,7 +14,6 @@ * limitations under the License. */ - import {TransportSerializers} from '../transport-serializer'; import {dict} from '../../../../src/utils/object'; import {isArray} from '../../../../src/types'; @@ -36,59 +35,69 @@ import {isArray} from '../../../../src/types'; * } */ const defaultTestData = { - 'in': [{ - 'baseUrl': 'https://base.com', - 'batchSegments': [{ - 'trigger': 'click', - 'timestamp': 0, - 'extraUrlParams': { - a: 1, - b: 'xyz', - }, - }], - }, { - 'baseUrl': 'https://base.com?${extraUrlParams}&z=1', - 'batchSegments': [{ - 'trigger': 'click', - 'timestamp': 0, - 'extraUrlParams': { - a: 1, - b: 'xyz', - }, - }], - }], - - 'out': [{ - generateRequest: { - url: 'https://base.com?a=1&b=xyz', - }, - generateBatchRequest: { - url: 'https://base.com?a=1&b=xyz', - }, - generateRequestWithPayload: { - url: 'https://base.com', - payload: '{"a":1,"b":"xyz"}', - }, - generateBatchRequestWithPayload: { - url: 'https://base.com', - payload: '[{"a":1,"b":"xyz"}]', - }, - }, { - generateRequest: { - url: 'https://base.com?a=1&b=xyz&z=1', + 'in': [ + { + 'baseUrl': 'https://base.com', + 'batchSegments': [ + { + 'trigger': 'click', + 'timestamp': 0, + 'extraUrlParams': { + a: 1, + b: 'xyz', + }, + }, + ], }, - generateBatchRequest: { - url: 'https://base.com?a=1&b=xyz&z=1', + { + 'baseUrl': 'https://base.com?${extraUrlParams}&z=1', + 'batchSegments': [ + { + 'trigger': 'click', + 'timestamp': 0, + 'extraUrlParams': { + a: 1, + b: 'xyz', + }, + }, + ], }, - generateRequestWithPayload: { - url: 'https://base.com?&z=1', - payload: '{"a":1,"b":"xyz"}', + ], + + 'out': [ + { + generateRequest: { + url: 'https://base.com?a=1&b=xyz', + }, + generateBatchRequest: { + url: 'https://base.com?a=1&b=xyz', + }, + generateRequestWithPayload: { + url: 'https://base.com', + payload: '{"a":1,"b":"xyz"}', + }, + generateBatchRequestWithPayload: { + url: 'https://base.com', + payload: '[{"a":1,"b":"xyz"}]', + }, }, - generateBatchRequestWithPayload: { - url: 'https://base.com?&z=1', - payload: '[{"a":1,"b":"xyz"}]', + { + generateRequest: { + url: 'https://base.com?a=1&b=xyz&z=1', + }, + generateBatchRequest: { + url: 'https://base.com?a=1&b=xyz&z=1', + }, + generateRequestWithPayload: { + url: 'https://base.com?&z=1', + payload: '{"a":1,"b":"xyz"}', + }, + generateBatchRequestWithPayload: { + url: 'https://base.com?&z=1', + payload: '[{"a":1,"b":"xyz"}]', + }, }, - }], + ], }; /** @@ -127,8 +136,9 @@ describe('Transport serializers', () => { 'extraUrlParams': null, }); try { - const output = - serializer.generateBatchRequest('base', [batchSegment]); + const output = serializer.generateBatchRequest('base', [ + batchSegment, + ]); expect(output.url).to.be.ok; } catch (e) { throw e; @@ -142,8 +152,9 @@ describe('Transport serializers', () => { 'extraUrlParams': '12?3', }); try { - const output = - serializer.generateBatchRequest('base', [batchSegment]); + const output = serializer.generateBatchRequest('base', [ + batchSegment, + ]); expect(output.url).to.not.contain('12?3'); } catch (e) { throw e; @@ -157,8 +168,9 @@ describe('Transport serializers', () => { 'extraUrlParams': '123', }); try { - const output = - serializer.generateBatchRequest('base', [batchSegment]); + const output = serializer.generateBatchRequest('base', [ + batchSegment, + ]); expect(typeof output.url).to.equal('string'); } catch (e) { throw e; @@ -166,7 +178,6 @@ describe('Transport serializers', () => { }); }); - describe('custom test', () => { let testData; let input; @@ -197,14 +208,18 @@ describe('Transport serializers', () => { expect(batchSegments).to.be.ok; expect(isArray(batchSegments)).to.be.true; const request = output[i]; - expect(serializer.generateRequest(baseUrl, batchSegments[0])) - .to.jsonEqual(request.generateRequest); - expect(serializer.generateRequest(baseUrl, batchSegments[0], true)) - .to.jsonEqual(request.generateRequestWithPayload); - expect(serializer.generateBatchRequest(baseUrl, batchSegments)) - .to.jsonEqual(request.generateRequest); - expect(serializer.generateBatchRequest(baseUrl, batchSegments, true)) - .to.jsonEqual(request.generateBatchRequestWithPayload); + expect( + serializer.generateRequest(baseUrl, batchSegments[0]) + ).to.jsonEqual(request.generateRequest); + expect( + serializer.generateRequest(baseUrl, batchSegments[0], true) + ).to.jsonEqual(request.generateRequestWithPayload); + expect( + serializer.generateBatchRequest(baseUrl, batchSegments) + ).to.jsonEqual(request.generateRequest); + expect( + serializer.generateBatchRequest(baseUrl, batchSegments, true) + ).to.jsonEqual(request.generateBatchRequestWithPayload); } }); }); diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index f11696f9c178..6608a7ca6bfe 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -24,422 +24,503 @@ import {getMode} from '../../../../src/mode'; import {installTimerService} from '../../../../src/service/timer-impl'; import {loadPromise} from '../../../../src/event-helper'; -describes.realWin('amp-analytics.transport', { - amp: false, - allowExternalResources: true, -}, env => { - - let sandbox; - let win; - let doc; - let openXhrStub; - let sendXhrStub; - let sendBeaconStub; - let imagePixelVerifier; - - beforeEach(() => { - sandbox = env.sandbox; - win = env.win; - doc = win.document; - openXhrStub = sandbox.stub(); - sendXhrStub = sandbox.stub(); - sendBeaconStub = sandbox.stub(); - }); - - it('prefers beacon over xhrpost and image', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: true, image: true, +describes.realWin( + 'amp-analytics.transport', + { + amp: false, + allowExternalResources: true, + }, + env => { + let sandbox; + let win; + let doc; + let openXhrStub; + let sendXhrStub; + let sendBeaconStub; + let imagePixelVerifier; + + beforeEach(() => { + sandbox = env.sandbox; + win = env.win; + doc = win.document; + openXhrStub = sandbox.stub(); + sendXhrStub = sandbox.stub(); + sendBeaconStub = sandbox.stub(); }); - expectBeacon('https://example.com/test', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('prefers xhrpost over image', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - beacon: false, xhrpost: true, image: true, + + it('prefers beacon over xhrpost and image', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: true, + image: true, + }); + expectBeacon('https://example.com/test', ''); + expectNoXhr(); + expectNoImagePixel(); }); - expectNoBeacon(); - expectXhr('https://example.com/test', ''); - expectNoImagePixel(); - }); - - it('reluctantly uses image if nothing else is enabled', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - image: true, + + it('prefers xhrpost over image', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + beacon: false, + xhrpost: true, + image: true, + }); + expectNoBeacon(); + expectXhr('https://example.com/test', ''); + expectNoImagePixel(); }); - expectNoBeacon(); - expectImagePixel('https://example.com/test'); - expectNoXhr(); - }); - - it('falls back to image setting suppressWarnings to true', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - beacon: false, xhrpost: false, image: {suppressWarnings: true}, + + it('reluctantly uses image if nothing else is enabled', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + image: true, + }); + expectNoBeacon(); + expectImagePixel('https://example.com/test'); + expectNoXhr(); }); - expectNoBeacon(); - expectNoXhr(); - expectImagePixel('https://example.com/test'); - }); - - it('falls back to image setting referrerPolicy', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: true, image: true, referrerPolicy: 'no-referrer', + + it('falls back to image setting suppressWarnings to true', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + beacon: false, + xhrpost: false, + image: {suppressWarnings: true}, + }); + expectNoBeacon(); + expectNoXhr(); + expectImagePixel('https://example.com/test'); }); - expectNoBeacon(); - expectNoXhr(); - expectImagePixel('https://example.com/test', 'no-referrer'); - }); - - it('falls back to xhrpost when enabled and beacon is not available', () => { - setupStubs(false, true); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: true, image: true, + + it('falls back to image setting referrerPolicy', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: true, + image: true, + referrerPolicy: 'no-referrer', + }); + expectNoBeacon(); + expectNoXhr(); + expectImagePixel('https://example.com/test', 'no-referrer'); }); - expectNoBeacon(); - expectXhr('https://example.com/test', ''); - expectNoImagePixel(); - }); - - it('falls back to image when beacon not found and xhr disabled', () => { - setupStubs(false, true); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: false, image: true, + + it('falls back to xhrpost when enabled and beacon is not available', () => { + setupStubs(false, true); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: true, + image: true, + }); + expectNoBeacon(); + expectXhr('https://example.com/test', ''); + expectNoImagePixel(); }); - expectNoBeacon(); - expectNoXhr(); - expectImagePixel('https://example.com/test'); - }); - - it('falls back to image when beacon and xhr are not available', () => { - setupStubs(false, false); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: true, image: true, + + it('falls back to image when beacon not found and xhr disabled', () => { + setupStubs(false, true); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: false, + image: true, + }); + expectNoBeacon(); + expectNoXhr(); + expectImagePixel('https://example.com/test'); }); - expectNoBeacon(); - expectNoXhr(); - expectImagePixel('https://example.com/test'); - }); - - it('does not send a request when no transport methods are enabled', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', {}); - expectNoBeacon(); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('does not send a request when URL is empty', () => { - setupStubs(true, true); - sendRequest(win, '', {beacon: true, xhrpost: true, image: true}); - expectNoBeacon(); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send single segment request', () => { - setupStubs(true, true); - new Transport(win, {beacon: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], false); - expectBeacon('https://e.com/test?a=1&b=hello', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send single segment request in batch', () => { - setupStubs(true, true); - new Transport(win, {beacon: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], true); - expectBeacon('https://e.com/test?a=1&b=hello', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send single segment request useBody', () => { - setupStubs(true, true); - new Transport(win, {beacon: true, useBody: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], false); - expectBeacon('https://e.com/test', '{"a":1,"b":"hello"}'); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send single segment request useBody in batch', () => { - setupStubs(true, true); - new Transport(win, {beacon: true, useBody: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], true); - expectBeacon('https://e.com/test', '[{"a":1,"b":"hello"}]'); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send multi-segment request w/o batch (only 1st sent)', () => { - setupStubs(true, true); - new Transport(win, {beacon: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }, { - extraUrlParams: { - a: 2, - b: 'world', - }, - }], false); - expectBeacon('https://e.com/test?a=1&b=hello', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send multi-segment request in batch', () => { - setupStubs(true, true); - new Transport(win, {beacon: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }, { - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], true); - expectBeacon('https://e.com/test?a=1&b=hello&a=1&b=hello', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send multi-segment request useBody in batch', () => { - setupStubs(true, true); - new Transport(win, {beacon: true, useBody: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }, { - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], true); - expectBeacon('https://e.com/test', '[{"a":1,"b":"hello"},{"a":1,"b":"hello"}]'); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('asserts that urls are https', () => { - allowConsoleError(() => { expect(() => { - sendRequest(win, 'http://example.com/test', {image: true}); - }).to.throw(/https/); }); - }); - - it('should NOT allow __amp_source_origin', () => { - allowConsoleError(() => { expect(() => { - sendRequest(win, 'https://twitter.com?__amp_source_origin=1', {image: true}); - }).to.throw(/Source origin is not allowed in/); }); - }); - - describe('sendRequestUsingIframe', () => { - const url = 'http://iframe.localhost:9876/test/fixtures/served/iframe.html'; - - function sendRequestUsingIframe(win, url) { - new Transport(win).sendRequestUsingIframe(url, {}); - } - it('should create and delete an iframe', () => { - const clock = lolex.install({target: win}); - installTimerService(win); - sendRequestUsingIframe(win, url); - const iframe = doc.querySelector('iframe[src="' + url + '"]'); - expect(iframe).to.be.ok; - expect(iframe.getAttribute('sandbox')).to.equal( - 'allow-scripts allow-same-origin'); - return loadPromise(iframe).then(() => { - clock.tick(4999); - expect(doc.querySelector('iframe[src="' + url + '"]')).to.be.ok; - clock.tick(1); - expect(doc.querySelector('iframe[src="' + url + '"]')).to.not.be.ok; + it('falls back to image when beacon and xhr are not available', () => { + setupStubs(false, false); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: true, + image: true, }); + expectNoBeacon(); + expectNoXhr(); + expectImagePixel('https://example.com/test'); }); - it('iframe asserts that urls are https', () => { - allowConsoleError(() => { expect(() => { - sendRequestUsingIframe(win, 'http://example.com/test'); - }).to.throw(/https/); }); + it('does not send a request when no transport methods are enabled', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', {}); + expectNoBeacon(); + expectNoXhr(); + expectNoImagePixel(); }); - it('forbids same origin', () => { - const fakeWin = { - location: { - href: 'https://example.com/abc', - }, - }; - allowConsoleError(() => { - expect(() => { - sendRequestUsingIframe(fakeWin, 'https://example.com/123'); - }).to.throw(/Origin of iframe request/); - }); + it('does not send a request when URL is empty', () => { + setupStubs(true, true); + sendRequest(win, '', {beacon: true, xhrpost: true, image: true}); + expectNoBeacon(); + expectNoXhr(); + expectNoImagePixel(); + }); + + it('send single segment request', () => { + setupStubs(true, true); + new Transport(win, {beacon: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + false + ); + expectBeacon('https://e.com/test?a=1&b=hello', ''); + expectNoXhr(); + expectNoImagePixel(); }); - }); - describe('iframe transport', () => { + it('send single segment request in batch', () => { + setupStubs(true, true); + new Transport(win, {beacon: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + true + ); + expectBeacon('https://e.com/test?a=1&b=hello', ''); + expectNoXhr(); + expectNoImagePixel(); + }); - it('does not initialize transport iframe if not used', () => { - const transport = new Transport(win, { - image: true, - xhrpost: true, - beacon: false, - }); + it('send single segment request useBody', () => { + setupStubs(true, true); + new Transport(win, {beacon: true, useBody: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + false + ); + expectBeacon('https://e.com/test', '{"a":1,"b":"hello"}'); + expectNoXhr(); + expectNoImagePixel(); + }); - const ampAnalyticsEl = null; + it('send single segment request useBody in batch', () => { + setupStubs(true, true); + new Transport(win, {beacon: true, useBody: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + true + ); + expectBeacon('https://e.com/test', '[{"a":1,"b":"hello"}]'); + expectNoXhr(); + expectNoImagePixel(); + }); + + it('send multi-segment request w/o batch (only 1st sent)', () => { + setupStubs(true, true); + new Transport(win, {beacon: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + { + extraUrlParams: { + a: 2, + b: 'world', + }, + }, + ], + false + ); + expectBeacon('https://e.com/test?a=1&b=hello', ''); + expectNoXhr(); + expectNoImagePixel(); + }); + + it('send multi-segment request in batch', () => { + setupStubs(true, true); + new Transport(win, {beacon: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + true + ); + expectBeacon('https://e.com/test?a=1&b=hello&a=1&b=hello', ''); + expectNoXhr(); + expectNoImagePixel(); + }); + + it('send multi-segment request useBody in batch', () => { + setupStubs(true, true); + new Transport(win, {beacon: true, useBody: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + true + ); + expectBeacon( + 'https://e.com/test', + '[{"a":1,"b":"hello"},{"a":1,"b":"hello"}]' + ); + expectNoXhr(); + expectNoImagePixel(); + }); - const preconnectSpy = sandbox.spy(); - transport.maybeInitIframeTransport(win, ampAnalyticsEl, { - preload: preconnectSpy, + it('asserts that urls are https', () => { + allowConsoleError(() => { + expect(() => { + sendRequest(win, 'http://example.com/test', {image: true}); + }).to.throw(/https/); }); - expect(transport.iframeTransport_).to.be.null; - expect(preconnectSpy).to.not.be.called; }); - it('initialize iframe transport when used', () => { - const transport = new Transport(win, { - iframe: '//test', + it('should NOT allow __amp_source_origin', () => { + allowConsoleError(() => { + expect(() => { + sendRequest(win, 'https://twitter.com?__amp_source_origin=1', { + image: true, + }); + }).to.throw(/Source origin is not allowed in/); }); + }); - const ad = doc.createElement('amp-ad'); - ad.getResourceId = () => '123'; - doc.body.appendChild(ad); - const frame = doc.createElement('iframe'); - ad.appendChild(frame); - frame.contentWindow.document.write( - ''); - frame.contentWindow.__AMP_TOP = win; - const ampAnalyticsEl = - frame.contentWindow.document.querySelector('amp-analytics'); - - const preconnectSpy = sandbox.spy(); - transport.maybeInitIframeTransport(win, ampAnalyticsEl, { - preload: preconnectSpy, + describe('sendRequestUsingIframe', () => { + const url = + 'http://iframe.localhost:9876/test/fixtures/served/iframe.html'; + + function sendRequestUsingIframe(win, url) { + new Transport(win).sendRequestUsingIframe(url, {}); + } + + it('should create and delete an iframe', () => { + const clock = lolex.install({target: win}); + installTimerService(win); + sendRequestUsingIframe(win, url); + const iframe = doc.querySelector('iframe[src="' + url + '"]'); + expect(iframe).to.be.ok; + expect(iframe.getAttribute('sandbox')).to.equal( + 'allow-scripts allow-same-origin' + ); + return loadPromise(iframe).then(() => { + clock.tick(4999); + expect(doc.querySelector('iframe[src="' + url + '"]')).to.be.ok; + clock.tick(1); + expect(doc.querySelector('iframe[src="' + url + '"]')).to.not.be.ok; + }); }); - expect(transport.iframeTransport_).to.be.ok; - expect(preconnectSpy).to.be.called; - transport.deleteIframeTransport(); - expect(transport.iframeTransport_).to.be.null; - }); + it('iframe asserts that urls are https', () => { + allowConsoleError(() => { + expect(() => { + sendRequestUsingIframe(win, 'http://example.com/test'); + }).to.throw(/https/); + }); + }); - it('initialize iframe transport when used with inabox', () => { - win.AMP_MODE = win.AMP_MODE || {}; - win.AMP_MODE.runtime = 'inabox'; - expect(getMode(win).runtime).to.equal('inabox'); + it('forbids same origin', () => { + const fakeWin = { + location: { + href: 'https://example.com/abc', + }, + }; + allowConsoleError(() => { + expect(() => { + sendRequestUsingIframe(fakeWin, 'https://example.com/123'); + }).to.throw(/Origin of iframe request/); + }); + }); + }); - const transport = new Transport(win, { - iframe: '//test', + describe('iframe transport', () => { + it('does not initialize transport iframe if not used', () => { + const transport = new Transport(win, { + image: true, + xhrpost: true, + beacon: false, + }); + + const ampAnalyticsEl = null; + + const preconnectSpy = sandbox.spy(); + transport.maybeInitIframeTransport(win, ampAnalyticsEl, { + preload: preconnectSpy, + }); + expect(transport.iframeTransport_).to.be.null; + expect(preconnectSpy).to.not.be.called; }); - const frame = doc.createElement('iframe'); - doc.body.appendChild(frame); - frame.contentWindow.document.write( - ''); - frame.contentWindow.__AMP_TOP = win; - const ampAnalyticsEl = - frame.contentWindow.document.querySelector('amp-analytics'); - - const preconnectSpy = sandbox.spy(); - transport.maybeInitIframeTransport(win, ampAnalyticsEl, { - preload: preconnectSpy, + it('initialize iframe transport when used', () => { + const transport = new Transport(win, { + iframe: '//test', + }); + + const ad = doc.createElement('amp-ad'); + ad.getResourceId = () => '123'; + doc.body.appendChild(ad); + const frame = doc.createElement('iframe'); + ad.appendChild(frame); + frame.contentWindow.document.write( + '' + ); + frame.contentWindow.__AMP_TOP = win; + const ampAnalyticsEl = frame.contentWindow.document.querySelector( + 'amp-analytics' + ); + + const preconnectSpy = sandbox.spy(); + transport.maybeInitIframeTransport(win, ampAnalyticsEl, { + preload: preconnectSpy, + }); + expect(transport.iframeTransport_).to.be.ok; + expect(preconnectSpy).to.be.called; + + transport.deleteIframeTransport(); + expect(transport.iframeTransport_).to.be.null; }); - expect(transport.iframeTransport_).to.be.ok; - expect(preconnectSpy).to.be.called; - transport.deleteIframeTransport(); - expect(transport.iframeTransport_).to.be.null; - }); + it('initialize iframe transport when used with inabox', () => { + win.AMP_MODE = win.AMP_MODE || {}; + win.AMP_MODE.runtime = 'inabox'; + expect(getMode(win).runtime).to.equal('inabox'); + + const transport = new Transport(win, { + iframe: '//test', + }); + + const frame = doc.createElement('iframe'); + doc.body.appendChild(frame); + frame.contentWindow.document.write( + '' + ); + frame.contentWindow.__AMP_TOP = win; + const ampAnalyticsEl = frame.contentWindow.document.querySelector( + 'amp-analytics' + ); + + const preconnectSpy = sandbox.spy(); + transport.maybeInitIframeTransport(win, ampAnalyticsEl, { + preload: preconnectSpy, + }); + expect(transport.iframeTransport_).to.be.ok; + expect(preconnectSpy).to.be.called; + + transport.deleteIframeTransport(); + expect(transport.iframeTransport_).to.be.null; + }); - it('send via iframe transport', () => { - setupStubs(true, true); - const transport = new Transport(win, { - beacon: true, xhrpost: true, image: true, - iframe: '//test', + it('send via iframe transport', () => { + setupStubs(true, true); + const transport = new Transport(win, { + beacon: true, + xhrpost: true, + image: true, + iframe: '//test', + }); + const iframeTransportSendRequestSpy = sandbox.spy(); + transport.iframeTransport_ = { + sendRequest: iframeTransportSendRequestSpy, + }; + transport.sendRequest('test test', [{}], false); + expectNoBeacon(); + expectNoXhr(); + expectNoImagePixel(); + expect(iframeTransportSendRequestSpy).to.be.calledWith('test test'); }); - const iframeTransportSendRequestSpy = sandbox.spy(); - transport.iframeTransport_ = { - sendRequest: iframeTransportSendRequestSpy, - }; - transport.sendRequest('test test', [{}], false); - expectNoBeacon(); - expectNoXhr(); - expectNoImagePixel(); - expect(iframeTransportSendRequestSpy).to.be.calledWith('test test'); }); - }); - - function setupStubs(beacon, xhr) { - const wi = mockWindowInterface(sandbox); - wi.getSendBeacon.returns(beacon ? sendBeaconStub : undefined); - - const FakeXMLHttpRequest = () => { - return { - withCredentials: false, - open: openXhrStub, - send: sendXhrStub, - setRequestHeader: () => {}, + + function setupStubs(beacon, xhr) { + const wi = mockWindowInterface(sandbox); + wi.getSendBeacon.returns(beacon ? sendBeaconStub : undefined); + + const FakeXMLHttpRequest = () => { + return { + withCredentials: false, + open: openXhrStub, + send: sendXhrStub, + setRequestHeader: () => {}, + }; }; - }; - wi.getXMLHttpRequest.returns(xhr ? FakeXMLHttpRequest : undefined); - sendBeaconStub.returns(beacon); + wi.getXMLHttpRequest.returns(xhr ? FakeXMLHttpRequest : undefined); + sendBeaconStub.returns(beacon); - imagePixelVerifier = new ImagePixelVerifier(wi); - } + imagePixelVerifier = new ImagePixelVerifier(wi); + } - function sendRequest(win, request, options) { - new Transport(win, options).sendRequest(request, [{}], false); - } + function sendRequest(win, request, options) { + new Transport(win, options).sendRequest(request, [{}], false); + } - function expectBeacon(url, payload) { - expect(sendBeaconStub).to.be.calledWith(url, payload); - } + function expectBeacon(url, payload) { + expect(sendBeaconStub).to.be.calledWith(url, payload); + } - function expectNoBeacon() { - expect(sendBeaconStub).to.not.be.called; - } + function expectNoBeacon() { + expect(sendBeaconStub).to.not.be.called; + } - function expectXhr(url, payload) { - expect(openXhrStub).to.be.calledWith('POST', url, true); - expect(sendXhrStub).to.be.calledWith(payload); - } + function expectXhr(url, payload) { + expect(openXhrStub).to.be.calledWith('POST', url, true); + expect(sendXhrStub).to.be.calledWith(payload); + } - function expectNoXhr() { - expect(openXhrStub).to.not.be.called; - expect(sendXhrStub).to.not.be.called; - } + function expectNoXhr() { + expect(openXhrStub).to.not.be.called; + expect(sendXhrStub).to.not.be.called; + } - function expectImagePixel(url, referrerPolicy) { - imagePixelVerifier.verifyRequest(url, referrerPolicy); - } + function expectImagePixel(url, referrerPolicy) { + imagePixelVerifier.verifyRequest(url, referrerPolicy); + } - function expectNoImagePixel() { - expect(imagePixelVerifier.hasRequestSent()).to.be.false; + function expectNoImagePixel() { + expect(imagePixelVerifier.hasRequestSent()).to.be.false; + } } -}); +); diff --git a/extensions/amp-analytics/0.1/test/test-variables.js b/extensions/amp-analytics/0.1/test/test-variables.js index da132ad46291..37cd8711f53f 100644 --- a/extensions/amp-analytics/0.1/test/test-variables.js +++ b/extensions/amp-analytics/0.1/test/test-variables.js @@ -1,4 +1,3 @@ - /** * Copyright 2015 The AMP HTML Authors. All Rights Reserved. * @@ -41,20 +40,19 @@ describe('amp-analytics.VariableService', function() { describe('encodeVars', () => { it('correctly encodes scalars and arrays', () => { expect(encodeVars('abc %&')).to.equal('abc%20%25%26'); - expect(encodeVars('SOME_MACRO(abc,123)')) - .to.equal('SOME_MACRO(abc,123)'); + expect(encodeVars('SOME_MACRO(abc,123)')).to.equal('SOME_MACRO(abc,123)'); const array = ['abc %&', 'a b']; expect(encodeVars(array)).to.equal('abc%20%25%26,a%20b'); // Test non-inplace semantics by testing again. expect(encodeVars(array)).to.equal('abc%20%25%26,a%20b'); - expect(encodeVars(['12.3', 'SOME_MACRO(abc,123)', 'ab/c'])) - .to.equal('12.3,SOME_MACRO(abc,123),ab%2Fc'); + expect(encodeVars(['12.3', 'SOME_MACRO(abc,123)', 'ab/c'])).to.equal( + '12.3,SOME_MACRO(abc,123),ab%2Fc' + ); }); }); describe('expand', () => { - const vars = { 'a': '${b}', 'b': '${c}', @@ -63,7 +61,9 @@ describe('amp-analytics.VariableService', function() { function check(template, expected, vars) { const actual = variables.expandTemplateSync( - template, new ExpansionOptions(vars)); + template, + new ExpansionOptions(vars) + ); expect(actual).to.equal(expected); } @@ -73,7 +73,9 @@ describe('amp-analytics.VariableService', function() { it('expands nested vars (no encode)', () => { const actual = variables.expandTemplateSync( - '${a}', new ExpansionOptions(vars, undefined, true)); + '${a}', + new ExpansionOptions(vars, undefined, true) + ); expect(actual).to.equal('https://www.google.com/a?b=1&c=2'); }); @@ -111,36 +113,47 @@ describe('amp-analytics.VariableService', function() { }); check('${foo}&${bar(3,4)}', 'FOO(1,2)&BAR(3,4)', { - 'foo': 'FOO(1,2)', 'bar': 'BAR', + 'foo': 'FOO(1,2)', + 'bar': 'BAR', }); // TODO: fix this, should be 'AAA(1,2)%26BBB(3,4)%26CCC(5,6)%26DDD(7,8)' check('${all}', 'AAA(1%2C2)%26BBB(3%2C4)%26CCC(5%2C6)%26DDD(7,8)', { - 'a': 'AAA', 'b': 'BBB', 'c': 'CCC(5,6)', 'd': 'DDD(7,8)', + 'a': 'AAA', + 'b': 'BBB', + 'c': 'CCC(5,6)', + 'd': 'DDD(7,8)', 'all': '${a(1,2)}&${b(3,4)}&${c}&${d}', }); }); it('respect freeze variables', () => { - const vars = new ExpansionOptions({'fooParam': 'QUERY_PARAM', - 'freeze': 'error'}); + const vars = new ExpansionOptions({ + 'fooParam': 'QUERY_PARAM', + 'freeze': 'error', + }); vars.freezeVar('freeze'); const actual = variables.expandTemplateSync( - '${fooParam(foo,bar)}${nonfreeze}${freeze}', vars); + '${fooParam(foo,bar)}${nonfreeze}${freeze}', + vars + ); expect(actual).to.equal('QUERY_PARAM(foo,bar)${freeze}'); }); it('expands array vars', () => { - check('${array}', - 'xy%26x,MACRO(abc,def),MACRO(abc%2Cdef)%26123,%24%7Bfoo%7D', { - 'foo': 'bar', - 'array': [ - 'xy&x', // special chars should be encoded - 'MACRO(abc,def)', // do not encode macro - 'MACRO(abc,def)&123', // this is not a macro - '${foo}', // vars in array is not expanded - ], - }); + check( + '${array}', + 'xy%26x,MACRO(abc,def),MACRO(abc%2Cdef)%26123,%24%7Bfoo%7D', + { + 'foo': 'bar', + 'array': [ + 'xy&x', // special chars should be encoded + 'MACRO(abc,def)', // do not encode macro + 'MACRO(abc,def)&123', // this is not a macro + '${foo}', // vars in array is not expanded + ], + } + ); }); it('handles empty var name', () => { @@ -149,18 +162,27 @@ describe('amp-analytics.VariableService', function() { describe('should handle recursive vars', () => { const recursiveVars = { - '1': '1${2}', '2': '2${3}', '3': '3${4}', '4': '4${1}', + '1': '1${2}', + '2': '2${3}', + '3': '3${4}', + '4': '4${1}', }; it('default to 2 recursions', () => { - expectAsyncConsoleError(/Maximum depth reached while expanding variables/); + expectAsyncConsoleError( + /Maximum depth reached while expanding variables/ + ); check('${1}', '123%24%7B4%7D', recursiveVars); }); it('customize recursions to 5', () => { - expectAsyncConsoleError(/Maximum depth reached while expanding variables/); + expectAsyncConsoleError( + /Maximum depth reached while expanding variables/ + ); const actual = variables.expandTemplateSync( - '${1}', new ExpansionOptions(recursiveVars, 5)); + '${1}', + new ExpansionOptions(recursiveVars, 5) + ); expect(actual).to.equal('123412%24%7B3%7D'); }); }); @@ -191,11 +213,14 @@ describe('amp-analytics.VariableService', function() { it('default works without first arg', () => check('$DEFAULT(,two)', 'two')); - it('default works without first arg length', - () => check('$DEFAULT($TRIM(), two)', 'two')); + it('default works without first arg length', () => + check('$DEFAULT($TRIM(), two)', 'two')); - it('hash works', () => check('$HASH(test)', - 'doQSMg97CqWBL85CjcRwazyuUOAqZMqhangiSb_o78S37xzLEmJV0ZYEff7fF6Cp')); + it('hash works', () => + check( + '$HASH(test)', + 'doQSMg97CqWBL85CjcRwazyuUOAqZMqhangiSb_o78S37xzLEmJV0ZYEff7fF6Cp' + )); it('substr works', () => check('$SUBSTR(Hello world!, 1, 4)', 'ello')); @@ -219,14 +244,18 @@ describe('amp-analytics.VariableService', function() { it('if works', () => check('$IF(hey, truthy, falsey)', 'truthy')); it('chaining works', () => { - return check('$SUBSTR(Hello world!, 6)', 'world!').then(() => - check('$TOUPPERCASE($SUBSTR(Hello world!, 6))', 'WORLD!')).then(() => - check('$BASE64($TOUPPERCASE($SUBSTR(Hello world!, 6)))', 'V09STEQh')) - .then(() => - check('$HASH($BASE64($TOUPPERCASE($SUBSTR(Hello world!, 6))))', - 'OPTTt2IGW8-R31MrIF_cRUwLTZ9jLDOXEuhNz_Q' + - 'S7Uc5ZmODduHWdplzrZ7Jsnqx') - ); + return check('$SUBSTR(Hello world!, 6)', 'world!') + .then(() => check('$TOUPPERCASE($SUBSTR(Hello world!, 6))', 'WORLD!')) + .then(() => + check('$BASE64($TOUPPERCASE($SUBSTR(Hello world!, 6)))', 'V09STEQh') + ) + .then(() => + check( + '$HASH($BASE64($TOUPPERCASE($SUBSTR(Hello world!, 6))))', + 'OPTTt2IGW8-R31MrIF_cRUwLTZ9jLDOXEuhNz_Q' + + 'S7Uc5ZmODduHWdplzrZ7Jsnqx' + ) + ); }); it('replaces common use case', () => { @@ -246,8 +275,10 @@ describe('amp-analytics.VariableService', function() { }); it('replaces respecting space as arg', () => { - return check('$REPLACE(this-is-a-test, `-`, ` `)', - 'this%20is%20a%20test'); + return check( + '$REPLACE(this-is-a-test, `-`, ` `)', + 'this%20is%20a%20test' + ); }); it('replaces respecting backticks', () => { @@ -263,13 +294,14 @@ describe('amp-analytics.VariableService', function() { const linkerReaderStub = sandbox.stub(linkerReader, 'get'); linkerReaderStub.withArgs('gl', 'cid').returns('a1b2c3'); linkerReaderStub.withArgs('gl', 'gclid').returns(123); - return check('LINKER_PARAM(gl, cid)&LINKER_PARAM(gl, gclid)', - 'a1b2c3&123'); + return check( + 'LINKER_PARAM(gl, cid)&LINKER_PARAM(gl, gclid)', + 'a1b2c3&123' + ); }); }); describe('getNameArgs:', () => { - function check(input, name, argList) { it('can parse ' + name, () => { expect(getNameArgsForTesting(input)).to.deep.equal({name, argList}); @@ -283,7 +315,6 @@ describe('amp-analytics.VariableService', function() { check('client id\nand something', 'client id\nand something', ''); check('client id\nclientId()', 'client id\nclientId()', ''); - check('clientId()', 'clientId', '()'); check('clientId(abc)', 'clientId', '(abc)'); check('clientId(abc,def)', 'clientId', '(abc,def)'); diff --git a/extensions/amp-analytics/0.1/test/test-vendors.js b/extensions/amp-analytics/0.1/test/test-vendors.js index 3c3c1d7e4736..98995823966f 100644 --- a/extensions/amp-analytics/0.1/test/test-vendors.js +++ b/extensions/amp-analytics/0.1/test/test-vendors.js @@ -31,163 +31,187 @@ const VENDOR_REQUESTS = require('./vendor-requests.json'); const AnalyticsConfig = Object.assign({}, ANALYTICS_CONFIG); describe('iframe transport', () => { - it('Should not contain iframe transport if not whitelisted', () => { for (const vendor in AnalyticsConfig) { const vendorEntry = AnalyticsConfig[vendor]; - if (hasOwn(vendorEntry, 'transport') && - hasOwn(vendorEntry.transport, 'iframe')) { - expect(vendorEntry['transport']['iframe']) - .to.equal(IFRAME_TRANSPORTS[vendor]); + if ( + hasOwn(vendorEntry, 'transport') && + hasOwn(vendorEntry.transport, 'iframe') + ) { + expect(vendorEntry['transport']['iframe']).to.equal( + IFRAME_TRANSPORTS[vendor] + ); } } }); }); -describes.realWin('amp-analytics', { - amp: { - extensions: ['amp-analytics'], +describes.realWin( + 'amp-analytics', + { + amp: { + extensions: ['amp-analytics'], + }, }, -}, function(env) { - let win, doc; - let requestVerifier; - - beforeEach(() => { - win = env.win; - doc = win.document; - const wi = mockWindowInterface(env.sandbox); - wi.getLocation.returns(win.location); - requestVerifier = new ImagePixelVerifier(wi); - }); + function(env) { + let win, doc; + let requestVerifier; + + beforeEach(() => { + win = env.win; + doc = win.document; + const wi = mockWindowInterface(env.sandbox); + wi.getLocation.returns(win.location); + requestVerifier = new ImagePixelVerifier(wi); + }); + + function getAnalyticsTag(config, attrs) { + config['transport'] = { + xhrpost: false, + beacon: false, + }; + config = JSON.stringify(config); + const el = doc.createElement('amp-analytics'); + const script = doc.createElement('script'); + script.textContent = config; + script.setAttribute('type', 'application/json'); + el.appendChild(script); + for (const k in attrs) { + el.setAttribute(k, attrs[k]); + } + doc.body.appendChild(el); - function getAnalyticsTag(config, attrs) { - config['transport'] = { - xhrpost: false, - beacon: false, - }; - config = JSON.stringify(config); - const el = doc.createElement('amp-analytics'); - const script = doc.createElement('script'); - script.textContent = config; - script.setAttribute('type', 'application/json'); - el.appendChild(script); - for (const k in attrs) { - el.setAttribute(k, attrs[k]); + el.connectedCallback(); + const analytics = new AmpAnalytics(el); + analytics.createdCallback(); + analytics.buildCallback(); + return analytics; } - doc.body.appendChild(el); - - el.connectedCallback(); - const analytics = new AmpAnalytics(el); - analytics.createdCallback(); - analytics.buildCallback(); - return analytics; - } - - /** - * Clears the properties in the config that should only be used in vendor - * configs. This is needed because we pass in all the vendor requests as - * inline config and iframePings/optout are not allowed to be used without - * AMP team's approval. - * - * @param {!JsonObject} config The inline config to update. - * @return {!JsonObject} - */ - function clearVendorOnlyConfig(config) { - for (const t in config.triggers) { - if (config.triggers[t].iframePing) { - config.triggers[t].iframePing = undefined; + /** + * Clears the properties in the config that should only be used in vendor + * configs. This is needed because we pass in all the vendor requests as + * inline config and iframePings/optout are not allowed to be used without + * AMP team's approval. + * + * @param {!JsonObject} config The inline config to update. + * @return {!JsonObject} + */ + function clearVendorOnlyConfig(config) { + for (const t in config.triggers) { + if (config.triggers[t].iframePing) { + config.triggers[t].iframePing = undefined; + } } + if (config.optout) { + config.optout = undefined; + } + return config; } - if (config.optout) { - config.optout = undefined; - } - return config; - } - describe('vendor request tests', () => { - for (const vendor in AnalyticsConfig) { - if (vendor === 'default') { - continue; - } - const config = AnalyticsConfig[vendor]; - if (!config.requests) { - delete AnalyticsConfig[vendor]; - continue; - } - describe('analytics vendor: ' + vendor, function() { - beforeEach(() => { - // Remove all the triggers to prevent unwanted requests, for instance - // one from a "visible" trigger. Those unwanted requests are a source - // of test flakiness. Especially they will alternate value of var - // $requestCount. - config.triggers = {}; - }); + describe('vendor request tests', () => { + for (const vendor in AnalyticsConfig) { + if (vendor === 'default') { + continue; + } + const config = AnalyticsConfig[vendor]; + if (!config.requests) { + delete AnalyticsConfig[vendor]; + continue; + } + describe('analytics vendor: ' + vendor, function() { + beforeEach(() => { + // Remove all the triggers to prevent unwanted requests, for instance + // one from a "visible" trigger. Those unwanted requests are a source + // of test flakiness. Especially they will alternate value of var + // $requestCount. + config.triggers = {}; + }); - for (const name in config.requests) { - it('should produce request: ' + name + - '. If this test fails update vendor-requests.json', function* () { - const urlReplacements = - Services.urlReplacementsForDoc(doc.documentElement); - const analytics = getAnalyticsTag(clearVendorOnlyConfig(config)); - sandbox.stub(urlReplacements.getVariableSource(), 'get').callsFake( - function(name) { - expect(this.replacements_).to.have.property(name); - const defaultValue = `_${name.toLowerCase()}_`; - return { - sync: () => defaultValue, - }; - }); - - sandbox.stub(ExpansionOptions.prototype, 'getVar').callsFake( - function(name) { - let val = this.vars[name]; - if (val == null || val == '') { - val = '!' + name; + for (const name in config.requests) { + it( + 'should produce request: ' + + name + + '. If this test fails update vendor-requests.json', + function*() { + const urlReplacements = Services.urlReplacementsForDoc( + doc.documentElement + ); + const analytics = getAnalyticsTag( + clearVendorOnlyConfig(config) + ); + sandbox + .stub(urlReplacements.getVariableSource(), 'get') + .callsFake(function(name) { + expect(this.replacements_).to.have.property(name); + const defaultValue = `_${name.toLowerCase()}_`; + return { + sync: () => defaultValue, + }; + }); + + sandbox + .stub(ExpansionOptions.prototype, 'getVar') + .callsFake(function(name) { + let val = this.vars[name]; + if (val == null || val == '') { + val = '!' + name; + } + return val; + }); + analytics.createdCallback(); + analytics.buildCallback(); + yield analytics.layoutCallback(); + + // Wait for event queue to clear. + yield macroTask(); + + analytics.handleEvent_( + { + request: name, + }, + { + vars: Object.create(null), } - return val; - }); - analytics.createdCallback(); - analytics.buildCallback(); - yield analytics.layoutCallback(); - - // Wait for event queue to clear. - yield macroTask(); - - analytics.handleEvent_({ - request: name, - }, { - vars: Object.create(null), - }); - yield macroTask(); - expect(requestVerifier.hasRequestSent()).to.be.true; - let url = requestVerifier.getLastRequestUrl(); - - const vendorData = VENDOR_REQUESTS[vendor]; - if (!vendorData) { - throw new Error('Add vendor ' + vendor + - ' to vendor-requests.json'); - } - const val = vendorData[name]; - if (val == '') { - url = ''; - } - if (val == null) { - throw new Error('Define ' + vendor + '.' + name + - ' in vendor-requests.json. Expected value: ' + url); - } - - // Write this out for easy copy pasting. - writeOutput(vendor, name, url); - - expect(url).to.equal(val); - }); - } - }); - } - }); -}); + ); + yield macroTask(); + expect(requestVerifier.hasRequestSent()).to.be.true; + let url = requestVerifier.getLastRequestUrl(); + + const vendorData = VENDOR_REQUESTS[vendor]; + if (!vendorData) { + throw new Error( + 'Add vendor ' + vendor + ' to vendor-requests.json' + ); + } + const val = vendorData[name]; + if (val == '') { + url = ''; + } + if (val == null) { + throw new Error( + 'Define ' + + vendor + + '.' + + name + + ' in vendor-requests.json. Expected value: ' + + url + ); + } + + // Write this out for easy copy pasting. + writeOutput(vendor, name, url); + + expect(url).to.equal(val); + } + ); + } + }); + } + }); + } +); const actualResults = {}; diff --git a/extensions/amp-analytics/0.1/test/test-visibility-manager-for-mapp.js b/extensions/amp-analytics/0.1/test/test-visibility-manager-for-mapp.js index 55105f4d4a8d..d8526c700a83 100644 --- a/extensions/amp-analytics/0.1/test/test-visibility-manager-for-mapp.js +++ b/extensions/amp-analytics/0.1/test/test-visibility-manager-for-mapp.js @@ -46,7 +46,6 @@ class MockVisibilityInterface { } } - describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { let win; let ampdoc; @@ -65,8 +64,7 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { viewer = win.services.viewer.obj; sandbox.stub(viewer, 'getFirstVisibleTime').callsFake(() => 1); visibilityInterface = new MockVisibilityInterface(); - root = - new VisibilityManagerForMApp(ampdoc, visibilityInterface); + root = new VisibilityManagerForMApp(ampdoc, visibilityInterface); win.IntersectionObserver = null; @@ -78,8 +76,7 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { it('should initialize correctly', () => { viewer.setVisibilityState_(VisibilityState.HIDDEN); visibilityInterface = new MockVisibilityInterface(0.5); - root = - new VisibilityManagerForMApp(ampdoc, visibilityInterface); + root = new VisibilityManagerForMApp(ampdoc, visibilityInterface); expect(root.parent).to.be.null; expect(root.ampdoc).to.equal(ampdoc); expect(root.getStartTime()).to.equal(viewer.getFirstVisibleTime()); @@ -111,8 +108,7 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { it('should switch root model to no-visibility on dispose', () => { visibilityInterface = new MockVisibilityInterface(1); - root = - new VisibilityManagerForMApp(ampdoc, visibilityInterface); + root = new VisibilityManagerForMApp(ampdoc, visibilityInterface); expect(root.getRootVisibility()).to.equal(1); root.dispose(); expect(root.getRootVisibility()).to.equal(0); @@ -121,15 +117,21 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { it('should not support listen on element', () => { const target = win.document.createElement('div'); //root.listenElement(target, {}, null, null, () => {}); - allowConsoleError(() => { expect(() => { - root.listenElement(target, {}, null, null, () => {}); - }).to.throw(/element level visibility not supported/); }); - allowConsoleError(() => { expect(() => { - root.observe(); - }).to.throw(/element level visibility not supported/); }); - allowConsoleError(() => { expect(() => { - root.getElementVisibility(); - }).to.throw(/element level visibility not supported/); }); + allowConsoleError(() => { + expect(() => { + root.listenElement(target, {}, null, null, () => {}); + }).to.throw(/element level visibility not supported/); + }); + allowConsoleError(() => { + expect(() => { + root.observe(); + }).to.throw(/element level visibility not supported/); + }); + allowConsoleError(() => { + expect(() => { + root.getElementVisibility(); + }).to.throw(/element level visibility not supported/); + }); }); it('should protect from invalid intersection values', () => { @@ -179,7 +181,9 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { // Back to visible range visibilityInterface.fireVisibilityChangeForTesting( - 0.3, layoutRectLtwh(1, 2, 100, 201)); + 0.3, + layoutRectLtwh(1, 2, 100, 201) + ); clock.tick(3); return eventPromise.then(state => { expect(disposed).to.be.calledOnce; diff --git a/extensions/amp-analytics/0.1/test/test-visibility-manager.js b/extensions/amp-analytics/0.1/test/test-visibility-manager.js index 33476c107dc3..5422288dddd2 100644 --- a/extensions/amp-analytics/0.1/test/test-visibility-manager.js +++ b/extensions/amp-analytics/0.1/test/test-visibility-manager.js @@ -27,7 +27,6 @@ import {VisibilityState} from '../../../../src/visibility-state'; import {layoutRectLtwh, rectIntersection} from '../../../../src/layout-rect'; class IntersectionObserverStub { - constructor(callback, options) { this.callback = callback; this.options = options; @@ -63,7 +62,6 @@ class IntersectionObserverStub { } } - describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { let win; let ampdoc; @@ -82,8 +80,7 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { viewer = win.services.viewer.obj; sandbox.stub(viewer, 'getFirstVisibleTime').callsFake(() => 1); viewport = win.services.viewport.obj; - startVisibilityHandlerCount = - viewer.visibilityObservable_.getHandlerCount(); + startVisibilityHandlerCount = viewer.visibilityObservable_.getHandlerCount(); root = new VisibilityManagerForDoc(ampdoc); @@ -166,8 +163,9 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { }); it('should switch visibility based on viewer for main doc', () => { - expect(viewer.visibilityObservable_.getHandlerCount()) - .equal(startVisibilityHandlerCount + 1); + expect(viewer.visibilityObservable_.getHandlerCount()).equal( + startVisibilityHandlerCount + 1 + ); expect(root.getRootVisibility()).to.equal(1); // Go prerender. @@ -198,11 +196,13 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { // Unrelated event. const otherTarget = win.document.createElement('div'); - inOb.callback([{ - target: otherTarget, - intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); + inOb.callback([ + { + target: otherTarget, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(root.getRootVisibility()).to.equal(0); // Move to the viewport. @@ -310,7 +310,12 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { root.listenRoot(spec, null, null, modelsCalled); root.listenElement(otherTarget, spec, null, null, modelsCalled); root.listenElement( - otherTarget, {totalTimeMin: 20}, null, null, modelsCalled); + otherTarget, + {totalTimeMin: 20}, + null, + null, + modelsCalled + ); expect(root.models_).to.have.length(3); root.models_.forEach(model => { model.unsubscribe(modelsDisposed); @@ -332,8 +337,9 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { expect(otherUnsubscribes.callCount).to.equal(2); // Viewer and viewport have been unsubscribed. - expect(viewer.visibilityObservable_.getHandlerCount()) - .equal(startVisibilityHandlerCount); + expect(viewer.visibilityObservable_.getHandlerCount()).equal( + startVisibilityHandlerCount + ); // Intersection observer disconnected. expect(inOb.disconnected).to.be.true; @@ -350,16 +356,20 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { // Check observer is correctly set. const inOb = root.getIntersectionObserver_(); expect(inOb).to.be.instanceOf(IntersectionObserverPolyfill); - expect(viewport.scrollObservable_.getHandlerCount()) - .to.equal(startScrollCount + 1); - expect(viewport.changeObservable_.getHandlerCount()) - .to.equal(startChangeCount + 1); + expect(viewport.scrollObservable_.getHandlerCount()).to.equal( + startScrollCount + 1 + ); + expect(viewport.changeObservable_.getHandlerCount()).to.equal( + startChangeCount + 1 + ); root.dispose(); - expect(viewport.scrollObservable_.getHandlerCount()) - .to.equal(startScrollCount); - expect(viewport.changeObservable_.getHandlerCount()) - .to.equal(startChangeCount); + expect(viewport.scrollObservable_.getHandlerCount()).to.equal( + startScrollCount + ); + expect(viewport.changeObservable_.getHandlerCount()).to.equal( + startChangeCount + ); }); it('should support polyfill on non-amp root element', () => { @@ -389,8 +399,12 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { } return null; }); - expect(rootElement.getLayoutBox()) - .to.contain({left: 0, top: 50, width: 100, height: 100}); + expect(rootElement.getLayoutBox()).to.contain({ + left: 0, + top: 50, + width: 100, + height: 100, + }); viewport.scrollObservable_.fire({type: 'scroll'}); expect(model.getVisibility_()).to.equal(0.5); @@ -404,8 +418,9 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { const disposed = sandbox.spy(); const spec = {totalTimeMin: 10}; root.listenRoot(spec, null, null, eventResolver); - sandbox.stub(root, 'getRootLayoutBox').callsFake( - () => layoutRectLtwh(11, 21, 101, 201)); + sandbox + .stub(root, 'getRootLayoutBox') + .callsFake(() => layoutRectLtwh(11, 21, 101, 201)); expect(root.models_).to.have.length(1); const model = root.models_[0]; @@ -481,9 +496,14 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { const testPromise = new Promise(resolve => { testPromiseResolver = resolve; }); - root.listenRoot({}, null, () => { - return testPromise; - }, eventResolver); + root.listenRoot( + {}, + null, + () => { + return testPromise; + }, + eventResolver + ); expect(root.models_).to.have.length(1); const model = root.models_[0]; model.unsubscribe(disposed); @@ -533,11 +553,13 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { expect(model.getVisibility_()).to.equal(0); // In viewport. - inOb.callback([{ - target, - intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); + inOb.callback([ + { + target, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(0.3); // Go invisible on root. @@ -575,26 +597,51 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { expect(model.getVisibility_()).to.equal(0); // Valid value. - inOb.callback([{target, intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(0.3); // Invalid negative value. - inOb.callback([{target, intersectionRatio: -0.01, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: -0.01, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(0); - inOb.callback([{target, intersectionRatio: -1000, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: -1000, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(0); // Invalid overflow value. - inOb.callback([{target, intersectionRatio: 1.01, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: 1.01, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(1); - inOb.callback([{target, intersectionRatio: 1000, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: 1000, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(1); }); @@ -618,11 +665,13 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { expect(inOb.elements).to.contain(target); // In viewport. - inOb.callback([{ - target, - intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); + inOb.callback([ + { + target, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model1.getVisibility_()).to.equal(0.3); expect(trackedElement.intersectionRatio).to.equal(0.3); @@ -646,29 +695,32 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { sandbox.stub(model1, 'reset_'); // Fire the first event. clock.tick(11); - return eventPromise.then(state => { - // First event fired. The first model should be cleaned up, but not - // the other. - expect(state.totalVisibleTime).to.equal(10); - expect(disposed1).to.be.calledOnce; - expect(root.models_).to.have.length(1); - expect(root.trackedElements_[target.__AMP_VIS_ID]) - .to.equal(trackedElement); - expect(trackedElement.listeners).to.have.length(1); - expect(inOb.elements).to.contain(target); - - // Fire the second event. - clock.tick(10); - return eventPromise2; - }).then(state => { - // Second event fired. Everything should be released now. - expect(state.totalVisibleTime).to.equal(20); - expect(disposed2).to.be.calledOnce; - expect(root.models_).to.have.length(0); - expect(root.trackedElements_[target.__AMP_VIS_ID]).to.not.exist; - expect(trackedElement.listeners).to.have.length(0); - expect(inOb.elements).to.not.contain(target); - }); + return eventPromise + .then(state => { + // First event fired. The first model should be cleaned up, but not + // the other. + expect(state.totalVisibleTime).to.equal(10); + expect(disposed1).to.be.calledOnce; + expect(root.models_).to.have.length(1); + expect(root.trackedElements_[target.__AMP_VIS_ID]).to.equal( + trackedElement + ); + expect(trackedElement.listeners).to.have.length(1); + expect(inOb.elements).to.contain(target); + + // Fire the second event. + clock.tick(10); + return eventPromise2; + }) + .then(state => { + // Second event fired. Everything should be released now. + expect(state.totalVisibleTime).to.equal(20); + expect(disposed2).to.be.calledOnce; + expect(root.models_).to.have.length(0); + expect(root.trackedElements_[target.__AMP_VIS_ID]).to.not.exist; + expect(trackedElement.listeners).to.have.length(0); + expect(inOb.elements).to.not.contain(target); + }); }); it('should listen on a resource', () => { @@ -680,17 +732,20 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { }, }; const resources = win.services.resources.obj; - sandbox.stub(resources, 'getResourceForElementOptional').callsFake( - () => resource); + sandbox + .stub(resources, 'getResourceForElementOptional') + .callsFake(() => resource); const spec = {totalTimeMin: 10}; root.listenElement(target, spec, null, null, eventResolver); const inOb = root.getIntersectionObserver_(); - inOb.callback([{ - target, - intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); + inOb.callback([ + { + target, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(root.models_).to.have.length(1); const model = root.models_[0]; @@ -708,227 +763,239 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { }); }); +describes.realWin( + 'EmbedAnalyticsRoot', + { + amp: {ampdoc: 'fie'}, + }, + env => { + let parentWin; + let win; + let ampdoc; + let embed; + let clock; + let viewer; + let viewport; + let parentRoot; + let root; + let inob; + + beforeEach(() => { + parentWin = env.parentWin; + win = env.win; + ampdoc = env.ampdoc; + embed = env.embed; + embed.host = ampdoc.win.document.createElement('amp-host'); + clock = sandbox.useFakeTimers(); + clock.tick(1); -describes.realWin('EmbedAnalyticsRoot', { - amp: {ampdoc: 'fie'}, -}, env => { - let parentWin; - let win; - let ampdoc; - let embed; - let clock; - let viewer; - let viewport; - let parentRoot; - let root; - let inob; - - beforeEach(() => { - parentWin = env.parentWin; - win = env.win; - ampdoc = env.ampdoc; - embed = env.embed; - embed.host = ampdoc.win.document.createElement('amp-host'); - clock = sandbox.useFakeTimers(); - clock.tick(1); - - viewport = parentWin.services.viewport.obj; - viewer = parentWin.services.viewer.obj; - sandbox.stub(viewer, 'getFirstVisibleTime').callsFake(() => 1); - - parentRoot = new VisibilityManagerForDoc(ampdoc); - parentWin.IntersectionObserver = IntersectionObserverStub; - parentWin.IntersectionObserverEntry = function() {}; - parentWin.IntersectionObserverEntry.prototype.intersectionRatio = 1; - - root = new VisibilityManagerForEmbed(parentRoot, embed); - inob = parentRoot.getIntersectionObserver_(); - }); - - it('should dispose with parent', () => { - const unsubscribeSpy = sandbox.spy(); - root.unsubscribe(unsubscribeSpy); - - expect(parentRoot.children_).to.have.length(1); - expect(parentRoot.children_[0]).to.equal(root); - - parentRoot.dispose(); - expect(parentRoot.children_).to.have.length(0); - expect(unsubscribeSpy).to.be.calledOnce; - }); - - it('should remove from parent when disposed', () => { - const unsubscribeSpy = sandbox.spy(); - root.unsubscribe(unsubscribeSpy); + viewport = parentWin.services.viewport.obj; + viewer = parentWin.services.viewer.obj; + sandbox.stub(viewer, 'getFirstVisibleTime').callsFake(() => 1); - expect(parentRoot.children_).to.have.length(1); - expect(parentRoot.children_[0]).to.equal(root); + parentRoot = new VisibilityManagerForDoc(ampdoc); + parentWin.IntersectionObserver = IntersectionObserverStub; + parentWin.IntersectionObserverEntry = function() {}; + parentWin.IntersectionObserverEntry.prototype.intersectionRatio = 1; - root.dispose(); - expect(parentRoot.children_).to.have.length(0); - expect(unsubscribeSpy).to.be.calledOnce; - }); + root = new VisibilityManagerForEmbed(parentRoot, embed); + inob = parentRoot.getIntersectionObserver_(); + }); - it('should initialize correctly backgrounded', () => { - viewer.setVisibilityState_(VisibilityState.HIDDEN); - root = new VisibilityManagerForEmbed(parentRoot, embed); + it('should dispose with parent', () => { + const unsubscribeSpy = sandbox.spy(); + root.unsubscribe(unsubscribeSpy); - expect(root.parent).to.equal(parentRoot); - expect(root.ampdoc).to.equal(ampdoc); - expect(root.getStartTime()).to.equal(embed.getStartTime()); - expect(root.isBackgrounded()).to.be.true; - expect(root.isBackgroundedAtStart()).to.be.true; + expect(parentRoot.children_).to.have.length(1); + expect(parentRoot.children_[0]).to.equal(root); - // Root model starts invisible. - expect(root.getRootVisibility()).to.equal(0); - }); + parentRoot.dispose(); + expect(parentRoot.children_).to.have.length(0); + expect(unsubscribeSpy).to.be.calledOnce; + }); - it('should initialize correctly foregrounded', () => { - expect(root.parent).to.equal(parentRoot); - expect(root.ampdoc).to.equal(ampdoc); - expect(root.getStartTime()).to.equal(embed.getStartTime()); - expect(root.isBackgrounded()).to.be.false; - expect(root.isBackgroundedAtStart()).to.be.false; + it('should remove from parent when disposed', () => { + const unsubscribeSpy = sandbox.spy(); + root.unsubscribe(unsubscribeSpy); - // Root model starts invisible. - root.setRootVisibility(1); - expect(root.getRootVisibility()).to.equal(1); - }); + expect(parentRoot.children_).to.have.length(1); + expect(parentRoot.children_[0]).to.equal(root); - it('should resolve root layout box', () => { - sandbox.stub(viewport, 'getLayoutRect').callsFake(element => { - if (element == embed.iframe) { - return layoutRectLtwh(11, 21, 101, 201); - } - return null; - }); - expect(root.getRootLayoutBox()).to.contain({ - left: 11, - top: 21, - width: 101, - height: 201, + root.dispose(); + expect(parentRoot.children_).to.have.length(0); + expect(unsubscribeSpy).to.be.calledOnce; }); - }); - - it('should ask parent to observe host element', () => { - const id = embed.host.__AMP_VIS_ID; - expect(parentRoot.trackedElements_[id]).to.be.ok; - root.dispose(); - expect(parentRoot.trackedElements_[id]).to.be.undefined; - }); + it('should initialize correctly backgrounded', () => { + viewer.setVisibilityState_(VisibilityState.HIDDEN); + root = new VisibilityManagerForEmbed(parentRoot, embed); - it('should delegate observation to parent', () => { - const inOb = { - observe: sandbox.spy(), - unobserve: sandbox.spy(), - }; - parentRoot.intersectionObserver_ = inOb; + expect(root.parent).to.equal(parentRoot); + expect(root.ampdoc).to.equal(ampdoc); + expect(root.getStartTime()).to.equal(embed.getStartTime()); + expect(root.isBackgrounded()).to.be.true; + expect(root.isBackgroundedAtStart()).to.be.true; - const listener = sandbox.spy(); - const target = win.document.createElement('div'); + // Root model starts invisible. + expect(root.getRootVisibility()).to.equal(0); + }); - // Observe. - const unlisten = root.observe(target, listener); - expect(inOb.observe).to.be.calledOnce; - expect(inOb.observe).to.be.calledWith(target); - const id = target.__AMP_VIS_ID; - expect(parentRoot.trackedElements_[id]).to.be.ok; + it('should initialize correctly foregrounded', () => { + expect(root.parent).to.equal(parentRoot); + expect(root.ampdoc).to.equal(ampdoc); + expect(root.getStartTime()).to.equal(embed.getStartTime()); + expect(root.isBackgrounded()).to.be.false; + expect(root.isBackgroundedAtStart()).to.be.false; - // Unobserve. - unlisten(); - expect(inOb.unobserve).to.be.calledOnce; - expect(inOb.unobserve).to.be.calledWith(target); - expect(parentRoot.trackedElements_[id]).to.be.undefined; - }); - - it('should depend on parent for visibility', () => { - const callbackSpy = sandbox.spy(); - const otherTarget = win.document.createElement('div'); - root.listenRoot({}, null, null, callbackSpy); - expect(root.models_).to.have.length(1); - const rootModel = root.models_[0]; + // Root model starts invisible. + root.setRootVisibility(1); + expect(root.getRootVisibility()).to.equal(1); + }); - root.listenElement(otherTarget, {}, null, null, callbackSpy); - expect(root.models_).to.have.length(2); - const elementModel = root.models_[1]; + it('should resolve root layout box', () => { + sandbox.stub(viewport, 'getLayoutRect').callsFake(element => { + if (element == embed.iframe) { + return layoutRectLtwh(11, 21, 101, 201); + } + return null; + }); + expect(root.getRootLayoutBox()).to.contain({ + left: 11, + top: 21, + width: 101, + height: 201, + }); + }); - // Set up. - expect(inob.elements).to.contain(embed.host); - expect(inob.elements).to.contain(otherTarget); + it('should ask parent to observe host element', () => { + const id = embed.host.__AMP_VIS_ID; + expect(parentRoot.trackedElements_[id]).to.be.ok; - // Start state. - expect(parentRoot.getRootVisibility()).to.equal(1); - expect(root.getRootVisibility()).to.equal(0); - expect(rootModel.getVisibility_()).to.equal(0); - expect(elementModel.getVisibility_()).to.equal(0); - - // Make root visible. - inob.callback([{ - target: embed.host, - intersectionRatio: 0.5, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0.5); - expect(rootModel.getVisibility_()).to.equal(0.5); - expect(elementModel.getVisibility_()).to.equal(0); - - // Make element visible. - inob.callback([{ - target: otherTarget, - intersectionRatio: 0.45, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0.5); - expect(rootModel.getVisibility_()).to.equal(0.5); - expect(elementModel.getVisibility_()).to.equal(0.45); - - // Hide parent. - viewer.setVisibilityState_(VisibilityState.HIDDEN); - expect(parentRoot.getRootVisibility()).to.equal(0); - expect(root.getRootVisibility()).to.equal(0); - expect(rootModel.getVisibility_()).to.equal(0); - expect(elementModel.getVisibility_()).to.equal(0); + root.dispose(); + expect(parentRoot.trackedElements_[id]).to.be.undefined; + }); - // Show parent. - viewer.setVisibilityState_(VisibilityState.VISIBLE); - expect(parentRoot.getRootVisibility()).to.equal(1); - expect(root.getRootVisibility()).to.equal(0.5); - expect(rootModel.getVisibility_()).to.equal(0.5); - expect(elementModel.getVisibility_()).to.equal(0.45); - - // Hide root. - inob.callback([{ - target: embed.host, - intersectionRatio: 0, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0); - expect(rootModel.getVisibility_()).to.equal(0); - expect(elementModel.getVisibility_()).to.equal(0); - - // Update element. - inob.callback([{ - target: otherTarget, - intersectionRatio: 0.55, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0); - expect(rootModel.getVisibility_()).to.equal(0); - expect(elementModel.getVisibility_()).to.equal(0); - - // Show root. - inob.callback([{ - target: embed.host, - intersectionRatio: 0.7, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0.7); - expect(rootModel.getVisibility_()).to.equal(0.7); - expect(elementModel.getVisibility_()).to.equal(0.55); - }); -}); + it('should delegate observation to parent', () => { + const inOb = { + observe: sandbox.spy(), + unobserve: sandbox.spy(), + }; + parentRoot.intersectionObserver_ = inOb; + + const listener = sandbox.spy(); + const target = win.document.createElement('div'); + + // Observe. + const unlisten = root.observe(target, listener); + expect(inOb.observe).to.be.calledOnce; + expect(inOb.observe).to.be.calledWith(target); + const id = target.__AMP_VIS_ID; + expect(parentRoot.trackedElements_[id]).to.be.ok; + + // Unobserve. + unlisten(); + expect(inOb.unobserve).to.be.calledOnce; + expect(inOb.unobserve).to.be.calledWith(target); + expect(parentRoot.trackedElements_[id]).to.be.undefined; + }); + it('should depend on parent for visibility', () => { + const callbackSpy = sandbox.spy(); + const otherTarget = win.document.createElement('div'); + root.listenRoot({}, null, null, callbackSpy); + expect(root.models_).to.have.length(1); + const rootModel = root.models_[0]; + + root.listenElement(otherTarget, {}, null, null, callbackSpy); + expect(root.models_).to.have.length(2); + const elementModel = root.models_[1]; + + // Set up. + expect(inob.elements).to.contain(embed.host); + expect(inob.elements).to.contain(otherTarget); + + // Start state. + expect(parentRoot.getRootVisibility()).to.equal(1); + expect(root.getRootVisibility()).to.equal(0); + expect(rootModel.getVisibility_()).to.equal(0); + expect(elementModel.getVisibility_()).to.equal(0); + + // Make root visible. + inob.callback([ + { + target: embed.host, + intersectionRatio: 0.5, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0.5); + expect(rootModel.getVisibility_()).to.equal(0.5); + expect(elementModel.getVisibility_()).to.equal(0); + + // Make element visible. + inob.callback([ + { + target: otherTarget, + intersectionRatio: 0.45, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0.5); + expect(rootModel.getVisibility_()).to.equal(0.5); + expect(elementModel.getVisibility_()).to.equal(0.45); + + // Hide parent. + viewer.setVisibilityState_(VisibilityState.HIDDEN); + expect(parentRoot.getRootVisibility()).to.equal(0); + expect(root.getRootVisibility()).to.equal(0); + expect(rootModel.getVisibility_()).to.equal(0); + expect(elementModel.getVisibility_()).to.equal(0); + + // Show parent. + viewer.setVisibilityState_(VisibilityState.VISIBLE); + expect(parentRoot.getRootVisibility()).to.equal(1); + expect(root.getRootVisibility()).to.equal(0.5); + expect(rootModel.getVisibility_()).to.equal(0.5); + expect(elementModel.getVisibility_()).to.equal(0.45); + + // Hide root. + inob.callback([ + { + target: embed.host, + intersectionRatio: 0, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0); + expect(rootModel.getVisibility_()).to.equal(0); + expect(elementModel.getVisibility_()).to.equal(0); + + // Update element. + inob.callback([ + { + target: otherTarget, + intersectionRatio: 0.55, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0); + expect(rootModel.getVisibility_()).to.equal(0); + expect(elementModel.getVisibility_()).to.equal(0); + + // Show root. + inob.callback([ + { + target: embed.host, + intersectionRatio: 0.7, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0.7); + expect(rootModel.getVisibility_()).to.equal(0.7); + expect(elementModel.getVisibility_()).to.equal(0.55); + }); + } +); describes.realWin('VisibilityManager integrated', {amp: true}, env => { let win, doc; @@ -1013,15 +1080,18 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { const resource = resources.getResourceForElement(ampElement); scrollTop = 10; - sandbox.stub(resource, 'getLayoutBox').callsFake( - () => layoutRectLtwh(0, scrollTop, 100, 100)); + sandbox + .stub(resource, 'getLayoutBox') + .callsFake(() => layoutRectLtwh(0, scrollTop, 100, 100)); }); }); function fireIntersect(intersectPercent) { scrollTop = 100 - intersectPercent; const entry = makeIntersectionEntry( - [0, scrollTop, 100, 100], [0, 0, 100, 100]); + [0, scrollTop, 100, 100], + [0, 0, 100, 100] + ); inObCallback([entry]); } @@ -1029,8 +1099,9 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { boundingClientRect = layoutRectLtwh.apply(null, boundingClientRect); rootBounds = layoutRectLtwh.apply(null, rootBounds); const intersect = rectIntersection(boundingClientRect, rootBounds); - const ratio = (intersect.width * intersect.height) / - (boundingClientRect.width * boundingClientRect.height); + const ratio = + (intersect.width * intersect.height) / + (boundingClientRect.width * boundingClientRect.height); return { intersectionRect: intersect, boundingClientRect, @@ -1048,48 +1119,63 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, {}, readyPromise, () => { - return readyReportPromise; - }, eventResolver); + visibility.listenElement( + ampElement, + {}, + readyPromise, + () => { + return readyReportPromise; + }, + eventResolver + ); - return Promise.resolve().then(() => { - clock.tick(100); - fireIntersect(25); // visible - readyResolver(); - }).then(() => { - clock.tick(5); - readyReportResolver(); - return eventPromise; - }).then(state => { - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 75, - firstSeenTime: 100, - lastSeenTime: 105, - lastVisibleTime: 105, - loadTimeVisibility: 25, - maxVisiblePercentage: 25, - minVisiblePercentage: 25, - opacity: 0.5, - totalVisibleTime: 5, - maxContinuousVisibleTime: 5, - intersectionRatio: 0.25, - intersectionRect: '{"left":0,"top":75,"width":100,"height":25,' + - '"bottom":100,"right":100,"x":0,"y":75}', + return Promise.resolve() + .then(() => { + clock.tick(100); + fireIntersect(25); // visible + readyResolver(); + }) + .then(() => { + clock.tick(5); + readyReportResolver(); + return eventPromise; + }) + .then(state => { + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 75, + firstSeenTime: 100, + lastSeenTime: 105, + lastVisibleTime: 105, + loadTimeVisibility: 25, + maxVisiblePercentage: 25, + minVisiblePercentage: 25, + opacity: 0.5, + totalVisibleTime: 5, + maxContinuousVisibleTime: 5, + intersectionRatio: 0.25, + intersectionRect: + '{"left":0,"top":75,"width":100,"height":25,' + + '"bottom":100,"right":100,"x":0,"y":75}', + }); }); - }); }); - it('should wait for readyPromise with readyReportPromise', async() => { + it('should wait for readyPromise with readyReportPromise', async () => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, {}, readyPromise, () => - readyReportPromise, eventResolver); + visibility.listenElement( + ampElement, + {}, + readyPromise, + () => readyReportPromise, + eventResolver + ); const model = visibility.models_[0]; await Promise.resolve(); @@ -1131,12 +1217,17 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { }); }); - it('should wait for readyReportPromise with reportWhen', async() => { + it('should wait for readyReportPromise with reportWhen', async () => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, {reportWhen: 'documentExit'}, - readyPromise, () => readyReportPromise, eventResolver); + visibility.listenElement( + ampElement, + {reportWhen: 'documentExit'}, + readyPromise, + () => readyReportPromise, + eventResolver + ); const model = visibility.models_[0]; await Promise.resolve(); @@ -1175,83 +1266,143 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { }); }); - it('should wait for readyReportPromise with reportWhen and never meets ' + - 'visiblePercentageMin', async() => { - viewer.setVisibilityState_(VisibilityState.VISIBLE); - visibility = new VisibilityManagerForDoc(ampdoc); + it( + 'should wait for readyReportPromise with reportWhen and never meets ' + + 'visiblePercentageMin', + async () => { + viewer.setVisibilityState_(VisibilityState.VISIBLE); + visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, { - reportWhen: 'documentExit', - visiblePercentageMin: 50, - }, - readyPromise, () => readyReportPromise, eventResolver); - const model = visibility.models_[0]; + visibility.listenElement( + ampElement, + { + reportWhen: 'documentExit', + visiblePercentageMin: 50, + }, + readyPromise, + () => readyReportPromise, + eventResolver + ); + const model = visibility.models_[0]; - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; - clock.tick(20); - fireIntersect(25); // Doesn't meet visiblePercentageMin. - clock.tick(30); + clock.tick(20); + fireIntersect(25); // Doesn't meet visiblePercentageMin. + clock.tick(30); - readyResolver(); - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; + readyResolver(); + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; - clock.tick(40); + clock.tick(40); - readyReportResolver(); - await Promise.resolve(); - expect(isModelResolved(model)).to.be.true; + readyReportResolver(); + await Promise.resolve(); + expect(isModelResolved(model)).to.be.true; - const state = await eventPromise; - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 75, - firstSeenTime: 50, - lastSeenTime: 90, - lastVisibleTime: 0, // Didn't meet visibility. - loadTimeVisibility: 25, - // FIXME: max/minVisiblePercentage should equal loadTimeVisibility. - // See https://github.com/ampproject/amphtml/issues/19567 - maxVisiblePercentage: 0, - minVisiblePercentage: 0, - totalVisibleTime: 0, - maxContinuousVisibleTime: 0, - }); - }); + const state = await eventPromise; + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 75, + firstSeenTime: 50, + lastSeenTime: 90, + lastVisibleTime: 0, // Didn't meet visibility. + loadTimeVisibility: 25, + // FIXME: max/minVisiblePercentage should equal loadTimeVisibility. + // See https://github.com/ampproject/amphtml/issues/19567 + maxVisiblePercentage: 0, + minVisiblePercentage: 0, + totalVisibleTime: 0, + maxContinuousVisibleTime: 0, + }); + } + ); + + it( + 'should accumulate timings and wait for readyReportPromise with ' + + 'reportWhen and high minTotalVisibleTime', + async () => { + viewer.setVisibilityState_(VisibilityState.VISIBLE); + visibility = new VisibilityManagerForDoc(ampdoc); + + visibility.listenElement( + ampElement, + { + reportWhen: 'documentExit', + minTotalVisibleTime: 100000, // Never met + }, + readyPromise, + () => readyReportPromise, + eventResolver + ); + const model = visibility.models_[0]; + + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; + + readyResolver(); + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; + + clock.tick(20); + fireIntersect(25); // visible + clock.tick(20); + fireIntersect(0); // hidden + clock.tick(20); + fireIntersect(35); // visible again + clock.tick(30); + fireIntersect(0); // hidden + clock.tick(20); + + readyReportResolver(); + await Promise.resolve(); + expect(isModelResolved(model)).to.be.true; + + const state = await eventPromise; + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 100, + firstSeenTime: 20, + lastSeenTime: 60, + lastVisibleTime: 90, + loadTimeVisibility: 25, + maxVisiblePercentage: 35, + minVisiblePercentage: 25, + totalVisibleTime: 50, + maxContinuousVisibleTime: 30, + }); + } + ); - it('should accumulate timings and wait for readyReportPromise with ' + - 'reportWhen and high minTotalVisibleTime', async() => { + it('should wait for readyReportPromise when missing readyPromise', async () => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, { - reportWhen: 'documentExit', - minTotalVisibleTime: 100000, // Never met - }, readyPromise, () => readyReportPromise, eventResolver); + visibility.listenElement( + ampElement, + {}, + null, + () => readyReportPromise, + eventResolver + ); const model = visibility.models_[0]; - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; - - readyResolver(); - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; - clock.tick(20); fireIntersect(25); // visible - clock.tick(20); - fireIntersect(0); // hidden - clock.tick(20); - fireIntersect(35); // visible again clock.tick(30); - fireIntersect(0); // hidden - clock.tick(20); + + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; readyReportResolver(); await Promise.resolve(); @@ -1264,75 +1415,44 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { elementHeight: 100, elementWidth: 100, elementX: 0, - elementY: 100, + elementY: 75, firstSeenTime: 20, - lastSeenTime: 60, - lastVisibleTime: 90, + lastSeenTime: 50, + lastVisibleTime: 50, loadTimeVisibility: 25, - maxVisiblePercentage: 35, + maxVisiblePercentage: 25, minVisiblePercentage: 25, - totalVisibleTime: 50, + totalVisibleTime: 30, maxContinuousVisibleTime: 30, }); }); - it('should wait for readyReportPromise when missing readyPromise', - async() => { - viewer.setVisibilityState_(VisibilityState.VISIBLE); - visibility = new VisibilityManagerForDoc(ampdoc); - - visibility.listenElement(ampElement, {}, null, () => - readyReportPromise, eventResolver); - const model = visibility.models_[0]; - - clock.tick(20); - fireIntersect(25); // visible - clock.tick(30); - - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; - - readyReportResolver(); - await Promise.resolve(); - expect(isModelResolved(model)).to.be.true; - - const state = await eventPromise; - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 75, - firstSeenTime: 20, - lastSeenTime: 50, - lastVisibleTime: 50, - loadTimeVisibility: 25, - maxVisiblePercentage: 25, - minVisiblePercentage: 25, - totalVisibleTime: 30, - maxContinuousVisibleTime: 30, - }); - }); - it('should execute "visible" trigger with percent range', () => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); const spy = sandbox.spy(); - visibility.listenElement(ampElement, { - 'visiblePercentageThresholds': [[0, 30], [50, 100]], - }, Promise.resolve(), null, spy); + visibility.listenElement( + ampElement, + { + 'visiblePercentageThresholds': [[0, 30], [50, 100]], + }, + Promise.resolve(), + null, + spy + ); - return Promise.resolve().then(() => { - fireIntersect(25); // visible - }).then(() => { - expect(spy).to.be.calledOnce; - fireIntersect(55); - return Promise.resolve().then(() => { - expect(spy).to.be.calledTwice; + return Promise.resolve() + .then(() => { + fireIntersect(25); // visible + }) + .then(() => { + expect(spy).to.be.calledOnce; + fireIntersect(55); + return Promise.resolve().then(() => { + expect(spy).to.be.calledTwice; + }); }); - }); }); it('should trigger "visible" with no duration condition', () => { @@ -1340,74 +1460,80 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { visibility = new VisibilityManagerForDoc(ampdoc); visibility.listenElement( - ampElement, - {visiblePercentageMin: 20}, - readyPromise, - null, - eventResolver); + ampElement, + {visiblePercentageMin: 20}, + readyPromise, + null, + eventResolver + ); // add multiple triggers on the same element visibility.listenElement( - ampElement, - {visiblePercentageMin: 30}, - readyPromise, - null, - eventResolver2); + ampElement, + {visiblePercentageMin: 30}, + readyPromise, + null, + eventResolver2 + ); // "observe" should not have been called since resource not loaded yet. expect(observeSpy).to.be.called; readyResolver(); - return Promise.resolve().then(() => { - expect(observeSpy).to.be.calledWith(ampElement); + return Promise.resolve() + .then(() => { + expect(observeSpy).to.be.calledWith(ampElement); - clock.tick(135); - fireIntersect(5); // below visiblePercentageMin, no trigger - - clock.tick(100); - fireIntersect(25); // above spec 1 min visible, trigger callback 1 - return eventPromise.then(state => { - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 75, - firstSeenTime: 135, - lastSeenTime: 235, - lastVisibleTime: 235, - loadTimeVisibility: 5, - maxVisiblePercentage: 25, - minVisiblePercentage: 25, - totalVisibleTime: 0, // duration metrics are always 0 - maxContinuousVisibleTime: 0, // as it triggers immediately - }); - expect(unobserveSpy).to.not.be.called; + clock.tick(135); + fireIntersect(5); // below visiblePercentageMin, no trigger clock.tick(100); - fireIntersect(35); // above spec 2 min visible, trigger callback 2 - return eventPromise2; - }).then(state => { - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 65, - firstSeenTime: 135, - lastSeenTime: 335, - lastVisibleTime: 335, - loadTimeVisibility: 5, - maxVisiblePercentage: 35, - minVisiblePercentage: 35, - totalVisibleTime: 0, // duration metrics is always 0 - maxContinuousVisibleTime: 0, // as it triggers immediately - }); + fireIntersect(25); // above spec 1 min visible, trigger callback 1 + return eventPromise + .then(state => { + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 75, + firstSeenTime: 135, + lastSeenTime: 235, + lastVisibleTime: 235, + loadTimeVisibility: 5, + maxVisiblePercentage: 25, + minVisiblePercentage: 25, + totalVisibleTime: 0, // duration metrics are always 0 + maxContinuousVisibleTime: 0, // as it triggers immediately + }); + expect(unobserveSpy).to.not.be.called; + + clock.tick(100); + fireIntersect(35); // above spec 2 min visible, trigger callback 2 + return eventPromise2; + }) + .then(state => { + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 65, + firstSeenTime: 135, + lastSeenTime: 335, + lastVisibleTime: 335, + loadTimeVisibility: 5, + maxVisiblePercentage: 35, + minVisiblePercentage: 35, + totalVisibleTime: 0, // duration metrics is always 0 + maxContinuousVisibleTime: 0, // as it triggers immediately + }); + }); + }) + .then(() => { + expect(unobserveSpy).to.be.called; // unobserve when all callback fired }); - }).then(() => { - expect(unobserveSpy).to.be.called; // unobserve when all callback fired - }); }); it('should trigger "visible" with duration condition', () => { @@ -1415,11 +1541,12 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { visibility = new VisibilityManagerForDoc(ampdoc); visibility.listenElement( - ampElement, - {continuousTimeMin: 1000}, - readyPromise, - null, - eventResolver); + ampElement, + {continuousTimeMin: 1000}, + readyPromise, + null, + eventResolver + ); const model = visibility.models_[0]; readyResolver(); @@ -1480,12 +1607,7 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { viewer.setVisibilityState_(VisibilityState.HIDDEN); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement( - ampElement, - {}, - readyPromise, - null, - eventResolver); + visibility.listenElement(ampElement, {}, readyPromise, null, eventResolver); viewer.setVisibilityState_(VisibilityState.VISIBLE); readyResolver(); @@ -1504,7 +1626,6 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { }); }); - describes.fakeWin('scroll depth', {amp: true}, env => { let ampdoc; let root; diff --git a/extensions/amp-analytics/0.1/test/test-visibility-model.js b/extensions/amp-analytics/0.1/test/test-visibility-model.js index 1f32a69930c3..c45de9ba6ef8 100644 --- a/extensions/amp-analytics/0.1/test/test-visibility-model.js +++ b/extensions/amp-analytics/0.1/test/test-visibility-model.js @@ -19,12 +19,11 @@ import {VisibilityModel} from '../visibility-model'; const NO_SPEC = {}; const NO_CALC = () => 0; - describes.sandboxed('VisibilityModel', {}, () => { let startTime; let clock; - const tick = async(timeout = 0) => { + const tick = async (timeout = 0) => { // Wait for the micro-task queue to clear since we need to wait for // internal promises to finish before running assertions; await Promise.resolve(); @@ -37,9 +36,7 @@ describes.sandboxed('VisibilityModel', {}, () => { clock.tick(startTime + 1); }); - describe('config', () => { - function config(spec) { return new VisibilityModel(spec, NO_CALC).spec_; } @@ -53,110 +50,106 @@ describes.sandboxed('VisibilityModel', {}, () => { it('should parse visiblePercentageMin', () => { expect(config({}).visiblePercentageMin).to.equal(0); - expect(config({visiblePercentageMin: ''}).visiblePercentageMin) - .to.equal(0); - expect(config({visiblePercentageMin: 0}).visiblePercentageMin) - .to.equal(0); - expect(config({visiblePercentageMin: '0'}).visiblePercentageMin) - .to.equal(0); - expect(config({visiblePercentageMin: 50}).visiblePercentageMin) - .to.equal(0.5); - expect(config({visiblePercentageMin: '50'}).visiblePercentageMin) - .to.equal(0.5); - expect(config({visiblePercentageMin: 100}).visiblePercentageMin) - .to.equal(1); - expect(config({visiblePercentageMin: '100'}).visiblePercentageMin) - .to.equal(1); + expect(config({visiblePercentageMin: ''}).visiblePercentageMin).to.equal( + 0 + ); + expect(config({visiblePercentageMin: 0}).visiblePercentageMin).to.equal( + 0 + ); + expect(config({visiblePercentageMin: '0'}).visiblePercentageMin).to.equal( + 0 + ); + expect(config({visiblePercentageMin: 50}).visiblePercentageMin).to.equal( + 0.5 + ); + expect( + config({visiblePercentageMin: '50'}).visiblePercentageMin + ).to.equal(0.5); + expect(config({visiblePercentageMin: 100}).visiblePercentageMin).to.equal( + 1 + ); + expect( + config({visiblePercentageMin: '100'}).visiblePercentageMin + ).to.equal(1); }); it('should parse visiblePercentageMax', () => { expect(config({}).visiblePercentageMax).to.equal(1); - expect(config({visiblePercentageMax: ''}).visiblePercentageMax) - .to.equal(1); - expect(config({visiblePercentageMax: 0}).visiblePercentageMax) - .to.equal(0); - expect(config({visiblePercentageMax: '0'}).visiblePercentageMax) - .to.equal(0); - expect(config({visiblePercentageMax: 50}).visiblePercentageMax) - .to.equal(0.5); - expect(config({visiblePercentageMax: '50'}).visiblePercentageMax) - .to.equal(0.5); - expect(config({visiblePercentageMax: 100}).visiblePercentageMax) - .to.equal(1); - expect(config({visiblePercentageMax: '100'}).visiblePercentageMax) - .to.equal(1); + expect(config({visiblePercentageMax: ''}).visiblePercentageMax).to.equal( + 1 + ); + expect(config({visiblePercentageMax: 0}).visiblePercentageMax).to.equal( + 0 + ); + expect(config({visiblePercentageMax: '0'}).visiblePercentageMax).to.equal( + 0 + ); + expect(config({visiblePercentageMax: 50}).visiblePercentageMax).to.equal( + 0.5 + ); + expect( + config({visiblePercentageMax: '50'}).visiblePercentageMax + ).to.equal(0.5); + expect(config({visiblePercentageMax: 100}).visiblePercentageMax).to.equal( + 1 + ); + expect( + config({visiblePercentageMax: '100'}).visiblePercentageMax + ).to.equal(1); }); it('should parse totalTimeMin', () => { expect(config({}).totalTimeMin).to.equal(0); - expect(config({totalTimeMin: ''}).totalTimeMin) - .to.equal(0); - expect(config({totalTimeMin: 0}).totalTimeMin) - .to.equal(0); - expect(config({totalTimeMin: '0'}).totalTimeMin) - .to.equal(0); - expect(config({totalTimeMin: 50}).totalTimeMin) - .to.equal(50); - expect(config({totalTimeMin: '50'}).totalTimeMin) - .to.equal(50); - expect(config({totalTimeMin: 100}).totalTimeMin) - .to.equal(100); - expect(config({totalTimeMin: '100'}).totalTimeMin) - .to.equal(100); + expect(config({totalTimeMin: ''}).totalTimeMin).to.equal(0); + expect(config({totalTimeMin: 0}).totalTimeMin).to.equal(0); + expect(config({totalTimeMin: '0'}).totalTimeMin).to.equal(0); + expect(config({totalTimeMin: 50}).totalTimeMin).to.equal(50); + expect(config({totalTimeMin: '50'}).totalTimeMin).to.equal(50); + expect(config({totalTimeMin: 100}).totalTimeMin).to.equal(100); + expect(config({totalTimeMin: '100'}).totalTimeMin).to.equal(100); }); it('should parse totalTimeMax', () => { expect(config({}).totalTimeMax).to.equal(Infinity); - expect(config({totalTimeMax: ''}).totalTimeMax) - .to.equal(Infinity); - expect(config({totalTimeMax: 0}).totalTimeMax) - .to.equal(Infinity); - expect(config({totalTimeMax: '0'}).totalTimeMax) - .to.equal(Infinity); - expect(config({totalTimeMax: 50}).totalTimeMax) - .to.equal(50); - expect(config({totalTimeMax: '50'}).totalTimeMax) - .to.equal(50); - expect(config({totalTimeMax: 100}).totalTimeMax) - .to.equal(100); - expect(config({totalTimeMax: '100'}).totalTimeMax) - .to.equal(100); + expect(config({totalTimeMax: ''}).totalTimeMax).to.equal(Infinity); + expect(config({totalTimeMax: 0}).totalTimeMax).to.equal(Infinity); + expect(config({totalTimeMax: '0'}).totalTimeMax).to.equal(Infinity); + expect(config({totalTimeMax: 50}).totalTimeMax).to.equal(50); + expect(config({totalTimeMax: '50'}).totalTimeMax).to.equal(50); + expect(config({totalTimeMax: 100}).totalTimeMax).to.equal(100); + expect(config({totalTimeMax: '100'}).totalTimeMax).to.equal(100); }); it('should parse continuousTimeMin', () => { expect(config({}).continuousTimeMin).to.equal(0); - expect(config({continuousTimeMin: ''}).continuousTimeMin) - .to.equal(0); - expect(config({continuousTimeMin: 0}).continuousTimeMin) - .to.equal(0); - expect(config({continuousTimeMin: '0'}).continuousTimeMin) - .to.equal(0); - expect(config({continuousTimeMin: 50}).continuousTimeMin) - .to.equal(50); - expect(config({continuousTimeMin: '50'}).continuousTimeMin) - .to.equal(50); - expect(config({continuousTimeMin: 100}).continuousTimeMin) - .to.equal(100); - expect(config({continuousTimeMin: '100'}).continuousTimeMin) - .to.equal(100); + expect(config({continuousTimeMin: ''}).continuousTimeMin).to.equal(0); + expect(config({continuousTimeMin: 0}).continuousTimeMin).to.equal(0); + expect(config({continuousTimeMin: '0'}).continuousTimeMin).to.equal(0); + expect(config({continuousTimeMin: 50}).continuousTimeMin).to.equal(50); + expect(config({continuousTimeMin: '50'}).continuousTimeMin).to.equal(50); + expect(config({continuousTimeMin: 100}).continuousTimeMin).to.equal(100); + expect(config({continuousTimeMin: '100'}).continuousTimeMin).to.equal( + 100 + ); }); it('should parse continuousTimeMax', () => { expect(config({}).continuousTimeMax).to.equal(Infinity); - expect(config({continuousTimeMax: ''}).continuousTimeMax) - .to.equal(Infinity); - expect(config({continuousTimeMax: 0}).continuousTimeMax) - .to.equal(Infinity); - expect(config({continuousTimeMax: '0'}).continuousTimeMax) - .to.equal(Infinity); - expect(config({continuousTimeMax: 50}).continuousTimeMax) - .to.equal(50); - expect(config({continuousTimeMax: '50'}).continuousTimeMax) - .to.equal(50); - expect(config({continuousTimeMax: 100}).continuousTimeMax) - .to.equal(100); - expect(config({continuousTimeMax: '100'}).continuousTimeMax) - .to.equal(100); + expect(config({continuousTimeMax: ''}).continuousTimeMax).to.equal( + Infinity + ); + expect(config({continuousTimeMax: 0}).continuousTimeMax).to.equal( + Infinity + ); + expect(config({continuousTimeMax: '0'}).continuousTimeMax).to.equal( + Infinity + ); + expect(config({continuousTimeMax: 50}).continuousTimeMax).to.equal(50); + expect(config({continuousTimeMax: '50'}).continuousTimeMax).to.equal(50); + expect(config({continuousTimeMax: 100}).continuousTimeMax).to.equal(100); + expect(config({continuousTimeMax: '100'}).continuousTimeMax).to.equal( + 100 + ); }); it('should parse repeat', () => { @@ -350,12 +343,15 @@ describes.sandboxed('VisibilityModel', {}, () => { let visibilityValueForTesting = null; beforeEach(() => { - vh = new VisibilityModel({ - minVisiblePercentage: 25, - totalTimeMin: 10, - continuousTimeMin: 10, - continuousTimeMax: 1000, - }, NO_CALC); + vh = new VisibilityModel( + { + minVisiblePercentage: 25, + totalTimeMin: 10, + continuousTimeMin: 10, + continuousTimeMax: 1000, + }, + NO_CALC + ); updateStub = sandbox.stub(vh, 'update').callsFake(() => { if (visibilityValueForTesting) { vh.update_(visibilityValueForTesting); @@ -531,15 +527,19 @@ describes.sandboxed('VisibilityModel', {}, () => { const shouldTriggerEventTestSpecs = [ {reportWhen: 'documentExit'}, {reportWhen: 'documentHidden'}, - {reportWhen: 'documentExit', totalTimeMin: 100000, - visiblePercentageMin: 50}, + { + reportWhen: 'documentExit', + totalTimeMin: 100000, + visiblePercentageMin: 50, + }, ]; for (const i in shouldTriggerEventTestSpecs) { - it('should trigger event with reportWhen,' + - `test case #${i}`, async() => { + it(`should trigger event with reportWhen, test case #${i}`, async () => { const vh = new VisibilityModel( - shouldTriggerEventTestSpecs[i], () => 0); + shouldTriggerEventTestSpecs[i], + () => 0 + ); vh.onTriggerEvent(eventSpy); // TODO(warrengm): Inverting the two following lines will break this @@ -565,7 +565,7 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('conditions met, wait to reset', () => { - const resolveSpy = vh.eventResolver_ = sandbox.spy(); + const resolveSpy = (vh.eventResolver_ = sandbox.spy()); vh.update_(1); vh.continuousTime_ = 10; vh.totalVisibleTime_ = 10; @@ -575,8 +575,8 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('conditions not met, reset', () => { - const updateCounterSpy = vh.updateCounters_ = sandbox.spy(); - const resetSpy = vh.reset_ = sandbox.spy(); + const updateCounterSpy = (vh.updateCounters_ = sandbox.spy()); + const resetSpy = (vh.reset_ = sandbox.spy()); vh.waitToReset_ = true; vh.update_(1); expect(resetSpy).to.not.be.called; @@ -586,9 +586,7 @@ describes.sandboxed('VisibilityModel', {}, () => { }); }); - describe('tracking math', () => { - it('should register "seen" values', () => { const vh = new VisibilityModel(NO_SPEC, NO_CALC); @@ -658,10 +656,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should match custom visibility position', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 10, - visiblePercentageMax: 90, - }, NO_CALC); + const vh = new VisibilityModel( + { + visiblePercentageMin: 10, + visiblePercentageMax: 90, + }, + NO_CALC + ); vh.updateCounters_(0); expect(vh.matchesVisibility_).to.be.false; @@ -808,10 +809,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should yield based on position only', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 10, - visiblePercentageMax: 90, - }, NO_CALC); + const vh = new VisibilityModel( + { + visiblePercentageMin: 10, + visiblePercentageMax: 90, + }, + NO_CALC + ); clock.tick(100); expect(vh.updateCounters_(0)).to.be.false; expect(vh.updateCounters_(0.1)).to.be.false; @@ -821,10 +825,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should yield based on total time only', () => { - const vh = new VisibilityModel({ - totalTimeMin: 10, - totalTimeMax: 90, - }, NO_CALC); + const vh = new VisibilityModel( + { + totalTimeMin: 10, + totalTimeMax: 90, + }, + NO_CALC + ); expect(vh.updateCounters_(0.1)).to.be.false; clock.tick(5); expect(vh.updateCounters_(0)).to.be.false; @@ -837,10 +844,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should yield based on continuous time only', () => { - const vh = new VisibilityModel({ - continuousTimeMin: 10, - continuousTimeMax: 90, - }, NO_CALC); + const vh = new VisibilityModel( + { + continuousTimeMin: 10, + continuousTimeMax: 90, + }, + NO_CALC + ); expect(vh.updateCounters_(0.1)).to.be.false; clock.tick(5); expect(vh.updateCounters_(0)).to.be.false; @@ -865,11 +875,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should trigger for visibility percent only', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 49, - visiblePercentageMax: 80, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + visiblePercentageMin: 49, + visiblePercentageMax: 80, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.63; vh.update(); @@ -882,11 +895,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should only update load-time visibility once', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 49, - visiblePercentageMax: 80, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + visiblePercentageMin: 49, + visiblePercentageMax: 80, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.49; vh.update(); @@ -901,10 +917,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with totalTimeMin condition', () => { - const vh = new VisibilityModel({ - totalTimeMin: 1000, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + totalTimeMin: 1000, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.63; vh.update(); @@ -933,10 +952,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with continuousTimeMin condition', () => { - const vh = new VisibilityModel({ - continuousTimeMin: 1000, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + continuousTimeMin: 1000, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.63; vh.update(); @@ -974,11 +996,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with totalTimeMin and visiblePercentageMin', () => { - const vh = new VisibilityModel({ - totalTimeMin: 1000, - visiblePercentageMin: 10, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + totalTimeMin: 1000, + visiblePercentageMin: 10, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.05; vh.update(); @@ -1009,11 +1034,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with continuousTimeMin=1k and totalTimeMin=2k', () => { - const vh = new VisibilityModel({ - totalTimeMin: 2000, - continuousTimeMin: 1000, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + totalTimeMin: 2000, + continuousTimeMin: 1000, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.05; vh.update(); @@ -1041,11 +1069,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with continuousTimeMin=1k and visPercentageMin=50', () => { - const vh = new VisibilityModel({ - continuousTimeMin: 1000, - visiblePercentageMin: 49, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + continuousTimeMin: 1000, + visiblePercentageMin: 49, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); clock.tick(999); visibility = 0; @@ -1074,11 +1105,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire for visiblePercentageMin=visiblePercentageMax=100', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 100, - visiblePercentageMax: 100, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + visiblePercentageMin: 100, + visiblePercentageMax: 100, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.99; clock.tick(200); vh.update(); @@ -1090,12 +1124,15 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire for visiblePercentageMin=visiblePercentageMax=0', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 0, - visiblePercentageMax: 0, - repeat: true, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + visiblePercentageMin: 0, + visiblePercentageMax: 0, + repeat: true, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); sandbox.stub(vh, 'reset_').callsFake(() => { vh.eventPromise_ = new Promise(unused => { vh.eventResolver_ = eventSpy; @@ -1145,11 +1182,14 @@ describes.sandboxed('VisibilityModel', {}, () => { calcVisibility = () => visibility; }); - it('should wait for repeat interval', function* () { - const vh = new VisibilityModel({ - visiblePercentageMin: 49, - repeat: true, - }, calcVisibility); + it('should wait for repeat interval', function*() { + const vh = new VisibilityModel( + { + visiblePercentageMin: 49, + repeat: true, + }, + calcVisibility + ); const spy = sandbox.spy(); vh.onTriggerEvent(() => { spy(); @@ -1167,11 +1207,14 @@ describes.sandboxed('VisibilityModel', {}, () => { expect(spy).to.be.calledOnce; }); - it('should wait for not match to fire again w/o interval', function* () { - const vh = new VisibilityModel({ - visiblePercentageMin: 49, - repeat: true, - }, calcVisibility); + it('should wait for not match to fire again w/o interval', function*() { + const vh = new VisibilityModel( + { + visiblePercentageMin: 49, + repeat: true, + }, + calcVisibility + ); const spy = sandbox.spy(); vh.onTriggerEvent(() => { spy(); @@ -1207,9 +1250,12 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should correctly update initialScrollDepth', () => { - const vh = new VisibilityModel({ - repeat: true, - }, calcVisibility); + const vh = new VisibilityModel( + { + repeat: true, + }, + calcVisibility + ); vh.maybeSetInitialScrollDepth(200); vh.maybeSetInitialScrollDepth(100); vh.maybeSetInitialScrollDepth(400); diff --git a/extensions/amp-analytics/0.1/transport-serializer.js b/extensions/amp-analytics/0.1/transport-serializer.js index 3b1d30b0c628..08abbfd4554a 100644 --- a/extensions/amp-analytics/0.1/transport-serializer.js +++ b/extensions/amp-analytics/0.1/transport-serializer.js @@ -43,7 +43,6 @@ export let RequestDef; * @interface */ export class TransportSerializerDef { - /** * @param {string} unusedBaseUrl * @param {!BatchSegmentDef} unusedSegment @@ -67,7 +66,6 @@ export class TransportSerializerDef { * @implements {TransportSerializerDef} */ class DefaultTransportSerializer { - /** @override */ generateRequest(baseUrl, segment, withPayload = false) { if (withPayload) { @@ -87,7 +85,8 @@ class DefaultTransportSerializer { return { url: baseUrl.replace(EXTRA_URL_PARAM_VAR, ''), payload: JSON.stringify( - segments.map(segment => segment['extraUrlParams'])), + segments.map(segment => segment['extraUrlParams']) + ), }; } return { @@ -115,9 +114,9 @@ export const TransportSerializers = { */ export function defaultSerializer(baseUrl, batchSegments) { const extraUrlParamsStr = batchSegments - .map(item => serializeQueryString(item['extraUrlParams'])) - .filter(queryString => !!queryString) - .join('&'); + .map(item => serializeQueryString(item['extraUrlParams'])) + .filter(queryString => !!queryString) + .join('&'); let requestUrl; if (baseUrl.indexOf(EXTRA_URL_PARAM_VAR) >= 0) { requestUrl = baseUrl.replace(EXTRA_URL_PARAM_VAR, extraUrlParamsStr); diff --git a/extensions/amp-analytics/0.1/transport.js b/extensions/amp-analytics/0.1/transport.js index 2125df2cf123..db6bb241aaac 100644 --- a/extensions/amp-analytics/0.1/transport.js +++ b/extensions/amp-analytics/0.1/transport.js @@ -45,12 +45,11 @@ const TAG_ = 'amp-analytics/transport'; * Transport defines the ways how the analytics pings are going to be sent. */ export class Transport { - /** * @param {!Window} win * @param {!JsonObject} options */ - constructor(win, options = /** @type {!JsonObject} */({})) { + constructor(win, options = /** @type {!JsonObject} */ ({})) { /** @private {!Window} */ this.win_ = win; @@ -58,7 +57,9 @@ export class Transport { this.options_ = options; /** @private {string|undefined} */ - this.referrerPolicy_ = /** @type {string|undefined} */ (this.options_['referrerPolicy']); + this.referrerPolicy_ = /** @type {string|undefined} */ (this.options_[ + 'referrerPolicy' + ]); // no-referrer is only supported in image transport if (this.referrerPolicy_ === 'no-referrer') { @@ -111,21 +112,28 @@ export class Transport { return; } - if (this.options_['beacon'] && Transport.sendRequestUsingBeacon( - this.win_, getRequest(this.useBody_))) { + if ( + this.options_['beacon'] && + Transport.sendRequestUsingBeacon(this.win_, getRequest(this.useBody_)) + ) { return; } - if (this.options_['xhrpost'] && Transport.sendRequestUsingXhr( - this.win_, getRequest(this.useBody_))) { + if ( + this.options_['xhrpost'] && + Transport.sendRequestUsingXhr(this.win_, getRequest(this.useBody_)) + ) { return; } const image = this.options_['image']; if (image) { - const suppressWarnings = (typeof image == 'object' && - image['suppressWarnings']); + const suppressWarnings = + typeof image == 'object' && image['suppressWarnings']; Transport.sendRequestUsingImage( - this.win_, getRequest(false), suppressWarnings, - /** @type {string|undefined} */ (this.referrerPolicy_)); + this.win_, + getRequest(false), + suppressWarnings, + /** @type {string|undefined} */ (this.referrerPolicy_) + ); return; } user().warn(TAG_, 'Failed to send request', url, this.options_); @@ -152,13 +160,20 @@ export class Transport { const type = element.getAttribute('type'); // In inabox there is no amp-ad element. - const ampAdResourceId = this.isInabox_ ? '1' : user().assertString( - getAmpAdResourceId(element, getTopWindow(win)), - 'No friendly amp-ad ancestor element was found ' + - 'for amp-analytics tag with iframe transport.'); + const ampAdResourceId = this.isInabox_ + ? '1' + : user().assertString( + getAmpAdResourceId(element, getTopWindow(win)), + 'No friendly amp-ad ancestor element was found ' + + 'for amp-analytics tag with iframe transport.' + ); this.iframeTransport_ = new IframeTransport( - win, type, this.options_, ampAdResourceId); + win, + type, + this.options_, + ampAdResourceId + ); } /** @@ -190,11 +205,12 @@ export class Transport { assertHttpsUrl(request, 'amp-analytics request'); userAssert( - parseUrlDeprecated(request).origin != + parseUrlDeprecated(request).origin != parseUrlDeprecated(this.win_.location.href).origin, - 'Origin of iframe request must not be equal to the document origin.' + + 'Origin of iframe request must not be equal to the document origin.' + ' See https://github.com/ampproject/' + - ' amphtml/blob/master/spec/amp-iframe-origin-policy.md for details.'); + ' amphtml/blob/master/spec/amp-iframe-origin-policy.md for details.' + ); /** @const {!Element} */ const iframe = this.win_.document.createElement('iframe'); @@ -215,7 +231,9 @@ export class Transport { * @return {!TransportSerializerDef} */ getSerializer_() { - return /** @type {!TransportSerializerDef} */(TransportSerializers['default']); + return /** @type {!TransportSerializerDef} */ (TransportSerializers[ + 'default' + ]); } /** @@ -226,14 +244,19 @@ export class Transport { */ static sendRequestUsingImage(win, request, suppressWarnings, referrerPolicy) { const image = createPixel(win, request.url, referrerPolicy); - loadPromise(image).then(() => { - dev().fine(TAG_, 'Sent image request', request.url); - }).catch(() => { - if (!suppressWarnings) { - user().warn(TAG_, 'Response unparseable or failed to send image ' + - 'request', request.url); - } - }); + loadPromise(image) + .then(() => { + dev().fine(TAG_, 'Sent image request', request.url); + }) + .catch(() => { + if (!suppressWarnings) { + user().warn( + TAG_, + 'Response unparseable or failed to send image request', + request.url + ); + } + }); } /** diff --git a/extensions/amp-analytics/0.1/variables.js b/extensions/amp-analytics/0.1/variables.js index 46676252a935..c53d24b69da0 100644 --- a/extensions/amp-analytics/0.1/variables.js +++ b/extensions/amp-analytics/0.1/variables.js @@ -84,8 +84,6 @@ export class ExpansionOptions { } } - - /** * @param {string} str * @param {string} s @@ -95,12 +93,16 @@ export class ExpansionOptions { function substrMacro(str, s, opt_l) { const start = Number(s); let {length} = str; - userAssert(isFiniteNumber(start), - 'Start index ' + start + 'in substr macro should be a number'); + userAssert( + isFiniteNumber(start), + 'Start index ' + start + 'in substr macro should be a number' + ); if (opt_l) { length = Number(opt_l); - userAssert(isFiniteNumber(length), - 'Length ' + length + ' in substr macro should be a number'); + userAssert( + isFiniteNumber(length), + 'Length ' + length + ' in substr macro should be a number' + ); } return str.substr(start, length); @@ -135,7 +137,6 @@ function replaceMacro(string, matchPattern, opt_newSubStr) { return string.replace(regex, opt_newSubStr); } - /** * Provides support for processing of advanced variable syntax like nested * expansions macros etc. @@ -145,7 +146,6 @@ export class VariableService { * @param {!Window} window */ constructor(window) { - /** @private {!Window} */ this.win_ = window; @@ -163,13 +163,15 @@ export class VariableService { this.register_('$NOT', value => String(!value)); this.register_('$BASE64', value => base64UrlEncodeFromString(value)); this.register_('$HASH', this.hashMacro_.bind(this)); - this.register_('$IF', - (value, thenValue, elseValue) => value ? thenValue : elseValue); + this.register_('$IF', (value, thenValue, elseValue) => + value ? thenValue : elseValue + ); this.register_('$REPLACE', replaceMacro); // TODO(ccordry): Make sure this stays a window level service when this // VariableService is migrated to document level. this.register_('LINKER_PARAM', (name, id) => - this.linkerReader_.get(name, id)); + this.linkerReader_.get(name, id) + ); } /** @@ -184,8 +186,7 @@ export class VariableService { * @param {*} macro */ register_(name, macro) { - devAssert(!this.macros_[name], 'Macro "' + name - + '" already registered.'); + devAssert(!this.macros_[name], 'Macro "' + name + '" already registered.'); this.macros_[name] = macro; } @@ -207,8 +208,11 @@ export class VariableService { expandTemplateSync(template, options) { return template.replace(/\${([^}]*)}/g, (match, key) => { if (options.iterations < 0) { - user().error(TAG, 'Maximum depth reached while expanding variables. ' + - 'Please ensure that the variables are not recursive.'); + user().error( + TAG, + 'Maximum depth reached while expanding variables. ' + + 'Please ensure that the variables are not recursive.' + ); return match; } @@ -227,13 +231,18 @@ export class VariableService { let value = options.getVar(name); if (typeof value == 'string') { - value = this.expandTemplateSync(value, - new ExpansionOptions(options.vars, options.iterations - 1, - true /* noEncode */)); + value = this.expandTemplateSync( + value, + new ExpansionOptions( + options.vars, + options.iterations - 1, + true /* noEncode */ + ) + ); } if (!options.noEncode) { - value = encodeVars(/** @type {string|?Array} */(value)); + value = encodeVars(/** @type {string|?Array} */ (value)); } if (value) { value += argList; @@ -242,7 +251,6 @@ export class VariableService { }); } - /** * @param {string} value * @return {!Promise} diff --git a/extensions/amp-analytics/0.1/vendors.js b/extensions/amp-analytics/0.1/vendors.js index cd93f09ddd2b..77c260f602bd 100644 --- a/extensions/amp-analytics/0.1/vendors.js +++ b/extensions/amp-analytics/0.1/vendors.js @@ -58,9 +58,7 @@ import {MOBIFY_CONFIG} from './vendors/mobify'; import {MPARTICLE_CONFIG} from './vendors/mparticle'; import {NEWRELIC_CONFIG} from './vendors/newrelic'; import {NIELSEN_CONFIG} from './vendors/nielsen'; -import { - NIELSEN_MARKETING_CLOUD_CONFIG, -} from './vendors/nielsen-marketing-cloud'; +import {NIELSEN_MARKETING_CLOUD_CONFIG} from './vendors/nielsen-marketing-cloud'; import {OEWADIRECT_CONFIG} from './vendors/oewadirect'; import {OEWA_CONFIG} from './vendors/oewa'; import {PARSELY_CONFIG} from './vendors/parsely'; @@ -72,9 +70,7 @@ import {PRESSBOARD_CONFIG} from './vendors/pressboard'; import {QUANTCAST_CONFIG} from './vendors/quantcast'; import {RETARGETLY_CONFIG} from './vendors/retargetly'; import {ADOBEANALYTICS_CONFIG} from './vendors/adobeanalytics'; -import { - ADOBEANALYTICS_NATIVECONFIG_CONFIG, -} from './vendors/adobeanalytics_nativeConfig'; +import {ADOBEANALYTICS_NATIVECONFIG_CONFIG} from './vendors/adobeanalytics_nativeConfig'; import {INFONLINE_CONFIG} from './vendors/infonline'; import {SIMPLEREACH_CONFIG} from './vendors/simplereach'; import {SEGMENT_CONFIG} from './vendors/segment'; @@ -91,9 +87,7 @@ import {LINKPULSE_CONFIG} from './vendors/linkpulse'; import {RAKAM_CONFIG} from './vendors/rakam'; import {IBEATANALYTICS_CONFIG} from './vendors/ibeatanalytics'; import {TOPMAILRU_CONFIG} from './vendors/topmailru'; -import { - ORACLEINFINITYANALYTICS_CONFIG, -} from './vendors/oracleInfinityAnalytics'; +import {ORACLEINFINITYANALYTICS_CONFIG} from './vendors/oracleInfinityAnalytics'; import {MOAT_CONFIG} from './vendors/moat'; import {BG_CONFIG} from './vendors/bg'; import {UPSCORE_CONFIG} from './vendors/upscore'; @@ -105,7 +99,6 @@ import {VPONANALYTICS_CONFIG} from './vendors/vponanalytics'; * @const {!JsonObject} */ export const ANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ - // Default parent configuration applied to all amp-analytics tags. 'default': { 'transport': {'beacon': true, 'xhrpost': true, 'image': true}, @@ -254,15 +247,13 @@ if (getMode().test || getMode().localDev) { ANALYTICS_CONFIG['_fake_'] = _FAKE_; } -ANALYTICS_CONFIG['infonline']['triggers']['pageview']['iframe' + -/* TEMPORARY EXCEPTION */ 'Ping'] = true; +ANALYTICS_CONFIG['infonline']['triggers']['pageview']['iframePing'] = true; -ANALYTICS_CONFIG['adobeanalytics_nativeConfig'] - ['triggers']['pageLoad']['iframe' + - /* TEMPORARY EXCEPTION */ 'Ping'] = true; +ANALYTICS_CONFIG['adobeanalytics_nativeConfig']['triggers']['pageLoad'][ + 'iframePing' +] = true; -ANALYTICS_CONFIG['oewa']['triggers']['pageview']['iframe' + -/* TEMPORARY EXCEPTION */ 'Ping'] = true; +ANALYTICS_CONFIG['oewa']['triggers']['pageview']['iframePing'] = true; mergeIframeTransportConfig(ANALYTICS_CONFIG, IFRAME_TRANSPORTS); @@ -276,8 +267,11 @@ function mergeIframeTransportConfig(config, iframeTransportConfig) { for (const vendor in iframeTransportConfig) { if (hasOwn(iframeTransportConfig, vendor)) { const url = iframeTransportConfig[vendor]; - config[vendor]['transport'] = - Object.assign({}, config[vendor]['transport'], {'iframe': url}); + config[vendor]['transport'] = Object.assign( + {}, + config[vendor]['transport'], + {'iframe': url} + ); } } } diff --git a/extensions/amp-analytics/0.1/vendors/acquialift.js b/extensions/amp-analytics/0.1/vendors/acquialift.js index 0bd20aa09b26..b2c62e705734 100644 --- a/extensions/amp-analytics/0.1/vendors/acquialift.js +++ b/extensions/amp-analytics/0.1/vendors/acquialift.js @@ -22,18 +22,18 @@ export const ACQUIALIFT_CONFIG = /** @type {!JsonObject} */ ({ }, 'transport': {'beacon': true, 'xhrpost': true, 'image': false}, 'requests': { - 'base': 'https://${decisionApiUrl}/capture?account_id=${accountId}&site_id=${siteId}', - 'basicCapture': '${base}' + + 'base': + 'https://${decisionApiUrl}/capture?account_id=${accountId}&site_id=${siteId}', + 'basicCapture': + '${base}' + '&ident=${clientId(tc_ptid)}' + '&identsrc=amp' + '&es=Amp' + '&url=${canonicalUrl}' + '&rurl=${documentReferrer}' + '&cttl=${title}', - 'pageview': '${basicCapture}' + - '&en=Content View', - 'click': '${basicCapture}' + - '&en=Click-Through', + 'pageview': '${basicCapture}&en=Content View', + 'click': '${basicCapture}&en=Click-Through', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/adobeanalytics.js b/extensions/amp-analytics/0.1/vendors/adobeanalytics.js index 34589c5a1532..23ac497e103d 100644 --- a/extensions/amp-analytics/0.1/vendors/adobeanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/adobeanalytics.js @@ -27,18 +27,20 @@ export const ADOBEANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'requestPath': '/b/ss/${reportSuites}/0/amp-1.0/s${random}', // vid starts with z to work around #2198 - 'basePrefix': 'vid=z${clientId(adobe_amp_id)}' + - '&ndh=0' + - '&ce=${documentCharset}' + - '&pageName=${pageName}' + - '&g=${ampdocUrl}' + - '&r=${documentReferrer}' + - '&bh=${availableScreenHeight}' + - '&bw=${availableScreenWidth}' + - '&c=${screenColorDepth}' + - '&j=amp' + - '&s=${screenWidth}x${screenHeight}', + 'basePrefix': + 'vid=z${clientId(adobe_amp_id)}' + + '&ndh=0' + + '&ce=${documentCharset}' + + '&pageName=${pageName}' + + '&g=${ampdocUrl}' + + '&r=${documentReferrer}' + + '&bh=${availableScreenHeight}' + + '&bw=${availableScreenWidth}' + + '&c=${screenColorDepth}' + + '&j=amp' + + '&s=${screenWidth}x${screenHeight}', 'pageview': 'https://${host}${requestPath}?${basePrefix}', - 'click': 'https://${host}${requestPath}?${basePrefix}&pe=lnk_${linkType}&pev1=${linkUrl}&pev2=${linkName}', + 'click': + 'https://${host}${requestPath}?${basePrefix}&pe=lnk_${linkType}&pev1=${linkUrl}&pev2=${linkName}', }, }); diff --git a/extensions/amp-analytics/0.1/vendors/afsanalytics.js b/extensions/amp-analytics/0.1/vendors/afsanalytics.js index 6cf4487ecd54..36fc402fb5e0 100644 --- a/extensions/amp-analytics/0.1/vendors/afsanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/afsanalytics.js @@ -25,7 +25,8 @@ export const AFSANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': '//${server}.afsanalytics.com', 'base': '${host}/cgi_bin/', - 'pageview': '${base}connect.cgi?usr=${websiteid}Pauto' + + 'pageview': + '${base}connect.cgi?usr=${websiteid}Pauto' + '&js=1' + '&=1' + '&title=${title}' + @@ -34,7 +35,8 @@ export const AFSANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ '&resolution=${screenWidth}x${screenHeight}' + '&color=${screenColorDepth}' + '&Tips=${random}', - 'click': '${base}click.cgi?usr=${websiteid}' + + 'click': + '${base}click.cgi?usr=${websiteid}' + '&event=${event}' + '&exit=${clicklabel}', }, diff --git a/extensions/amp-analytics/0.1/vendors/alexametrics.js b/extensions/amp-analytics/0.1/vendors/alexametrics.js index 928bb545d595..bbd0ac8bb4b4 100644 --- a/extensions/amp-analytics/0.1/vendors/alexametrics.js +++ b/extensions/amp-analytics/0.1/vendors/alexametrics.js @@ -16,8 +16,10 @@ export const ALEXAMETRICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'base': 'https://${ampAtrkHost}/atrk.gif?account=${atrk_acct}&domain=${domain}', - 'pageview': '${base}&jsv=amp-${ampVersion}' + + 'base': + 'https://${ampAtrkHost}/atrk.gif?account=${atrk_acct}&domain=${domain}', + 'pageview': + '${base}&jsv=amp-${ampVersion}' + '&frame_height=${viewportHeight}&frame_width=${viewportWidth}' + '&title=${title}&time=${timestamp}&time_zone_offset=${timezone}' + '&screen_params=${screenWidth}x${screenHeight}x${screenColorDepth}' + diff --git a/extensions/amp-analytics/0.1/vendors/atinternet.js b/extensions/amp-analytics/0.1/vendors/atinternet.js index 83639c49c27f..28774fdd5d75 100644 --- a/extensions/amp-analytics/0.1/vendors/atinternet.js +++ b/extensions/amp-analytics/0.1/vendors/atinternet.js @@ -21,12 +21,12 @@ export const ATINTERNET_CONFIG = /** @type {!JsonObject} */ ({ 'domain': '.xiti.com', }, 'requests': { - 'base': 'https://${log}${domain}/${pixelPath}?s=${site}&ts=${timestamp}&r=${screenWidth}x${screenHeight}x${screenColorDepth}&re=${availableScreenWidth}x${availableScreenHeight}', + 'base': + 'https://${log}${domain}/${pixelPath}?s=${site}&ts=${timestamp}&r=${screenWidth}x${screenHeight}x${screenColorDepth}&re=${availableScreenWidth}x${availableScreenHeight}', 'suffix': '&medium=amp&${extraUrlParams}&ref=${documentReferrer}', - 'pageview': '${base}&' + - 'p=${title}&' + - 's2=${level2}${suffix}', - 'click': '${base}&' + + 'pageview': '${base}&p=${title}&s2=${level2}${suffix}', + 'click': + '${base}&' + 'pclick=${title}&' + 's2click=${level2}&' + 'p=${label}&' + diff --git a/extensions/amp-analytics/0.1/vendors/baiduanalytics.js b/extensions/amp-analytics/0.1/vendors/baiduanalytics.js index cdac22f0ae26..53dcc9f5aba3 100644 --- a/extensions/amp-analytics/0.1/vendors/baiduanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/baiduanalytics.js @@ -17,11 +17,9 @@ export const BAIDUANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://hm.baidu.com', - 'base': '${host}/hm.gif?' + - 'si=${token}&nv=0&st=4&v=pixel-1.0&rnd=${timestamp}', + 'base': '${host}/hm.gif?si=${token}&nv=0&st=4&v=pixel-1.0&rnd=${timestamp}', 'pageview': '${base}&et=0', - 'event': '${base}&ep=${category}*${action}*' + - '${label}*${value}&et=4&api=8_0', + 'event': '${base}&ep=${category}*${action}*${label}*${value}&et=4&api=8_0', }, 'transport': { 'beacon': false, diff --git a/extensions/amp-analytics/0.1/vendors/bg.js b/extensions/amp-analytics/0.1/vendors/bg.js index 055bf6d10729..2c88980a93da 100644 --- a/extensions/amp-analytics/0.1/vendors/bg.js +++ b/extensions/amp-analytics/0.1/vendors/bg.js @@ -14,5 +14,4 @@ * limitations under the License. */ -export const BG_CONFIG = /** @type {!JsonObject} */ ({ -}); +export const BG_CONFIG = /** @type {!JsonObject} */ ({}); diff --git a/extensions/amp-analytics/0.1/vendors/burt.js b/extensions/amp-analytics/0.1/vendors/burt.js index 9fda1e7a5589..1f171145dd89 100644 --- a/extensions/amp-analytics/0.1/vendors/burt.js +++ b/extensions/amp-analytics/0.1/vendors/burt.js @@ -22,17 +22,19 @@ export const BURT_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': '//${trackingKey}.c.richmetrics.com/', - 'base': '${host}imglog?' + + 'base': + '${host}imglog?' + 'e=${trackingKey}&' + 'pi=${trackingKey}' + - '|${pageViewId}' + - '|${canonicalPath}' + - '|${clientId(burt-amp-user-id)}&' + + '|${pageViewId}' + + '|${canonicalPath}' + + '|${clientId(burt-amp-user-id)}&' + 'ui=${clientId(burt-amp-user-id)}&' + 'v=amp&' + 'ts=${timestamp}&' + 'sn=${requestCount}&', - 'pageview': '${base}' + + 'pageview': + '${base}' + 'type=page&' + 'ca=${category}&' + 'sc=${subCategory}&' + @@ -44,8 +46,7 @@ export const BURT_CONFIG = /** @type {!JsonObject} */ ({ 'sd=${screenWidth}x${screenHeight}&' + 'wd=${availableScreenWidth}x${availableScreenHeight}&' + 'ws=${scrollLeft}x${scrollTop}', - 'pageping': '${base}' + - 'type=pageping', + 'pageping': '${base}type=pageping', }, 'triggers': { 'pageview': { diff --git a/extensions/amp-analytics/0.1/vendors/byside.js b/extensions/amp-analytics/0.1/vendors/byside.js index 7c2c38e830cf..7a382b2d89b4 100644 --- a/extensions/amp-analytics/0.1/vendors/byside.js +++ b/extensions/amp-analytics/0.1/vendors/byside.js @@ -26,8 +26,9 @@ export const BYSIDE_CONFIG = /** @type {!JsonObject} */ ({ 'host': '//${webcareZone}.byside.com/', 'base': '${host}BWA${webcareId}/amp/', 'pageview': '${base}pixel.php', - 'event': '${base}signal.php?event_id=${eventId}' + - '&event_label=${eventLabel}&fields=${fields}', + 'event': + '${base}signal.php?event_id=${eventId}' + + '&event_label=${eventLabel}&fields=${fields}', }, 'extraUrlParams': { 'webcare_id': '${webcareId}', diff --git a/extensions/amp-analytics/0.1/vendors/chartbeat.js b/extensions/amp-analytics/0.1/vendors/chartbeat.js index be2581fc02cb..f9117c2c5d82 100644 --- a/extensions/amp-analytics/0.1/vendors/chartbeat.js +++ b/extensions/amp-analytics/0.1/vendors/chartbeat.js @@ -17,7 +17,8 @@ export const CHARTBEAT_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://ping.chartbeat.net', - 'basePrefix': '/ping?h=${domain}&' + + 'basePrefix': + '/ping?h=${domain}&' + 'p=${canonicalPath}&' + 'u=${clientId(_cb)}&' + 'd=${canonicalHost}&' + diff --git a/extensions/amp-analytics/0.1/vendors/clicky.js b/extensions/amp-analytics/0.1/vendors/clicky.js index 8e582e42c166..5293ae50cd42 100644 --- a/extensions/amp-analytics/0.1/vendors/clicky.js +++ b/extensions/amp-analytics/0.1/vendors/clicky.js @@ -19,11 +19,10 @@ export const CLICKY_CONFIG = /** @type {!JsonObject} */ ({ 'site_id': '', }, 'requests': { - 'base': 'https://in.getclicky.com/in.php?' + - 'site_id=${site_id}', - 'baseSuffix': '&mime=${contentType}&' + - 'x=${random}', - 'pageview': '${base}&' + + 'base': 'https://in.getclicky.com/in.php?site_id=${site_id}', + 'baseSuffix': '&mime=${contentType}&x=${random}', + 'pageview': + '${base}&' + 'res=${screenWidth}x${screenHeight}&' + 'lang=${browserLanguage}&' + 'secure=1&' + @@ -31,9 +30,7 @@ export const CLICKY_CONFIG = /** @type {!JsonObject} */ ({ 'href=${canonicalPath}&' + 'title=${title}' + '${baseSuffix}', - 'interval': '${base}&' + - 'type=ping' + - '${baseSuffix}', + 'interval': '${base}&type=ping${baseSuffix}', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/colanalytics.js b/extensions/amp-analytics/0.1/vendors/colanalytics.js index dda474f3380f..a3b065eceb4b 100644 --- a/extensions/amp-analytics/0.1/vendors/colanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/colanalytics.js @@ -18,7 +18,8 @@ export const COLANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://ase.clmbtech.com', 'base': '${host}/message', - 'pageview': '${base}?cid=${id}' + + 'pageview': + '${base}?cid=${id}' + '&val_101=${id}' + '&val_101=${canonicalPath}' + '&ch=${canonicalHost}' + diff --git a/extensions/amp-analytics/0.1/vendors/comscore.js b/extensions/amp-analytics/0.1/vendors/comscore.js index 67aa0d06b6a8..eb7a94b77bd7 100644 --- a/extensions/amp-analytics/0.1/vendors/comscore.js +++ b/extensions/amp-analytics/0.1/vendors/comscore.js @@ -21,7 +21,8 @@ export const COMSCORE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://sb.scorecardresearch.com', 'base': '${host}/b?', - 'pageview': '${base}c1=2' + + 'pageview': + '${base}c1=2' + '&c2=${c2}' + '&cs_pv=${pageViewId}' + '&c12=${clientId(comScore)}' + diff --git a/extensions/amp-analytics/0.1/vendors/cxense.js b/extensions/amp-analytics/0.1/vendors/cxense.js index f17e12881d1f..b82c3452d1aa 100644 --- a/extensions/amp-analytics/0.1/vendors/cxense.js +++ b/extensions/amp-analytics/0.1/vendors/cxense.js @@ -18,11 +18,12 @@ export const CXENSE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://scomcluster.cxense.com', 'base': '${host}/Repo/rep.gif', - 'pageview': '${base}?ver=1&typ=pgv&sid=${siteId}&ckp=${clientId(cX_P)}&' + - 'loc=${sourceUrl}&rnd=${random}&ref=${documentReferrer}&' + - 'ltm=${timestamp}&wsz=${screenWidth}x${screenHeight}&' + - 'bln=${browserLanguage}&chs=${documentCharset}&' + - 'col=${screenColorDepth}&tzo=${timezone}&cp_cx_channel=amp', + 'pageview': + '${base}?ver=1&typ=pgv&sid=${siteId}&ckp=${clientId(cX_P)}&' + + 'loc=${sourceUrl}&rnd=${random}&ref=${documentReferrer}&' + + 'ltm=${timestamp}&wsz=${screenWidth}x${screenHeight}&' + + 'bln=${browserLanguage}&chs=${documentCharset}&' + + 'col=${screenColorDepth}&tzo=${timezone}&cp_cx_channel=amp', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/dynatrace.js b/extensions/amp-analytics/0.1/vendors/dynatrace.js index f66d0d836855..af3d414caf47 100644 --- a/extensions/amp-analytics/0.1/vendors/dynatrace.js +++ b/extensions/amp-analytics/0.1/vendors/dynatrace.js @@ -16,8 +16,10 @@ export const DYNATRACE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'endpoint': '${protocol}://${tenant}${separator}${environment}:${port}/ampbf/${tenantpath}', - 'pageview': '${endpoint}?type=js&' + + 'endpoint': + '${protocol}://${tenant}${separator}${environment}:${port}/ampbf/${tenantpath}', + 'pageview': + '${endpoint}?type=js&' + 'flavor=amp&' + 'v=1&' + 'a=1%7C1%7C_load_%7C_load_%7C-%7C${navTiming(navigationStart)}%7C' + diff --git a/extensions/amp-analytics/0.1/vendors/epica.js b/extensions/amp-analytics/0.1/vendors/epica.js index c682f207ff8d..68c60e24d726 100644 --- a/extensions/amp-analytics/0.1/vendors/epica.js +++ b/extensions/amp-analytics/0.1/vendors/epica.js @@ -25,7 +25,8 @@ export const EPICA_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://cat.poder.io/api/v1/pixel', - 'base': '?writeKey=${writeKey}' + + 'base': + '?writeKey=${writeKey}' + '&context.library.name=amp' + '&anonymousId=${anonymousId}' + '&context.locale=${browserLanguage}' + diff --git a/extensions/amp-analytics/0.1/vendors/euleriananalytics.js b/extensions/amp-analytics/0.1/vendors/euleriananalytics.js index 07fa6cefc2cb..e4a47e95056f 100644 --- a/extensions/amp-analytics/0.1/vendors/euleriananalytics.js +++ b/extensions/amp-analytics/0.1/vendors/euleriananalytics.js @@ -21,21 +21,23 @@ export const EULERIANANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'base': 'https://${analyticsHost}', - 'basePrefix': '-/${random}?' + + 'basePrefix': + '-/${random}?' + 'euid-amp=${clientId(etuix)}&' + 'url=${documentLocation}&', - 'pageview': '${base}/col2/${basePrefix}' + + 'pageview': + '${base}/col2/${basePrefix}' + 'rf=${externalReferrer}&' + 'urlp=${pagePath}&' + 'ss=${screenWidth}x${screenHeight}&' + 'sd=${screenColorDepth}', - 'action': '${base}/action/${basePrefix}' + + 'action': + '${base}/action/${basePrefix}' + 'eact=${actionCode}&' + 'actr=${actionRef}', - 'user': '${base}/uparam/${basePrefix}' + - 'euk${userParamKey}=${userParamVal}', - 'contextflag': '${base}/cflag2/${basePrefix}' + - 'ecf0k=${cflagKey}&ecf0v=${cflagVal}', + 'user': '${base}/uparam/${basePrefix}euk${userParamKey}=${userParamVal}', + 'contextflag': + '${base}/cflag2/${basePrefix}ecf0k=${cflagKey}&ecf0v=${cflagVal}', }, 'transport': { 'beacon': false, diff --git a/extensions/amp-analytics/0.1/vendors/facebookpixel.js b/extensions/amp-analytics/0.1/vendors/facebookpixel.js index 1b3103b931f5..1a5c684b0420 100644 --- a/extensions/amp-analytics/0.1/vendors/facebookpixel.js +++ b/extensions/amp-analytics/0.1/vendors/facebookpixel.js @@ -21,73 +21,82 @@ export const FACEBOOKPIXEL_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://www.facebook.com', 'base': '${host}/tr?noscript=1', - 'pageview': '${base}&ev=PageView&' + - 'id=${pixelId}', - 'event': '${base}&ev=${eventName}&' + - 'id=${pixelId}' + - '&cd[content_name]=${content_name}', - 'eventViewContent': '${base}&ev=ViewContent&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_type]=${content_type}' + - '&cd[content_ids]=${content_ids}', - 'eventSearch': '${base}&ev=Search&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_category]=${content_category}' + - '&cd[content_ids]=${content_ids}' + - '&cd[search_string]=${search_string}', - 'eventAddToCart': '${base}&ev=AddToCart&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_type]=${content_type}' + - '&cd[content_ids]=${content_ids}', - 'eventAddToWishlist': '${base}&ev=AddToWishlist&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_category]=${content_category}' + - '&cd[content_ids]=${content_ids}', - 'eventInitiateCheckout': '${base}&ev=InitiateCheckout&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_category]=${content_category}' + - '&cd[num_items]=${num_items}' + - '&cd[content_ids]=${content_ids}', - 'eventAddPaymentInfo': '${base}&ev=AddPaymentInfo&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_category]=${content_category}' + - '&cd[content_ids]=${content_ids}', - 'eventPurchase': '${base}&ev=Purchase&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_type]=${content_type}' + - '&cd[content_ids]=${content_ids}' + - '&cd[num_items]=${num_items}', - 'eventLead': '${base}&ev=Lead&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_category]=${content_category}', - 'eventCompleteRegistration': '${base}&ev=CompleteRegistration&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[status]=${status}', + 'pageview': '${base}&ev=PageView&id=${pixelId}', + 'event': + '${base}&ev=${eventName}&' + + 'id=${pixelId}' + + '&cd[content_name]=${content_name}', + 'eventViewContent': + '${base}&ev=ViewContent&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_type]=${content_type}' + + '&cd[content_ids]=${content_ids}', + 'eventSearch': + '${base}&ev=Search&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_category]=${content_category}' + + '&cd[content_ids]=${content_ids}' + + '&cd[search_string]=${search_string}', + 'eventAddToCart': + '${base}&ev=AddToCart&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_type]=${content_type}' + + '&cd[content_ids]=${content_ids}', + 'eventAddToWishlist': + '${base}&ev=AddToWishlist&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_category]=${content_category}' + + '&cd[content_ids]=${content_ids}', + 'eventInitiateCheckout': + '${base}&ev=InitiateCheckout&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_category]=${content_category}' + + '&cd[num_items]=${num_items}' + + '&cd[content_ids]=${content_ids}', + 'eventAddPaymentInfo': + '${base}&ev=AddPaymentInfo&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_category]=${content_category}' + + '&cd[content_ids]=${content_ids}', + 'eventPurchase': + '${base}&ev=Purchase&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_type]=${content_type}' + + '&cd[content_ids]=${content_ids}' + + '&cd[num_items]=${num_items}', + 'eventLead': + '${base}&ev=Lead&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_category]=${content_category}', + 'eventCompleteRegistration': + '${base}&ev=CompleteRegistration&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[status]=${status}', }, 'triggers': { 'trackPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/gemius.js b/extensions/amp-analytics/0.1/vendors/gemius.js index a13a85365d02..da6a0e84df24 100644 --- a/extensions/amp-analytics/0.1/vendors/gemius.js +++ b/extensions/amp-analytics/0.1/vendors/gemius.js @@ -19,7 +19,8 @@ export const GEMIUS_CONFIG = /** @type {!JsonObject} */ ({ 'dnt': '0', }, 'requests': { - 'base': 'https://${prefix}.hit.gemius.pl/_${timestamp}/redot.gif?l=91&id=${identifier}&screen=${screenWidth}x${screenHeight}&window=${viewportWidth}x${viewportHeight}&fr=1&href=${sourceUrl}&ref=${documentReferrer}&extra=gemamp%3D1%7Campid%3D${clientId(gemius)}%7C${extraparams}&nc=${dnt}', + 'base': + 'https://${prefix}.hit.gemius.pl/_${timestamp}/redot.gif?l=91&id=${identifier}&screen=${screenWidth}x${screenHeight}&window=${viewportWidth}x${viewportHeight}&fr=1&href=${sourceUrl}&ref=${documentReferrer}&extra=gemamp%3D1%7Campid%3D${clientId(gemius)}%7C${extraparams}&nc=${dnt}', 'pageview': '${base}&et=view&hsrc=1', 'event': '${base}&et=action&hsrc=3', }, diff --git a/extensions/amp-analytics/0.1/vendors/googleadwords.js b/extensions/amp-analytics/0.1/vendors/googleadwords.js index ee193e88093d..c6b076f3c23f 100644 --- a/extensions/amp-analytics/0.1/vendors/googleadwords.js +++ b/extensions/amp-analytics/0.1/vendors/googleadwords.js @@ -19,27 +19,29 @@ export const GOOGLEADWORDS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'conversion_prefix': 'https://www.googleadservices.com/pagead/conversion/', 'remarketing_prefix': - 'https://googleads.g.doubleclick.net/pagead/viewthroughconversion/', - 'common_params': '${googleConversionId}/?' + - 'cv=amp2&' + // Increment when making changes. - 'label=${googleConversionLabel}&' + - 'random=${random}&' + - 'url=${sourceUrl}&' + - 'ref=${documentReferrer}&' + - 'fst=${pageViewId}&' + - 'num=${counter(googleadwords)}&' + - 'fmt=3&' + - 'async=1&' + - 'u_h=${screenHeight}&u_w=${screenWidth}&' + - 'u_ah=${availableScreenHeight}&u_aw=${availableScreenWidth}&' + - 'u_cd=${screenColorDepth}&' + - 'u_tz=${timezone}&' + - 'tiba=${title}&' + - 'guid=ON&script=0', - 'conversion_params': 'value=${googleConversionValue}&' + - 'currency_code=${googleConversionCurrency}&' + - 'bg=${googleConversionColor}&' + - 'hl=${googleConversionLanguage}', + 'https://googleads.g.doubleclick.net/pagead/viewthroughconversion/', + 'common_params': + '${googleConversionId}/?' + + 'cv=amp2&' + // Increment when making changes. + 'label=${googleConversionLabel}&' + + 'random=${random}&' + + 'url=${sourceUrl}&' + + 'ref=${documentReferrer}&' + + 'fst=${pageViewId}&' + + 'num=${counter(googleadwords)}&' + + 'fmt=3&' + + 'async=1&' + + 'u_h=${screenHeight}&u_w=${screenWidth}&' + + 'u_ah=${availableScreenHeight}&u_aw=${availableScreenWidth}&' + + 'u_cd=${screenColorDepth}&' + + 'u_tz=${timezone}&' + + 'tiba=${title}&' + + 'guid=ON&script=0', + 'conversion_params': + 'value=${googleConversionValue}&' + + 'currency_code=${googleConversionCurrency}&' + + 'bg=${googleConversionColor}&' + + 'hl=${googleConversionLanguage}', 'conversion': '${conversion_prefix}${common_params}&${conversion_params}', 'remarketing': '${remarketing_prefix}${common_params}', }, diff --git a/extensions/amp-analytics/0.1/vendors/googleanalytics.js b/extensions/amp-analytics/0.1/vendors/googleanalytics.js index 078d9697d94b..3fcfdffdd45d 100644 --- a/extensions/amp-analytics/0.1/vendors/googleanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/googleanalytics.js @@ -25,59 +25,64 @@ export const GOOGLEANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://www.google-analytics.com', - 'basePrefix': 'v=1&' + - '_v=a1&' + - 'ds=${dataSource}&' + - '${anonymizeIP}&' + - '_s=${requestCount}&' + - 'dt=${title}&' + - 'sr=${screenWidth}x${screenHeight}&' + - '_utmht=${timestamp}&' + - 'cid=${clientId}&' + - 'tid=${account}&' + - 'dl=${documentLocation}&' + - 'dr=${externalReferrer}&' + - 'sd=${screenColorDepth}&' + - 'ul=${browserLanguage}&' + - 'de=${documentCharset}', - 'baseSuffix': '&a=${pageViewId}&' + - 'z=${random}', - 'pageview': '${host}/r/collect?${basePrefix}&' + - 't=pageview&' + - 'jid=${random}&' + - '_r=1' + - '${baseSuffix}', - 'event': '${host}/collect?${basePrefix}&' + - 't=event&' + - 'jid=&' + - 'ec=${eventCategory}&' + - 'ea=${eventAction}&' + - 'el=${eventLabel}&' + - 'ev=${eventValue}' + - '${baseSuffix}', - 'social': '${host}/collect?${basePrefix}&' + - 't=social&' + - 'jid=&' + - 'sa=${socialAction}&' + - 'sn=${socialNetwork}&' + - 'st=${socialTarget}' + - '${baseSuffix}', - 'timing': '${host}/collect?${basePrefix}&' + - 't=${timingRequestType}&' + - 'jid=&' + - 'plt=${pageLoadTime}&' + - 'dns=${domainLookupTime}&' + - 'tcp=${tcpConnectTime}&' + - 'rrt=${redirectTime}&' + - 'srt=${serverResponseTime}&' + - 'pdt=${pageDownloadTime}&' + - 'clt=${contentLoadTime}&' + - 'dit=${domInteractiveTime}' + - '${baseSuffix}', - 'error': '${host}/collect?${basePrefix}&' + - 't=exception&' + - 'exd=${errorParam}' + - '${baseSuffix}', + 'basePrefix': + 'v=1&' + + '_v=a1&' + + 'ds=${dataSource}&' + + '${anonymizeIP}&' + + '_s=${requestCount}&' + + 'dt=${title}&' + + 'sr=${screenWidth}x${screenHeight}&' + + '_utmht=${timestamp}&' + + 'cid=${clientId}&' + + 'tid=${account}&' + + 'dl=${documentLocation}&' + + 'dr=${externalReferrer}&' + + 'sd=${screenColorDepth}&' + + 'ul=${browserLanguage}&' + + 'de=${documentCharset}', + 'baseSuffix': '&a=${pageViewId}&z=${random}', + 'pageview': + '${host}/r/collect?${basePrefix}&' + + 't=pageview&' + + 'jid=${random}&' + + '_r=1' + + '${baseSuffix}', + 'event': + '${host}/collect?${basePrefix}&' + + 't=event&' + + 'jid=&' + + 'ec=${eventCategory}&' + + 'ea=${eventAction}&' + + 'el=${eventLabel}&' + + 'ev=${eventValue}' + + '${baseSuffix}', + 'social': + '${host}/collect?${basePrefix}&' + + 't=social&' + + 'jid=&' + + 'sa=${socialAction}&' + + 'sn=${socialNetwork}&' + + 'st=${socialTarget}' + + '${baseSuffix}', + 'timing': + '${host}/collect?${basePrefix}&' + + 't=${timingRequestType}&' + + 'jid=&' + + 'plt=${pageLoadTime}&' + + 'dns=${domainLookupTime}&' + + 'tcp=${tcpConnectTime}&' + + 'rrt=${redirectTime}&' + + 'srt=${serverResponseTime}&' + + 'pdt=${pageDownloadTime}&' + + 'clt=${contentLoadTime}&' + + 'dit=${domInteractiveTime}' + + '${baseSuffix}', + 'error': + '${host}/collect?${basePrefix}&' + + 't=exception&' + + 'exd=${errorParam}' + + '${baseSuffix}', }, 'triggers': { 'performanceTiming': { diff --git a/extensions/amp-analytics/0.1/vendors/gtag.js b/extensions/amp-analytics/0.1/vendors/gtag.js index b5f9a65321f2..651609aa9be2 100644 --- a/extensions/amp-analytics/0.1/vendors/gtag.js +++ b/extensions/amp-analytics/0.1/vendors/gtag.js @@ -40,83 +40,78 @@ export const GTAG_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'uaHost': 'https://www.google-analytics.com', 'uaBasePrefix': - 'v=1&' + - '_v=a1&' + - 'ds=${dataSource}&' + - '${anonymizeIP}&' + - '_s=${requestCount}&' + - 'dt=${title}&' + - 'sr=${screenWidth}x${screenHeight}&' + - 'cid=${clientId}&' + - 'tid=${trackingId}&' + - 'dl=${sourceUrl}&' + - 'dr=${externalReferrer}&' + - 'sd=${screenColorDepth}&' + - 'ul=${browserLanguage}&' + - 'de=${documentCharset}', - 'uaBaseSuffix': - '&a=${pageViewId}&' + - 'z=${random}', - 'uaPageviewCommon': - '&t=pageview&' + - 'jid=${random}&' + - 'gjid=${random}&' + - '_r=1', + 'v=1&' + + '_v=a1&' + + 'ds=${dataSource}&' + + '${anonymizeIP}&' + + '_s=${requestCount}&' + + 'dt=${title}&' + + 'sr=${screenWidth}x${screenHeight}&' + + 'cid=${clientId}&' + + 'tid=${trackingId}&' + + 'dl=${sourceUrl}&' + + 'dr=${externalReferrer}&' + + 'sd=${screenColorDepth}&' + + 'ul=${browserLanguage}&' + + 'de=${documentCharset}', + 'uaBaseSuffix': '&a=${pageViewId}&z=${random}', + 'uaPageviewCommon': '&t=pageview&jid=${random}&gjid=${random}&_r=1', 'uaPageview': - '${uaHost}/r/collect?${uaBasePrefix}' + - '${uaPageviewCommon}' + - '${uaBaseSuffix}', + '${uaHost}/r/collect?${uaBasePrefix}' + + '${uaPageviewCommon}' + + '${uaBaseSuffix}', 'uaPageviewNpa': - '${uaHost}/collect?${uaBasePrefix}' + - '${uaPageviewCommon}' + - '${uaBaseSuffix}', + '${uaHost}/collect?${uaBasePrefix}' + + '${uaPageviewCommon}' + + '${uaBaseSuffix}', 'uaEvent': - '${uaHost}/collect?${uaBasePrefix}&' + - 't=event&' + - 'jid=' + - '${uaBaseSuffix}', + '${uaHost}/collect?${uaBasePrefix}&' + + 't=event&' + + 'jid=' + + '${uaBaseSuffix}', 'uaTiming': - '${uaHost}/collect?${uaBasePrefix}&' + - 'jid=&' + - 'plt=${pageLoadTime}&' + - 'dns=${domainLookupTime}&' + - 'tcp=${tcpConnectTime}&' + - 'rrt=${redirectTime}&' + - 'srt=${serverResponseTime}&' + - 'pdt=${pageDownloadTime}&' + - 'clt=${contentLoadTime}&' + - 'dit=${domInteractiveTime}' + - '${uaBaseSuffix}', + '${uaHost}/collect?${uaBasePrefix}&' + + 'jid=&' + + 'plt=${pageLoadTime}&' + + 'dns=${domainLookupTime}&' + + 'tcp=${tcpConnectTime}&' + + 'rrt=${redirectTime}&' + + 'srt=${serverResponseTime}&' + + 'pdt=${pageDownloadTime}&' + + 'clt=${contentLoadTime}&' + + 'dit=${domInteractiveTime}' + + '${uaBaseSuffix}', 'uaError': - '${uaHost}/collect?${uaBasePrefix}&' + - 't=exception&' + - 'exd=${errorParam}' + - '${uaBaseSuffix}', - 'awConversionPrefix': - 'https://www.googleadservices.com/pagead/conversion/', + '${uaHost}/collect?${uaBasePrefix}&' + + 't=exception&' + + 'exd=${errorParam}' + + '${uaBaseSuffix}', + 'awConversionPrefix': 'https://www.googleadservices.com/pagead/conversion/', 'awRemarketingPrefix': - 'https://googleads.g.doubleclick.net/pagead/viewthroughconversion/', + 'https://googleads.g.doubleclick.net/pagead/viewthroughconversion/', 'awCommonParams': - '${conversionId}/?' + - 'cv=amp3&' + // Increment when making changes. - 'label=${conversionLabel}&' + - 'random=${random}&' + - 'url=${sourceUrl}&' + - 'ref=${documentReferrer}&' + - 'fst=${pageViewId}&' + - 'num=${counter(googleadwords)}&' + - 'fmt=3&' + - 'async=1&' + - 'u_h=${screenHeight}&u_w=${screenWidth}&' + - 'u_ah=${availableScreenHeight}&u_aw=${availableScreenWidth}&' + - 'u_cd=${screenColorDepth}&' + - 'u_tz=${timezone}&' + - 'tiba=${title}&' + - 'guid=ON&script=0', + '${conversionId}/?' + + 'cv=amp3&' + // Increment when making changes. + 'label=${conversionLabel}&' + + 'random=${random}&' + + 'url=${sourceUrl}&' + + 'ref=${documentReferrer}&' + + 'fst=${pageViewId}&' + + 'num=${counter(googleadwords)}&' + + 'fmt=3&' + + 'async=1&' + + 'u_h=${screenHeight}&u_w=${screenWidth}&' + + 'u_ah=${availableScreenHeight}&u_aw=${availableScreenWidth}&' + + 'u_cd=${screenColorDepth}&' + + 'u_tz=${timezone}&' + + 'tiba=${title}&' + + 'guid=ON&script=0', 'awConversion': '${awConversionPrefix}${awCommonParams}', 'awRemarketing': '${awRemarketingPrefix}${awCommonParams}', - 'flBase': 'https://ad.doubleclick.net/activity;src=${flSrc};type=${flType};cat=${flCat}', - 'flDynamicBase': 'https://${flSrc}.fls.doubleclick.net/activityi;src=${flSrc};type=${flType};cat=${flCat}', + 'flBase': + 'https://ad.doubleclick.net/activity;src=${flSrc};type=${flType};cat=${flCat}', + 'flDynamicBase': + 'https://${flSrc}.fls.doubleclick.net/activityi;src=${flSrc};type=${flType};cat=${flCat}', 'dnsBase': 'https://ad.doubleclick.net/ddm/clk/', }, 'transport': { diff --git a/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js b/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js index 778e8973f352..a760206169e6 100644 --- a/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js @@ -18,29 +18,30 @@ export const IBEATANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://ibeat.indiatimes.com', 'base': 'https://ibeat.indiatimes.com/iBeat/pageTrendlogAmp.html', - 'pageview': '${base}?' + - '&h=${h}' + - '&d=${h}' + - '&url=${url}' + - '&k=${key}' + - '&ts=${time}' + - '&ch=${channel}' + - '&sid=${uid}' + - '&at=${agentType}' + - '&ref=${documentReferrer}' + - '&aid=${aid}' + - '&loc=1' + - '&ct=1' + - '&cat=${cat}' + - '&scat=${scat}' + - '&ac=1' + - '&tg=${tags}' + - '&ctids=${catIds}' + - '&pts=${pagePublishTime}' + - '&auth=${author}' + - '&pos=${position}' + - '&iBeatField=${ibeatFields}' + - '&cid=${clientId(MSCSAuthDetails)}', + 'pageview': + '${base}?' + + '&h=${h}' + + '&d=${h}' + + '&url=${url}' + + '&k=${key}' + + '&ts=${time}' + + '&ch=${channel}' + + '&sid=${uid}' + + '&at=${agentType}' + + '&ref=${documentReferrer}' + + '&aid=${aid}' + + '&loc=1' + + '&ct=1' + + '&cat=${cat}' + + '&scat=${scat}' + + '&ac=1' + + '&tg=${tags}' + + '&ctids=${catIds}' + + '&pts=${pagePublishTime}' + + '&auth=${author}' + + '&pos=${position}' + + '&iBeatField=${ibeatFields}' + + '&cid=${clientId(MSCSAuthDetails)}', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/infonline.js b/extensions/amp-analytics/0.1/vendors/infonline.js index 12aed4022f58..3819a7890393 100644 --- a/extensions/amp-analytics/0.1/vendors/infonline.js +++ b/extensions/amp-analytics/0.1/vendors/infonline.js @@ -21,7 +21,8 @@ export const INFONLINE_CONFIG = /** @type {!JsonObject} */ ({ }, 'transport': {'beacon': false, 'xhrpost': false, 'image': true}, 'requests': { - 'pageview': '${url}?st=${st}' + + 'pageview': + '${url}?st=${st}' + '&sv=${sv}' + '&ap=${ap}' + '&co=${co}' + diff --git a/extensions/amp-analytics/0.1/vendors/iplabel.js b/extensions/amp-analytics/0.1/vendors/iplabel.js index fb718d348e5a..496a4d59f6e6 100644 --- a/extensions/amp-analytics/0.1/vendors/iplabel.js +++ b/extensions/amp-analytics/0.1/vendors/iplabel.js @@ -18,31 +18,32 @@ export const IPLABEL_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'collectorUrl': 'm.col.ip-label.net', 'endpoint': 'https://${collectorUrl}/coll/', - 'onload': '${endpoint}?' + - 'T=${trackerId}&' + - 'm=' + - '2502|${navTiming(navigationStart)}|' + - '2508|${navTiming(domainLookupStart)}|' + - '2509|${navTiming(domainLookupEnd)}|' + - '2510|${navTiming(connectStart)}|' + - '2512|${navTiming(connectEnd)}|' + - '2514|${navTiming(responseStart)}|' + - '2515|${navTiming(responseEnd)}|' + - '2517|${navTiming(domInteractive)}|' + - '2520|${navTiming(loadEventStart)}' + - '&ts=${timestamp}' + - '&ua=${userAgent}' + - '&d=${ipldim}' + - '&i=${clientip}' + - '&d[1]=${customdim}' + - '&d[2]=${business}' + - '&d[3]=${abtesting}' + - '&d[4]=${infrastructure}' + - '&d[5]=${customer}' + - '&u=${urlgroup}' + - '&w=${availableScreenWidth}&h=${availableScreenHeight}' + - '&r=${documentReferrer}' + - '&l=${browserLanguage}', + 'onload': + '${endpoint}?' + + 'T=${trackerId}&' + + 'm=' + + '2502|${navTiming(navigationStart)}|' + + '2508|${navTiming(domainLookupStart)}|' + + '2509|${navTiming(domainLookupEnd)}|' + + '2510|${navTiming(connectStart)}|' + + '2512|${navTiming(connectEnd)}|' + + '2514|${navTiming(responseStart)}|' + + '2515|${navTiming(responseEnd)}|' + + '2517|${navTiming(domInteractive)}|' + + '2520|${navTiming(loadEventStart)}' + + '&ts=${timestamp}' + + '&ua=${userAgent}' + + '&d=${ipldim}' + + '&i=${clientip}' + + '&d[1]=${customdim}' + + '&d[2]=${business}' + + '&d[3]=${abtesting}' + + '&d[4]=${infrastructure}' + + '&d[5]=${customer}' + + '&u=${urlgroup}' + + '&w=${availableScreenWidth}&h=${availableScreenHeight}' + + '&r=${documentReferrer}' + + '&l=${browserLanguage}', }, 'triggers': { 'trackPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/krux.js b/extensions/amp-analytics/0.1/vendors/krux.js index 08a412375d50..b5f4733a34d6 100644 --- a/extensions/amp-analytics/0.1/vendors/krux.js +++ b/extensions/amp-analytics/0.1/vendors/krux.js @@ -17,7 +17,8 @@ export const KRUX_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'beaconHost': 'https://beacon.krxd.net', - 'timing': 't_navigation_type=0&' + + 'timing': + 't_navigation_type=0&' + 't_dns=${domainLookupTime}&' + 't_tcp=${tcpConnectTime}&' + 't_http_request=${serverResponseTime}&' + @@ -25,7 +26,8 @@ export const KRUX_CONFIG = /** @type {!JsonObject} */ ({ 't_content_ready=${contentLoadTime}&' + 't_window_load=${pageLoadTime}&' + 't_redirect=${redirectTime}', - 'common': 'source=amp&' + + 'common': + 'source=amp&' + 'confid=${confid}&' + '_kpid=${pubid}&' + '_kcp_s=${site}&' + diff --git a/extensions/amp-analytics/0.1/vendors/linkpulse.js b/extensions/amp-analytics/0.1/vendors/linkpulse.js index 7ff88d0407b5..63071f87c508 100644 --- a/extensions/amp-analytics/0.1/vendors/linkpulse.js +++ b/extensions/amp-analytics/0.1/vendors/linkpulse.js @@ -27,35 +27,38 @@ export const LINKPULSE_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'base': 'https://${host}', - 'pageview': '${base}/p?i=${id}' + - '&r=${documentReferrer}' + - '&p=${pageUrl}' + - '&s=${section}' + - '&t=${type}' + - '&c=${channel}' + - '&mt=${title}' + - '&_t=amp' + - '&_r=${random}', - 'pageload': '${base}/pl?i=${id}' + - '&ct=${domInteractiveTime}' + - '&rt=${pageDownloadTime}' + - '&pt=${pageLoadTime}' + - '&p=${pageUrl}' + - '&c=${channel}' + - '&t=${type}' + - '&s=${section}' + - '&_t=amp' + - '&_r=${random}', - 'ping': '${base}/u?i=${id}' + - '&u=${clientId(_lp4_u)}' + - '&p=${pageUrl}' + - '&uActive=true' + - '&isPing=yes' + - '&c=${channel}' + - '&t=${type}' + - '&s=${section}' + - '&_t=amp' + - '&_r=${random}', + 'pageview': + '${base}/p?i=${id}' + + '&r=${documentReferrer}' + + '&p=${pageUrl}' + + '&s=${section}' + + '&t=${type}' + + '&c=${channel}' + + '&mt=${title}' + + '&_t=amp' + + '&_r=${random}', + 'pageload': + '${base}/pl?i=${id}' + + '&ct=${domInteractiveTime}' + + '&rt=${pageDownloadTime}' + + '&pt=${pageLoadTime}' + + '&p=${pageUrl}' + + '&c=${channel}' + + '&t=${type}' + + '&s=${section}' + + '&_t=amp' + + '&_r=${random}', + 'ping': + '${base}/u?i=${id}' + + '&u=${clientId(_lp4_u)}' + + '&p=${pageUrl}' + + '&uActive=true' + + '&isPing=yes' + + '&c=${channel}' + + '&t=${type}' + + '&s=${section}' + + '&_t=amp' + + '&_r=${random}', }, 'triggers': { 'pageview': { diff --git a/extensions/amp-analytics/0.1/vendors/marinsoftware.js b/extensions/amp-analytics/0.1/vendors/marinsoftware.js index 1e6cdbfab22f..214fb8b7c926 100644 --- a/extensions/amp-analytics/0.1/vendors/marinsoftware.js +++ b/extensions/amp-analytics/0.1/vendors/marinsoftware.js @@ -17,26 +17,26 @@ export const MARINSOFTWARE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'base': 'https://tracker.marinsm.com/tp', - 'baseParams': 'cid=${trackerId}' + + 'baseParams': + 'cid=${trackerId}' + '&Version=${ampVersion}' + '&ds=AMP' + '&ref=${externalReferrer}' + '&page=${sourceUrl}' + '&uuid=${clientId(marin_amp_id)}' + '&rnd=${random}', - 'pageView': '${base}?' + - '${baseParams}' + - '&act=1', - 'conversion': '${base}?' + + 'pageView': '${base}?${baseParams}&act=1', + 'conversion': + '${base}?' + '${baseParams}' + '&act=2' + '&trans=UTM:I' + - '|${orderId}' + - '|${marinConversionType}' + - '|${productName}' + - '|${category}' + - '|${price}' + - '|${quantity}', + '|${orderId}' + + '|${marinConversionType}' + + '|${productName}' + + '|${category}' + + '|${price}' + + '|${quantity}', }, 'transport': { 'beacon': true, diff --git a/extensions/amp-analytics/0.1/vendors/mediametrie.js b/extensions/amp-analytics/0.1/vendors/mediametrie.js index 89b4ec89ff2d..c1ab050e2d68 100644 --- a/extensions/amp-analytics/0.1/vendors/mediametrie.js +++ b/extensions/amp-analytics/0.1/vendors/mediametrie.js @@ -17,7 +17,8 @@ export const MEDIAMETRIE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://prof.estat.com/m/web', - 'pageview': '${host}/${serial}?' + + 'pageview': + '${host}/${serial}?' + 'c=${level1}' + '&dom=${ampdocUrl}' + '&enc=${documentCharset}' + diff --git a/extensions/amp-analytics/0.1/vendors/mediarithmics.js b/extensions/amp-analytics/0.1/vendors/mediarithmics.js index 2eba2e209a9a..5c61b8e36fa3 100644 --- a/extensions/amp-analytics/0.1/vendors/mediarithmics.js +++ b/extensions/amp-analytics/0.1/vendors/mediarithmics.js @@ -23,7 +23,8 @@ export const MEDIARITHMICS_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://${domain}', - 'pageview': '${host}/v1/visits/pixel?' + + 'pageview': + '${host}/v1/visits/pixel?' + '$site_token=${site_token}' + '&$url=${url}' + '&$ev=${event_name}' + diff --git a/extensions/amp-analytics/0.1/vendors/mediator.js b/extensions/amp-analytics/0.1/vendors/mediator.js index 66f8a4e44688..c56c07455134 100644 --- a/extensions/amp-analytics/0.1/vendors/mediator.js +++ b/extensions/amp-analytics/0.1/vendors/mediator.js @@ -18,8 +18,7 @@ export const MEDIATOR_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': '//collector.mediator.media/script/${mediator_id}/amp/', 'renderstart': '${host}init/?url=${canonicalUrl}', - 'prefix': '${host}register/?url=${canonicalUrl}' + - '&ref=${documentReferrer}&', + 'prefix': '${host}register/?url=${canonicalUrl}&ref=${documentReferrer}&', 'suffix': 'vh=${viewportHeight}&sh=${scrollHeight}&st=${scrollTop}', 'pageview': '${prefix}e=v', 'timer': '${prefix}e=t&${suffix}', @@ -43,36 +42,28 @@ export const MEDIATOR_CONFIG = /** @type {!JsonObject} */ ({ 'scrollPing0': { 'on': 'scroll', 'scrollSpec': { - 'verticalBoundaries': [ - 5, - ], + 'verticalBoundaries': [5], }, 'request': 's0', }, 'scrollPing1': { 'on': 'scroll', 'scrollSpec': { - 'verticalBoundaries': [ - 35, - ], + 'verticalBoundaries': [35], }, 'request': 's1', }, 'scrollPing2': { 'on': 'scroll', 'scrollSpec': { - 'verticalBoundaries': [ - 65, - ], + 'verticalBoundaries': [65], }, 'request': 's2', }, 'scrollPing3': { 'on': 'scroll', 'scrollSpec': { - 'verticalBoundaries': [ - 95, - ], + 'verticalBoundaries': [95], }, 'request': 's3', }, diff --git a/extensions/amp-analytics/0.1/vendors/metrika.js b/extensions/amp-analytics/0.1/vendors/metrika.js index 9f86735439f6..c02dffd57364 100644 --- a/extensions/amp-analytics/0.1/vendors/metrika.js +++ b/extensions/amp-analytics/0.1/vendors/metrika.js @@ -18,16 +18,18 @@ export const METRIKA_CONFIG = /** @type {!JsonObject} */ ({ 'transport': {'beacon': true, 'xhrpost': true, 'image': false}, 'requests': { 'pageview': '${_watch}?browser-info=${_brInfo}&${_siteInfo}&${_suffix}', - 'notBounce': '${_watch}?browser-info=ar%3A1%3Anb%3A1%3A${_brInfo}' + - '&${_suffix}', + 'notBounce': + '${_watch}?browser-info=ar%3A1%3Anb%3A1%3A${_brInfo}&${_suffix}', 'externalLink': '${_watch}?browser-info=ln%3A1%3A${_brInfo}&${_suffix}', - 'reachGoal': '${_watch}?browser-info=ar%3A1%3A${_brInfo}&${_siteInfo}' + + 'reachGoal': + '${_watch}?browser-info=ar%3A1%3A${_brInfo}&${_siteInfo}' + '&${_goalSuffix}', '_domain': 'https://mc.yandex.ru', '_watch': '${_domain}/watch/${counterId}', '_suffix': 'page-url=${sourceUrl}&page-ref=${documentReferrer}', - '_goalSuffix': 'page-url=goal%3A%2F%2F${sourceHost}%2F${goalId}' + - '&page-ref=${sourceUrl}', + '_goalSuffix': + 'page-url=goal%3A%2F%2F${sourceHost}%2F${goalId}' + + '&page-ref=${sourceUrl}', '_techInfo': [ 'amp%3A1%3Az%3A${timezone}%3Ai%3A${timestamp}%3Arn%3A${random}', 'la%3A${browserLanguage}%3Aen%3A${documentCharset}', diff --git a/extensions/amp-analytics/0.1/vendors/moat.js b/extensions/amp-analytics/0.1/vendors/moat.js index 72ffbf34bc1c..b2b3e0bc91cf 100644 --- a/extensions/amp-analytics/0.1/vendors/moat.js +++ b/extensions/amp-analytics/0.1/vendors/moat.js @@ -19,95 +19,105 @@ export const MOAT_CONFIG = /** @type {!JsonObject} */ ({ 'element': ':root', }, 'requests': { - 'load': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'load', - 'pcode': '${pcode}', - 'l0t': '${l0t}', - 'acctType': '${acctType}', - 'adType': '${adType}', - 'qs': '${qs}', - 'element': { - 'src': '${htmlAttr(img,src,width)}', - 'viewer': '${viewer}', - }, - 'document': { - 'AMPDocumentHostname': '${ampdocHostname}', - 'AMPDocumentURL': '${ampdocUrl}', - 'canonicalHost': '${canonicalHost}', - 'canonicalHostname': '${canonicalHostname}', - 'canonicalPath': '${canonicalPath}', - 'canonicalURL': '${canonicalUrl}', - 'documentCharset': '${documentCharset}', - 'documentReferrer': '${documentReferrer}', - 'externalReferrer': '${externalReferrer}', - 'sourceURL': '${sourceUrl}', - 'sourceHost': '${sourceHost}', - 'sourceHostname': '${sourceHostname}', - 'sourcePath': '${sourcePath}', - 'title': '${title}', - 'viewer': '${viewer}', - }, - 'device': { - 'availableScreenHeight': '${availableScreenHeight}', - 'availableScreenWidth': '${availableScreenWidth}', - 'browserLanguage': '${browserLanguage}', - 'screenColorDepth': '${screenColorDepth}', - 'screenHeight': '${screenHeight}', - 'screenWidth': '${screenWidth}', - 'scrollHeight': '${scrollHeight}', - 'scrollWidth': '${scrollWidth}', - 'scrollLeft': '${scrollLeft}', - 'scrollTop': '${scrollTop}', - 'timezone': '${timezone}', - 'userAgent': '${userAgent}', + 'load': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'load', + 'pcode': '${pcode}', + 'l0t': '${l0t}', + 'acctType': '${acctType}', + 'adType': '${adType}', + 'qs': '${qs}', + 'element': { + 'src': '${htmlAttr(img,src,width)}', + 'viewer': '${viewer}', + }, + 'document': { + 'AMPDocumentHostname': '${ampdocHostname}', + 'AMPDocumentURL': '${ampdocUrl}', + 'canonicalHost': '${canonicalHost}', + 'canonicalHostname': '${canonicalHostname}', + 'canonicalPath': '${canonicalPath}', + 'canonicalURL': '${canonicalUrl}', + 'documentCharset': '${documentCharset}', + 'documentReferrer': '${documentReferrer}', + 'externalReferrer': '${externalReferrer}', + 'sourceURL': '${sourceUrl}', + 'sourceHost': '${sourceHost}', + 'sourceHostname': '${sourceHostname}', + 'sourcePath': '${sourcePath}', + 'title': '${title}', + 'viewer': '${viewer}', + }, + 'device': { + 'availableScreenHeight': '${availableScreenHeight}', + 'availableScreenWidth': '${availableScreenWidth}', + 'browserLanguage': '${browserLanguage}', + 'screenColorDepth': '${screenColorDepth}', + 'screenHeight': '${screenHeight}', + 'screenWidth': '${screenWidth}', + 'scrollHeight': '${scrollHeight}', + 'scrollWidth': '${scrollWidth}', + 'scrollLeft': '${scrollLeft}', + 'scrollTop': '${scrollTop}', + 'timezone': '${timezone}', + 'userAgent': '${userAgent}', + 'viewportHeight': '${viewportHeight}', + 'viewportWidth': '${viewportWidth}', + }, + 'requestCount': '${requestCount}', + 'timeStamp': '${timestamp}', + }) + ), + 'unload': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'unload', + 'pcode': '${pcode}', + 'l0t': '${l0t}', + 'requestCount': '${requestCount}', + 'timeStamp': '${timestamp}', + }) + ), + 'click': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'click', + 'pcode': '${pcode}', + 'l0t': '${l0t}', + 'requestCount': '${requestCount}', + 'timeStamp': '${timestamp}', + }) + ), + 'viewability': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'viewability', + 'pcode': '${pcode}', + 'l0t': '${l0t}', + 'backgroundState': '${backgroundState}', + 'intersectionRect': '${intersectionRect}', + 'intersectionRatio': '${intersectionRatio}', + 'maxVisiblePercentage': '${maxVisiblePercentage}', + 'minVisiblePercentage': '${minVisiblePercentage}', + 'x': '${elementX}', + 'y': '${elementY}', + 'height': '${elementHeight}', + 'width': '${elementWidth}', 'viewportHeight': '${viewportHeight}', 'viewportWidth': '${viewportWidth}', - }, - 'requestCount': '${requestCount}', - 'timeStamp': '${timestamp}', - })), - 'unload': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'unload', - 'pcode': '${pcode}', - 'l0t': '${l0t}', - 'requestCount': '${requestCount}', - 'timeStamp': '${timestamp}', - })), - 'click': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'click', - 'pcode': '${pcode}', - 'l0t': '${l0t}', - 'requestCount': '${requestCount}', - 'timeStamp': '${timestamp}', - })), - 'viewability': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'viewability', - 'pcode': '${pcode}', - 'l0t': '${l0t}', - 'backgroundState': '${backgroundState}', - 'intersectionRect': '${intersectionRect}', - 'intersectionRatio': '${intersectionRatio}', - 'maxVisiblePercentage': '${maxVisiblePercentage}', - 'minVisiblePercentage': '${minVisiblePercentage}', - 'x': '${elementX}', - 'y': '${elementY}', - 'height': '${elementHeight}', - 'width': '${elementWidth}', - 'viewportHeight': '${viewportHeight}', - 'viewportWidth': '${viewportWidth}', - 'opacity': '${opacity}', - 'timeStamp': '${timestamp}', - 'requestCount': '${requestCount}', - })), - 'iframe': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'iframe', - 'pcode': '${pcode}', - 'height': '${elementHeight}', - 'width': '${elementWidth}', - 'x': '${elementX}', - 'y': '${elementY}', - 'requestCount': '${requestCount}', - })), + 'opacity': '${opacity}', + 'timeStamp': '${timestamp}', + 'requestCount': '${requestCount}', + }) + ), + 'iframe': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'iframe', + 'pcode': '${pcode}', + 'height': '${elementHeight}', + 'width': '${elementWidth}', + 'x': '${elementX}', + 'y': '${elementY}', + 'requestCount': '${requestCount}', + }) + ), }, 'triggers': { 'load': { @@ -131,10 +141,28 @@ export const MOAT_CONFIG = /** @type {!JsonObject} */ ({ 'visibilitySpec': { 'repeat': true, 'visiblePercentageThresholds': [ - [0,0],[0,5],[5,10],[10,15],[15,20],[20,25], - [25,30],[30,35],[35,40],[40,45],[45,50], - [50,55],[55,60],[60,65],[65,70],[70,75], - [75,80],[80,85],[85,90],[90,95],[95,100],[100,100], + [0, 0], + [0, 5], + [5, 10], + [10, 15], + [15, 20], + [20, 25], + [25, 30], + [30, 35], + [35, 40], + [40, 45], + [45, 50], + [50, 55], + [55, 60], + [60, 65], + [65, 70], + [70, 75], + [75, 80], + [80, 85], + [85, 90], + [90, 95], + [95, 100], + [100, 100], ], }, }, @@ -144,7 +172,7 @@ export const MOAT_CONFIG = /** @type {!JsonObject} */ ({ 'request': 'iframe', 'visibilitySpec': { 'repeat': true, - 'visiblePercentageThresholds': [[0,0]], + 'visiblePercentageThresholds': [[0, 0]], }, }, }, diff --git a/extensions/amp-analytics/0.1/vendors/mobify.js b/extensions/amp-analytics/0.1/vendors/mobify.js index b417546f6fa8..717bc272d08d 100644 --- a/extensions/amp-analytics/0.1/vendors/mobify.js +++ b/extensions/amp-analytics/0.1/vendors/mobify.js @@ -31,18 +31,22 @@ export const MOBIFY_CONFIG = /** @type {!JsonObject} */ ({ '%22referrer%22%3a%22${documentReferrer}%22', '%22templateName%22%3a%22${templateName}%22', ].join('%2c'), - '_basePrefix': '${_host}/s.gif?' + + '_basePrefix': + '${_host}/s.gif?' + 'slug=${projectSlug}&' + 'timestamp_local=${timestamp}&' + 'channel=web&' + 'dimensions=%7b${_dimensions}%7d', - 'ampstart': '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + + 'ampstart': + '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + '%22action%22%3a%22ampStart%22%2c%22value%22' + '%3a${navTiming(navigationStart,domLoading)}%7d', 'pageview': '${_basePrefix}&data=%7b%22action%22%3a%22pageview%22%7d', - 'pageload': '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + + 'pageload': + '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + '%22action%22%3a%22load%22%2c%22value%22%3a${pageLoadTime}%7d', - 'pagedcl': '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + + 'pagedcl': + '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + '%22action%22%3a%22DOMContentLoaded%22%2c%22value%22' + '%3a${contentLoadTime}%7d', }, diff --git a/extensions/amp-analytics/0.1/vendors/mparticle.js b/extensions/amp-analytics/0.1/vendors/mparticle.js index 6ffe57363a53..5ad33f93ba51 100644 --- a/extensions/amp-analytics/0.1/vendors/mparticle.js +++ b/extensions/amp-analytics/0.1/vendors/mparticle.js @@ -23,31 +23,34 @@ export const MPARTICLE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://pixels.mparticle.com', 'endpointPath': '/v1/${apiKey}/Pixel', - 'baseParams': 'et=${eventType}&' + - 'amp_id=${amp_clientId}&' + - 'attrs_k=${eventAttributes_Keys}&' + - 'attrs_v=${eventAttributes_Values}&' + - 'ua_k=${userAttributes_Keys}&' + - 'ua_v=${userAttributes_Values}&' + - 'ui_t=${userIdentities_Types}&' + - 'ui_v=${userIdentities_Values}&' + - 'flags_k=${customFlags_Keys}&' + - 'flags_v=${customFlags_Values}&' + - 'ct=${timestamp}&' + - 'dbg=${debug}&' + - 'lc=${location}&' + - 'av=${appVersion}', - 'pageview': '${host}${endpointPath}?' + - 'dt=ScreenView&' + - 'n=${pageName}&' + - 'hn=${ampdocUrl}&' + - 'ttl=${title}&' + - 'path=${canonicalPath}&' + - '${baseParams}', - 'event': '${host}${endpointPath}?' + - 'dt=AppEvent&' + - 'n=${eventName}&' + - '${baseParams}', + 'baseParams': + 'et=${eventType}&' + + 'amp_id=${amp_clientId}&' + + 'attrs_k=${eventAttributes_Keys}&' + + 'attrs_v=${eventAttributes_Values}&' + + 'ua_k=${userAttributes_Keys}&' + + 'ua_v=${userAttributes_Values}&' + + 'ui_t=${userIdentities_Types}&' + + 'ui_v=${userIdentities_Values}&' + + 'flags_k=${customFlags_Keys}&' + + 'flags_v=${customFlags_Values}&' + + 'ct=${timestamp}&' + + 'dbg=${debug}&' + + 'lc=${location}&' + + 'av=${appVersion}', + 'pageview': + '${host}${endpointPath}?' + + 'dt=ScreenView&' + + 'n=${pageName}&' + + 'hn=${ampdocUrl}&' + + 'ttl=${title}&' + + 'path=${canonicalPath}&' + + '${baseParams}', + 'event': + '${host}${endpointPath}?' + + 'dt=AppEvent&' + + 'n=${eventName}&' + + '${baseParams}', }, 'transport': { 'beacon': false, diff --git a/extensions/amp-analytics/0.1/vendors/mpulse.js b/extensions/amp-analytics/0.1/vendors/mpulse.js index c327613b2072..8dfe7bcae27a 100644 --- a/extensions/amp-analytics/0.1/vendors/mpulse.js +++ b/extensions/amp-analytics/0.1/vendors/mpulse.js @@ -16,7 +16,8 @@ export const MPULSE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'onvisible': 'https://${beacon_url}?' + + 'onvisible': + 'https://${beacon_url}?' + 'h.d=${h.d}' + '&h.key=${h.key}' + '&h.t=${h.t}' + diff --git a/extensions/amp-analytics/0.1/vendors/navegg.js b/extensions/amp-analytics/0.1/vendors/navegg.js index 9cdea202e32f..f2fa45e83501 100644 --- a/extensions/amp-analytics/0.1/vendors/navegg.js +++ b/extensions/amp-analytics/0.1/vendors/navegg.js @@ -18,13 +18,13 @@ export const NAVEGG_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'pageview': 'https://amp.navdmp.com/amp?' + - 'aid=${clientId(navegg_id)}&' + - 'url=${canonicalUrl}&' + - 'ref=${documentReferrer}&' + - 'tit=${title}&' + - 'lan=${browserLanguage}' + - '&acc=${account}&' + - 'v=7', + 'aid=${clientId(navegg_id)}&' + + 'url=${canonicalUrl}&' + + 'ref=${documentReferrer}&' + + 'tit=${title}&' + + 'lan=${browserLanguage}' + + '&acc=${account}&' + + 'v=7', }, 'triggers': { 'trackpageview': { diff --git a/extensions/amp-analytics/0.1/vendors/newrelic.js b/extensions/amp-analytics/0.1/vendors/newrelic.js index 2d7a7839d3bb..ef879f0f0666 100644 --- a/extensions/amp-analytics/0.1/vendors/newrelic.js +++ b/extensions/amp-analytics/0.1/vendors/newrelic.js @@ -16,12 +16,13 @@ export const NEWRELIC_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'pageview': 'https://${beacon}/amp?appId=${appId}' + + 'pageview': + 'https://${beacon}/amp?appId=${appId}' + '&licenseKey=${licenseKey}' + '&Url=${ampdocUrl}' + '&canonicalUrl=${canonicalUrl}' + '&timeToDomContentLoadedEventEnd=' + - '${navTiming(domContentLoadedEventEnd)}' + + '${navTiming(domContentLoadedEventEnd)}' + '&timeToDomInteractive=${navTiming(domInteractive)}' + '&timeToDomComplete=${navTiming(domComplete)}' + '&timeToDomLoading=${navTiming(domLoading)}' + diff --git a/extensions/amp-analytics/0.1/vendors/nielsen.js b/extensions/amp-analytics/0.1/vendors/nielsen.js index 6596f6d09a26..ed886a95386e 100644 --- a/extensions/amp-analytics/0.1/vendors/nielsen.js +++ b/extensions/amp-analytics/0.1/vendors/nielsen.js @@ -20,8 +20,10 @@ export const NIELSEN_CONFIG = /** @type {!JsonObject} */ ({ 'prefix': '', }, 'requests': { - 'session': 'https://${prefix}uaid-linkage.imrworldwide.com/cgi-bin/gn?prd=session&c13=asid,P${apid}&sessionId=${sessionId}_${pageViewId}&pingtype=4&enc=false&c61=createtm,${timestamp}&rnd=${random}', - 'cloudapi': 'https://${prefix}cloudapi.imrworldwide.com/nmapi/v2/${apid}/${sessionId}_${pageViewId}/a?b=%7B%22devInfo%22%3A%7B%22devId%22%3A%22${sessionId}_${pageViewId}%22%2C%22apn%22%3A%22${apn}%22%2C%22apv%22%3A%22${apv}%22%2C%22apid%22%3A%22${apid}%22%7D%2C%22metadata%22%3A%7B%22static%22%3A%7B%22type%22%3A%22static%22%2C%22section%22%3A%22${section}%22%2C%22assetid%22%3A%22${pageViewId}%22%2C%22segA%22%3A%22${segA}%22%2C%22segB%22%3A%22${segB}%22%2C%22segC%22%3A%22${segC}%22%2C%22adModel%22%3A%220%22%2C%22dataSrc%22%3A%22cms%22%7D%2C%22content%22%3A%7B%7D%2C%22ad%22%3A%7B%7D%7D%2C%22event%22%3A%22playhead%22%2C%22position%22%3A%22${timestamp}%22%2C%22data%22%3A%7B%22hidden%22%3A%22${backgroundState}%22%2C%22blur%22%3A%22${backgroundState}%22%2C%22position%22%3A%22${timestamp}%22%7D%2C%22type%22%3A%22static%22%2C%22utc%22%3A%22${timestamp}%22%2C%22index%22%3A%22${requestCount}%22%7D', + 'session': + 'https://${prefix}uaid-linkage.imrworldwide.com/cgi-bin/gn?prd=session&c13=asid,P${apid}&sessionId=${sessionId}_${pageViewId}&pingtype=4&enc=false&c61=createtm,${timestamp}&rnd=${random}', + 'cloudapi': + 'https://${prefix}cloudapi.imrworldwide.com/nmapi/v2/${apid}/${sessionId}_${pageViewId}/a?b=%7B%22devInfo%22%3A%7B%22devId%22%3A%22${sessionId}_${pageViewId}%22%2C%22apn%22%3A%22${apn}%22%2C%22apv%22%3A%22${apv}%22%2C%22apid%22%3A%22${apid}%22%7D%2C%22metadata%22%3A%7B%22static%22%3A%7B%22type%22%3A%22static%22%2C%22section%22%3A%22${section}%22%2C%22assetid%22%3A%22${pageViewId}%22%2C%22segA%22%3A%22${segA}%22%2C%22segB%22%3A%22${segB}%22%2C%22segC%22%3A%22${segC}%22%2C%22adModel%22%3A%220%22%2C%22dataSrc%22%3A%22cms%22%7D%2C%22content%22%3A%7B%7D%2C%22ad%22%3A%7B%7D%7D%2C%22event%22%3A%22playhead%22%2C%22position%22%3A%22${timestamp}%22%2C%22data%22%3A%7B%22hidden%22%3A%22${backgroundState}%22%2C%22blur%22%3A%22${backgroundState}%22%2C%22position%22%3A%22${timestamp}%22%7D%2C%22type%22%3A%22static%22%2C%22utc%22%3A%22${timestamp}%22%2C%22index%22%3A%22${requestCount}%22%7D', }, 'triggers': { 'visible': { diff --git a/extensions/amp-analytics/0.1/vendors/oewa.js b/extensions/amp-analytics/0.1/vendors/oewa.js index ddb5233c701a..d4e0b9c032d6 100644 --- a/extensions/amp-analytics/0.1/vendors/oewa.js +++ b/extensions/amp-analytics/0.1/vendors/oewa.js @@ -17,7 +17,8 @@ export const OEWA_CONFIG = /** @type {!JsonObject} */ ({ 'transport': {'beacon': false, 'xhrpost': false, 'image': true}, 'requests': { - 'pageview': '${url}?s=${s}' + + 'pageview': + '${url}?s=${s}' + '&=1' + '&cp=${cp}' + '&host=${canonicalHost}' + diff --git a/extensions/amp-analytics/0.1/vendors/oewadirect.js b/extensions/amp-analytics/0.1/vendors/oewadirect.js index 1f03e1d139f5..fa8e8596175b 100644 --- a/extensions/amp-analytics/0.1/vendors/oewadirect.js +++ b/extensions/amp-analytics/0.1/vendors/oewadirect.js @@ -17,7 +17,8 @@ export const OEWADIRECT_CONFIG = /** @type {!JsonObject} */ ({ 'transport': {'beacon': false, 'xhrpost': false, 'image': true}, 'requests': { - 'pageview': 'https://${s}.oewabox.at/j0=,,,r=${canonicalUrl};+,amp=1+cp=${cp}+ssl=1+hn=${canonicalHost};;;?lt=${pageViewId}&x=${screenWidth}x${screenHeight}x24&c=CLIENT_ID(oewa)', + 'pageview': + 'https://${s}.oewabox.at/j0=,,,r=${canonicalUrl};+,amp=1+cp=${cp}+ssl=1+hn=${canonicalHost};;;?lt=${pageViewId}&x=${screenWidth}x${screenHeight}x24&c=CLIENT_ID(oewa)', }, 'triggers': { 'pageview': { diff --git a/extensions/amp-analytics/0.1/vendors/parsely.js b/extensions/amp-analytics/0.1/vendors/parsely.js index b4c0967f93a0..8ac942be4fbe 100644 --- a/extensions/amp-analytics/0.1/vendors/parsely.js +++ b/extensions/amp-analytics/0.1/vendors/parsely.js @@ -17,21 +17,24 @@ export const PARSELY_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://srv.pixel.parsely.com', - 'basePrefix': '${host}/plogger/?' + + 'basePrefix': + '${host}/plogger/?' + 'rand=${timestamp}&' + 'idsite=${apikey}&' + 'url=${ampdocUrl}&' + 'urlref=${documentReferrer}&' + 'screen=${screenWidth}x${screenHeight}%7C' + - '${availableScreenWidth}x${availableScreenHeight}%7C' + - '${screenColorDepth}&' + + '${availableScreenWidth}x${availableScreenHeight}%7C' + + '${screenColorDepth}&' + 'title=${title}&' + 'date=${timestamp}&' + 'ampid=${clientId(_parsely_visitor)}', - 'pageview': '${basePrefix}&action=pageview&metadata=' + - '{\"canonical_url\":\"${canonicalUrl}\"}', - 'heartbeat': '${basePrefix}&action=heartbeat' + - '&tt=${totalEngagedTime}&inc=${incrementalEngagedTime(parsely-js)}', + 'pageview': + '${basePrefix}&action=pageview&metadata=' + + '{"canonical_url":"${canonicalUrl}"}', + 'heartbeat': + '${basePrefix}&action=heartbeat' + + '&tt=${totalEngagedTime}&inc=${incrementalEngagedTime(parsely-js)}', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/permutive.js b/extensions/amp-analytics/0.1/vendors/permutive.js index 482a0a03243b..514f8f0ad7af 100644 --- a/extensions/amp-analytics/0.1/vendors/permutive.js +++ b/extensions/amp-analytics/0.1/vendors/permutive.js @@ -19,20 +19,18 @@ export const PERMUTIVE_CONFIG = /** @type {!JsonObject} */ ({ 'identity': '${clientId(_ga)}', }, 'requests': { - 'track': 'https://${namespace}.amp.permutive.com/track' + + 'track': + 'https://${namespace}.amp.permutive.com/track' + '?k=${key}' + '&i=${identity}' + '&it=amp', - 'pageview': '${track}' + + 'pageview': + '${track}' + '&e=Pageview' + '&_ep_isp_info=%24ip_isp_info' + '&_ep_geo_info=%24ip_geo_info', - 'engagement': '${track}' + - '&e=PageviewEngagement' + - '&_ep_engaged_time=5', - 'completion': '${track}' + - '&e=PageviewEngagement' + - '&_ep_completion=0.25', + 'engagement': '${track}&e=PageviewEngagement&_ep_engaged_time=5', + 'completion': '${track}&e=PageviewEngagement&_ep_completion=0.25', }, 'triggers': { 'trackPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/piStats.js b/extensions/amp-analytics/0.1/vendors/piStats.js index 381a5d63f670..c33d89263922 100644 --- a/extensions/amp-analytics/0.1/vendors/piStats.js +++ b/extensions/amp-analytics/0.1/vendors/piStats.js @@ -17,19 +17,20 @@ export const PISTATS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://events.pi-stats.com', - 'basePrefix': '${host}/eventsamp/?' + - 'e=PageLoad&' + - 'pid=${property}&' + - 'url=${ampdocUrl}&' + - 'cnt=${cntId}&' + - 'lang=${language}&' + - 'ref=${documentReferrer}&' + - 'id=${clientId(piStatsDEVICEID)}&' + - 'ua=${userAgent}&' + - 'ctype=web&' + - 'blang=${browserLanguage}&' + - 'v=2.0&' + - 'dist=Javascript', + 'basePrefix': + '${host}/eventsamp/?' + + 'e=PageLoad&' + + 'pid=${property}&' + + 'url=${ampdocUrl}&' + + 'cnt=${cntId}&' + + 'lang=${language}&' + + 'ref=${documentReferrer}&' + + 'id=${clientId(piStatsDEVICEID)}&' + + 'ua=${userAgent}&' + + 'ctype=web&' + + 'blang=${browserLanguage}&' + + 'v=2.0&' + + 'dist=Javascript', 'pageview': '${basePrefix}&eventtype=pageview', }, 'triggers': { diff --git a/extensions/amp-analytics/0.1/vendors/piano.js b/extensions/amp-analytics/0.1/vendors/piano.js index 291a605b72ca..105e310df5f9 100644 --- a/extensions/amp-analytics/0.1/vendors/piano.js +++ b/extensions/amp-analytics/0.1/vendors/piano.js @@ -18,9 +18,11 @@ export const PIANO_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://api-v3.tinypass.com', 'basePrefix': '/api/v3', - 'baseSuffix': '&pageview_id=${pageViewId}&rand=${random}&' + + 'baseSuffix': + '&pageview_id=${pageViewId}&rand=${random}&' + 'amp_client_id=${clientId}&aid=${aid}', - 'pageview': '${host}${basePrefix}/page/track?url=${canonicalUrl}&' + + 'pageview': + '${host}${basePrefix}/page/track?url=${canonicalUrl}&' + 'referer=${documentReferrer}&content_created=${contentCreated}&' + 'content_author=${contentAuthor}&content_section=${contentSection}&' + 'timezone_offset=${timezone}&tags=${tags}&_url=${ampdocUrl}&' + diff --git a/extensions/amp-analytics/0.1/vendors/pinpoll.js b/extensions/amp-analytics/0.1/vendors/pinpoll.js index 030e4b958f70..0e548ef6dbf0 100644 --- a/extensions/amp-analytics/0.1/vendors/pinpoll.js +++ b/extensions/amp-analytics/0.1/vendors/pinpoll.js @@ -16,7 +16,8 @@ export const PINPOLL_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'pageview': '${protocol}://${host}/${version}?' + + 'pageview': + '${protocol}://${host}/${version}?' + 'url=${sourceUrl}&' + 'sourceHost=${sourceHost}&' + 'sourceHostname=${sourceHostname}&' + diff --git a/extensions/amp-analytics/0.1/vendors/pressboard.js b/extensions/amp-analytics/0.1/vendors/pressboard.js index be0cb359bd97..1a67b1477c0f 100644 --- a/extensions/amp-analytics/0.1/vendors/pressboard.js +++ b/extensions/amp-analytics/0.1/vendors/pressboard.js @@ -29,7 +29,8 @@ export const PRESSBOARD_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://adserver.pressboard.ca', - 'common_params': '&=1&url=${canonicalUrl}' + + 'common_params': + '&=1&url=${canonicalUrl}' + '&referrer=${documentReferrer}' + '&ts=${timestamp}' + '&ua=${userAgent}' + @@ -38,7 +39,8 @@ export const PRESSBOARD_CONFIG = /** @type {!JsonObject} */ ({ '&mid=${mediaId}&cid=${campaignId}&sid=${storyRequestId}' + '&geoid=${geoNameId}&cn=${country}&rg=${region}&ct=${city}' + '&dbi=${dbInstance}&tz=${timeZoneOffset}', - 'conversion_params': '&hbt=${requestCount}' + + 'conversion_params': + '&hbt=${requestCount}' + '&pvid=${pageViewId}' + '&asurl=${sourceUrl}' + '&ash=${scrollHeight}' + @@ -47,7 +49,8 @@ export const PRESSBOARD_CONFIG = /** @type {!JsonObject} */ ({ '&avh=${viewportHeight}' + '&ast=${scrollTop}' + '&atet=${totalEngagedTime}', - 'conversion': '${host}' + + 'conversion': + '${host}' + '/track/attention-amp?' + '${common_params}' + '${conversion_params}', diff --git a/extensions/amp-analytics/0.1/vendors/quantcast.js b/extensions/amp-analytics/0.1/vendors/quantcast.js index 5c26ca874603..178ca4f66fc5 100644 --- a/extensions/amp-analytics/0.1/vendors/quantcast.js +++ b/extensions/amp-analytics/0.1/vendors/quantcast.js @@ -20,7 +20,8 @@ export const QUANTCAST_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://pixel.quantserve.com/pixel', - 'pageview': '${host};r=${random};a=${pcode};labels=${labels};' + + 'pageview': + '${host};r=${random};a=${pcode};labels=${labels};' + 'fpan=;fpa=${clientId(__qca)};ns=0;ce=1;cm=;je=0;' + 'sr=${screenWidth}x${screenHeight}x${screenColorDepth};' + 'enc=n;et=${timestamp};ref=${documentReferrer};url=${canonicalUrl}', diff --git a/extensions/amp-analytics/0.1/vendors/rakam.js b/extensions/amp-analytics/0.1/vendors/rakam.js index de2da5421410..3738987c2a19 100644 --- a/extensions/amp-analytics/0.1/vendors/rakam.js +++ b/extensions/amp-analytics/0.1/vendors/rakam.js @@ -19,7 +19,8 @@ export const RAKAM_CONFIG = /** @type {!JsonObject} */ ({ 'deviceId': 'CLIENT_ID(rakam_device_id)', }, 'requests': { - 'base': '?api.api_key=${writeKey}' + + 'base': + '?api.api_key=${writeKey}' + '&prop._platform=amp' + '&prop._device_id=${deviceId}' + '&prop.locale=${browserLanguage}' + @@ -31,7 +32,9 @@ export const RAKAM_CONFIG = /** @type {!JsonObject} */ ({ '&prop.timezone=${timezone}' + '&prop._time=${timestamp}' + '&prop.resolution=${screenWidth} × ${screenHeight}', - 'pageview': 'https://${apiEndpoint}/event/pixel${base}&collection=${pageViewName}', - 'custom': 'https://${apiEndpoint}/event/pixel${base}&collection=${collection}', + 'pageview': + 'https://${apiEndpoint}/event/pixel${base}&collection=${pageViewName}', + 'custom': + 'https://${apiEndpoint}/event/pixel${base}&collection=${collection}', }, }); diff --git a/extensions/amp-analytics/0.1/vendors/reppublika.js b/extensions/amp-analytics/0.1/vendors/reppublika.js index 7a631f0b4686..8fb797407cc2 100644 --- a/extensions/amp-analytics/0.1/vendors/reppublika.js +++ b/extensions/amp-analytics/0.1/vendors/reppublika.js @@ -18,7 +18,8 @@ export const REPPUBLIKA_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://t5.mindtake.com', 'basePrefix': '/tag/cid/', - 'baseSuffix': 'Service=${service}&Category=${category}&' + + 'baseSuffix': + 'Service=${service}&Category=${category}&' + 'Url=${sourceUrl}&Device=${device}&uid=${random}', 'pageview': '${host}${basePrefix}${code}/track.gif?${baseSuffix}', }, diff --git a/extensions/amp-analytics/0.1/vendors/retargetly.js b/extensions/amp-analytics/0.1/vendors/retargetly.js index 399aa93b24d4..581a160df7e0 100644 --- a/extensions/amp-analytics/0.1/vendors/retargetly.js +++ b/extensions/amp-analytics/0.1/vendors/retargetly.js @@ -17,9 +17,10 @@ export const RETARGETLY_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://api.retargetly.com', - 'page': '${host}/api?id=${accountId}&src=${sourceId}&url=${sourceUrl}' + - '&n=${title}&ref=${documentReferrer}&ua=${userAgent}' + - '&random=${random}&bl=${browserLanguage}&source=amp', + 'page': + '${host}/api?id=${accountId}&src=${sourceId}&url=${sourceUrl}' + + '&n=${title}&ref=${documentReferrer}&ua=${userAgent}' + + '&random=${random}&bl=${browserLanguage}&source=amp', }, 'transport': { 'beacon': false, @@ -33,5 +34,3 @@ export const RETARGETLY_CONFIG = /** @type {!JsonObject} */ ({ }, }, }); - - diff --git a/extensions/amp-analytics/0.1/vendors/segment.js b/extensions/amp-analytics/0.1/vendors/segment.js index 49667f4eccce..c6e3f4bbf1eb 100644 --- a/extensions/amp-analytics/0.1/vendors/segment.js +++ b/extensions/amp-analytics/0.1/vendors/segment.js @@ -25,7 +25,8 @@ export const SEGMENT_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://api.segment.io/v1/pixel', - 'base': '?writeKey=${writeKey}' + + 'base': + '?writeKey=${writeKey}' + '&context.library.name=amp' + '&anonymousId=${anonymousId}' + '&context.locale=${browserLanguage}' + diff --git a/extensions/amp-analytics/0.1/vendors/shinystat.js b/extensions/amp-analytics/0.1/vendors/shinystat.js index 32161d6313b5..f0d37662b916 100644 --- a/extensions/amp-analytics/0.1/vendors/shinystat.js +++ b/extensions/amp-analytics/0.1/vendors/shinystat.js @@ -22,19 +22,19 @@ export const SHINYSTAT_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'base': 'https://amp.shinystat.com/cgi-bin/shinyamp.cgi', - 'commpar': 'AMP=1&RM=${random}' + - '&USER=${account}' + - '&PAG=${page}' + - '&HR=${sourceUrl}' + - '&REFER=${documentReferrer}' + - '&RES=${screenWidth}X${screenHeight}' + - '&COLOR=${screenColorDepth}' + - '&CID=${clientId(AMP_CID)}' + - '&PAGID=${pageViewId}' + - '&TITL=${title}' + - '&RQC=${requestCount}', - 'pagepar': '&VIE=${viewer}' + - '&PLT=${pageLoadTime}', + 'commpar': + 'AMP=1&RM=${random}' + + '&USER=${account}' + + '&PAG=${page}' + + '&HR=${sourceUrl}' + + '&REFER=${documentReferrer}' + + '&RES=${screenWidth}X${screenHeight}' + + '&COLOR=${screenColorDepth}' + + '&CID=${clientId(AMP_CID)}' + + '&PAGID=${pageViewId}' + + '&TITL=${title}' + + '&RQC=${requestCount}', + 'pagepar': '&VIE=${viewer}&PLT=${pageLoadTime}', 'eventpar': '&SSXL=1', 'linkpar': '&LINK=${outboundLink}', 'pageview': '${base}?${commpar}${pagepar}', diff --git a/extensions/amp-analytics/0.1/vendors/simplereach.js b/extensions/amp-analytics/0.1/vendors/simplereach.js index d33de25b8a30..b8a9270bf962 100644 --- a/extensions/amp-analytics/0.1/vendors/simplereach.js +++ b/extensions/amp-analytics/0.1/vendors/simplereach.js @@ -24,7 +24,8 @@ export const SIMPLEREACH_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://edge.simplereach.com', - 'baseParams': 'amp=true' + + 'baseParams': + 'amp=true' + '&pid=${pid}' + '&title=${title}' + '&url=${canonicalUrl}' + @@ -39,9 +40,7 @@ export const SIMPLEREACH_CONFIG = /** @type {!JsonObject} */ ({ '&article_id=${article_id}' + '&ignore_metadata=${ignore_metadata}', 'visible': '${host}/n?${baseParams}', - 'timer': '${host}/t?${baseParams}' + - '&t=5000' + - '&e=5000', + 'timer': '${host}/t?${baseParams}&t=5000&e=5000', }, 'triggers': { 'visible': { diff --git a/extensions/amp-analytics/0.1/vendors/snowplow.js b/extensions/amp-analytics/0.1/vendors/snowplow.js index 8f04a2ea7c07..4bc0a669f81a 100644 --- a/extensions/amp-analytics/0.1/vendors/snowplow.js +++ b/extensions/amp-analytics/0.1/vendors/snowplow.js @@ -20,18 +20,20 @@ export const SNOWPLOW_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'aaVersion': 'amp-0.2', - 'basePrefix': 'https://${collectorHost}/i?url=${canonicalUrl}&page=${title}&' + - 'res=${screenWidth}x${screenHeight}&stm=${timestamp}&' + - 'tz=${timezone}&aid=${appId}&p=web&tv=${aaVersion}&' + - 'cd=${screenColorDepth}&cs=${documentCharset}&' + - 'duid=${duid}&' + - 'lang=${browserLanguage}&refr=${documentReferrer}&stm=${timezone}&' + - 'vp=${viewportWidth}x${viewportHeight}', + 'basePrefix': + 'https://${collectorHost}/i?url=${canonicalUrl}&page=${title}&' + + 'res=${screenWidth}x${screenHeight}&stm=${timestamp}&' + + 'tz=${timezone}&aid=${appId}&p=web&tv=${aaVersion}&' + + 'cd=${screenColorDepth}&cs=${documentCharset}&' + + 'duid=${duid}&' + + 'lang=${browserLanguage}&refr=${documentReferrer}&stm=${timezone}&' + + 'vp=${viewportWidth}x${viewportHeight}', 'pageView': '${basePrefix}&e=pv', - 'structEvent': '${basePrefix}&e=se&' + - 'se_ca=${structEventCategory}&se_ac=${structEventAction}&' + - 'se_la=${structEventLabel}&se_pr=${structEventProperty}&' + - 'se_va=${structEventValue}', + 'structEvent': + '${basePrefix}&e=se&' + + 'se_ca=${structEventCategory}&se_ac=${structEventAction}&' + + 'se_la=${structEventLabel}&se_pr=${structEventProperty}&' + + 'se_va=${structEventValue}', }, 'transport': { 'beacon': false, diff --git a/extensions/amp-analytics/0.1/vendors/teaanalytics.js b/extensions/amp-analytics/0.1/vendors/teaanalytics.js index fb4956b5d119..977296771267 100644 --- a/extensions/amp-analytics/0.1/vendors/teaanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/teaanalytics.js @@ -22,27 +22,26 @@ export const TEAANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ requests: { domain: 'https://${channel}/v1/amp', commonParams: - 'user.user_unique_id=${userUniqueId}' + - '&header.app_id=${app_id}' + - '&header.language=${browserLanguage}' + - '&header.screen_height=${screenHeight}' + - '&header.screen_width=${screenWidth}' + - '&header.resolution=${screenHeight}x${screenWidth}' + - '&header.tz_offset=${timezone}' + - '&header.tz_name=${timezoneCode}' + - '&header.referrer=${documentReferrer}' + - '&header.custom.user_agent=${userAgent}' + - '&event.local_time_ms=${timestamp}' + - '&event.params._staging_flag=${debug}' + - '&verbose=${debug}', - base: '${domain}?' + - '${commonParams}' + - '&rnd=${random}', - pageview: '${base}' + - '&event=predefine_pageview' + - '&event.params.url=${sourceUrl}' + - '&event.params.url_path=${sourcePath}' + - '&event.params.title=${title}', + 'user.user_unique_id=${userUniqueId}' + + '&header.app_id=${app_id}' + + '&header.language=${browserLanguage}' + + '&header.screen_height=${screenHeight}' + + '&header.screen_width=${screenWidth}' + + '&header.resolution=${screenHeight}x${screenWidth}' + + '&header.tz_offset=${timezone}' + + '&header.tz_name=${timezoneCode}' + + '&header.referrer=${documentReferrer}' + + '&header.custom.user_agent=${userAgent}' + + '&event.local_time_ms=${timestamp}' + + '&event.params._staging_flag=${debug}' + + '&verbose=${debug}', + base: '${domain}?${commonParams}&rnd=${random}', + pageview: + '${base}' + + '&event=predefine_pageview' + + '&event.params.url=${sourceUrl}' + + '&event.params.url_path=${sourcePath}' + + '&event.params.title=${title}', event: '${base}', }, }); diff --git a/extensions/amp-analytics/0.1/vendors/tealiumcollect.js b/extensions/amp-analytics/0.1/vendors/tealiumcollect.js index 2fbe70c217c5..4539dbfbaadf 100644 --- a/extensions/amp-analytics/0.1/vendors/tealiumcollect.js +++ b/extensions/amp-analytics/0.1/vendors/tealiumcollect.js @@ -28,30 +28,33 @@ export const TEALIUMCOLLECT_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://collect.tealiumiq.com', - 'base': '${host}/event?${tealium}&' + - '${dom1}&${dom2}&${datetime}&' + - 'tealium_event=${tealium_event}&' + - 'amp_version=${ampVersion}&' + - 'amp_request_count=${requestCount}', - 'tealium': 'tealium_account=${account}&' + - 'tealium_profile=${profile}&' + - 'tealium_datasource=${datasource}&' + - 'tealium_visitor_id=${visitor_id}', - 'dom1': 'url=${sourceUrl}&doc_url=${ampdocUrl}&' + - 'domain=${sourceHost}&pathname=${sourcePath}&' + - 'amp_hostname=${ampdocHostname}&' + - 'canonical_hostname=${canonicalHostname}', - 'dom2': 'title=${title}&' + - 'viewport_width=${availableScreenWidth}&' + - 'viewport_height=${availableScreenHeight}', - 'datetime': 'timestamp=${timestamp}&' + - 'tz=${timezone}&lang=${browserLanguage}', - 'pageview': '${base}&referrer=${documentReferrer}&' + - 'screen_size=${screenWidth}x${screenHeight}&' + - 'content_load_ms=${contentLoadTime}&' + - 'page_view_id=${pageViewId}', - 'event': '${base}&' + - 'scroll_y=${scrollTop}&scroll_x=${scrollLeft}', + 'base': + '${host}/event?${tealium}&' + + '${dom1}&${dom2}&${datetime}&' + + 'tealium_event=${tealium_event}&' + + 'amp_version=${ampVersion}&' + + 'amp_request_count=${requestCount}', + 'tealium': + 'tealium_account=${account}&' + + 'tealium_profile=${profile}&' + + 'tealium_datasource=${datasource}&' + + 'tealium_visitor_id=${visitor_id}', + 'dom1': + 'url=${sourceUrl}&doc_url=${ampdocUrl}&' + + 'domain=${sourceHost}&pathname=${sourcePath}&' + + 'amp_hostname=${ampdocHostname}&' + + 'canonical_hostname=${canonicalHostname}', + 'dom2': + 'title=${title}&' + + 'viewport_width=${availableScreenWidth}&' + + 'viewport_height=${availableScreenHeight}', + 'datetime': 'timestamp=${timestamp}&tz=${timezone}&lang=${browserLanguage}', + 'pageview': + '${base}&referrer=${documentReferrer}&' + + 'screen_size=${screenWidth}x${screenHeight}&' + + 'content_load_ms=${contentLoadTime}&' + + 'page_view_id=${pageViewId}', + 'event': '${base}&scroll_y=${scrollTop}&scroll_x=${scrollLeft}', }, 'triggers': { 'defaultPageview': { @@ -63,4 +66,3 @@ export const TEALIUMCOLLECT_CONFIG = /** @type {!JsonObject} */ ({ }, }, }); - diff --git a/extensions/amp-analytics/0.1/vendors/top100.js b/extensions/amp-analytics/0.1/vendors/top100.js index ad01f0d02947..57985d364fe9 100644 --- a/extensions/amp-analytics/0.1/vendors/top100.js +++ b/extensions/amp-analytics/0.1/vendors/top100.js @@ -23,23 +23,24 @@ export const TOP100_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://kraken.rambler.ru', - 'base': '${host}/cnt/?pid=${pid}' + - '&rid=${rid}' + - '&v=${version}' + - '&rn=${random}' + - '&ruid=${ruid}' + - '&ct=amp', - 'pageview': '${base}&et=pv' + - '${_pageData}' + - '${_screenData}', - '_screenData': '&sr=${screenWidth}x${screenHeight}' + - '&cd=${screenColorDepth}-bit' + - '&bs=${scrollWidth}x${scrollHeight}', - '_pageData': '&pt=${title}' + - '&rf=${documentReferrer}' + - '&en=${documentCharset}' + - '&la=${browserLanguage}' + - '&tz=${timezone}', + 'base': + '${host}/cnt/?pid=${pid}' + + '&rid=${rid}' + + '&v=${version}' + + '&rn=${random}' + + '&ruid=${ruid}' + + '&ct=amp', + 'pageview': '${base}&et=pv${_pageData}${_screenData}', + '_screenData': + '&sr=${screenWidth}x${screenHeight}' + + '&cd=${screenColorDepth}-bit' + + '&bs=${scrollWidth}x${scrollHeight}', + '_pageData': + '&pt=${title}' + + '&rf=${documentReferrer}' + + '&en=${documentCharset}' + + '&la=${browserLanguage}' + + '&tz=${timezone}', }, 'triggers': { 'trackPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/topmailru.js b/extensions/amp-analytics/0.1/vendors/topmailru.js index 995062c0d447..26da8bc32850 100644 --- a/extensions/amp-analytics/0.1/vendors/topmailru.js +++ b/extensions/amp-analytics/0.1/vendors/topmailru.js @@ -26,17 +26,20 @@ export const TOPMAILRU_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'pageView': '${_domain}/counter?${_basicMessage};title=${title}', - 'reachGoal': '${_domain}/tracker?${_basicMessage};title=${title}' + - ';e=RG%3A${value}%2F${goal}', - 'sendEvent': '${_domain}/tracker?${_basicMessage}' + - ';e=CE%3A${value}%2F${category}%3B${action}%3B${label}', + 'reachGoal': + '${_domain}/tracker?${_basicMessage};title=${title}' + + ';e=RG%3A${value}%2F${goal}', + 'sendEvent': + '${_domain}/tracker?${_basicMessage}' + + ';e=CE%3A${value}%2F${category}%3B${action}%3B${label}', '_domain': 'https://top-fwz1.mail.ru', - '_basicMessage': 'js=13;id=${id};u=${url};r=${referrer}' + - ';s=${screenWidth}*${screenHeight}' + - ';vp=${viewportWidth}*${viewportHeight}' + - ';st=${start};gender=${gender};age=${age}' + - ';pid=${pid};userid=${userid};device=${device}' + - ';params=${params};_=${random}', + '_basicMessage': + 'js=13;id=${id};u=${url};r=${referrer}' + + ';s=${screenWidth}*${screenHeight}' + + ';vp=${viewportWidth}*${viewportHeight}' + + ';st=${start};gender=${gender};age=${age}' + + ';pid=${pid};userid=${userid};device=${device}' + + ';params=${params};_=${random}', }, 'triggers': { 'pageView': { diff --git a/extensions/amp-analytics/0.1/vendors/treasuredata.js b/extensions/amp-analytics/0.1/vendors/treasuredata.js index 4754b3eee9c6..b78d0def0cec 100644 --- a/extensions/amp-analytics/0.1/vendors/treasuredata.js +++ b/extensions/amp-analytics/0.1/vendors/treasuredata.js @@ -23,7 +23,8 @@ export const TREASUREDATA_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'base': 'https://${host}/postback/v3/event/${database}', - 'baseParams': 'td_write_key=${writeKey}' + + 'baseParams': + 'td_write_key=${writeKey}' + '&td_global_id=td_global_id' + '&td_client_id=CLIENT_ID(_td)' + '&td_charset=DOCUMENT_CHARSET' + diff --git a/extensions/amp-analytics/0.1/vendors/umenganalytics.js b/extensions/amp-analytics/0.1/vendors/umenganalytics.js index b75b084707b3..c12e352a96dc 100644 --- a/extensions/amp-analytics/0.1/vendors/umenganalytics.js +++ b/extensions/amp-analytics/0.1/vendors/umenganalytics.js @@ -22,16 +22,17 @@ export const UMENGANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'eventProps': '', }, 'requests': { - 'base': 'https://b.cnzz.com/utrack?' + - '&_siteid=${siteid}' + - '&_distinct_id=${clientId(umeng_amp_id)}' + - '&_t=${timestamp}' + - '&_s=google' + - '&_b=web' + - '&_r=${externalReferrer}' + - '&_h=${screenHeight}' + - '&_w=${screenWidth}' + - '&_ivt=${initial_view_time}', + 'base': + 'https://b.cnzz.com/utrack?' + + '&_siteid=${siteid}' + + '&_distinct_id=${clientId(umeng_amp_id)}' + + '&_t=${timestamp}' + + '&_s=google' + + '&_b=web' + + '&_r=${externalReferrer}' + + '&_h=${screenHeight}' + + '&_w=${screenWidth}' + + '&_ivt=${initial_view_time}', 'pageview': '${base}&_ename=$w_page_view&_eprops=${eventProps}', 'event': '${base}&_ename=${eventName}&_eprops=${eventProps}', }, diff --git a/extensions/amp-analytics/0.1/vendors/upscore.js b/extensions/amp-analytics/0.1/vendors/upscore.js index e98bac80e80e..6b2fb1b1b82e 100644 --- a/extensions/amp-analytics/0.1/vendors/upscore.js +++ b/extensions/amp-analytics/0.1/vendors/upscore.js @@ -14,27 +14,29 @@ * limitations under the License. */ -export const UPSCORE_CONFIG = /**@type {!JsonObject} */({ +export const UPSCORE_CONFIG = /**@type {!JsonObject} */ ({ 'requests': { 'host': 'https://hit-pool.upscore.com/amp?', - 'basePrefix': 'u_id=${clientId(upscore)}&' + - 'hit_id=${pageViewId}&' + - 'scTop=${scrollTop}&' + - 'scHeight=${scrollHeight}&' + - 'vHeight=${viewportHeight}&' + - 'domain=${domain}&' + - 'load=${domInteractiveTime}&' + - 'timespent=${totalEngagedTime}', - 'initialHit': 'author=${author}&' + - 'creator=${creator}&' + - 'o_id=${object_id}&' + - 'o_type=${object_type}&' + - 'pubdate=${pubdate}&' + - 'ref=${documentReferrer}&' + - 'section=${section}&' + - 'url=${ampdocUrl}&' + - 'agent=${userAgent}&' + - 'location=${ampGeo(ISOCountry)}', + 'basePrefix': + 'u_id=${clientId(upscore)}&' + + 'hit_id=${pageViewId}&' + + 'scTop=${scrollTop}&' + + 'scHeight=${scrollHeight}&' + + 'vHeight=${viewportHeight}&' + + 'domain=${domain}&' + + 'load=${domInteractiveTime}&' + + 'timespent=${totalEngagedTime}', + 'initialHit': + 'author=${author}&' + + 'creator=${creator}&' + + 'o_id=${object_id}&' + + 'o_type=${object_type}&' + + 'pubdate=${pubdate}&' + + 'ref=${documentReferrer}&' + + 'section=${section}&' + + 'url=${ampdocUrl}&' + + 'agent=${userAgent}&' + + 'location=${ampGeo(ISOCountry)}', 'finalbeat': '${host}${basePrefix}&type=final', 'heartbeat': '${host}${basePrefix}&type=pulse', 'pageview': '${host}${basePrefix}&${initialHit}&type=init', diff --git a/extensions/amp-analytics/0.1/vendors/vponanalytics.js b/extensions/amp-analytics/0.1/vendors/vponanalytics.js index ce2372d656da..1523da8ee1b2 100644 --- a/extensions/amp-analytics/0.1/vendors/vponanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/vponanalytics.js @@ -21,17 +21,20 @@ export const VPONANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://tags-dmp.vpadn.com', 'sync': 'https://ids-dmp.vpadn.com/set?t=${timestamp}&dn=&ctid=${ctid}', - 'scroll': '${host}/et?t=${timestamp}&sdkn=j&sdkv=1.2.0&lk=${licence_key}' + - '&en=UTF-8&ctid=${ctid}&ev=element_interact&' + - 'pl={\"name\":\"${category}\",\"action\":\"${action}\",' + - '\"value\":\"${documentReferrer}\"}', - 'event': '${host}/et?t=${timestamp}&sdkn=j&sdkv=1.2.0&lk=${licence_key}' + - '&en=UTF-8&ctid=${ctid}&ev=${ev_name}&pl=${payload}', - 'elementInteract': '${host}/et?t=${timestamp}&sdkn=j&' + - 'sdkv=1.2.0&lk=${licence_key}' + - '&en=UTF-8&ctid=${ctid}&ev=element_interact&' + - 'pl={\"name\":\"${category}\",\"action\":\"${action}\",' + - '\"value\":\"${label}\"}', + 'scroll': + '${host}/et?t=${timestamp}&sdkn=j&sdkv=1.2.0&lk=${licence_key}' + + '&en=UTF-8&ctid=${ctid}&ev=element_interact&' + + 'pl={"name":"${category}","action":"${action}",' + + '"value":"${documentReferrer}"}', + 'event': + '${host}/et?t=${timestamp}&sdkn=j&sdkv=1.2.0&lk=${licence_key}' + + '&en=UTF-8&ctid=${ctid}&ev=${ev_name}&pl=${payload}', + 'elementInteract': + '${host}/et?t=${timestamp}&sdkn=j&' + + 'sdkv=1.2.0&lk=${licence_key}' + + '&en=UTF-8&ctid=${ctid}&ev=element_interact&' + + 'pl={"name":"${category}","action":"${action}",' + + '"value":"${label}"}', }, 'extraUrlParams': { 'is_amp': '1', @@ -47,8 +50,9 @@ export const VPONANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'request': 'event', 'vars': { 'ev_name': 'page_view', - 'payload': '{\"title\":\"${title}\",\"current\":\"${canonicalUrl}\"' + - ',\"previous\":\"${documentReferrer}\"}', + 'payload': + '{"title":"${title}","current":"${canonicalUrl}"' + + ',"previous":"${documentReferrer}"}', }, }, }, diff --git a/extensions/amp-analytics/0.1/vendors/webtrekk.js b/extensions/amp-analytics/0.1/vendors/webtrekk.js index 97c0415d79ac..f705ee86bdf5 100644 --- a/extensions/amp-analytics/0.1/vendors/webtrekk.js +++ b/extensions/amp-analytics/0.1/vendors/webtrekk.js @@ -17,30 +17,36 @@ export const WEBTREKK_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'trackURL': 'https://${trackDomain}/${trackId}/wt', - 'parameterPrefix': '?p=432,${contentId},1,' + + 'parameterPrefix': + '?p=432,${contentId},1,' + '${screenWidth}x${screenHeight},${screenColorDepth},1,' + '${timestamp},${documentReferrer},${viewportWidth}x' + '${viewportHeight},0&tz=${timezone}' + '&eid=${clientId(amp-wt3-eid)}&la=${browserLanguage}', 'parameterSuffix': '&pu=${sourceUrl}', - 'pageParameter': '&cp1=${pageParameter1}' + + 'pageParameter': + '&cp1=${pageParameter1}' + '&cp2=${pageParameter2}&cp3=${pageParameter3}' + '&cp4=${pageParameter4}&cp5=${pageParameter5}' + '&cp6=${pageParameter6}&cp7=${pageParameter7}' + '&cp8=${pageParameter8}&cp9=${pageParameter9}' + '&cp10=${pageParameter10}', - 'pageCategories': '&cg1=${pageCategory1}' + + 'pageCategories': + '&cg1=${pageCategory1}' + '&cg2=${pageCategory2}&cg3=${pageCategory3}' + '&cg4=${pageCategory4}&cg5=${pageCategory5}' + '&cg6=${pageCategory6}&cg7=${pageCategory7}' + '&cg8=${pageCategory8}&cg9=${pageCategory9}' + '&cg10=${pageCategory10}', - 'pageview': '${trackURL}${parameterPrefix}${pageParameter}' + + 'pageview': + '${trackURL}${parameterPrefix}${pageParameter}' + '${pageCategories}${parameterSuffix}', - 'actionParameter': '&ck1=${actionParameter1}' + + 'actionParameter': + '&ck1=${actionParameter1}' + '&ck2=${actionParameter2}&ck3=${actionParameter3}' + '&ck4=${actionParameter4}&ck5=${actionParameter5}', - 'event': '${trackURL}${parameterPrefix}&ct=${actionName}' + + 'event': + '${trackURL}${parameterPrefix}&ct=${actionName}' + '${actionParameter}${parameterSuffix}', }, 'transport': { diff --git a/extensions/amp-analytics/0.1/vendors/webtrekk_v2.js b/extensions/amp-analytics/0.1/vendors/webtrekk_v2.js index e8d94d41e9c3..3a852c40cb63 100644 --- a/extensions/amp-analytics/0.1/vendors/webtrekk_v2.js +++ b/extensions/amp-analytics/0.1/vendors/webtrekk_v2.js @@ -23,22 +23,27 @@ export const WEBTREKK_V2_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'trackURL': 'https://${trackDomain}/${trackId}/wt', - 'basePrefix': '?p=440,${contentId},1,' + + 'basePrefix': + '?p=440,${contentId},1,' + '${screenWidth}x${screenHeight},${screenColorDepth},1,', - 'baseSuffix': ',${documentReferrer},' + + 'baseSuffix': + ',${documentReferrer},' + '${viewportWidth}x${viewportHeight},0' + '&tz=${timezone}&eid=${everId}&la=${browserLanguage}', 'parameterPrefix': '${basePrefix}${timestamp}${baseSuffix}', 'parameterSuffix': '&pu=${sourceUrl}&eor=1', - 'pageview': '${trackURL}${parameterPrefix}&${extraUrlParams}' + + 'pageview': + '${trackURL}${parameterPrefix}&${extraUrlParams}' + '&cp570=${pageLoadTime}${parameterSuffix}', - 'event': '${trackURL}${parameterPrefix}&ct=${actionName}' + + 'event': + '${trackURL}${parameterPrefix}&ct=${actionName}' + '&${extraUrlParams}${parameterSuffix}', - 'scroll': '${trackURL}${parameterPrefix}&ct=${actionName}' + + 'scroll': + '${trackURL}${parameterPrefix}&ct=${actionName}' + '&ck540=${verticalScrollBoundary}${parameterSuffix}', - 'mediaPrefix': '${trackURL}${basePrefix}${baseSuffix}' + - '&mi=${mediaName}', - 'mediaSuffix': '&mt1=${currentTime}&mt2=${duration}' + + 'mediaPrefix': '${trackURL}${basePrefix}${baseSuffix}&mi=${mediaName}', + 'mediaSuffix': + '&mt1=${currentTime}&mt2=${duration}' + '&${extraUrlParams}${parameterSuffix}&x=${playedTotal}', 'mediaPlay': '${mediaPrefix}&mk=play${mediaSuffix}', 'mediaPause': '${mediaPrefix}&mk=pause${mediaSuffix}', diff --git a/extensions/amp-analytics/0.1/visibility-manager-for-mapp.js b/extensions/amp-analytics/0.1/visibility-manager-for-mapp.js index 76dd2301f5d7..b741ab589c07 100644 --- a/extensions/amp-analytics/0.1/visibility-manager-for-mapp.js +++ b/extensions/amp-analytics/0.1/visibility-manager-for-mapp.js @@ -52,7 +52,8 @@ export class VisibilityManagerForMApp extends VisibilityManager { // Initate the listener this.visibilityInterface_.onVisibilityChange( - this.onVisibilityChangeHandler_.bind(this)); + this.onVisibilityChangeHandler_.bind(this) + ); } /** @override */ @@ -81,23 +82,27 @@ export class VisibilityManagerForMApp extends VisibilityManager { return this.backgroundedAtStart_; } - /** @override */ getRootMinOpacity() { // Copied the implementation from VisibilityManagerForDoc, // doesn't count iframe opacity const root = this.ampdoc.getRootNode(); const rootElement = dev().assertElement( - root.documentElement || root.body || root); + root.documentElement || root.body || root + ); return getMinOpacity(rootElement); } /** @override */ listenElement() { // #listenElement not supported in mApp - devAssert(false, '%s: element level visibility not supported, ' + + devAssert( + false, + '%s: element level visibility not supported, ' + 'getElementIntersectionRect should not be called in ' + - 'VisibilityManager for mApp', TAG); + 'VisibilityManager for mApp', + TAG + ); return () => {}; } @@ -107,8 +112,9 @@ export class VisibilityManagerForMApp extends VisibilityManager { getRootLayoutBox() { // By the time `#getRootLayoutBox` is called, it is guaranteed that // onVisibilityChangeHandler has been called at least once - return /** @type {!../../../src/layout-rect.LayoutRectDef} */ ( - devAssert(this.intersectionRect_)); + return /** @type {!../../../src/layout-rect.LayoutRectDef} */ (devAssert( + this.intersectionRect_ + )); } /** @@ -132,9 +138,13 @@ export class VisibilityManagerForMApp extends VisibilityManager { * @override */ observe() { - devAssert(false, '%s: element level visibility not supported, ' + + devAssert( + false, + '%s: element level visibility not supported, ' + 'getElementIntersectionRect should not be called in ' + - 'VisibilityManager for mApp', TAG); + 'VisibilityManager for mApp', + TAG + ); return () => {}; } @@ -142,9 +152,13 @@ export class VisibilityManagerForMApp extends VisibilityManager { * @override */ getElementVisibility() { - devAssert(false, '%s: element level visibility not supported, ' + + devAssert( + false, + '%s: element level visibility not supported, ' + 'getElementIntersectionRect should not be called in ' + - 'VisibilityManager for mApp', TAG); + 'VisibilityManager for mApp', + TAG + ); return 0; } @@ -153,9 +167,12 @@ export class VisibilityManagerForMApp extends VisibilityManager { * @return {?JsonObject} */ getElementIntersectionRect() { - dev().error(TAG, 'element level visibility not supported, ' + + dev().error( + TAG, + 'element level visibility not supported, ' + 'getElementIntersectionRect should not be called in ' + - 'VisibilityManager for mApp'); + 'VisibilityManager for mApp' + ); return dict({}); } } diff --git a/extensions/amp-analytics/0.1/visibility-manager.js b/extensions/amp-analytics/0.1/visibility-manager.js index 7d64ec7fa6ae..4e3e4ba739f8 100644 --- a/extensions/amp-analytics/0.1/visibility-manager.js +++ b/extensions/amp-analytics/0.1/visibility-manager.js @@ -51,7 +51,6 @@ function getElementId(element) { return id; } - /** * A base class for `VisibilityManagerForDoc` and `VisibilityManagerForEmbed`. * The instance of this class corresponds 1:1 to `AnalyticsRoot`. It represents @@ -178,11 +177,11 @@ export class VisibilityManager { isBackgroundedAtStart() {} /** - * Returns the root's, root's parent's and root's children's - * lowest opacity value - * @return {number} - * @abstract - */ + * Returns the root's, root's parent's and root's children's + * lowest opacity value + * @return {number} + * @abstract + */ getRootMinOpacity() {} /** @@ -252,8 +251,13 @@ export class VisibilityManager { */ listenRoot(spec, readyPromise, createReportPromiseFunc, callback) { const calcVisibility = this.getRootVisibility.bind(this); - return this.createModelAndListen_(calcVisibility, spec, readyPromise, - createReportPromiseFunc, callback); + return this.createModelAndListen_( + calcVisibility, + spec, + readyPromise, + createReportPromiseFunc, + callback + ); } /** @@ -268,10 +272,21 @@ export class VisibilityManager { * @return {!UnlistenDef} */ listenElement( - element, spec, readyPromise, createReportPromiseFunc, callback) { + element, + spec, + readyPromise, + createReportPromiseFunc, + callback + ) { const calcVisibility = this.getElementVisibility.bind(this, element); - return this.createModelAndListen_(calcVisibility, spec, readyPromise, - createReportPromiseFunc, callback, element); + return this.createModelAndListen_( + calcVisibility, + spec, + readyPromise, + createReportPromiseFunc, + callback, + element + ); } /** @@ -284,11 +299,19 @@ export class VisibilityManager { * @param {!Element=} opt_element * @return {!UnlistenDef} */ - createModelAndListen_(calcVisibility, spec, - readyPromise, createReportPromiseFunc, callback, opt_element) { - if (spec['visiblePercentageThresholds'] && - spec['visiblePercentageMin'] == undefined && - spec['visiblePercentageMax'] == undefined) { + createModelAndListen_( + calcVisibility, + spec, + readyPromise, + createReportPromiseFunc, + callback, + opt_element + ) { + if ( + spec['visiblePercentageThresholds'] && + spec['visiblePercentageMin'] == undefined && + spec['visiblePercentageMax'] == undefined + ) { const unlisteners = []; const ranges = spec['visiblePercentageThresholds']; if (!ranges || !isArray(ranges)) { @@ -298,14 +321,18 @@ export class VisibilityManager { for (let i = 0; i < ranges.length; i++) { const percents = ranges[i]; if (!isArray(percents) || percents.length != 2) { - user().error(TAG, - 'visiblePercentageThresholds entry length is not 2'); + user().error( + TAG, + 'visiblePercentageThresholds entry length is not 2' + ); continue; } if (!isFiniteNumber(percents[0]) || !isFiniteNumber(percents[1])) { // not valid number - user().error(TAG, - 'visiblePercentageThresholds entry is not valid number'); + user().error( + TAG, + 'visiblePercentageThresholds entry is not valid number' + ); continue; } const min = Number(percents[0]); @@ -315,26 +342,46 @@ export class VisibilityManager { // special cases: if min and max are both 0, or both 100, then both // are inclusive. Otherwise it would not be possible to trigger an // event on exactly 0% or 100%. - if (min < 0 || max > 100 || min > max || - (min == max && min != 100 && max != 0)) { - user().error(TAG, - 'visiblePercentageThresholds entry invalid min/max value'); + if ( + min < 0 || + max > 100 || + min > max || + (min == max && min != 100 && max != 0) + ) { + user().error( + TAG, + 'visiblePercentageThresholds entry invalid min/max value' + ); continue; } const newSpec = spec; newSpec['visiblePercentageMin'] = min; newSpec['visiblePercentageMax'] = max; const model = new VisibilityModel(newSpec, calcVisibility); - unlisteners.push(this.listen_(model, spec, readyPromise, - createReportPromiseFunc, callback, opt_element)); + unlisteners.push( + this.listen_( + model, + spec, + readyPromise, + createReportPromiseFunc, + callback, + opt_element + ) + ); } return () => { unlisteners.forEach(unlistener => unlistener()); }; } const model = new VisibilityModel(spec, calcVisibility); - return this.listen_(model, spec, readyPromise, - createReportPromiseFunc, callback, opt_element); + return this.listen_( + model, + spec, + readyPromise, + createReportPromiseFunc, + callback, + opt_element + ); } /** @@ -347,8 +394,14 @@ export class VisibilityManager { * @return {!UnlistenDef} * @private */ - listen_(model, spec, - readyPromise, createReportPromiseFunc, callback, opt_element) { + listen_( + model, + spec, + readyPromise, + createReportPromiseFunc, + callback, + opt_element + ) { if (createReportPromiseFunc) { model.setReportReady(createReportPromiseFunc); } @@ -382,19 +435,21 @@ export class VisibilityManager { let layoutBox; if (opt_element) { state['opacity'] = getMinOpacity(opt_element); - const resource = - this.resources_.getResourceForElementOptional(opt_element); - layoutBox = - resource ? - resource.getLayoutBox() : - viewport.getLayoutRect(opt_element); + const resource = this.resources_.getResourceForElementOptional( + opt_element + ); + layoutBox = resource + ? resource.getLayoutBox() + : viewport.getLayoutRect(opt_element); const intersectionRatio = this.getElementVisibility(opt_element); const intersectionRect = this.getElementIntersectionRect(opt_element); - Object.assign(state, dict({ - 'intersectionRatio': intersectionRatio, - 'intersectionRect': JSON.stringify(intersectionRect), - })); - + Object.assign( + state, + dict({ + 'intersectionRatio': intersectionRatio, + 'intersectionRect': JSON.stringify(intersectionRect), + }) + ); } else { state['opacity'] = this.getRootMinOpacity(); state['intersectionRatio'] = this.getRootVisibility(); @@ -403,16 +458,25 @@ export class VisibilityManager { model.maybeDispose(); if (layoutBox) { - Object.assign(state, dict({ - 'elementX': layoutBox.left, - 'elementY': layoutBox.top, - 'elementWidth': layoutBox.width, - 'elementHeight': layoutBox.height, - })); + Object.assign( + state, + dict({ + 'elementX': layoutBox.left, + 'elementY': layoutBox.top, + 'elementWidth': layoutBox.width, + 'elementHeight': layoutBox.height, + }) + ); state['initialScrollDepth'] = layoutPositionRelativeToScrolledViewport( - layoutBox, viewport, model.getInitialScrollDepth()); + layoutBox, + viewport, + model.getInitialScrollDepth() + ); state['maxScrollDepth'] = layoutPositionRelativeToScrolledViewport( - layoutBox, viewport, this.getMaxScrollDepth()); + layoutBox, + viewport, + this.getMaxScrollDepth() + ); } callback(state); }); @@ -466,7 +530,6 @@ export class VisibilityManager { getElementIntersectionRect(unusedElement) {} } - /** * The implementation of `VisibilityManager` for an AMP document. Two * distinct modes are supported: the main AMP doc and a in-a-box doc. @@ -507,20 +570,23 @@ export class VisibilityManagerForDoc extends VisibilityManager { // In-a-box: visibility depends on the InOb. const root = this.ampdoc.getRootNode(); const rootElement = dev().assertElement( - root.documentElement || root.body || root); - this.unsubscribe(this.observe( - rootElement, - this.setRootVisibility.bind(this))); + root.documentElement || root.body || root + ); + this.unsubscribe( + this.observe(rootElement, this.setRootVisibility.bind(this)) + ); } else { // Main document: visibility is based on the viewer. this.setRootVisibility(this.viewer_.isVisible() ? 1 : 0); - this.unsubscribe(this.viewer_.onVisibilityChanged(() => { - const isVisible = this.viewer_.isVisible(); - if (!isVisible) { - this.backgrounded_ = true; - } - this.setRootVisibility(isVisible ? 1 : 0); - })); + this.unsubscribe( + this.viewer_.onVisibilityChanged(() => { + const isVisible = this.viewer_.isVisible(); + if (!isVisible) { + this.backgrounded_ = true; + } + this.setRootVisibility(isVisible ? 1 : 0); + }) + ); } } @@ -552,7 +618,8 @@ export class VisibilityManagerForDoc extends VisibilityManager { getRootMinOpacity() { const root = this.ampdoc.getRootNode(); const rootElement = dev().assertElement( - root.documentElement || root.body || root); + root.documentElement || root.body || root + ); return getMinOpacity(rootElement); } @@ -561,7 +628,8 @@ export class VisibilityManagerForDoc extends VisibilityManager { // This code is the same for "in-a-box" and standalone doc. const root = this.ampdoc.getRootNode(); const rootElement = dev().assertElement( - root.documentElement || root.body || root); + root.documentElement || root.body || root + ); return this.viewport_.getLayoutRect(rootElement); } @@ -648,14 +716,16 @@ export class VisibilityManagerForDoc extends VisibilityManager { const {win} = this.ampdoc; if (nativeIntersectionObserverSupported(win)) { return new win.IntersectionObserver( - this.onIntersectionChanges_.bind(this), - {threshold: DEFAULT_THRESHOLD}); + this.onIntersectionChanges_.bind(this), + {threshold: DEFAULT_THRESHOLD} + ); } // Polyfill. const intersectionObserverPolyfill = new IntersectionObserverPolyfill( - this.onIntersectionChanges_.bind(this), - {threshold: DEFAULT_THRESHOLD}); + this.onIntersectionChanges_.bind(this), + {threshold: DEFAULT_THRESHOLD} + ); const ticker = () => { intersectionObserverPolyfill.tick(this.viewport_.getRect()); }; @@ -695,14 +765,17 @@ export class VisibilityManagerForDoc extends VisibilityManager { let intersection = change.intersectionRect; // IntersectionRect type now changed from ClientRect to DOMRectReadOnly. // TODO(@zhouyx): Fix all InOb related type. - intersection = layoutRectLtwh(Number(intersection.left), - Number(intersection.top), - Number(intersection.width), - Number(intersection.height)); + intersection = layoutRectLtwh( + Number(intersection.left), + Number(intersection.top), + Number(intersection.width), + Number(intersection.height) + ); this.onIntersectionChange_( - change.target, - change.intersectionRatio, - intersection); + change.target, + change.intersectionRatio, + intersection + ); }); } @@ -726,7 +799,6 @@ export class VisibilityManagerForDoc extends VisibilityManager { } } - /** * The implementation of `VisibilityManager` for a FIE embed. This visibility * root delegates most of tracking functions to its parent, the ampdoc root. @@ -745,9 +817,12 @@ export class VisibilityManagerForEmbed extends VisibilityManager { /** @const @private {boolean} */ this.backgroundedAtStart_ = this.parent.isBackgrounded(); - this.unsubscribe(this.parent.observe( + this.unsubscribe( + this.parent.observe( dev().assertElement(embed.host), - this.setRootVisibility.bind(this))); + this.setRootVisibility.bind(this) + ) + ); } /** @override */ @@ -812,5 +887,4 @@ export class VisibilityManagerForEmbed extends VisibilityManager { } return this.parent.getElementIntersectionRect(element); } - } diff --git a/extensions/amp-analytics/0.1/visibility-model.js b/extensions/amp-analytics/0.1/visibility-model.js index 8b870c5d9eed..98781ee02aad 100644 --- a/extensions/amp-analytics/0.1/visibility-model.js +++ b/extensions/amp-analytics/0.1/visibility-model.js @@ -161,8 +161,10 @@ export class VisibilityModel { * @private */ reset_() { - devAssert(!this.eventResolver_, - 'Attempt to refresh visible event before previous one resolve'); + devAssert( + !this.eventResolver_, + 'Attempt to refresh visible event before previous one resolve' + ); const deferred = new Deferred(); this.eventPromise_ = deferred.promise; this.eventResolver_ = deferred.resolve; @@ -320,7 +322,7 @@ export class VisibilityModel { // When ignoreVisibilityForReport_ is true, we update counters but fire the // event when the report ready promise is resolved. const conditionsMet = - this.updateCounters_(visibility) || this.ignoreVisibilityForReport_; + this.updateCounters_(visibility) || this.ignoreVisibilityForReport_; if (conditionsMet) { if (this.scheduledUpdateTimeoutId_) { clearTimeout(this.scheduledUpdateTimeoutId_); @@ -366,8 +368,11 @@ export class VisibilityModel { * @return {boolean} */ isVisibilityMatch_(visibility) { - devAssert(visibility >= 0 && visibility <= 1, - 'invalid visibility value: %s', visibility); + devAssert( + visibility >= 0 && visibility <= 1, + 'invalid visibility value: %s', + visibility + ); // Special case: If visiblePercentageMin is 100%, then it doesn't make // sense to do the usual (min, max] since that would never be true. if (this.spec_['visiblePercentageMin'] == 1) { @@ -378,8 +383,10 @@ export class VisibilityModel { if (this.spec_['visiblePercentageMax'] == 0) { return visibility == 0; } - return visibility > this.spec_['visiblePercentageMin'] && - visibility <= this.spec_['visiblePercentageMax']; + return ( + visibility > this.spec_['visiblePercentageMin'] && + visibility <= this.spec_['visiblePercentageMax'] + ); } /** @@ -388,8 +395,11 @@ export class VisibilityModel { * @private */ updateCounters_(visibility) { - devAssert(visibility >= 0 && visibility <= 1, - 'invalid visibility value: %s', visibility); + devAssert( + visibility >= 0 && visibility <= 1, + 'invalid visibility value: %s', + visibility + ); const now = Date.now(); if (visibility > 0) { @@ -397,14 +407,15 @@ export class VisibilityModel { this.lastSeenTime_ = now; // Consider it as load time visibility if this happens within 300ms of // page load. - if (!this.loadTimeVisibility_ && (now - this.createdTime_) < 300) { + if (!this.loadTimeVisibility_ && now - this.createdTime_ < 300) { this.loadTimeVisibility_ = visibility; } } const prevMatchesVisibility = this.matchesVisibility_; - const timeSinceLastUpdate = - this.lastVisibleUpdateTime_ ? now - this.lastVisibleUpdateTime_ : 0; + const timeSinceLastUpdate = this.lastVisibleUpdateTime_ + ? now - this.lastVisibleUpdateTime_ + : 0; this.matchesVisibility_ = this.isVisibilityMatch_(visibility); if (this.matchesVisibility_) { this.everMatchedVisibility_ = true; @@ -412,8 +423,10 @@ export class VisibilityModel { // Keep counting. this.totalVisibleTime_ += timeSinceLastUpdate; this.continuousTime_ += timeSinceLastUpdate; - this.maxContinuousVisibleTime_ = - Math.max(this.maxContinuousVisibleTime_, this.continuousTime_); + this.maxContinuousVisibleTime_ = Math.max( + this.maxContinuousVisibleTime_, + this.continuousTime_ + ); } else { // The resource came into view: start counting. devAssert(!this.lastVisibleUpdateTime_); @@ -421,19 +434,22 @@ export class VisibilityModel { } this.lastVisibleUpdateTime_ = now; this.minVisiblePercentage_ = - this.minVisiblePercentage_ > 0 ? - Math.min(this.minVisiblePercentage_, visibility) : - visibility; - this.maxVisiblePercentage_ = - Math.max(this.maxVisiblePercentage_, visibility); + this.minVisiblePercentage_ > 0 + ? Math.min(this.minVisiblePercentage_, visibility) + : visibility; + this.maxVisiblePercentage_ = Math.max( + this.maxVisiblePercentage_, + visibility + ); this.lastVisibleTime_ = now; } else if (prevMatchesVisibility) { // The resource went out of view. Do final calculations and reset state. devAssert(this.lastVisibleUpdateTime_ > 0); this.maxContinuousVisibleTime_ = Math.max( - this.maxContinuousVisibleTime_, - this.continuousTime_ + timeSinceLastUpdate); + this.maxContinuousVisibleTime_, + this.continuousTime_ + timeSinceLastUpdate + ); // Reset for next visibility event. this.lastVisibleUpdateTime_ = 0; @@ -442,11 +458,13 @@ export class VisibilityModel { this.lastVisibleTime_ = now; } - return this.everMatchedVisibility_ && - (this.totalVisibleTime_ >= this.spec_['totalTimeMin']) && - (this.totalVisibleTime_ <= this.spec_['totalTimeMax']) && - (this.maxContinuousVisibleTime_ >= this.spec_['continuousTimeMin']) && - (this.maxContinuousVisibleTime_ <= this.spec_['continuousTimeMax']); + return ( + this.everMatchedVisibility_ && + this.totalVisibleTime_ >= this.spec_['totalTimeMin'] && + this.totalVisibleTime_ <= this.spec_['totalTimeMax'] && + this.maxContinuousVisibleTime_ >= this.spec_['continuousTimeMin'] && + this.maxContinuousVisibleTime_ <= this.spec_['continuousTimeMax'] + ); } /** @@ -478,18 +496,22 @@ export class VisibilityModel { */ computeTimeToWait_() { const waitForContinuousTime = Math.max( - this.spec_['continuousTimeMin'] - this.continuousTime_, 0); + this.spec_['continuousTimeMin'] - this.continuousTime_, + 0 + ); const waitForTotalTime = Math.max( - this.spec_['totalTimeMin'] - this.totalVisibleTime_, 0); + this.spec_['totalTimeMin'] - this.totalVisibleTime_, + 0 + ); const maxWaitTime = Math.max(waitForContinuousTime, waitForTotalTime); return Math.min( - maxWaitTime, - waitForContinuousTime || Infinity, - waitForTotalTime || Infinity); + maxWaitTime, + waitForContinuousTime || Infinity, + waitForTotalTime || Infinity + ); } } - /** * Calculates the specified time based on the given `baseTime`. * @param {time} time diff --git a/extensions/amp-anim/0.1/amp-anim.js b/extensions/amp-anim/0.1/amp-anim.js index c189593d4820..4cb901442db8 100644 --- a/extensions/amp-anim/0.1/amp-anim.js +++ b/extensions/amp-anim/0.1/amp-anim.js @@ -21,15 +21,19 @@ import {isLayoutSizeDefined} from '../../../src/layout'; import {propagateObjectFitStyles} from '../../../src/style'; const TAG = 'amp-anim'; -const BUILD_ATTRIBUTES = ['alt', 'aria-label', 'aria-describedby', - 'aria-labelledby']; +const BUILD_ATTRIBUTES = [ + 'alt', + 'aria-label', + 'aria-describedby', + 'aria-labelledby', +]; const LAYOUT_ATTRIBUTES = ['src', 'srcset']; /** @visibleForTesting */ -export const SRC_PLACEHOLDER = 'data:image/gif;base64,' + -'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; +export const SRC_PLACEHOLDER = + 'data:image/gif;base64,' + + 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; export class AmpAnim extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -59,15 +63,16 @@ export class AmpAnim extends AMP.BaseElement { if (this.element.getAttribute('role') == 'img') { this.element.removeAttribute('role'); this.user().error( - 'AMP-ANIM', 'Setting role=img on amp-anim elements ' + + 'AMP-ANIM', + 'Setting role=img on amp-anim elements ' + 'breaks screen readers. Please just set alt or ARIA attributes, ' + 'they will be correctly propagated for the underlying ' + - 'element.'); + 'element.' + ); } // The image is initially hidden if a placeholder is available. - st.toggle(dev().assertElement(this.img_), - !this.getPlaceholder()); + st.toggle(dev().assertElement(this.img_), !this.getPlaceholder()); this.element.appendChild(this.img_); } diff --git a/extensions/amp-anim/0.1/test/test-amp-anim.js b/extensions/amp-anim/0.1/test/test-amp-anim.js index 82e961c1f547..823a9bb825cb 100644 --- a/extensions/amp-anim/0.1/test/test-amp-anim.js +++ b/extensions/amp-anim/0.1/test/test-amp-anim.js @@ -19,114 +19,117 @@ import {AmpAnim, SRC_PLACEHOLDER} from '../amp-anim'; const EXAMPLE_SRCSET = `https://media.giphy.com/media/yFQ0ywscgobJK/giphy.gif 1282w, https://media.giphy.com/media/vFKqnCdLPNOKc/giphy.gif 1923w`; -describes.realWin('amp-anim', { - amp: { - ampdoc: 'single', - extensions: ['amp-anim'], +describes.realWin( + 'amp-anim', + { + amp: { + ampdoc: 'single', + extensions: ['amp-anim'], + }, }, -}, env => { - - it('should propagate ARIA attributes', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('srcset', EXAMPLE_SRCSET); - el.setAttribute('width', 100); - el.setAttribute('height', 100); - el.setAttribute('aria-label', 'Hello'); - el.setAttribute('aria-labelledby', 'id2'); - el.setAttribute('aria-describedby', 'id3'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.getAttribute('aria-label')).to.equal('Hello'); - expect(img.getAttribute('aria-labelledby')).to.equal('id2'); - expect(img.getAttribute('aria-describedby')).to.equal('id3'); - expect(img.getAttribute('decoding')).to.equal('async'); - }); - - it('should propagate src and srcset', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('srcset', EXAMPLE_SRCSET); - el.setAttribute('width', 100); - el.setAttribute('height', 100); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.getAttribute('src')).to.equal('test.jpg'); - expect(img.getAttribute('srcset')).to.equal(EXAMPLE_SRCSET); - }); - - it('should set src to placeholder on unlayout and reset on layout', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('srcset', EXAMPLE_SRCSET); - el.setAttribute('width', 100); - el.setAttribute('height', 100); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.getAttribute('src')).to.equal('test.jpg'); - expect(img.getAttribute('srcset')).to.equal(EXAMPLE_SRCSET); - - impl.unlayoutCallback(); - expect(img.getAttribute('src')).to.equal(SRC_PLACEHOLDER); - - impl.layoutCallback(); - expect(img.getAttribute('src')).to.equal('test.jpg'); - }); - - it('should propagate the object-fit attribute', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('object-fit', 'cover'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.style.objectFit).to.equal('cover'); - }); - - it('should not propagate the object-fit attribute if invalid', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('object-fit', 'foo 80%'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.style.objectFit).to.be.empty; - }); - - it('should propagate the object-position attribute', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('object-position', '20% 80%'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.style.objectPosition).to.equal('20% 80%'); - }); - - it('should not propagate the object-position attribute if invalid', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('object-position', 'url:("example.com")'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.style.objectPosition).to.be.empty; - }); -}); + env => { + it('should propagate ARIA attributes', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('srcset', EXAMPLE_SRCSET); + el.setAttribute('width', 100); + el.setAttribute('height', 100); + el.setAttribute('aria-label', 'Hello'); + el.setAttribute('aria-labelledby', 'id2'); + el.setAttribute('aria-describedby', 'id3'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.getAttribute('aria-label')).to.equal('Hello'); + expect(img.getAttribute('aria-labelledby')).to.equal('id2'); + expect(img.getAttribute('aria-describedby')).to.equal('id3'); + expect(img.getAttribute('decoding')).to.equal('async'); + }); + + it('should propagate src and srcset', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('srcset', EXAMPLE_SRCSET); + el.setAttribute('width', 100); + el.setAttribute('height', 100); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.getAttribute('src')).to.equal('test.jpg'); + expect(img.getAttribute('srcset')).to.equal(EXAMPLE_SRCSET); + }); + + it('should set src to placeholder on unlayout and reset on layout', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('srcset', EXAMPLE_SRCSET); + el.setAttribute('width', 100); + el.setAttribute('height', 100); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.getAttribute('src')).to.equal('test.jpg'); + expect(img.getAttribute('srcset')).to.equal(EXAMPLE_SRCSET); + + impl.unlayoutCallback(); + expect(img.getAttribute('src')).to.equal(SRC_PLACEHOLDER); + + impl.layoutCallback(); + expect(img.getAttribute('src')).to.equal('test.jpg'); + }); + + it('should propagate the object-fit attribute', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('object-fit', 'cover'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.style.objectFit).to.equal('cover'); + }); + + it('should not propagate the object-fit attribute if invalid', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('object-fit', 'foo 80%'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.style.objectFit).to.be.empty; + }); + + it('should propagate the object-position attribute', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('object-position', '20% 80%'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.style.objectPosition).to.equal('20% 80%'); + }); + + it('should not propagate the object-position attribute if invalid', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('object-position', 'url:("example.com")'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.style.objectPosition).to.be.empty; + }); + } +); diff --git a/extensions/amp-animation/0.1/amp-animation.js b/extensions/amp-animation/0.1/amp-animation.js index 62580045f5ab..60a3803a7bd9 100644 --- a/extensions/amp-animation/0.1/amp-animation.js +++ b/extensions/amp-animation/0.1/amp-animation.js @@ -23,8 +23,7 @@ import {WebAnimationService} from './web-animation-service'; import {childElementByTag} from '../../../src/dom'; import {clamp} from '../../../src/utils/math'; import {getDetail, listen} from '../../../src/event-helper'; -import {getFriendlyIframeEmbedOptional} - from '../../../src/friendly-iframe-embed'; +import {getFriendlyIframeEmbedOptional} from '../../../src/friendly-iframe-embed'; import {getParentWindowFrameElement} from '../../../src/service'; import {installWebAnimationsIfNecessary} from './web-animations-polyfill'; import {isFiniteNumber} from '../../../src/types'; @@ -34,9 +33,7 @@ import {user, userAssert} from '../../../src/log'; const TAG = 'amp-animation'; - export class AmpAnimation extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -77,24 +74,28 @@ export class AmpAnimation extends AMP.BaseElement { const trigger = this.element.getAttribute('trigger'); if (trigger) { this.triggerOnVisibility_ = userAssert( - trigger == 'visibility', - 'Only allowed value for "trigger" is "visibility": %s', - this.element); + trigger == 'visibility', + 'Only allowed value for "trigger" is "visibility": %s', + this.element + ); } // TODO(dvoytenko): Remove once we support direct parent visibility. if (trigger == 'visibility') { userAssert( - this.element.parentNode == this.element.ownerDocument.body || + this.element.parentNode == this.element.ownerDocument.body || this.element.parentNode == ampdoc.getBody(), - '%s is only allowed as a direct child of element when trigger' - + ' is visibility. This restriction will be removed soon.', TAG); + '%s is only allowed as a direct child of element when trigger' + + ' is visibility. This restriction will be removed soon.', + TAG + ); } // Parse config. const scriptElement = userAssert( - childElementByTag(this.element, 'script'), - '"`; - tag.textContent = JSON.stringify(content); - return tag; - }; + describe('getAllLdJsonTypes', () => { + const createLdJsonTag = content => { + const tag = html` + + `; + tag.textContent = JSON.stringify(content); + return tag; + }; + + it('returns empty', () => { + expect(DocMetaAnnotations.getAllLdJsonTypes(env.ampdoc)).to.be + .empty; + }); - it('returns empty', () => { - expect(DocMetaAnnotations.getAllLdJsonTypes(env.ampdoc)).to.be.empty; + it('returns all found @types', () => { + const expectedA = 'foo'; + const expectedB = 'bar'; + const expectedC = 'baz'; + + mockRootNodeContent([ + html` + + `, + createLdJsonTag({'@type': expectedA}), + createLdJsonTag({'tacos': 'sí por favor'}), + createLdJsonTag({'@type': expectedB}), + createLdJsonTag({'@type': expectedC}), + createLdJsonTag(''), + ]); + + expect( + DocMetaAnnotations.getAllLdJsonTypes(env.ampdoc) + ).to.deep.equal([expectedA, expectedB, expectedC]); + }); }); + }); - it('returns all found @types', () => { - const expectedA = 'foo'; - const expectedB = 'bar'; - const expectedC = 'baz'; - - mockRootNodeContent([ - html``, - createLdJsonTag({'@type': expectedA}), - createLdJsonTag({'tacos': 'sí por favor'}), - createLdJsonTag({'@type': expectedB}), - createLdJsonTag({'@type': expectedC}), - createLdJsonTag(''), - ]); - - expect(DocMetaAnnotations.getAllLdJsonTypes(env.ampdoc)) - .to.deep.equal([ - expectedA, - expectedB, - expectedC, - ]); + describe('by LD+JSON @type', () => { + it('rejects doc with invalid LD+JSON @type', () => { + mockIsProxyOrigin(true); + mockLdJsonSchemaTypes('hamberder'); + expectIsEnabled(false); }); - }); - - }); + ldJsonSchemaTypes.forEach(type => { + const typeSubObj = `{..."@type": "${type}"}`; - describe('by LD+JSON @type', () => { + it(`accepts docs with ${typeSubObj} schema and proxy origin`, () => { + mockLdJsonSchemaTypes(type); + mockIsProxyOrigin(true); + expectIsEnabled(true); + }); - it('rejects doc with invalid LD+JSON @type', () => { - mockIsProxyOrigin(true); - mockLdJsonSchemaTypes('hamberder'); - expectIsEnabled(false); - }); + it(`rejects docs with ${typeSubObj} schema, lightbox explicit`, () => { + const doc = env.win.document; - ldJsonSchemaTypes.forEach(type => { - const typeSubObj = `{..."@type": "${type}"}`; + const extensionScript = createElementWithAttributes(doc, 'script', { + 'custom-element': REQUIRED_EXTENSION, + }); - it(`accepts docs with ${typeSubObj} schema and proxy origin`, () => { - mockLdJsonSchemaTypes(type); - mockIsProxyOrigin(true); - expectIsEnabled(true); - }); + const lightboxable = createElementWithAttributes(doc, 'amp-img', { + [LIGHTBOXABLE_ATTR]: '', + }); - it(`rejects docs with ${typeSubObj} schema, lightbox explicit`, () => { - const doc = env.win.document; + doc.head.appendChild(extensionScript); + doc.body.appendChild(lightboxable); - const extensionScript = createElementWithAttributes(doc, 'script', { - 'custom-element': REQUIRED_EXTENSION, + mockLdJsonSchemaTypes(type); + mockIsProxyOrigin(true); + expectIsEnabled(false); }); - const lightboxable = createElementWithAttributes(doc, 'amp-img', { - [LIGHTBOXABLE_ATTR]: '', + it(`rejects docs with ${typeSubObj} schema, non-proxy origin`, () => { + mockLdJsonSchemaTypes(type); + mockIsProxyOrigin(false); + expectIsEnabled(false); }); - - doc.head.appendChild(extensionScript); - doc.body.appendChild(lightboxable); - - mockLdJsonSchemaTypes(type); - mockIsProxyOrigin(true); - expectIsEnabled(false); }); + }); - it(`rejects docs with ${typeSubObj} schema, non-proxy origin`, () => { - mockLdJsonSchemaTypes(type); - mockIsProxyOrigin(false); + describe('by og:type', () => { + it('rejects doc with invalid ', () => { + mockIsProxyOrigin(true); + mockOgType('cinnamonroll'); expectIsEnabled(false); }); - }); - }); + ogTypes.forEach(type => { + const ogTypeMeta = ``; - describe('by og:type', () => { + it(`accepts docs with ${ogTypeMeta} and proxy origin`, () => { + mockOgType(type); + mockIsProxyOrigin(true); + expectIsEnabled(true); + }); - it('rejects doc with invalid ', () => { - mockIsProxyOrigin(true); - mockOgType('cinnamonroll'); - expectIsEnabled(false); - }); + it(`rejects docs with ${ogTypeMeta}, but lightbox explicit`, () => { + const doc = env.win.document; - ogTypes.forEach(type => { - const ogTypeMeta = ``; + const extensionScript = createElementWithAttributes(doc, 'script', { + 'custom-element': REQUIRED_EXTENSION, + }); - it(`accepts docs with ${ogTypeMeta} and proxy origin`, () => { - mockOgType(type); - mockIsProxyOrigin(true); - expectIsEnabled(true); - }); + const lightboxable = createElementWithAttributes(doc, 'amp-img', { + [LIGHTBOXABLE_ATTR]: '', + }); - it(`rejects docs with ${ogTypeMeta}, but lightbox explicit`, () => { - const doc = env.win.document; + doc.head.appendChild(extensionScript); + doc.body.appendChild(lightboxable); - const extensionScript = createElementWithAttributes(doc, 'script', { - 'custom-element': REQUIRED_EXTENSION, + mockOgType(type); + mockIsProxyOrigin(true); + expectIsEnabled(false); }); - const lightboxable = createElementWithAttributes(doc, 'amp-img', { - [LIGHTBOXABLE_ATTR]: '', + it(`rejects docs with ${ogTypeMeta} for non-proxy origin`, () => { + mockOgType(type); + mockIsProxyOrigin(false); + expectIsEnabled(false); }); - - doc.head.appendChild(extensionScript); - doc.body.appendChild(lightboxable); - - mockOgType(type); - mockIsProxyOrigin(true); - expectIsEnabled(false); }); - - it(`rejects docs with ${ogTypeMeta} for non-proxy origin`, () => { - mockOgType(type); - mockIsProxyOrigin(false); - expectIsEnabled(false); - }); - }); }); - }); - - describe('apply', () => { + describe('apply', () => { + it('sets attribute', async () => { + const element = html` + + `; - it('sets attribute', async() => { - const element = html``; + await apply(env.ampdoc, element); - await apply(env.ampdoc, element); - - expect(element).to.have.attribute(LIGHTBOXABLE_ATTR); - }); + expect(element).to.have.attribute(LIGHTBOXABLE_ATTR); + }); - it('sets unique group for each element', async() => { - const candidates = [1, 2, 3].map(() => html``); + it('sets unique group for each element', async () => { + const candidates = [1, 2, 3].map( + () => + html` + + ` + ); - await Promise.all(candidates.map(c => apply(env.ampdoc, c))); + await Promise.all(candidates.map(c => apply(env.ampdoc, c))); - squaredCompare(candidates, (a, b) => { - expect(a.getAttribute(LIGHTBOXABLE_ATTR)) - .not.to.equal(b.getAttribute(LIGHTBOXABLE_ATTR)); + squaredCompare(candidates, (a, b) => { + expect(a.getAttribute(LIGHTBOXABLE_ATTR)).not.to.equal( + b.getAttribute(LIGHTBOXABLE_ATTR) + ); + }); }); - }); - it('dispatches event', async() => { - const element = html``; + it('dispatches event', async () => { + const element = html` + + `; - element.dispatchCustomEvent = env.sandbox.spy(); + element.dispatchCustomEvent = env.sandbox.spy(); - await apply(env.ampdoc, element); + await apply(env.ampdoc, element); - expect(element.dispatchCustomEvent.withArgs(AutoLightboxEvents.NEWLY_SET)) - .to.have.been.calledOnce; + expect( + element.dispatchCustomEvent.withArgs(AutoLightboxEvents.NEWLY_SET) + ).to.have.been.calledOnce; + }); }); - - }); - -}); + } +); diff --git a/extensions/amp-auto-lightbox/0.1/test/test-carousel-criteria.js b/extensions/amp-auto-lightbox/0.1/test/test-carousel-criteria.js index f468a4f2a262..b0f4744c1221 100644 --- a/extensions/amp-auto-lightbox/0.1/test/test-carousel-criteria.js +++ b/extensions/amp-auto-lightbox/0.1/test/test-carousel-criteria.js @@ -18,119 +18,177 @@ import {CarouselCriteria} from '../carousel-criteria'; import {htmlFor} from '../../../../src/static-template'; import {toggleExperiment} from '../../../../src/experiments'; - const TAG = 'amp-auto-lightbox'; - -describes.realWin(TAG, { - amp: { - amp: true, - ampdoc: 'single', - experiments: ['amp-auto-lightbox-carousel'], +describes.realWin( + TAG, + { + amp: { + amp: true, + ampdoc: 'single', + experiments: ['amp-auto-lightbox-carousel'], + }, }, -}, env => { + env => { + let html; + + function buildCarousel(slides) { + const element = html` + + `; + slides.forEach(slide => { + slide.classList.add('amp-carousel-slide'); + element.appendChild(slide); + }); + env.win.document.body.appendChild(element); + return element; + } + + beforeEach(() => { + html = htmlFor(env.win.document.body); + toggleExperiment(env.win, 'amp-auto-lightbox-carousel', true); + }); + + it('rejects carousels without ', () => { + const root = buildCarousel([ + html` +
    Slide 1
    + `, + html` +
    Slide 2
    + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; + }); - let html; + it('rejects carousels with but non-image slides', () => { + const root = buildCarousel([ + html` + + `, + html` + + `, + html` +
    Slide
    + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; + }); - function buildCarousel(slides) { - const element = html``; - slides.forEach(slide => { - slide.classList.add('amp-carousel-slide'); - element.appendChild(slide); + it('accepts carousels with only ', () => { + const root = buildCarousel([ + html` + + `, + html` + + `, + html` + + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; }); - env.win.document.body.appendChild(element); - return element; - } - beforeEach(() => { - html = htmlFor(env.win.document.body); - toggleExperiment(env.win, 'amp-auto-lightbox-carousel', true); - }); - - it('rejects carousels without ', () => { - const root = buildCarousel([ - html`
    Slide 1
    `, - html`
    Slide 2
    `, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; - }); - - it('rejects carousels with but non-image slides', () => { - const root = buildCarousel([ - html``, - html``, - html`
    Slide
    `, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; - }); - - it('accepts carousels with only ', () => { - const root = buildCarousel([ - html``, - html``, - html``, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; - }); - - it('accepts carousels with only (nested)', () => { - const root = buildCarousel([ - html`
    `, - html`
    `, - html`
    `, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; - }); - - it('accepts carousels with in every slide (mixed)', () => { - const root = buildCarousel([ - html`
    Hello world!
    `, - html``, - html`
    Hola
    `, - html`

    My Image

    `, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; - }); - - it('rejects deep trees with only ', () => { - const deep = html`
    - -
    `; - - const root = buildCarousel([ - deep, - deep.cloneNode(/* deep */ true), - deep.cloneNode(/* deep */ true), - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; - }); - - it('rejects wide trees with only ', () => { - const wide = html`
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    `; - - const root = buildCarousel([ - wide, - wide.cloneNode(/* deep */ true), - wide.cloneNode(/* deep */ true), - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; - }); - -}); + it('accepts carousels with only (nested)', () => { + const root = buildCarousel([ + html` +
    + `, + html` +
    + `, + html` +
    + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; + }); + + it('accepts carousels with in every slide (mixed)', () => { + const root = buildCarousel([ + html` +
    Hello world!
    + `, + html` + + `, + html` +
    + +
    Hola
    +
    + `, + html` +
    +

    My Image

    + +
    + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; + }); + + it('rejects deep trees with only ', () => { + const deep = html` +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + `; + + const root = buildCarousel([ + deep, + deep.cloneNode(/* deep */ true), + deep.cloneNode(/* deep */ true), + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; + }); + + it('rejects wide trees with only ', () => { + const wide = html` +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + `; + + const root = buildCarousel([ + wide, + wide.cloneNode(/* deep */ true), + wide.cloneNode(/* deep */ true), + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; + }); + } +); diff --git a/extensions/amp-autocomplete/0.1/amp-autocomplete.js b/extensions/amp-autocomplete/0.1/amp-autocomplete.js index 8f1d31115642..30de9d19b678 100644 --- a/extensions/amp-autocomplete/0.1/amp-autocomplete.js +++ b/extensions/amp-autocomplete/0.1/amp-autocomplete.js @@ -19,8 +19,10 @@ import {CSS} from '../../../build/amp-autocomplete-0.1.css'; import {Keys} from '../../../src/utils/key-codes'; import {Layout} from '../../../src/layout'; import {Services} from '../../../src/services'; -import {UrlReplacementPolicy, - batchFetchJsonFor} from '../../../src/batched-json'; +import { + UrlReplacementPolicy, + batchFetchJsonFor, +} from '../../../src/batched-json'; import {childElementsByTag, removeChildren} from '../../../src/dom'; import {createCustomEvent} from '../../../src/event-helper'; import {dev, user, userAssert} from '../../../src/log'; @@ -49,7 +51,6 @@ export const FilterType = { }; export class AmpAutocomplete extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -141,62 +142,85 @@ export class AmpAutocomplete extends AMP.BaseElement { /** @override */ buildCallback() { - userAssert(isExperimentOn(this.win, 'amp-autocomplete'), - `Experiment ${EXPERIMENT} is not turned on.`); + userAssert( + isExperimentOn(this.win, 'amp-autocomplete'), + `Experiment ${EXPERIMENT} is not turned on.` + ); this.action_ = Services.actionServiceForDoc(this.element); this.viewport_ = Services.viewportForDoc(this.element); - const jsonScript = - this.element.querySelector('script[type="application/json"]'); + const jsonScript = this.element.querySelector( + 'script[type="application/json"]' + ); if (jsonScript) { this.sourceData_ = this.getInlineData_(jsonScript); } else if (!this.element.hasAttribute('src')) { - user().warn(TAG, 'Expected a '; - element.build(); + element.build(); - expect(ampState.fetchAndUpdate_).to.not.have.been.called; - expect(ampState.fetch_).to.not.have.been.called; - expect(ampState.updateState_).calledWithMatch({foo: 'bar'}); - }); + expect(ampState.fetchAndUpdate_).to.not.have.been.called; + expect(ampState.fetch_).to.not.have.been.called; + expect(ampState.updateState_).calledWithMatch({foo: 'bar'}); + }); - it('should parse child and fetch `src` if both provided', () => { - element.innerHTML = ''; - element.setAttribute('src', 'https://foo.com/bar?baz=1'); - element.build(); + it('should parse child and fetch `src` if both provided', () => { + element.innerHTML = + ''; + element.setAttribute('src', 'https://foo.com/bar?baz=1'); + element.build(); - // IMPORTANT: No CORS fetch should happen until viewer is visible. - expect(ampState.fetch_).to.not.have.been.called; + // IMPORTANT: No CORS fetch should happen until viewer is visible. + expect(ampState.fetch_).to.not.have.been.called; - whenFirstVisiblePromiseResolve(); - return whenFirstVisiblePromise.then(() => { - expect(ampState.updateState_).calledWithMatch({foo: 'bar'}); - expect(ampState.fetchAndUpdate_).calledWithExactly(/* isInit */ true); - return getViewerAuthTokenIfAvailableStub(); - }).then(() => { - return ampState.fetch_(); - }).then(() => { - expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); + whenFirstVisiblePromiseResolve(); + return whenFirstVisiblePromise + .then(() => { + expect(ampState.updateState_).calledWithMatch({foo: 'bar'}); + expect(ampState.fetchAndUpdate_).calledWithExactly(/* isInit */ true); + return getViewerAuthTokenIfAvailableStub(); + }) + .then(() => { + return ampState.fetch_(); + }) + .then(() => { + expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); + }); }); - }); - it('should fetch json if `src` is mutated', () => { - sandbox.stub(viewer, 'hasBeenVisible').returns(false); + it('should fetch json if `src` is mutated', () => { + sandbox.stub(viewer, 'hasBeenVisible').returns(false); - element.setAttribute('src', 'https://foo.com/bar?baz=1'); - element.build(); + element.setAttribute('src', 'https://foo.com/bar?baz=1'); + element.build(); - // IMPORTANT: No CORS fetch should happen until viewer is visible. - expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; - expect(ampState.fetch_).to.not.have.been.called; + // IMPORTANT: No CORS fetch should happen until viewer is visible. + expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; + expect(ampState.fetch_).to.not.have.been.called; - allowConsoleError(() => { - element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); - }); + allowConsoleError(() => { + element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); + }); - expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; - expect(ampState.fetch_).to.not.have.been.called; + expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; + expect(ampState.fetch_).to.not.have.been.called; - viewer.hasBeenVisible.returns(true); - element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); + viewer.hasBeenVisible.returns(true); + element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); - expect(ampState.fetchAndUpdate_).to.have.been.calledTwice; - expect(ampState.fetch_).to.not.have.been.called; + expect(ampState.fetchAndUpdate_).to.have.been.calledTwice; + expect(ampState.fetch_).to.not.have.been.called; - whenFirstVisiblePromiseResolve(); - return whenFirstVisiblePromise + whenFirstVisiblePromiseResolve(); + return whenFirstVisiblePromise .then(() => getViewerAuthTokenIfAvailableStub()) .then(() => ampState.fetch_()) .then(() => { expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); }); - }); - - it('should fetch with auth token if `crossorigin` attribute exists' - + ' with `amp-viewer-auth-token-via-post`', () => { - sandbox.stub(viewer, 'hasBeenVisible').returns(false); - getViewerAuthTokenIfAvailableStub.returns(Promise.resolve('idToken')); - - element.setAttribute('src', 'https://foo.com/bar?baz=1'); - element.setAttribute('crossorigin', 'amp-viewer-auth-token-via-post'); - element.build(); - - // IMPORTANT: No CORS fetch should happen until viewer is visible. - expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; - expect(ampState.fetch_).to.not.have.been.called; - - allowConsoleError(() => { - element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); }); - expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; - expect(ampState.fetch_).to.not.have.been.called; + it( + 'should fetch with auth token if `crossorigin` attribute exists' + + ' with `amp-viewer-auth-token-via-post`', + () => { + sandbox.stub(viewer, 'hasBeenVisible').returns(false); + getViewerAuthTokenIfAvailableStub.returns(Promise.resolve('idToken')); - viewer.hasBeenVisible.returns(true); - element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); + element.setAttribute('src', 'https://foo.com/bar?baz=1'); + element.setAttribute('crossorigin', 'amp-viewer-auth-token-via-post'); + element.build(); - expect(ampState.fetchAndUpdate_).to.have.been.calledTwice; - expect(ampState.fetch_).to.not.have.been.called; + // IMPORTANT: No CORS fetch should happen until viewer is visible. + expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; + expect(ampState.fetch_).to.not.have.been.called; - whenFirstVisiblePromiseResolve(); - return whenFirstVisiblePromise - .then(() => ampState.prepareAndSendFetch_({win}, element)) - .then(() => { - expect(fetchStub).to.have.been.called; - expect(fetchStub.firstCall.args.slice(-1).pop()) - .to.be.equal('idToken'); - expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); + allowConsoleError(() => { + element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); }); - }); -}); + + expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; + expect(ampState.fetch_).to.not.have.been.called; + + viewer.hasBeenVisible.returns(true); + element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); + + expect(ampState.fetchAndUpdate_).to.have.been.calledTwice; + expect(ampState.fetch_).to.not.have.been.called; + + whenFirstVisiblePromiseResolve(); + return whenFirstVisiblePromise + .then(() => ampState.prepareAndSendFetch_({win}, element)) + .then(() => { + expect(fetchStub).to.have.been.called; + expect(fetchStub.firstCall.args.slice(-1).pop()).to.be.equal( + 'idToken' + ); + expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); + }); + } + ); + } +); diff --git a/extensions/amp-bind/0.1/test/test-bind-evaluator.js b/extensions/amp-bind/0.1/test/test-bind-evaluator.js index dd910fe7ab1e..990ef1c38a2f 100644 --- a/extensions/amp-bind/0.1/test/test-bind-evaluator.js +++ b/extensions/amp-bind/0.1/test/test-bind-evaluator.js @@ -42,33 +42,41 @@ describe('BindEvaluator', () => { it('should allow callers to add bindings multiple times', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'oneplusone + 2', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'oneplusone + 2', + }, + ]); expect(numberOfBindings()).to.equal(1); - evaluator.addBindings([{ - tagName: 'SPAN', - property: 'text', - expressionString: 'oneplusone + 3', - }]); + evaluator.addBindings([ + { + tagName: 'SPAN', + property: 'text', + expressionString: 'oneplusone + 3', + }, + ]); expect(numberOfBindings()).to.equal(2); }); it('should allow callers to remove bindings', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'oneplusone + 2', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'oneplusone + 2', + }, + ]); expect(numberOfBindings()).to.equal(1); - evaluator.addBindings([{ - tagName: 'SPAN', - property: 'text', - expressionString: 'oneplusone + 3', - }]); + evaluator.addBindings([ + { + tagName: 'SPAN', + property: 'text', + expressionString: 'oneplusone + 3', + }, + ]); expect(numberOfBindings()).to.equal(2); evaluator.removeBindingsWithExpressionStrings(['oneplusone + 2']); expect(numberOfBindings()).to.equal(1); @@ -77,15 +85,18 @@ describe('BindEvaluator', () => { }); it('should only evaluate duplicate expressions once', () => { - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: '1+1', - }, { - tagName: 'DIV', - property: 'text', - expressionString: '1+1', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: '1+1', + }, + { + tagName: 'DIV', + property: 'text', + expressionString: '1+1', + }, + ]); const stub = sandbox.stub(BindExpression.prototype, 'evaluate'); stub.returns('stubbed'); evaluator.evaluateBindings({}); @@ -94,15 +105,18 @@ describe('BindEvaluator', () => { it('should clean up removed expressions from its cache', () => { expect(numberOfCachedExpressions()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'oneplusone + 2', - }, { - tagName: 'A', - property: 'href', - expressionString: 'url', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'oneplusone + 2', + }, + { + tagName: 'A', + property: 'href', + expressionString: 'url', + }, + ]); expect(numberOfCachedExpressions()).to.equal(2); evaluator.removeBindingsWithExpressionStrings(['url']); expect(numberOfCachedExpressions()).to.equal(1); @@ -110,11 +124,13 @@ describe('BindEvaluator', () => { it('should evaluate expressions given a scope with needed bindings', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'oneplusone + 2', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'oneplusone + 2', + }, + ]); expect(numberOfBindings()).to.equal(1); const {results, errors} = evaluator.evaluateBindings({oneplusone: 2}); expect(results['oneplusone + 2']).to.equal(4); @@ -123,11 +139,13 @@ describe('BindEvaluator', () => { it('should treat out-of-scope vars as null', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'outOfScope', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'outOfScope', + }, + ]); expect(numberOfBindings()).to.equal(1); const {results, errors} = evaluator.evaluateBindings({}); expect(results['outOfScope']).to.be.null; @@ -136,22 +154,27 @@ describe('BindEvaluator', () => { it('should validate a common expression on each respective binding', () => { const string = /* eslint no-script-url: 0 */ '"javascript:alert(1)"'; - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: string, - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: string, + }, + ]); let {results, errors} = evaluator.evaluateBindings({}); - expect(results[string]) - .to.equal(/* eslint no-script-url: 0 */ 'javascript:alert(1)'); + expect(results[string]).to.equal( + /* eslint no-script-url: 0 */ 'javascript:alert(1)' + ); expect(errors[string]).to.be.undefined; // An expression used in a single invalid binding should be removed. - evaluator.addBindings([{ - tagName: 'A', - property: 'href', - expressionString: string, - }]); + evaluator.addBindings([ + { + tagName: 'A', + property: 'href', + expressionString: string, + }, + ]); ({results, errors} = evaluator.evaluateBindings({})); expect(results[string]).to.be.undefined; expect(errors[string].message).to.match(/not a valid result/); @@ -159,16 +182,20 @@ describe('BindEvaluator', () => { it('should evaluate expressions with macros', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addMacros([{ - id: 'add', - argumentNames: ['a', 'b'], - expressionString: 'a + b', - }]); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'add(oneplusone, 2)', - }]); + evaluator.addMacros([ + { + id: 'add', + argumentNames: ['a', 'b'], + expressionString: 'a + b', + }, + ]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'add(oneplusone, 2)', + }, + ]); expect(numberOfBindings()).to.equal(1); const {results, errors} = evaluator.evaluateBindings({oneplusone: 2}); expect(results['add(oneplusone, 2)']).to.equal(4); @@ -177,20 +204,25 @@ describe('BindEvaluator', () => { it('should evaluate expressions with nested macros', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addMacros([{ - id: 'add', - argumentNames: ['a', 'b'], - expressionString: 'a + b', - }, { - id: 'addThree', - argumentNames: ['a', 'b', 'c'], - expressionString: 'add(add(a, b), c)', - }]); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'addThree(oneplusone, 2, 2)', - }]); + evaluator.addMacros([ + { + id: 'add', + argumentNames: ['a', 'b'], + expressionString: 'a + b', + }, + { + id: 'addThree', + argumentNames: ['a', 'b', 'c'], + expressionString: 'add(add(a, b), c)', + }, + ]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'addThree(oneplusone, 2, 2)', + }, + ]); expect(numberOfBindings()).to.equal(1); const {results, errors} = evaluator.evaluateBindings({oneplusone: 2}); expect(results['addThree(oneplusone, 2, 2)']).to.equal(6); @@ -198,43 +230,52 @@ describe('BindEvaluator', () => { }); it('should not allow recursive macros', () => { - evaluator.addMacros([{ - id: 'recurse', - expressionString: 'recurse()', - }]); + evaluator.addMacros([ + { + id: 'recurse', + expressionString: 'recurse()', + }, + ]); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'recurse()', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'recurse()', + }, + ]); const {results, errors} = evaluator.evaluateBindings({}); expect(results['recurse()']).to.be.undefined; expect(errors['recurse()'].message).to.match( - /recurse is not a supported function/); + /recurse is not a supported function/ + ); }); it('should not allow cyclic references in macros', () => { - evaluator.addMacros([{ - id: 'foo', - argumentNames: ['x'], - expressionString: 'bar(x)', - }, { - id: 'bar', - argumentNames: ['x'], - expressionString: 'foo(x)', - }]); - - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'bar()', - }]); + evaluator.addMacros([ + { + id: 'foo', + argumentNames: ['x'], + expressionString: 'bar(x)', + }, + { + id: 'bar', + argumentNames: ['x'], + expressionString: 'foo(x)', + }, + ]); + + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'bar()', + }, + ]); const {results, errors} = evaluator.evaluateBindings({}); expect(results['bar()']).to.be.undefined; - expect(errors['bar()'].message).to.match( - /bar is not a supported function/); + expect(errors['bar()'].message).to.match(/bar is not a supported function/); }); }); diff --git a/extensions/amp-bind/0.1/test/test-bind-expression.js b/extensions/amp-bind/0.1/test/test-bind-expression.js index 14ca226aa296..d5138f9bbc48 100644 --- a/extensions/amp-bind/0.1/test/test-bind-expression.js +++ b/extensions/amp-bind/0.1/test/test-bind-expression.js @@ -74,27 +74,69 @@ describe('BindExpression', () => { }); it('disallow: operators with side effects', () => { - expect(() => { evaluate('foo = 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo += 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo -= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo *= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo /= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo %= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo **= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo <<= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo >>= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo >>>= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo &= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo ^= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo |= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo++', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo--', {foo: 0}); }).to.throw(); - expect(() => { evaluate('~foo', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo << 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo >> 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo >>> 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('new Object()', {foo: 0}); }).to.throw(); - expect(() => { evaluate('delete foo', {foo: 0}); }).to.throw(); + expect(() => { + evaluate('foo = 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo += 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo -= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo *= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo /= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo %= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo **= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo <<= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo >>= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo >>>= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo &= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo ^= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo |= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo++', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo--', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('~foo', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo << 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo >> 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo >>> 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('new Object()', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('delete foo', {foo: 0}); + }).to.throw(); }); /** @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators */ @@ -109,10 +151,18 @@ describe('BindExpression', () => { expect(evaluate('new')).to.be.null; expect(evaluate('super')).to.be.null; - expect(() => { evaluate('function*'); }).to.throw(); - expect(() => { evaluate('/ab+c/i'); }).to.throw(); - expect(() => { evaluate('yield*'); }).to.throw(); - expect(() => { evaluate('async function*'); }).to.throw(); + expect(() => { + evaluate('function*'); + }).to.throw(); + expect(() => { + evaluate('/ab+c/i'); + }).to.throw(); + expect(() => { + evaluate('yield*'); + }).to.throw(); + expect(() => { + evaluate('async function*'); + }).to.throw(); }); }); @@ -162,8 +212,7 @@ describe('BindExpression', () => { expect(evaluate('"abc".indexOf("b")')).to.equal(1); expect(evaluate('"aaa".lastIndexOf("a")')).to.equal(2); expect(evaluate('"abc".slice(0, 2)')).to.equal('ab'); - expect(evaluate('"a-b-c".split("-")')) - .to.deep.equal(['a', 'b', 'c']); + expect(evaluate('"a-b-c".split("-")')).to.deep.equal(['a', 'b', 'c']); expect(evaluate('"abc".substr(1)')).to.equal('bc'); expect(evaluate('"abc".substring(0, 2)')).to.equal('ab'); expect(evaluate('"ABC".toLowerCase()')).to.equal('abc'); @@ -215,10 +264,12 @@ describe('BindExpression', () => { expect(evaluate('foo', {foo: 'bar'})).to.equal('bar'); expect(evaluate('foo', {foo: 1})).to.equal(1); expect(evaluate('foo', {foo: [1, 2, 3]})).to.deep.equal([1, 2, 3]); - expect(evaluate('foo', {foo: {'bar': 'qux'}})) - .to.deep.equal({bar: 'qux'}); - expect(evaluate('{"foo": bar}', {bar: 'qux'})) - .to.deep.equal({foo: 'qux'}); + expect(evaluate('foo', {foo: {'bar': 'qux'}})).to.deep.equal({ + bar: 'qux', + }); + expect(evaluate('{"foo": bar}', {bar: 'qux'})).to.deep.equal({ + foo: 'qux', + }); expect(evaluate('[foo]', {foo: 'bar'})).to.deep.equal(['bar']); expect(evaluate('foo[1]', {foo: ['b', 'c']})).to.equal('c'); expect(evaluate('foo.length', {foo: [1, 2, 3]})).to.equal(3); @@ -230,17 +281,15 @@ describe('BindExpression', () => { it('literals', () => { expect(evaluate('[]')).to.deep.equal([]); expect(evaluate('["a", "b"].length')).to.equal(2); - expect(evaluate('[1, "a", [], {}]')) - .to.deep.equal([1, 'a', [], {}]); + expect(evaluate('[1, "a", [], {}]')).to.deep.equal([1, 'a', [], {}]); expect(evaluate('["a", "b"][1]')).to.equal('b'); expect(evaluate('["a", foo][1]', {foo: 'b'})).to.equal('b'); }); it('trailing commas in literals', () => { - expect(evaluate('[1,2,3,]')).to.deep.equal([1,2,3]); + expect(evaluate('[1,2,3,]')).to.deep.equal([1, 2, 3]); expect(evaluate('["a", "b",].length')).to.equal(2); - expect(evaluate('[1, "a", [], {},]')) - .to.deep.equal([1, 'a', [], {}]); + expect(evaluate('[1, "a", [], {},]')).to.deep.equal([1, 'a', [], {}]); expect(evaluate('["a", "b",][1]')).to.equal('b'); expect(evaluate('["a", foo,][1]', {foo: 'b'})).to.equal('b'); }); @@ -255,29 +304,44 @@ describe('BindExpression', () => { }); it('prototype functions', () => { - expect(evaluate('["a", "b"].concat(["c", "d"])')) - .to.deep.equal(['a', 'b', 'c', 'd']); + expect(evaluate('["a", "b"].concat(["c", "d"])')).to.deep.equal([ + 'a', + 'b', + 'c', + 'd', + ]); expect(evaluate('["a", "a"].indexOf("a")')).to.equal(0); expect(evaluate('["a", "b", "c"].join("-")')).to.equal('a-b-c'); expect(evaluate('["a", "a"].lastIndexOf("a")')).to.equal(1); - expect(evaluate('["a", "b", "c"].slice(1, 2)')) - .to.deep.equal(['b']); + expect(evaluate('["a", "b", "c"].slice(1, 2)')).to.deep.equal(['b']); expect(evaluate('[1, 2, 3, 4, 5].includes(3)')).to.be.true; }); it('custom Array#sort()', () => { expect(evaluate('[11, 1, 2].sort()')).to.deep.equal([1, 11, 2]); - expect(evaluate('[11, 1, 2].sort((x, y) => x - y)')) - .to.deep.equal([1, 2, 11]); + expect(evaluate('[11, 1, 2].sort((x, y) => x - y)')).to.deep.equal([ + 1, + 2, + 11, + ]); const a = [11, 1, 2]; expect(evaluate('a.sort()', {a})).to.deep.equal([1, 11, 2]); - expect(evaluate('a.sort((x, y) => x - y)', {a})) - .to.deep.equal([1, 2, 11]); + expect(evaluate('a.sort((x, y) => x - y)', {a})).to.deep.equal([ + 1, + 2, + 11, + ]); // Sort should be out-of-place i.e. does not sort the caller. - expect(evaluate('a.sort().concat(a)', {a})) - .to.deep.equal([1, 11, 2, 11, 1, 2]); + expect(evaluate('a.sort().concat(a)', {a})).to.deep.equal([ + 1, + 11, + 2, + 11, + 1, + 2, + ]); }); it('custom Array#splice()', () => { @@ -293,8 +357,12 @@ describe('BindExpression', () => { expect(evaluate('a.splice(1, 1, 47)', {a})).to.deep.equal([1, 47, 3]); // Splice should be out-of-place i.e. does not splice the caller. - expect(evaluate('a.splice(1).concat(a)', {a})) - .to.deep.equal([1, 1, 2, 3]); + expect(evaluate('a.splice(1).concat(a)', {a})).to.deep.equal([ + 1, + 1, + 2, + 3, + ]); }); it('non-whitelisted functions', () => { @@ -396,10 +464,12 @@ describe('BindExpression', () => { }); it('encodeURI and encodeURIComponent', () => { - expect(evaluate('encodeURI("http://google.com/s p a c e.html")')) - .to.equal('http://google.com/s%20p%20a%20c%20e.html'); - expect(evaluate('encodeURIComponent("http://google.com/foo?foo=bar")')) - .to.equal('http%3A%2F%2Fgoogle.com%2Ffoo%3Ffoo%3Dbar'); + expect( + evaluate('encodeURI("http://google.com/s p a c e.html")') + ).to.equal('http://google.com/s%20p%20a%20c%20e.html'); + expect( + evaluate('encodeURIComponent("http://google.com/foo?foo=bar")') + ).to.equal('http%3A%2F%2Fgoogle.com%2Ffoo%3Ffoo%3Dbar'); }); it('splice()', () => { @@ -429,26 +499,45 @@ describe('BindExpression', () => { }); it('disallow: function declarations', () => { - expect(() => { evaluate('(function() {})'); }).to.throw(); - expect(() => { evaluate('function foo() {}'); }).to.throw(); - expect(() => { evaluate('new Function()'); }).to.throw(); - expect(() => { evaluate('Function()'); }).to.throw(); - expect(() => { evaluate('() => {}'); }).to.throw(); - expect(() => { evaluate('class Foo {}'); }).to.throw(); + expect(() => { + evaluate('(function() {})'); + }).to.throw(); + expect(() => { + evaluate('function foo() {}'); + }).to.throw(); + expect(() => { + evaluate('new Function()'); + }).to.throw(); + expect(() => { + evaluate('Function()'); + }).to.throw(); + expect(() => { + evaluate('() => {}'); + }).to.throw(); + expect(() => { + evaluate('class Foo {}'); + }).to.throw(); }); it('disallow: invocation of custom functions in scope', () => { const scope = { foo: { - bar: () => { 'bar'; }, + bar: () => { + 'bar'; + }, + }, + baz: () => { + 'baz'; }, - baz: () => { 'baz'; }, qux: window.Function, }; // baz() throws a parse error because functions must have a caller. - expect(() => { evaluate('baz()', scope); }).to.throw(); - expect(() => { evaluate('foo.bar()', scope); }) - .to.throw(Error, unsupportedFunctionError); + expect(() => { + evaluate('baz()', scope); + }).to.throw(); + expect(() => { + evaluate('foo.bar()', scope); + }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('foo.qux("a", "return a")', scope); }).to.throw(unsupportedFunctionError); @@ -490,8 +579,11 @@ describe('BindExpression', () => { // Only allow objects in arguments for some functions. expect(evaluate('keys({x: 2})')).to.deep.equal(['x']); expect(evaluate('values({x: 2})')).to.deep.equal([2]); - expect(evaluate('splice([1, 3], 1, 0, {x: 2})')) - .to.deep.equal([1, {x: 2}, 3]); + expect(evaluate('splice([1, 3], 1, 0, {x: 2})')).to.deep.equal([ + 1, + {x: 2}, + 3, + ]); }); }); @@ -542,13 +634,21 @@ describe('BindExpression', () => { }); it('disallow: loops', () => { - expect(() => { evaluate('if (foo) "bar"', {foo: 0}); }).to.throw(); + expect(() => { + evaluate('if (foo) "bar"', {foo: 0}); + }).to.throw(); expect(() => { evaluate('switch (foo) { case 0: "bar" }', {foo: 0}); }).to.throw(); - expect(() => { evaluate('for (;;) {}'); }).to.throw(); - expect(() => { evaluate('while (true) {}'); }).to.throw(); - expect(() => { evaluate('do {} while (true)'); }).to.throw(); + expect(() => { + evaluate('for (;;) {}'); + }).to.throw(); + expect(() => { + evaluate('while (true) {}'); + }).to.throw(); + expect(() => { + evaluate('do {} while (true)'); + }).to.throw(); expect(() => { evaluate('for (var i in foo) {}', {foo: [1, 2, 3]}); }).to.throw(); @@ -566,16 +666,36 @@ describe('BindExpression', () => { expect(evaluate('NaN')).to.be.null; expect(evaluate('undefined')).to.be.null; - expect(() => { evaluate('eval()'); }).to.throw(); - expect(() => { evaluate('uneval()'); }).to.throw(); - expect(() => { evaluate('isFinite()'); }).to.throw(); - expect(() => { evaluate('isNaN()'); }).to.throw(); - expect(() => { evaluate('parseFloat()'); }).to.throw(); - expect(() => { evaluate('parseInt()'); }).to.throw(); - expect(() => { evaluate('decodeURI()'); }).to.throw(); - expect(() => { evaluate('decodeURIComponent()'); }).to.throw(); - expect(() => { evaluate('escape()'); }).to.throw(); - expect(() => { evaluate('unescape()'); }).to.throw(); + expect(() => { + evaluate('eval()'); + }).to.throw(); + expect(() => { + evaluate('uneval()'); + }).to.throw(); + expect(() => { + evaluate('isFinite()'); + }).to.throw(); + expect(() => { + evaluate('isNaN()'); + }).to.throw(); + expect(() => { + evaluate('parseFloat()'); + }).to.throw(); + expect(() => { + evaluate('parseInt()'); + }).to.throw(); + expect(() => { + evaluate('decodeURI()'); + }).to.throw(); + expect(() => { + evaluate('decodeURIComponent()'); + }).to.throw(); + expect(() => { + evaluate('escape()'); + }).to.throw(); + expect(() => { + evaluate('unescape()'); + }).to.throw(); expect(evaluate('Object')).to.be.null; expect(evaluate('Function')).to.be.null; @@ -635,8 +755,8 @@ describe('BindExpression', () => { }); it('disallow: exceeding maximum AST size', () => { - expect(new BindExpression('1 + 1', {}, /* maxAstSize */ 3)) - .to.not.be.null; + expect(new BindExpression('1 + 1', {}, /* maxAstSize */ 3)).to.not.be + .null; // The expression '1 + 1' should have an AST size of 3 -- one for each // literal, and a PLUS expression wrapping them. @@ -653,16 +773,16 @@ describe('BindExpression', () => { expect(add.getExpressionSize()).to.equal(3); // The expression add(1, 1) should have an AST size of 3. - expect(new BindExpression('add(1, 1)', {add}, /* maxAstSize */ 3)) - .to.not.be.null; + expect(new BindExpression('add(1, 1)', {add}, /* maxAstSize */ 3)).to.not + .be.null; expect(() => { new BindExpression('add(1, 1)', {add}, /* maxAstSize */ 2); }).to.throw(expressionSizeExceededError); // The expression add(1, 1 + 1) should have an AST size of 5. - expect(new BindExpression('add(1, 1 + 1)', {add}, /* maxAstSize */ 5)) - .to.not.be.null; + expect(new BindExpression('add(1, 1 + 1)', {add}, /* maxAstSize */ 5)).to + .not.be.null; expect(() => { new BindExpression('add(1, 1 + 1)', {add}, /* maxAstSize */ 4); @@ -704,27 +824,51 @@ describe('BindExpression', () => { }); it('disallow: usage other than as function parameter', () => { - expect(() => { evaluate('() => 123'); }).to.throw(); - expect(() => { evaluate('x => 123'); }).to.throw(); - expect(() => { evaluate('(x, y) => 123'); }).to.throw(); + expect(() => { + evaluate('() => 123'); + }).to.throw(); + expect(() => { + evaluate('x => 123'); + }).to.throw(); + expect(() => { + evaluate('(x, y) => 123'); + }).to.throw(); - expect(() => { evaluate('(() => 123).constructor()'); }).to.throw(); - expect(() => { evaluate('(x => 123).constructor()'); }).to.throw(); - expect(() => { evaluate('((x, y) => 123).constructor()'); }).to.throw(); + expect(() => { + evaluate('(() => 123).constructor()'); + }).to.throw(); + expect(() => { + evaluate('(x => 123).constructor()'); + }).to.throw(); + expect(() => { + evaluate('((x, y) => 123).constructor()'); + }).to.throw(); - expect(() => { evaluate('(() => 123).name'); }).to.throw(); - expect(() => { evaluate('(x => 123).name'); }).to.throw(); - expect(() => { evaluate('((x, y) => 123).name'); }).to.throw(); + expect(() => { + evaluate('(() => 123).name'); + }).to.throw(); + expect(() => { + evaluate('(x => 123).name'); + }).to.throw(); + expect(() => { + evaluate('((x, y) => 123).name'); + }).to.throw(); }); it('disallow: `arguments` or `this`', () => { const a = [1, 2, 3]; - expect(evaluate('a.map(() => arguments)', {a})) - .to.deep.equal([null, null, null]); + expect(evaluate('a.map(() => arguments)', {a})).to.deep.equal([ + null, + null, + null, + ]); expect(evaluate('a.reduce(() => arguments)', {a})).to.deep.equal(null); - expect(evaluate('a.map(() => this)', {a})) - .to.deep.equal([null, null, null]); + expect(evaluate('a.map(() => this)', {a})).to.deep.equal([ + null, + null, + null, + ]); expect(evaluate('a.reduce(() => this)', {a})).to.deep.equal(null); }); }); diff --git a/extensions/amp-bind/0.1/test/test-bind-validator.js b/extensions/amp-bind/0.1/test/test-bind-validator.js index 496d4a9b5abc..1686c3dbf5e0 100644 --- a/extensions/amp-bind/0.1/test/test-bind-validator.js +++ b/extensions/amp-bind/0.1/test/test-bind-validator.js @@ -126,35 +126,64 @@ describe('BindValidator (allowUrlProperties=true)', () => { it('should NOT allow invalid "class" attribute values', () => { expect(val.isResultValid('DIV', 'class', 'foo')).to.be.true; - expect(val.isResultValid( - 'DIV', 'class', 'i-amphtml-foo')).to.be.false; - expect(val.isResultValid( - 'DIV', 'class', 'foo i-amphtml-bar')).to.be.false; + expect(val.isResultValid('DIV', 'class', 'i-amphtml-foo')).to.be.false; + expect(val.isResultValid('DIV', 'class', 'foo i-amphtml-bar')).to.be + .false; }); it('should NOT sanitize "text" attribute values', () => { expect(val.isResultValid('P', 'text', 'Hello World')).to.be.true; expect(val.isResultValid('P', 'text', '')).to.be.true; expect(val.isResultValid('P', 'text', null)).to.be.true; - expect(val.isResultValid( - 'P', 'text', '')).to.be.true; + expect(val.isResultValid('P', 'text', '')).to.be + .true; }); it('should block dangerous attribute URLs in standard elements', () => { - expect(val.isResultValid('A', 'href', - /* eslint no-script-url: 0 */ 'javascript:alert(1)')).to.be.false; - expect(val.isResultValid('A', 'href', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; - - expect(val.isResultValid('SOURCE', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)')).to.be.false; - expect(val.isResultValid('SOURCE', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; - - expect(val.isResultValid('TRACK', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)')).to.be.false; - expect(val.isResultValid('TRACK', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; + expect( + val.isResultValid( + 'A', + 'href', + /* eslint no-script-url: 0 */ 'javascript:alert(1)' + ) + ).to.be.false; + expect( + val.isResultValid( + 'A', + 'href', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; + + expect( + val.isResultValid( + 'SOURCE', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)' + ) + ).to.be.false; + expect( + val.isResultValid( + 'SOURCE', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; + + expect( + val.isResultValid( + 'TRACK', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)' + ) + ).to.be.false; + expect( + val.isResultValid( + 'TRACK', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; }); it('should NOT allow unsupported "type" values', () => { @@ -184,25 +213,39 @@ describe('BindValidator (allowUrlProperties=true)', () => { it('should support ', () => { expect(val.canBind('AMP-IMG', 'src')).to.be.true; - expect(val.isResultValid( - 'AMP-IMG', 'src', 'http://foo.com/bar.jpg')).to.be.true; - expect(val.isResultValid('AMP-IMG', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; - expect(val.isResultValid( - 'AMP-IMG', 'src', '?__amp_source_origin=foo')).to.be.false; - - expect(val.isResultValid( + expect(val.isResultValid('AMP-IMG', 'src', 'http://foo.com/bar.jpg')).to + .be.true; + expect( + val.isResultValid( + 'AMP-IMG', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; + expect(val.isResultValid('AMP-IMG', 'src', '?__amp_source_origin=foo')).to + .be.false; + + expect( + val.isResultValid( 'AMP-IMG', 'srcset', - 'http://a.com/b.jpg 1x, http://c.com/d.jpg 2x')).to.be.true; - expect(val.isResultValid( + 'http://a.com/b.jpg 1x, http://c.com/d.jpg 2x' + ) + ).to.be.true; + expect( + val.isResultValid( 'AMP-IMG', 'srcset', - /* eslint no-script-url: 0 */ 'javascript:alert(1);')).to.be.false; - expect(val.isResultValid( + /* eslint no-script-url: 0 */ 'javascript:alert(1);' + ) + ).to.be.false; + expect( + val.isResultValid( 'AMP-IMG', 'src', - 'http://a.com/b.jpg 1x, ?__amp_source_origin=foo 2x')).to.be.false; + 'http://a.com/b.jpg 1x, ?__amp_source_origin=foo 2x' + ) + ).to.be.false; }); it('should support ', () => { @@ -223,12 +266,12 @@ describe('BindValidator (allowUrlProperties=true)', () => { it('should support ', () => { expect(val.canBind('AMP-STATE', 'src')).to.be.true; - expect(val.isResultValid( - 'AMP-STATE', 'src', 'https://foo.com/bar.json')).to.be.true; - expect(val.isResultValid( - 'AMP-STATE', 'src', 'http://foo.com/bar.json')).to.be.false; - expect(val.isResultValid( - 'AMP-STATE', 'src', 'data://foo.com/bar.json')).to.be.false; + expect(val.isResultValid('AMP-STATE', 'src', 'https://foo.com/bar.json')) + .to.be.true; + expect(val.isResultValid('AMP-STATE', 'src', 'http://foo.com/bar.json')) + .to.be.false; + expect(val.isResultValid('AMP-STATE', 'src', 'data://foo.com/bar.json')) + .to.be.false; }); it('should support ', () => { @@ -236,18 +279,28 @@ describe('BindValidator (allowUrlProperties=true)', () => { expect(val.canBind('AMP-VIDEO', 'poster')).to.be.true; expect(val.canBind('AMP-VIDEO', 'src')).to.be.true; - expect(val.isResultValid( - 'AMP-VIDEO', 'src', 'https://foo.com/bar.mp4')).to.be.true; - expect(val.isResultValid( - 'AMP-VIDEO', 'src', 'http://foo.com/bar.mp4')).to.be.false; - expect(val.isResultValid('AMP-VIDEO', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; + expect(val.isResultValid('AMP-VIDEO', 'src', 'https://foo.com/bar.mp4')) + .to.be.true; + expect(val.isResultValid('AMP-VIDEO', 'src', 'http://foo.com/bar.mp4')).to + .be.false; + expect( + val.isResultValid( + 'AMP-VIDEO', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; }); it('should support (svg) image', () => { expect(val.canBind('IMAGE', 'xlink:href')).to.be.true; - expect(val.isResultValid('IMAGE', 'xlink:href', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; + expect( + val.isResultValid( + 'IMAGE', + 'xlink:href', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; }); }); }); @@ -269,13 +322,16 @@ describe('BindValidator (allowUrlProperties=false)', () => { it('should not validate results of URL properties', () => { expect(val.isResultValid('A', 'href', 'https://google.com')).to.be.false; - expect(val.isResultValid('AMP-IMG', 'src', 'https://foo.com/bar.jpg')) - .to.be.false; - expect(val.isResultValid( + expect(val.isResultValid('AMP-IMG', 'src', 'https://foo.com/bar.jpg')).to.be + .false; + expect( + val.isResultValid( 'AMP-IMG', 'srcset', - 'http://a.com/b.jpg 1x, http://c.com/d.jpg 2x')).to.be.false; + 'http://a.com/b.jpg 1x, http://c.com/d.jpg 2x' + ) + ).to.be.false; expect(val.isResultValid('IMAGE', 'xlink:href', 'https://foo.com/bar.jpg')) - .to.be.false; + .to.be.false; }); }); diff --git a/extensions/amp-bodymovin-animation/0.1/amp-bodymovin-animation.js b/extensions/amp-bodymovin-animation/0.1/amp-bodymovin-animation.js index 9d4128f35fe2..a938713dacd4 100644 --- a/extensions/amp-bodymovin-animation/0.1/amp-bodymovin-animation.js +++ b/extensions/amp-bodymovin-animation/0.1/amp-bodymovin-animation.js @@ -33,7 +33,6 @@ import {userAssert} from '../../../src/log'; const TAG = 'amp-bodymovin-animation'; export class AmpBodymovinAnimation extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -73,9 +72,10 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { * @override */ preconnectCallback(opt_onLayout) { - const scriptToLoad = this.renderer_ === 'svg' ? - 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin_light.min.js' : - 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin.min.js'; + const scriptToLoad = + this.renderer_ === 'svg' + ? 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin_light.min.js' + : 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin.min.js'; preloadBootstrap(this.win, this.preconnect); this.preconnect.url(scriptToLoad, opt_onLayout); } @@ -85,23 +85,47 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { this.loop_ = this.element.getAttribute('loop') || 'true'; this.autoplay_ = !this.element.hasAttribute('noautoplay'); this.renderer_ = this.element.getAttribute('renderer') || 'svg'; - userAssert(this.element.hasAttribute('src'), - 'The src attribute must be specified for '); + userAssert( + this.element.hasAttribute('src'), + 'The src attribute must be specified for ' + ); assertHttpsUrl(this.element.getAttribute('src'), this.element); const deferred = new Deferred(); this.playerReadyPromise_ = deferred.promise; this.playerReadyResolver_ = deferred.resolve; // Register relevant actions - this.registerAction('play', () => { this.play_(); }, ActionTrust.LOW); - this.registerAction('pause', () => { this.pause_(); }, ActionTrust.LOW); - this.registerAction('stop', () => { this.stop_(); }, ActionTrust.LOW); - this.registerAction('seekTo', invocation => { - const {args} = invocation; - if (args) { - this.seekTo_(args); - } - }, ActionTrust.LOW); + this.registerAction( + 'play', + () => { + this.play_(); + }, + ActionTrust.LOW + ); + this.registerAction( + 'pause', + () => { + this.pause_(); + }, + ActionTrust.LOW + ); + this.registerAction( + 'stop', + () => { + this.stop_(); + }, + ActionTrust.LOW + ); + this.registerAction( + 'seekTo', + invocation => { + const {args} = invocation; + if (args) { + this.seekTo_(args); + } + }, + ActionTrust.LOW + ); } /** @override */ @@ -115,19 +139,25 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { animationData: data, }; const iframe = getIframe( - this.win, this.element, 'bodymovinanimation', opt_context); - return Services.vsyncFor(this.win).mutatePromise(() => { - this.applyFillContent(iframe); - this.unlistenMessage_ = listen( + this.win, + this.element, + 'bodymovinanimation', + opt_context + ); + return Services.vsyncFor(this.win) + .mutatePromise(() => { + this.applyFillContent(iframe); + this.unlistenMessage_ = listen( this.win, 'message', this.handleBodymovinMessages_.bind(this) - ); - this.element.appendChild(iframe); - this.iframe_ = iframe; - }).then(() => { - return this.playerReadyPromise_; - }); + ); + this.element.appendChild(iframe); + this.iframe_ = iframe; + }) + .then(() => { + return this.playerReadyPromise_; + }); }); } @@ -154,8 +184,13 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { if (this.iframe_ && event.source != this.iframe_.contentWindow) { return; } - if (!getData(event) || !(isObject(getData(event)) - || startsWith(/** @type {string} */ (getData(event)), '{'))) { + if ( + !getData(event) || + !( + isObject(getData(event)) || + startsWith(/** @type {string} */ (getData(event)), '{') + ) + ) { return; // Doesn't look like JSON. } @@ -181,12 +216,14 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { sendCommand_(action, opt_valueType, opt_value) { this.playerReadyPromise_.then(() => { if (this.iframe_ && this.iframe_.contentWindow) { - const message = JSON.stringify(dict({ - 'action': action, - 'valueType': opt_valueType || '', - 'value': opt_value || '', - })); - this.iframe_.contentWindow. /*OK*/postMessage(message, '*'); + const message = JSON.stringify( + dict({ + 'action': action, + 'valueType': opt_valueType || '', + 'value': opt_value || '', + }) + ); + this.iframe_.contentWindow./*OK*/ postMessage(message, '*'); } }); } diff --git a/extensions/amp-bodymovin-animation/0.1/test/integration/test-amp-bodymovin-animation.js b/extensions/amp-bodymovin-animation/0.1/test/integration/test-amp-bodymovin-animation.js index 04af04b91a67..30637a67bfdf 100644 --- a/extensions/amp-bodymovin-animation/0.1/test/integration/test-amp-bodymovin-animation.js +++ b/extensions/amp-bodymovin-animation/0.1/test/integration/test-amp-bodymovin-animation.js @@ -14,30 +14,41 @@ * limitations under the License. */ -describe.configure().ifChrome().run('amp-bodymovin-animation', function() { - const extensions = ['amp-bodymovin-animation']; - const bodymovinBody = ` +describe + .configure() + .ifChrome() + .run('amp-bodymovin-animation', function() { + const extensions = ['amp-bodymovin-animation']; + const bodymovinBody = `
    Stop
    `; - describes.integration('amp-bodymovin-animation iframe renders', { - body: bodymovinBody, - extensions, - }, unusedEnv => { - // TODO(nainar): Add test. - }); + describes.integration( + 'amp-bodymovin-animation iframe renders', + { + body: bodymovinBody, + extensions, + }, + unusedEnv => { + // TODO(nainar): Add test. + } + ); - describes.integration('amp-bodymovin-animation actions work', { - body: bodymovinBody, - extensions, - }, unusedEnv => { - // TODO(nainar): Add test. - }); + describes.integration( + 'amp-bodymovin-animation actions work', + { + body: bodymovinBody, + extensions, + }, + unusedEnv => { + // TODO(nainar): Add test. + } + ); - const bodymovinNoAutoplayBody = ` + const bodymovinNoAutoplayBody = ` Play
    Pause
    `; - describes.integration('amp-bodymovin-animation actions work', { - body: bodymovinNoAutoplayBody, - extensions, - }, unusedEnv => { - // TODO(nainar): Add test. - }); + describes.integration( + 'amp-bodymovin-animation actions work', + { + body: bodymovinNoAutoplayBody, + extensions, + }, + unusedEnv => { + // TODO(nainar): Add test. + } + ); - const bodymovinSeekToBody = ` + const bodymovinSeekToBody = `
    Seek to 1/2
    `; - describes.integration('amp-bodymovin-animation actions work', { - body: bodymovinSeekToBody, - extensions, - }, unusedEnv => { - // TODO(nainar): Add test. + describes.integration( + 'amp-bodymovin-animation actions work', + { + body: bodymovinSeekToBody, + extensions, + }, + unusedEnv => { + // TODO(nainar): Add test. + } + ); }); -}); diff --git a/extensions/amp-brid-player/0.1/amp-brid-player.js b/extensions/amp-brid-player/0.1/amp-brid-player.js index 691ef1c4e8fa..bf761cd1a731 100644 --- a/extensions/amp-brid-player/0.1/amp-brid-player.js +++ b/extensions/amp-brid-player/0.1/amp-brid-player.js @@ -33,9 +33,7 @@ import { } from '../../../src/dom'; import {getData, listen} from '../../../src/event-helper'; import {htmlFor} from '../../../src/static-template'; -import { - installVideoManagerForDoc, -} from '../../../src/service/video-manager-impl'; +import {installVideoManagerForDoc} from '../../../src/service/video-manager-impl'; import {isLayoutSizeDefined} from '../../../src/layout'; const TAG = 'amp-brid-player'; @@ -44,7 +42,6 @@ const TAG = 'amp-brid-player'; * @implements {../../../src/video-interface.VideoInterface} */ class AmpBridPlayer extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -115,11 +112,18 @@ class AmpBridPlayer extends AMP.BaseElement { } //Create iframe - const src = 'https://services.brid.tv/services/iframe/' + - encodeURIComponent(feedType) + - '/' + encodeURIComponent(this.feedID_) + - '/' + encodeURIComponent(this.partnerID_) + - '/' + encodeURIComponent(this.playerID_) + '/0/' + itemsNum + '/?amp=1'; + const src = + 'https://services.brid.tv/services/iframe/' + + encodeURIComponent(feedType) + + '/' + + encodeURIComponent(this.feedID_) + + '/' + + encodeURIComponent(this.partnerID_) + + '/' + + encodeURIComponent(this.playerID_) + + '/0/' + + itemsNum + + '/?amp=1'; this.videoIframeSrc_ = assertAbsoluteHttpOrHttpsUrl(src); @@ -131,21 +135,25 @@ class AmpBridPlayer extends AMP.BaseElement { const {element} = this; this.partnerID_ = userAssert( - element.getAttribute('data-partner'), - 'The data-partner attribute is required for %s', - element); + element.getAttribute('data-partner'), + 'The data-partner attribute is required for %s', + element + ); - this.playerID_ = userAssert(element.getAttribute('data-player'), - 'The data-player attribute is required for %s', - element); + this.playerID_ = userAssert( + element.getAttribute('data-player'), + 'The data-player attribute is required for %s', + element + ); this.feedID_ = userAssert( - (element.getAttribute('data-video') || - element.getAttribute('data-playlist') || - element.getAttribute('data-outstream')), - 'Either the data-video or the data-playlist or the data-outstream ' + + element.getAttribute('data-video') || + element.getAttribute('data-playlist') || + element.getAttribute('data-outstream'), + 'Either the data-video or the data-playlist or the data-outstream ' + 'attributes must be specified for %s', - element); + element + ); const deferred = new Deferred(); this.playerReadyPromise_ = deferred.promise; @@ -162,13 +170,12 @@ class AmpBridPlayer extends AMP.BaseElement { this.iframe_ = /** @type {HTMLIFrameElement} */ (iframe); this.unlistenMessage_ = listen( - this.win, - 'message', - this.handleBridMessage_.bind(this) + this.win, + 'message', + this.handleBridMessage_.bind(this) ); - return this.loadPromise(iframe) - .then(() => this.playerReadyPromise_); + return this.loadPromise(iframe).then(() => this.playerReadyPromise_); } /** @override */ @@ -196,8 +203,10 @@ class AmpBridPlayer extends AMP.BaseElement { createPlaceholderCallback() { const {element} = this; - if (!element.hasAttribute('data-video') && - !element.hasAttribute('data-playlist')) { + if ( + !element.hasAttribute('data-video') && + !element.hasAttribute('data-playlist') + ) { return; } @@ -213,13 +222,15 @@ class AmpBridPlayer extends AMP.BaseElement { this.propagateAttributes(['aria-label'], placeholder); this.applyFillContent(placeholder); - placeholder.setAttribute('src', - `https://cdn.brid.tv/live/partners/${encodeURIComponent(partnerID)}` + - `/snapshot/${encodeURIComponent(feedID)}.jpg`); + placeholder.setAttribute( + 'src', + `https://cdn.brid.tv/live/partners/${encodeURIComponent(partnerID)}` + + `/snapshot/${encodeURIComponent(feedID)}.jpg` + ); - const altText = placeholder.hasAttribute('aria-label') ? - 'Loading video - ' + placeholder.getAttribute('aria-label') : - 'Loading video'; + const altText = placeholder.hasAttribute('aria-label') + ? 'Loading video - ' + placeholder.getAttribute('aria-label') + : 'Loading video'; placeholder.setAttribute('alt', altText); @@ -233,12 +244,11 @@ class AmpBridPlayer extends AMP.BaseElement { * @private * */ sendCommand_(command, opt_arg) { - this.playerReadyPromise_.then(() => { if (this.iframe_ && this.iframe_.contentWindow) { const args = opt_arg === undefined ? '' : '|' + opt_arg; const message = 'Brid|' + command + args; - this.iframe_.contentWindow./*OK*/postMessage(message, '*'); + this.iframe_.contentWindow./*OK*/ postMessage(message, '*'); } }); } @@ -388,7 +398,6 @@ class AmpBridPlayer extends AMP.BaseElement { } } - AMP.extension(TAG, '0.1', AMP => { AMP.registerElement(TAG, AmpBridPlayer); }); diff --git a/extensions/amp-brid-player/0.1/test/test-amp-brid-player.js b/extensions/amp-brid-player/0.1/test/test-amp-brid-player.js index 30f76f52a8e1..37631b0bec76 100644 --- a/extensions/amp-brid-player/0.1/test/test-amp-brid-player.js +++ b/extensions/amp-brid-player/0.1/test/test-amp-brid-player.js @@ -19,100 +19,118 @@ import {Services} from '../../../../src/services'; import {VideoEvents} from '../../../../src/video-interface'; import {listenOncePromise} from '../../../../src/event-helper'; - -describes.realWin('amp-brid-player', { - amp: { - extensions: ['amp-brid-player'], +describes.realWin( + 'amp-brid-player', + { + amp: { + extensions: ['amp-brid-player'], + }, }, -}, env => { - let win, doc; - let timer; - - beforeEach(() => { - win = env.win; - doc = win.document; - timer = Services.timerFor(win); - }); - - function getBridPlayer(attributes, opt_responsive) { - const bc = doc.createElement('amp-brid-player'); + env => { + let win, doc; + let timer; + + beforeEach(() => { + win = env.win; + doc = win.document; + timer = Services.timerFor(win); + }); - for (const key in attributes) { - bc.setAttribute(key, attributes[key]); - } - bc.setAttribute('width', '640'); - bc.setAttribute('height', '360'); - if (opt_responsive) { - bc.setAttribute('layout', 'responsive'); + function getBridPlayer(attributes, opt_responsive) { + const bc = doc.createElement('amp-brid-player'); + + for (const key in attributes) { + bc.setAttribute(key, attributes[key]); + } + bc.setAttribute('width', '640'); + bc.setAttribute('height', '360'); + if (opt_responsive) { + bc.setAttribute('layout', 'responsive'); + } + + // see yt test implementation + timer.promise(50).then(() => { + const bridTimerIframe = bc.querySelector('iframe'); + + bc.implementation_.handleBridMessage_({ + origin: 'https://services.brid.tv', + source: bridTimerIframe.contentWindow, + data: 'Brid|0|trigger|ready', + }); + }); + doc.body.appendChild(bc); + return bc + .build() + .then(() => { + bc.layoutCallback(); + }) + .then(() => bc); } - // see yt test implementation - timer.promise(50).then(() => { - const bridTimerIframe = bc.querySelector('iframe'); - - bc.implementation_.handleBridMessage_({ - origin: 'https://services.brid.tv', - source: bridTimerIframe.contentWindow, - data: 'Brid|0|trigger|ready', + it('renders', () => { + return getBridPlayer({ + 'data-partner': '264', + 'data-player': '4144', + 'data-video': '13663', + }).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.tagName).to.equal('IFRAME'); + expect(iframe.src).to.equal( + 'https://services.brid.tv/services/iframe/video/13663/264/4144/0/1/?amp=1' + ); }); }); - doc.body.appendChild(bc); - return bc.build().then(() => { bc.layoutCallback(); }).then(() => bc); - } - - it('renders', () => { - return getBridPlayer({ - 'data-partner': '264', - 'data-player': '4144', - 'data-video': '13663', - }).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.tagName).to.equal('IFRAME'); - expect(iframe.src).to.equal( - 'https://services.brid.tv/services/iframe/video/13663/264/4144/0/1/?amp=1'); - }); - }); - it('renders responsively', () => { - return getBridPlayer({ - 'data-partner': '1177', - 'data-player': '979', - 'data-video': '5204', - }, true).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.className).to.match(/i-amphtml-fill-content/); + it('renders responsively', () => { + return getBridPlayer( + { + 'data-partner': '1177', + 'data-player': '979', + 'data-video': '5204', + }, + true + ).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.className).to.match(/i-amphtml-fill-content/); + }); }); - }); - it('requires data-partner', () => { - return allowConsoleError(() => { return getBridPlayer({ - 'data-player': '4144', - 'data-video': '13663', - }).should.eventually.be.rejectedWith( - /The data-partner attribute is required for/); + it('requires data-partner', () => { + return allowConsoleError(() => { + return getBridPlayer({ + 'data-player': '4144', + 'data-video': '13663', + }).should.eventually.be.rejectedWith( + /The data-partner attribute is required for/ + ); + }); }); - }); - it('requires data-player', () => { - return allowConsoleError(() => { return getBridPlayer({ - 'data-partner': '264', - 'data-video': '13663', - }).should.eventually.be.rejectedWith( - /The data-player attribute is required for/); + it('requires data-player', () => { + return allowConsoleError(() => { + return getBridPlayer({ + 'data-partner': '264', + 'data-video': '13663', + }).should.eventually.be.rejectedWith( + /The data-player attribute is required for/ + ); + }); }); - }); - it('should forward events from brid-player to the amp element', () => { - return getBridPlayer({ - 'data-partner': '1177', - 'data-player': '979', - 'data-video': '5204', - }, true).then(bc => { - const iframe = bc.querySelector('iframe'); - - return Promise.resolve() + it('should forward events from brid-player to the amp element', () => { + return getBridPlayer( + { + 'data-partner': '1177', + 'data-player': '979', + 'data-video': '5204', + }, + true + ).then(bc => { + const iframe = bc.querySelector('iframe'); + + return Promise.resolve() .then(() => { const p = listenOncePromise(bc, VideoEvents.PLAYING); sendFakeMessage(bc, iframe, 'trigger|play'); @@ -133,62 +151,66 @@ describes.realWin('amp-brid-player', { sendFakeMessage(bc, iframe, 'volume|1'); return p; }); + }); }); - }); + function sendFakeMessage(bc, iframe, command) { + bc.implementation_.handleBridMessage_({ + origin: 'https://services.brid.tv', + source: iframe.contentWindow, + data: 'Brid|0|' + command, + }); + } - function sendFakeMessage(bc, iframe, command) { - bc.implementation_.handleBridMessage_({ - origin: 'https://services.brid.tv', - source: iframe.contentWindow, - data: 'Brid|0|' + command, - }); - } - - describe('createPlaceholderCallback', () => { - it('should create a placeholder image', () => { - return getBridPlayer({ - 'data-partner': '264', - 'data-player': '979', - 'data-video': '13663', - }).then(brid => { - const img = brid.querySelector('amp-img'); - expect(img).to.not.be.null; - expect(img.getAttribute('src')).to.equal( - 'https://cdn.brid.tv/live/partners/264/snapshot/13663.jpg'); - expect(img.getAttribute('layout')).to.equal('fill'); - expect(img.hasAttribute('placeholder')).to.be.true; - expect(img.getAttribute('alt')).to.equal('Loading video'); - expect(img.getAttribute('referrerpolicy')).to.equal('origin'); + describe('createPlaceholderCallback', () => { + it('should create a placeholder image', () => { + return getBridPlayer({ + 'data-partner': '264', + 'data-player': '979', + 'data-video': '13663', + }).then(brid => { + const img = brid.querySelector('amp-img'); + expect(img).to.not.be.null; + expect(img.getAttribute('src')).to.equal( + 'https://cdn.brid.tv/live/partners/264/snapshot/13663.jpg' + ); + expect(img.getAttribute('layout')).to.equal('fill'); + expect(img.hasAttribute('placeholder')).to.be.true; + expect(img.getAttribute('alt')).to.equal('Loading video'); + expect(img.getAttribute('referrerpolicy')).to.equal('origin'); + }); }); - }); - it('should propagate aria label for placeholder image', () => { - return getBridPlayer({ - 'data-partner': '264', - 'data-player': '979', - 'data-video': '13663', - 'aria-label': 'great video', - }).then(brid => { - const img = brid.querySelector('amp-img'); - expect(img).to.not.be.null; - expect(img.getAttribute('alt')).to.equal('Loading video - great video'); + it('should propagate aria label for placeholder image', () => { + return getBridPlayer({ + 'data-partner': '264', + 'data-player': '979', + 'data-video': '13663', + 'aria-label': 'great video', + }).then(brid => { + const img = brid.querySelector('amp-img'); + expect(img).to.not.be.null; + expect(img.getAttribute('alt')).to.equal( + 'Loading video - great video' + ); + }); }); - }); - it('should create a fallback for default snapshot', () => { - return getBridPlayer({ - 'data-partner': '264', - 'data-player': '979', - 'data-video': '13663', - }).then(brid => { - const img = brid.querySelector('amp-img'); - const fallbackImg = img.querySelector('amp-img'); - expect(fallbackImg).to.not.be.null; - expect(fallbackImg.getAttribute('src')).to.equal( - 'https://cdn.brid.tv/live/default/defaultSnapshot.png'); - expect(fallbackImg.getAttribute('layout')).to.equal('fill'); - expect(fallbackImg.hasAttribute('fallback')).to.be.true; - expect(fallbackImg.getAttribute('referrerpolicy')).to.equal('origin'); + it('should create a fallback for default snapshot', () => { + return getBridPlayer({ + 'data-partner': '264', + 'data-player': '979', + 'data-video': '13663', + }).then(brid => { + const img = brid.querySelector('amp-img'); + const fallbackImg = img.querySelector('amp-img'); + expect(fallbackImg).to.not.be.null; + expect(fallbackImg.getAttribute('src')).to.equal( + 'https://cdn.brid.tv/live/default/defaultSnapshot.png' + ); + expect(fallbackImg.getAttribute('layout')).to.equal('fill'); + expect(fallbackImg.hasAttribute('fallback')).to.be.true; + expect(fallbackImg.getAttribute('referrerpolicy')).to.equal('origin'); + }); }); }); - }); -}); + } +); diff --git a/extensions/amp-brightcove/0.1/amp-brightcove.js b/extensions/amp-brightcove/0.1/amp-brightcove.js index e8785a592b88..5f58c60a8b05 100644 --- a/extensions/amp-brightcove/0.1/amp-brightcove.js +++ b/extensions/amp-brightcove/0.1/amp-brightcove.js @@ -35,19 +35,14 @@ import { removeElement, } from '../../../src/dom'; import {getData, listen} from '../../../src/event-helper'; -import { - installVideoManagerForDoc, -} from '../../../src/service/video-manager-impl'; +import {installVideoManagerForDoc} from '../../../src/service/video-manager-impl'; import {isLayoutSizeDefined} from '../../../src/layout'; - /** @private @const {string} */ const TAG = 'amp-brightcove'; - /** @implements {../../../src/video-interface.VideoInterface} */ class AmpBrightcove extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -118,12 +113,17 @@ class AmpBrightcove extends AMP.BaseElement { this.playerReadyResolver_ = deferred.resolve; // Warn if the player does not have video interface support - this.readyTimeout_ = /** @type {number} */ ( - Services.timerFor(window).delay(() => { - user().warn(TAG, - 'Did not receive ready callback from player %s.' + - ' Ensure it has the videojs-amp-support plugin.', this.playerId_); - }, 3000)); + this.readyTimeout_ = /** @type {number} */ (Services.timerFor(window).delay( + () => { + user().warn( + TAG, + 'Did not receive ready callback from player %s.' + + ' Ensure it has the videojs-amp-support plugin.', + this.playerId_ + ); + }, + 3000 + )); this.playerReadyResolver_(this.iframe_); } @@ -134,13 +134,11 @@ class AmpBrightcove extends AMP.BaseElement { this.iframe_ = iframe; - this.unlistenMessage_ = listen( - this.win, - 'message', - e => this.handlePlayerMessage_(e)); + this.unlistenMessage_ = listen(this.win, 'message', e => + this.handlePlayerMessage_(e) + ); - return this.loadPromise(iframe) - .then(() => this.playerReadyPromise_); + return this.loadPromise(iframe).then(() => this.playerReadyPromise_); } /** @@ -154,10 +152,15 @@ class AmpBrightcove extends AMP.BaseElement { // We still need to check this.iframe_ as the component may have // been unlaid out by now. if (this.iframe_ && this.iframe_.contentWindow) { - this.iframe_.contentWindow. /*OK*/ postMessage(JSON.stringify(dict({ - 'command': command, - 'args': arg, - })), 'https://players.brightcove.net'); + this.iframe_.contentWindow./*OK*/ postMessage( + JSON.stringify( + dict({ + 'command': command, + 'args': arg, + }) + ), + 'https://players.brightcove.net' + ); } }); } @@ -209,21 +212,22 @@ class AmpBrightcove extends AMP.BaseElement { this.duration_ = data['dur']; } - if (redispatch(element, eventType, { - 'ready': VideoEvents.LOAD, - 'playing': VideoEvents.PLAYING, - 'pause': VideoEvents.PAUSE, - 'ended': VideoEvents.ENDED, - 'ads-ad-started': VideoEvents.AD_START, - 'ads-ad-ended': VideoEvents.AD_END, - })) { + if ( + redispatch(element, eventType, { + 'ready': VideoEvents.LOAD, + 'playing': VideoEvents.PLAYING, + 'pause': VideoEvents.PAUSE, + 'ended': VideoEvents.ENDED, + 'ads-ad-started': VideoEvents.AD_START, + 'ads-ad-ended': VideoEvents.AD_END, + }) + ) { return; } if (eventType === 'volumechange') { const muted = data['muted']; - if (muted == null || - this.muted_ == muted) { + if (muted == null || this.muted_ == muted) { return; } this.muted_ = muted; @@ -239,18 +243,20 @@ class AmpBrightcove extends AMP.BaseElement { onReady_(data) { this.hasAmpSupport_ = true; - Services.timerFor(this.win) - .cancel(this.readyTimeout_); + Services.timerFor(this.win).cancel(this.readyTimeout_); const {element} = this; installVideoManagerForDoc(element); Services.videoManagerForDoc(element).register(this); - dev().info(TAG, - 'Player %s ready. ' + + dev().info( + TAG, + 'Player %s ready. ' + 'Brightcove Player version: %s AMP Support version: %s', - this.playerId_, data['bcVersion'], data['ampSupportVersion'] + this.playerId_, + data['bcVersion'], + data['ampSupportVersion'] ); } @@ -261,34 +267,34 @@ class AmpBrightcove extends AMP.BaseElement { getIframeSrc_() { const {element: el} = this; const account = userAssert( - el.getAttribute('data-account'), - 'The data-account attribute is required for %s', - el); - const embed = (el.getAttribute('data-embed') || 'default'); + el.getAttribute('data-account'), + 'The data-account attribute is required for %s', + el + ); + const embed = el.getAttribute('data-embed') || 'default'; - this.playerId_ = (el.getAttribute('data-player') || + this.playerId_ = + el.getAttribute('data-player') || el.getAttribute('data-player-id') || - 'default'); + 'default'; const src = - `https://players.brightcove.net/${encodeURIComponent(account)}` + - `/${encodeURIComponent(this.playerId_)}` + - `_${encodeURIComponent(embed)}/index.html` + - // These are encodeURIComponent'd in encodeId_(). - (el.getAttribute('data-playlist-id') ? - '?playlistId=' + this.encodeId_(el.getAttribute('data-playlist-id')) : - (el.getAttribute('data-video-id') ? - '?videoId=' + this.encodeId_(el.getAttribute('data-video-id')) : - '' - ) - ); + `https://players.brightcove.net/${encodeURIComponent(account)}` + + `/${encodeURIComponent(this.playerId_)}` + + `_${encodeURIComponent(embed)}/index.html` + + // These are encodeURIComponent'd in encodeId_(). + (el.getAttribute('data-playlist-id') + ? '?playlistId=' + this.encodeId_(el.getAttribute('data-playlist-id')) + : el.getAttribute('data-video-id') + ? '?videoId=' + this.encodeId_(el.getAttribute('data-video-id')) + : ''); const customReferrer = el.getAttribute('data-referrer'); if (customReferrer) { el.setAttribute( - 'data-param-referrer', - this.urlReplacements_.expandUrlSync(customReferrer) + 'data-param-referrer', + this.urlReplacements_.expandUrlSync(customReferrer) ); } @@ -305,9 +311,13 @@ class AmpBrightcove extends AMP.BaseElement { const embed = mutations['data-embed']; const playlistId = mutations['data-playlist-id']; const videoId = mutations['data-video-id']; - if (account !== undefined || playerId !== undefined || - playlistId !== undefined || embed !== undefined || - videoId !== undefined) { + if ( + account !== undefined || + playerId !== undefined || + playlistId !== undefined || + embed !== undefined || + videoId !== undefined + ) { if (this.iframe_) { this.iframe_.src = this.getIframeSrc_(); } @@ -315,10 +325,10 @@ class AmpBrightcove extends AMP.BaseElement { } /** - * @param {string} id - * @return {string} - * @private - */ + * @param {string} id + * @return {string} + * @private + */ encodeId_(id) { /* id is either a Brightcove-assigned id, or a customer-generated reference id. reference ids are prefixed 'ref:' and the colon @@ -331,8 +341,12 @@ class AmpBrightcove extends AMP.BaseElement { /** @override */ pauseCallback() { - if (this.iframe_ && this.iframe_.contentWindow && - this.hasAmpSupport_ && this.playing_) { + if ( + this.iframe_ && + this.iframe_.contentWindow && + this.hasAmpSupport_ && + this.playing_ + ) { this.pause(); } } @@ -463,7 +477,6 @@ class AmpBrightcove extends AMP.BaseElement { } } - AMP.extension(TAG, '0.1', AMP => { AMP.registerElement(TAG, AmpBrightcove); }); diff --git a/extensions/amp-brightcove/0.1/test/test-amp-brightcove.js b/extensions/amp-brightcove/0.1/test/test-amp-brightcove.js index 803261c69160..20a9890ddcc2 100644 --- a/extensions/amp-brightcove/0.1/test/test-amp-brightcove.js +++ b/extensions/amp-brightcove/0.1/test/test-amp-brightcove.js @@ -19,174 +19,190 @@ import {VideoEvents} from '../../../../src/video-interface'; import {listenOncePromise} from '../../../../src/event-helper'; import {parseUrlDeprecated} from '../../../../src/url'; - -describes.realWin('amp-brightcove', { - amp: { - extensions: ['amp-brightcove'], +describes.realWin( + 'amp-brightcove', + { + amp: { + extensions: ['amp-brightcove'], + }, }, -}, env => { - let win, doc; - - beforeEach(() => { - win = env.win; - doc = win.document; - }); - - function getBrightcove(attributes, opt_responsive) { - const bc = doc.createElement('amp-brightcove'); - for (const key in attributes) { - bc.setAttribute(key, attributes[key]); + env => { + let win, doc; + + beforeEach(() => { + win = env.win; + doc = win.document; + }); + + function getBrightcove(attributes, opt_responsive) { + const bc = doc.createElement('amp-brightcove'); + for (const key in attributes) { + bc.setAttribute(key, attributes[key]); + } + bc.setAttribute('width', '111'); + bc.setAttribute('height', '222'); + if (opt_responsive) { + bc.setAttribute('layout', 'responsive'); + } + doc.body.appendChild(bc); + return bc + .build() + .then(() => bc.layoutCallback()) + .then(() => bc); } - bc.setAttribute('width', '111'); - bc.setAttribute('height', '222'); - if (opt_responsive) { - bc.setAttribute('layout', 'responsive'); + + function fakePostMessage(bc, info) { + bc.implementation_.handlePlayerMessage_({ + origin: 'https://players.brightcove.net', + source: bc.querySelector('iframe').contentWindow, + data: JSON.stringify(info), + }); } - doc.body.appendChild(bc); - return bc.build().then(() => bc.layoutCallback()).then(() => bc); - } - function fakePostMessage(bc, info) { - bc.implementation_.handlePlayerMessage_({ - origin: 'https://players.brightcove.net', - source: bc.querySelector('iframe').contentWindow, - data: JSON.stringify(info), + it('renders', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.tagName).to.equal('IFRAME'); + expect(iframe.src).to.equal( + 'https://players.brightcove.net/1290862519001/default_default' + + '/index.html?videoId=ref:amp-test-video&playsinline=true' + ); + }); }); - } - it('renders', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.tagName).to.equal('IFRAME'); - expect(iframe.src).to.equal( - 'https://players.brightcove.net/1290862519001/default_default' + - '/index.html?videoId=ref:amp-test-video&playsinline=true'); + it('renders responsively', () => { + return getBrightcove( + { + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }, + true + ).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.className).to.match(/i-amphtml-fill-content/); + }); }); - }); - - it('renders responsively', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }, true).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.className).to.match(/i-amphtml-fill-content/); + + it('requires data-account', () => { + expectAsyncConsoleError(/The data-account attribute is required for/, 1); + return getBrightcove({}).should.eventually.be.rejectedWith( + /The data-account attribute is required for/ + ); }); - }); - - it('requires data-account', () => { - expectAsyncConsoleError(/The data-account attribute is required for/, 1); - return getBrightcove({}).should.eventually.be.rejectedWith( - /The data-account attribute is required for/); - }); - - it('removes iframe after unlayoutCallback', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }, true).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - const obj = bc.implementation_; - obj.unlayoutCallback(); - expect(bc.querySelector('iframe')).to.be.null; - expect(obj.iframe_).to.be.null; + + it('removes iframe after unlayoutCallback', () => { + return getBrightcove( + { + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }, + true + ).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + const obj = bc.implementation_; + obj.unlayoutCallback(); + expect(bc.querySelector('iframe')).to.be.null; + expect(obj.iframe_).to.be.null; + }); }); - }); - - it('should pass data-param-* attributes to the iframe src', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - 'data-param-my-param': 'hello world', - }).then(bc => { - const iframe = bc.querySelector('iframe'); - const params = parseUrlDeprecated(iframe.src).search.split('&'); - expect(params).to.contain('myParam=hello%20world'); + + it('should pass data-param-* attributes to the iframe src', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + 'data-param-my-param': 'hello world', + }).then(bc => { + const iframe = bc.querySelector('iframe'); + const params = parseUrlDeprecated(iframe.src).search.split('&'); + expect(params).to.contain('myParam=hello%20world'); + }); }); - }); - it('should propagate mutated attributes', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }).then(bc => { - const iframe = bc.querySelector('iframe'); + it('should propagate mutated attributes', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }).then(bc => { + const iframe = bc.querySelector('iframe'); - expect(iframe.src).to.equal( + expect(iframe.src).to.equal( 'https://players.brightcove.net/1290862519001/default_default' + - '/index.html?videoId=ref:amp-test-video&playsinline=true'); + '/index.html?videoId=ref:amp-test-video&playsinline=true' + ); - bc.setAttribute('data-account', '12345'); - bc.setAttribute('data-video-id', 'abcdef'); - bc.mutatedAttributesCallback({ - 'data-account': '12345', - 'data-video-id': 'abcdef', - }); + bc.setAttribute('data-account', '12345'); + bc.setAttribute('data-video-id', 'abcdef'); + bc.mutatedAttributesCallback({ + 'data-account': '12345', + 'data-video-id': 'abcdef', + }); - expect(iframe.src).to.equal('https://players.brightcove.net/' + - '12345/default_default/index.html?videoId=abcdef&playsinline=true'); + expect(iframe.src).to.equal( + 'https://players.brightcove.net/' + + '12345/default_default/index.html?videoId=abcdef&playsinline=true' + ); + }); }); - }); - - it('should give precedence to playlist id', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - 'data-playlist-id': 'ref:test-playlist', - }).then(bc => { - const iframe = bc.querySelector('iframe'); - - expect(iframe.src).to.contain('playlistId=ref:test-playlist'); - expect(iframe.src).not.to.contain('videoId'); + + it('should give precedence to playlist id', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + 'data-playlist-id': 'ref:test-playlist', + }).then(bc => { + const iframe = bc.querySelector('iframe'); + + expect(iframe.src).to.contain('playlistId=ref:test-playlist'); + expect(iframe.src).not.to.contain('videoId'); + }); }); - }); - it('should allow both playlist and video id to be unset', () => { - return getBrightcove({ - 'data-account': '1290862519001', - }).then(bc => { - const iframe = bc.querySelector('iframe'); + it('should allow both playlist and video id to be unset', () => { + return getBrightcove({ + 'data-account': '1290862519001', + }).then(bc => { + const iframe = bc.querySelector('iframe'); - expect(iframe.src).not.to.contain('&playlistId'); - expect(iframe.src).not.to.contain('&videoId'); + expect(iframe.src).not.to.contain('&playlistId'); + expect(iframe.src).not.to.contain('&videoId'); + }); }); - }); - it('should pass referrer', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-referrer': 'COUNTER', - }).then(bc => { - const iframe = bc.querySelector('iframe'); + it('should pass referrer', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-referrer': 'COUNTER', + }).then(bc => { + const iframe = bc.querySelector('iframe'); - expect(iframe.src).to.contain('referrer=1'); + expect(iframe.src).to.contain('referrer=1'); + }); }); - }); - it('should force playsinline', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - 'data-param-playsinline': 'false', - }).then(bc => { - const iframe = bc.querySelector('iframe'); + it('should force playsinline', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + 'data-param-playsinline': 'false', + }).then(bc => { + const iframe = bc.querySelector('iframe'); - expect(iframe.src).to.contain('playsinline=true'); + expect(iframe.src).to.contain('playsinline=true'); + }); }); - }); - - it('should forward events', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }).then(bc => { - return Promise.resolve() + + it('should forward events', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }).then(bc => { + return Promise.resolve() .then(() => { const p = listenOncePromise(bc, VideoEvents.LOAD); fakePostMessage(bc, {event: 'ready', muted: false, playing: false}); @@ -194,32 +210,47 @@ describes.realWin('amp-brightcove', { }) .then(() => { const p = listenOncePromise(bc, VideoEvents.AD_START); - fakePostMessage(bc, - {event: 'ads-ad-started', muted: false, playing: false}); + fakePostMessage(bc, { + event: 'ads-ad-started', + muted: false, + playing: false, + }); return p; }) .then(() => { const p = listenOncePromise(bc, VideoEvents.AD_END); - fakePostMessage(bc, - {event: 'ads-ad-ended', muted: false, playing: false}); + fakePostMessage(bc, { + event: 'ads-ad-ended', + muted: false, + playing: false, + }); return p; }) .then(() => { const p = listenOncePromise(bc, VideoEvents.PLAYING); - fakePostMessage(bc, - {event: 'playing', muted: false, playing: true}); + fakePostMessage(bc, { + event: 'playing', + muted: false, + playing: true, + }); return p; }) .then(() => { const p = listenOncePromise(bc, VideoEvents.MUTED); - fakePostMessage(bc, - {event: 'volumechange', muted: true, playing: true}); + fakePostMessage(bc, { + event: 'volumechange', + muted: true, + playing: true, + }); return p; }) .then(() => { const p = listenOncePromise(bc, VideoEvents.UNMUTED); - fakePostMessage(bc, - {event: 'volumechange', muted: false, playing: true}); + fakePostMessage(bc, { + event: 'volumechange', + muted: false, + playing: true, + }); return p; }) .then(() => { @@ -232,6 +263,7 @@ describes.realWin('amp-brightcove', { fakePostMessage(bc, {event: 'ended', muted: false, playing: false}); return p; }); + }); }); - }); -}); + } +); diff --git a/extensions/amp-byside-content/0.1/amp-byside-content.js b/extensions/amp-byside-content/0.1/amp-byside-content.js index 3f566d6fae6d..561117df387b 100644 --- a/extensions/amp-byside-content/0.1/amp-byside-content.js +++ b/extensions/amp-byside-content/0.1/amp-byside-content.js @@ -65,7 +65,6 @@ const DEFAULT_LANG_ = 'pt'; let iframeCount_ = 0; export class AmpBysideContent extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -111,9 +110,11 @@ export class AmpBysideContent extends AMP.BaseElement { /** @const {function()} */ this.boundUpdateSize_ = debounce( - this.win, data => { - this.updateSize_(/** @type {Object} */ (data)); - }, 100 + this.win, + data => { + this.updateSize_(/** @type {Object} */ (data)); + }, + 100 ); } @@ -135,28 +136,28 @@ export class AmpBysideContent extends AMP.BaseElement { /** @override */ buildCallback() { this.webcareId_ = userAssert( - this.element.getAttribute('data-webcare-id'), - 'The data-webcare-id attribute is required for <%s> %s', - TAG_, - this.element + this.element.getAttribute('data-webcare-id'), + 'The data-webcare-id attribute is required for <%s> %s', + TAG_, + this.element ); this.label_ = userAssert( - this.element.getAttribute('data-label'), - 'The data-label attribute is required for <%s> %s', - TAG_, - this.element + this.element.getAttribute('data-label'), + 'The data-label attribute is required for <%s> %s', + TAG_, + this.element ); - this.webcareZone_ = (this.element.getAttribute('data-webcare-zone') || - DEFAULT_WEBCARE_ZONE_); - this.channel_ = (this.element.getAttribute('data-channel') || ''); - this.lang_ = (this.element.getAttribute('data-lang') || DEFAULT_LANG_); - this.fid_ = (this.element.getAttribute('data-fid') || ''); + this.webcareZone_ = + this.element.getAttribute('data-webcare-zone') || DEFAULT_WEBCARE_ZONE_; + this.channel_ = this.element.getAttribute('data-channel') || ''; + this.lang_ = this.element.getAttribute('data-lang') || DEFAULT_LANG_; + this.fid_ = this.element.getAttribute('data-fid') || ''; this.origin_ = this.composeOrigin_(); - this.baseUrl_ = this.origin_ + '/BWA' + - encodeURIComponent(this.webcareId_) + '/amp/'; + this.baseUrl_ = + this.origin_ + '/BWA' + encodeURIComponent(this.webcareId_) + '/amp/'; } /** @override */ @@ -183,8 +184,8 @@ export class AmpBysideContent extends AMP.BaseElement { iframe.setAttribute('allowtransparency', 'true'); iframe.setAttribute('allowfullscreen', 'true'); iframe.setAttribute( - 'sandbox', - 'allow-scripts allow-same-origin allow-popups' + 'sandbox', + 'allow-scripts allow-same-origin allow-popups' ); setStyles(iframe, { @@ -194,30 +195,33 @@ export class AmpBysideContent extends AMP.BaseElement { this.element.appendChild(this.getOverflowElement_()); this.applyFillContent(iframe); - return this.composeSrcUrl_().then(src => { - this.iframeSrc_ = assertHttpsUrl(src, this.element, this.getName_()); - iframe.src = this.iframeSrc_; + return this.composeSrcUrl_() + .then(src => { + this.iframeSrc_ = assertHttpsUrl(src, this.element, this.getName_()); + iframe.src = this.iframeSrc_; - const unlisten = listenFor(iframe, 'embed-size', this.boundUpdateSize_); - this.unlisteners_.push(unlisten); + const unlisten = listenFor(iframe, 'embed-size', this.boundUpdateSize_); + this.unlisteners_.push(unlisten); - this.element.appendChild(iframe); + this.element.appendChild(iframe); - return (this.iframePromise_ = this.loadPromise(iframe)); - }).then(() => { - this.getVsync().mutate(() => { - setStyles(iframe, { - 'opacity': 1, + return (this.iframePromise_ = this.loadPromise(iframe)); + }) + .then(() => { + this.getVsync().mutate(() => { + setStyles(iframe, { + 'opacity': 1, + }); }); }); - }); } /** @private */ composeOrigin_() { - const subDomain = this.webcareZone_ === MAIN_WEBCARE_ZONE_ ? - MAIN_WEBCARE_ZONE_SUBDOMAIN_ : - this.webcareZone_; + const subDomain = + this.webcareZone_ === MAIN_WEBCARE_ZONE_ + ? MAIN_WEBCARE_ZONE_SUBDOMAIN_ + : this.webcareZone_; return 'https://' + encodeURIComponent(subDomain) + '.' + BYSIDE_DOMAIN_; } @@ -231,7 +235,7 @@ export class AmpBysideContent extends AMP.BaseElement { 'bwch': this.channel_ || '', 'lang': this.lang_ || '', 'fid': this.fid_ || '', - 'bwit': (this.fid_ ? 'I' : 'A'), + 'bwit': this.fid_ ? 'I' : 'A', 'tuid': 'CLIENT_ID(byside_webcare_tuid)', 'suid': '', 'puid': 'PAGE_VIEW_IDpTIMESTAMP', @@ -272,16 +276,28 @@ export class AmpBysideContent extends AMP.BaseElement { */ getOverflowElement_() { const doc = /** @type {!Document} */ (this.element.ownerDocument); - const overflow = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-overflow', - 'overflow': '', - })); - const overflowContent = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-overflow-content', - })); - const arrow = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-arrow-down', - })); + const overflow = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-overflow', + 'overflow': '', + }) + ); + const overflowContent = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-overflow-content', + }) + ); + const arrow = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-arrow-down', + }) + ); overflowContent.appendChild(arrow); overflow.appendChild(overflowContent); @@ -291,12 +307,20 @@ export class AmpBysideContent extends AMP.BaseElement { /** @return {!Element} @private */ createBySideLoader_() { const doc = /** @type {!Document} */ (this.element.ownerDocument); - const loadingContainer = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-loading-container', - })); - const loadingAnimation = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-loading-animation', - })); + const loadingContainer = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-loading-container', + }) + ); + const loadingAnimation = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-loading-animation', + }) + ); loadingContainer.appendChild(loadingAnimation); return loadingContainer; @@ -316,33 +340,41 @@ export class AmpBysideContent extends AMP.BaseElement { const height = parseInt(data['height'], 10); if (!isNaN(height)) { newHeight = Math.max( - height + (this.element./*OK*/offsetHeight - - this.iframe_./*OK*/offsetHeight), - height); + height + + (this.element./*OK*/ offsetHeight - + this.iframe_./*OK*/ offsetHeight), + height + ); } const width = parseInt(data['width'], 10); if (!isNaN(width)) { newWidth = Math.max( - width + (this.element./*OK*/offsetWidth - - this.iframe_./*OK*/offsetWidth), - width); + width + + (this.element./*OK*/ offsetWidth - this.iframe_./*OK*/ offsetWidth), + width + ); } if (newHeight !== undefined || newWidth !== undefined) { - this.attemptChangeSize(newHeight, newWidth).then(() => { - if (newHeight !== undefined) { - this.element.setAttribute('height', newHeight); - } - if (newWidth !== undefined) { - this.element.setAttribute('width', newWidth); - } - }, () => {}); + this.attemptChangeSize(newHeight, newWidth).then( + () => { + if (newHeight !== undefined) { + this.element.setAttribute('height', newHeight); + } + if (newWidth !== undefined) { + this.element.setAttribute('width', newWidth); + } + }, + () => {} + ); } else { - user().warn(TAG_, - 'Ignoring embed-size request because ' - + 'no width or height value is provided', - this.element); + user().warn( + TAG_, + 'Ignoring embed-size request because ' + + 'no width or height value is provided', + this.element + ); } }); } diff --git a/extensions/amp-byside-content/0.1/test/test-amp-byside-content.js b/extensions/amp-byside-content/0.1/test/test-amp-byside-content.js index d71b1b008322..3ebe84906e8f 100644 --- a/extensions/amp-byside-content/0.1/test/test-amp-byside-content.js +++ b/extensions/amp-byside-content/0.1/test/test-amp-byside-content.js @@ -17,173 +17,184 @@ import '../amp-byside-content'; import {mockServiceForDoc} from '../../../../testing/test-helper'; -describes.realWin('amp-byside-content', { - amp: { - extensions: ['amp-byside-content'], +describes.realWin( + 'amp-byside-content', + { + amp: { + extensions: ['amp-byside-content'], + }, + ampAdCss: true, }, - ampAdCss: true, -}, env => { - let win, doc, urlMock; - - beforeEach(() => { - win = env.win; - doc = win.document; - urlMock = mockServiceForDoc(sandbox, env.ampdoc, 'url-replace', [ - 'expandUrlAsync', - ]); - }); - - function getElement(attributes, opt_responsive, opt_beforeLayoutCallback) { - const elem = doc.createElement('amp-byside-content'); - - for (const key in attributes) { - elem.setAttribute(key, attributes[key]); - } + env => { + let win, doc, urlMock; + + beforeEach(() => { + win = env.win; + doc = win.document; + urlMock = mockServiceForDoc(sandbox, env.ampdoc, 'url-replace', [ + 'expandUrlAsync', + ]); + }); - elem.setAttribute('width', '640'); - elem.setAttribute('height', '360'); - if (opt_responsive) { - elem.setAttribute('layout', 'responsive'); - } + function getElement(attributes, opt_responsive, opt_beforeLayoutCallback) { + const elem = doc.createElement('amp-byside-content'); - doc.body.appendChild(elem); - return elem.build().then(() => { - urlMock.expandUrlAsync - .returns(Promise.resolve(elem.implementation_.baseUrl_)) - .withArgs(sinon.match.any); - if (opt_beforeLayoutCallback) { - opt_beforeLayoutCallback(elem); + for (const key in attributes) { + elem.setAttribute(key, attributes[key]); } - return elem.layoutCallback(); - }).then(() => elem); - } + elem.setAttribute('width', '640'); + elem.setAttribute('height', '360'); + if (opt_responsive) { + elem.setAttribute('layout', 'responsive'); + } - function testIframe(elem) { - const iframe = elem.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.getAttribute('frameborder')).to.equal('0'); - expect(iframe.className).to.match(/i-amphtml-fill-content/); - expect(iframe.fakeSrc).to.satisfy(src => { - return src.startsWith(elem.implementation_.baseUrl_); - }); - } + doc.body.appendChild(elem); + return elem + .build() + .then(() => { + urlMock.expandUrlAsync + .returns(Promise.resolve(elem.implementation_.baseUrl_)) + .withArgs(sinon.match.any); + if (opt_beforeLayoutCallback) { + opt_beforeLayoutCallback(elem); + } + + return elem.layoutCallback(); + }) + .then(() => elem); + } - it('renders', () => { - return getElement({ - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'amp-simple', - }).then(elem => { - testIframe(elem); + function testIframe(elem) { + const iframe = elem.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.getAttribute('frameborder')).to.equal('0'); + expect(iframe.className).to.match(/i-amphtml-fill-content/); + expect(iframe.fakeSrc).to.satisfy(src => { + return src.startsWith(elem.implementation_.baseUrl_); + }); + } + + it('renders', () => { + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'amp-simple', + }).then(elem => { + testIframe(elem); + }); }); - }); - it('requires data-label', () => { - return allowConsoleError(() => { return getElement({ - 'data-webcare-id': 'D6604AE5D0', - }).should.eventually.be.rejectedWith( - /The data-label attribute is required for/); + it('requires data-label', () => { + return allowConsoleError(() => { + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + }).should.eventually.be.rejectedWith( + /The data-label attribute is required for/ + ); + }); }); - }); - it('requires data-webcare-id', () => { - return allowConsoleError(() => { return getElement({ - 'data-label': 'placeholder-label', - }).should.eventually.be.rejectedWith( - /The data-webcare-id attribute is required for/); + it('requires data-webcare-id', () => { + return allowConsoleError(() => { + return getElement({ + 'data-label': 'placeholder-label', + }).should.eventually.be.rejectedWith( + /The data-webcare-id attribute is required for/ + ); + }); }); - }); - - it('generates correct default origin', () => { - return getElement({ - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }).then(elem => { - expect(elem.implementation_.origin_).to.equal( + + it('generates correct default origin', () => { + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }).then(elem => { + expect(elem.implementation_.origin_).to.equal( 'https://webcare.byside.com' - ); + ); + }); }); - }); - it('generates correct provided webcare zone', () => { - const webcareZone = 'sa1'; + it('generates correct provided webcare zone', () => { + const webcareZone = 'sa1'; - return getElement({ - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - 'data-webcare-zone': webcareZone, - }).then(elem => { - expect(elem.implementation_.origin_).to.equal( + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + 'data-webcare-zone': webcareZone, + }).then(elem => { + expect(elem.implementation_.origin_).to.equal( 'https://' + webcareZone + '.byside.com' - ); + ); + }); }); - }); - - it('should create a loading animation', () => { - return getElement({ - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }).then(elem => { - const loader = elem.querySelector( + + it('should create a loading animation', () => { + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }).then(elem => { + const loader = elem.querySelector( '.i-amphtml-byside-content-loading-animation' - ); - expect(loader).to.not.be.null; + ); + expect(loader).to.not.be.null; + }); }); - }); - it('builds a placeholder loading animation without inserting iframe', () => { - const attributes = { - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }; + it('builds a placeholder loading animation without inserting iframe', () => { + const attributes = { + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }; - return getElement(attributes, true, elem => { - const placeholder = elem.querySelector('[placeholder]'); - const iframe = elem.querySelector('iframe'); - expect(iframe).to.be.null; - expect(placeholder).to.not.have.display('none'); - }).then(elem => { - const placeholder = elem.querySelector('[placeholder]'); - elem.getVsync = () => { - return { - mutate: fn => fn(), + return getElement(attributes, true, elem => { + const placeholder = elem.querySelector('[placeholder]'); + const iframe = elem.querySelector('iframe'); + expect(iframe).to.be.null; + expect(placeholder).to.not.have.display('none'); + }).then(elem => { + const placeholder = elem.querySelector('[placeholder]'); + elem.getVsync = () => { + return { + mutate: fn => fn(), + }; }; - }; - // test iframe - testIframe(elem); + // test iframe + testIframe(elem); - // test placeholder too - elem.implementation_.iframePromise_.then(() => { - expect(placeholder).to.have.display('none'); + // test placeholder too + elem.implementation_.iframePromise_.then(() => { + expect(placeholder).to.have.display('none'); + }); }); }); - }); - it('passes down sandbox attribute to iframe', () => { - const sandbox = 'allow-scripts allow-same-origin allow-popups'; - const attributes = { - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }; + it('passes down sandbox attribute to iframe', () => { + const sandbox = 'allow-scripts allow-same-origin allow-popups'; + const attributes = { + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }; - return getElement(attributes, false).then(elem => { - const iframe = elem.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.getAttribute('sandbox')).to.equal(sandbox); + return getElement(attributes, false).then(elem => { + const iframe = elem.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.getAttribute('sandbox')).to.equal(sandbox); + }); }); - }); - it('sets scrollable atribute in iframe', () => { - const attributes = { - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }; + it('sets scrollable atribute in iframe', () => { + const attributes = { + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }; - return getElement(attributes, false).then(elem => { - const iframe = elem.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.getAttribute('scrolling')).to.equal('no'); + return getElement(attributes, false).then(elem => { + const iframe = elem.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.getAttribute('scrolling')).to.equal('no'); + }); }); - }); -}); + } +); diff --git a/extensions/amp-call-tracking/0.1/amp-call-tracking.js b/extensions/amp-call-tracking/0.1/amp-call-tracking.js index 1a8e34ca9e0b..3a3dc08e0e7e 100644 --- a/extensions/amp-call-tracking/0.1/amp-call-tracking.js +++ b/extensions/amp-call-tracking/0.1/amp-call-tracking.js @@ -19,14 +19,12 @@ import {Services} from '../../../src/services'; import {assertHttpsUrl} from '../../../src/url'; import {user, userAssert} from '../../../src/log'; - /** * Bookkeeps all unique URL requests so that no URL is called twice. * @type {!Object} */ let cachedResponsePromises_ = {}; - /** * Fetches vendor response. * @param {!Window} win @@ -36,25 +34,22 @@ let cachedResponsePromises_ = {}; function fetch_(win, url) { if (!(url in cachedResponsePromises_)) { cachedResponsePromises_[url] = Services.xhrFor(win) - .fetchJson(url, {credentials: 'include'}) - .then(res => res.json()); + .fetchJson(url, {credentials: 'include'}) + .then(res => res.json()); } return cachedResponsePromises_[url]; } - /** @visibleForTesting */ export function clearResponseCacheForTesting() { cachedResponsePromises_ = {}; } - /** * Implementation of `amp-call-tracking` component. See * {@link ../amp-call-tracking.md} for the spec. */ export class AmpCallTracking extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -74,7 +69,9 @@ export class AmpCallTracking extends AMP.BaseElement { /** @override */ buildCallback() { this.configUrl_ = assertHttpsUrl( - this.element.getAttribute('config'), this.element); + this.element.getAttribute('config'), + this.element + ); this.hyperlink_ = this.element.firstElementChild; } @@ -82,21 +79,22 @@ export class AmpCallTracking extends AMP.BaseElement { /** @override */ layoutCallback() { return Services.urlReplacementsForDoc(this.element) - .expandUrlAsync(user().assertString(this.configUrl_)) - .then(url => fetch_(this.win, url)) - .then(data => { - userAssert('phoneNumber' in data, - 'Response must contain a non-empty phoneNumber field %s', - this.element); - - this.hyperlink_.setAttribute('href', `tel:${data['phoneNumber']}`); - this.hyperlink_.textContent = data['formattedPhoneNumber'] - || data['phoneNumber']; - }); + .expandUrlAsync(user().assertString(this.configUrl_)) + .then(url => fetch_(this.win, url)) + .then(data => { + userAssert( + 'phoneNumber' in data, + 'Response must contain a non-empty phoneNumber field %s', + this.element + ); + + this.hyperlink_.setAttribute('href', `tel:${data['phoneNumber']}`); + this.hyperlink_.textContent = + data['formattedPhoneNumber'] || data['phoneNumber']; + }); } } - AMP.extension('amp-call-tracking', '0.1', AMP => { AMP.registerElement('amp-call-tracking', AmpCallTracking); }); diff --git a/extensions/amp-call-tracking/0.1/test/test-amp-call-tracking.js b/extensions/amp-call-tracking/0.1/test/test-amp-call-tracking.js index 4ea093fd8946..30f626dae49d 100644 --- a/extensions/amp-call-tracking/0.1/test/test-amp-call-tracking.js +++ b/extensions/amp-call-tracking/0.1/test/test-amp-call-tracking.js @@ -18,113 +18,126 @@ import '../amp-call-tracking'; import {Services} from '../../../../src/services'; import {clearResponseCacheForTesting} from '../amp-call-tracking'; - -describes.realWin('amp-call-tracking', { - amp: { - extensions: ['amp-call-tracking'], +describes.realWin( + 'amp-call-tracking', + { + amp: { + extensions: ['amp-call-tracking'], + }, }, -}, env => { - let win, doc; - let xhrMock; - - beforeEach(() => { - win = env.win; - doc = win.document; - xhrMock = sandbox.mock(Services.xhrFor(win)); - }); + env => { + let win, doc; + let xhrMock; + + beforeEach(() => { + win = env.win; + doc = win.document; + xhrMock = sandbox.mock(Services.xhrFor(win)); + }); - afterEach(() => { - clearResponseCacheForTesting(); - xhrMock.verify(); - }); + afterEach(() => { + clearResponseCacheForTesting(); + xhrMock.verify(); + }); - function getCallTrackingEl(config = {}) { - const hyperlink = doc.createElement('a'); - const callTrackingEl = doc.createElement('amp-call-tracking'); + function getCallTrackingEl(config = {}) { + const hyperlink = doc.createElement('a'); + const callTrackingEl = doc.createElement('amp-call-tracking'); - callTrackingEl.setAttribute('config', config.url); + callTrackingEl.setAttribute('config', config.url); - hyperlink.setAttribute('href', `tel:${config.defaultNumber}`); - hyperlink.textContent = config.defaultContent || config.defaultNumber; + hyperlink.setAttribute('href', `tel:${config.defaultNumber}`); + hyperlink.textContent = config.defaultContent || config.defaultNumber; - callTrackingEl.appendChild(hyperlink); + callTrackingEl.appendChild(hyperlink); - doc.body.appendChild(callTrackingEl); - return callTrackingEl.build().then(() => { - return callTrackingEl.layoutCallback(); - }).then(() => callTrackingEl); - } + doc.body.appendChild(callTrackingEl); + return callTrackingEl + .build() + .then(() => { + return callTrackingEl.layoutCallback(); + }) + .then(() => callTrackingEl); + } - function mockXhrResponse(url, response) { - xhrMock + function mockXhrResponse(url, response) { + xhrMock .expects('fetchJson') .withArgs(url, sandbox.match(init => init.credentials == 'include')) - .returns(Promise.resolve({ - json() { - return Promise.resolve(response); - }, - })); - } - - function expectHyperlinkToBe(callTrackingEl, href, textContent) { - const hyperlink = callTrackingEl.getRealChildren()[0]; - - expect(hyperlink.getAttribute('href')).to.equal(href); - expect(hyperlink.textContent).to.equal(textContent); - } - - it('should render with required response fields', () => { - const url = 'https://example.com/test.json'; - - const defaultNumber = '123456'; - const defaultContent = '+1 (23) 456'; - - const phoneNumber = '981234'; - - mockXhrResponse(url, {phoneNumber}); - - return getCallTrackingEl({ - url, - defaultNumber, - defaultContent, - }).then(callTrackingEl => { - expectHyperlinkToBe(callTrackingEl, `tel:${phoneNumber}`, phoneNumber); + .returns( + Promise.resolve({ + json() { + return Promise.resolve(response); + }, + }) + ); + } + + function expectHyperlinkToBe(callTrackingEl, href, textContent) { + const hyperlink = callTrackingEl.getRealChildren()[0]; + + expect(hyperlink.getAttribute('href')).to.equal(href); + expect(hyperlink.textContent).to.equal(textContent); + } + + it('should render with required response fields', () => { + const url = 'https://example.com/test.json'; + + const defaultNumber = '123456'; + const defaultContent = '+1 (23) 456'; + + const phoneNumber = '981234'; + + mockXhrResponse(url, {phoneNumber}); + + return getCallTrackingEl({ + url, + defaultNumber, + defaultContent, + }).then(callTrackingEl => { + expectHyperlinkToBe(callTrackingEl, `tel:${phoneNumber}`, phoneNumber); + }); }); - }); - it('should use all response fields to compose hyperlink', () => { - const url = 'https://example.com/test.json'; + it('should use all response fields to compose hyperlink', () => { + const url = 'https://example.com/test.json'; - const defaultNumber = '123456'; - const defaultContent = '+1 (23) 456'; + const defaultNumber = '123456'; + const defaultContent = '+1 (23) 456'; - const phoneNumber = '187654321'; - const formattedPhoneNumber = '+1 (87) 654-321'; + const phoneNumber = '187654321'; + const formattedPhoneNumber = '+1 (87) 654-321'; - mockXhrResponse(url, {phoneNumber, formattedPhoneNumber}); + mockXhrResponse(url, {phoneNumber, formattedPhoneNumber}); - return getCallTrackingEl({ - url, - defaultNumber, - defaultContent, - }).then(callTrackingEl => { - expectHyperlinkToBe( - callTrackingEl, `tel:${phoneNumber}`, formattedPhoneNumber); + return getCallTrackingEl({ + url, + defaultNumber, + defaultContent, + }).then(callTrackingEl => { + expectHyperlinkToBe( + callTrackingEl, + `tel:${phoneNumber}`, + formattedPhoneNumber + ); + }); }); - }); - it('should fail when response does not contain a phoneNumber field', () => { - const url = 'https://example.com/test.json'; + it('should fail when response does not contain a phoneNumber field', () => { + const url = 'https://example.com/test.json'; - const defaultNumber = '123456'; - const defaultContent = '+1 (23) 456'; + const defaultNumber = '123456'; + const defaultContent = '+1 (23) 456'; - mockXhrResponse(url, {}); + mockXhrResponse(url, {}); - return expect(getCallTrackingEl({ - url, - defaultNumber, - defaultContent, - })).rejectedWith(/Response must contain a non-empty phoneNumber field/); - }); -}); + return expect( + getCallTrackingEl({ + url, + defaultNumber, + defaultContent, + }) + ).rejectedWith(/Response must contain a non-empty phoneNumber field/); + }); + } +); diff --git a/extensions/amp-carousel/0.1/base-carousel.js b/extensions/amp-carousel/0.1/base-carousel.js index 4154adf87c1a..a31ae2a44921 100644 --- a/extensions/amp-carousel/0.1/base-carousel.js +++ b/extensions/amp-carousel/0.1/base-carousel.js @@ -20,7 +20,6 @@ import {Services} from '../../../src/services'; * @abstract */ export class BaseCarousel extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -38,8 +37,8 @@ export class BaseCarousel extends AMP.BaseElement { /** @override */ buildCallback() { const input = Services.inputFor(this.win); - this.showControls_ = input.isMouseDetected() || - this.element.hasAttribute('controls'); + this.showControls_ = + input.isMouseDetected() || this.element.hasAttribute('controls'); if (this.showControls_) { this.element.classList.add('i-amphtml-carousel-has-controls'); @@ -174,9 +173,13 @@ export class BaseCarousel extends AMP.BaseElement { this.mutateElement(() => { this.element.classList.remove(className); this.prevButton_.classList.toggle( - 'i-amphtml-screen-reader', !this.showControls_); + 'i-amphtml-screen-reader', + !this.showControls_ + ); this.nextButton_.classList.toggle( - 'i-amphtml-screen-reader', !this.showControls_); + 'i-amphtml-screen-reader', + !this.showControls_ + ); }); }, 4000); }); @@ -199,8 +202,10 @@ export class BaseCarousel extends AMP.BaseElement { * @protected */ getNextButtonTitle() { - return this.element.getAttribute('data-next-button-aria-label') - || 'Next item in carousel'; + return ( + this.element.getAttribute('data-next-button-aria-label') || + 'Next item in carousel' + ); } /** @@ -208,8 +213,10 @@ export class BaseCarousel extends AMP.BaseElement { * @protected */ getPrevButtonTitle() { - return this.element.getAttribute('data-prev-button-aria-label') - || 'Previous item in carousel'; + return ( + this.element.getAttribute('data-prev-button-aria-label') || + 'Previous item in carousel' + ); } /** @override */ diff --git a/extensions/amp-carousel/0.1/base-slides.js b/extensions/amp-carousel/0.1/base-slides.js index ce87e6193a71..6bc7fb8ec32b 100644 --- a/extensions/amp-carousel/0.1/base-slides.js +++ b/extensions/amp-carousel/0.1/base-slides.js @@ -21,7 +21,6 @@ import {isFiniteNumber} from '../../../src/types'; import {userAssert} from '../../../src/log'; export class BaseSlides extends BaseCarousel { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -74,14 +73,18 @@ export class BaseSlides extends BaseCarousel { this.setupAutoplay_(); } - this.registerAction('toggleAutoplay', invocation => { - const {args} = invocation; - if (args && args['toggleOn'] !== undefined) { - this.toggleAutoplay_(args['toggleOn']); - } else { - this.toggleAutoplay_(!this.hasAutoplay_); - } - }, ActionTrust.LOW); + this.registerAction( + 'toggleAutoplay', + invocation => { + const {args} = invocation; + if (args && args['toggleOn'] !== undefined) { + this.toggleAutoplay_(args['toggleOn']); + } else { + this.toggleAutoplay_(!this.hasAutoplay_); + } + }, + ActionTrust.LOW + ); } /** @@ -129,18 +132,18 @@ export class BaseSlides extends BaseCarousel { updateViewportState(unusedInViewport) {} /** - * Checks if a carousel is eligible to loop, regardless of the loop attribute. - * @return {boolean} - * @protected - */ + * Checks if a carousel is eligible to loop, regardless of the loop attribute. + * @return {boolean} + * @protected + */ isLoopingEligible() { return false; } /** - * Sets up the `autoplay` configuration. - * @private - */ + * Sets up the `autoplay` configuration. + * @private + */ setupAutoplay_() { const delayValue = Number(this.element.getAttribute('delay')); // If it isn't a number and is not greater than 0 then don't assign @@ -162,19 +165,20 @@ export class BaseSlides extends BaseCarousel { } /** - * Starts the autoplay delay if allowed. - * @private - */ + * Starts the autoplay delay if allowed. + * @private + */ autoplay_() { if (!this.shouldAutoplay_ || this.autoplayLoops_ == 0) { return; } this.clearAutoplay(); - this.autoplayTimeoutId_ = /** @type {number} */ ( - Services.timerFor(this.win).delay( - this.go.bind( - this, /* dir */ 1, /* animate */ true, /* autoplay */ true), - this.autoplayDelay_)); + this.autoplayTimeoutId_ = /** @type {number} */ (Services.timerFor( + this.win + ).delay( + this.go.bind(this, /* dir */ 1, /* animate */ true, /* autoplay */ true), + this.autoplayDelay_ + )); } /** @@ -204,9 +208,9 @@ export class BaseSlides extends BaseCarousel { } /** - * Clear the autoplay timer. - * @protected - */ + * Clear the autoplay timer. + * @protected + */ clearAutoplay() { if (this.autoplayTimeoutId_ !== null) { Services.timerFor(this.win).cancel(this.autoplayTimeoutId_); @@ -215,9 +219,9 @@ export class BaseSlides extends BaseCarousel { } /** - * Remove autoplay. - * @protected - */ + * Remove autoplay. + * @protected + */ removeAutoplay() { this.clearAutoplay(); if (this.loopAdded_) { diff --git a/extensions/amp-carousel/0.1/scrollable-carousel.js b/extensions/amp-carousel/0.1/scrollable-carousel.js index 545e5d37c35e..266d996af234 100644 --- a/extensions/amp-carousel/0.1/scrollable-carousel.js +++ b/extensions/amp-carousel/0.1/scrollable-carousel.js @@ -27,7 +27,6 @@ import {numeric} from '../../../src/transition'; const TAG = 'amp-scrollable-carousel'; export class AmpScrollableCarousel extends BaseCarousel { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -64,8 +63,9 @@ export class AmpScrollableCarousel extends BaseCarousel { this.container_.classList.add('i-amphtml-scrollable-carousel-container'); this.element.appendChild(this.container_); - this.useLayers_ = isExperimentOn(this.win, 'layers') && isExperimentOn( - this.win, 'layers-prioritization'); + this.useLayers_ = + isExperimentOn(this.win, 'layers') && + isExperimentOn(this.win, 'layers-prioritization'); this.cells_.forEach(cell => { if (!this.useLayers_) { @@ -78,16 +78,19 @@ export class AmpScrollableCarousel extends BaseCarousel { this.cancelTouchEvents_(); - this.container_.addEventListener( - 'scroll', this.scrollHandler_.bind(this)); + this.container_.addEventListener('scroll', this.scrollHandler_.bind(this)); - this.registerAction('goToSlide', invocation => { - const {args} = invocation; - if (args) { - const index = parseInt(args['index'], 10); - this.goToSlide_(index); - } - }, ActionTrust.LOW); + this.registerAction( + 'goToSlide', + invocation => { + const {args} = invocation; + if (args) { + const index = parseInt(args['index'], 10); + this.goToSlide_(index); + } + }, + ActionTrust.LOW + ); if (this.useLayers_) { this.declareLayer(this.container_); @@ -122,15 +125,20 @@ export class AmpScrollableCarousel extends BaseCarousel { if (!animate) { this.commitSwitch_(newPos); - this.container_./*OK*/scrollLeft = newPos; + this.container_./*OK*/ scrollLeft = newPos; } else { /** @const {!TransitionDef} */ const interpolate = numeric(oldPos, newPos); const duration = 200; const curve = 'ease-in-out'; - Animation.animate(this.element, pos => { - this.container_./*OK*/scrollLeft = interpolate(pos); - }, duration, curve).thenAlways(() => { + Animation.animate( + this.element, + pos => { + this.container_./*OK*/ scrollLeft = interpolate(pos); + }, + duration, + curve + ).thenAlways(() => { this.commitSwitch_(newPos); }); } @@ -164,9 +172,14 @@ export class AmpScrollableCarousel extends BaseCarousel { const interpolate = numeric(oldPos, newPos); const duration = 200; const curve = 'ease-in-out'; - Animation.animate(this.element, pos => { - this.container_./*OK*/scrollLeft = interpolate(pos); - }, duration, curve).thenAlways(() => { + Animation.animate( + this.element, + pos => { + this.container_./*OK*/ scrollLeft = interpolate(pos); + }, + duration, + curve + ).thenAlways(() => { this.commitSwitch_(newPos); }); }; @@ -179,9 +192,9 @@ export class AmpScrollableCarousel extends BaseCarousel { * @param {number} index */ getPosForSlideIndex_(index) { - const containerWidth = this.element./*OK*/offsetWidth; - const targetPosition = this.cells_[index]./*OK*/offsetLeft; - const targetWidth = this.cells_[index]./*OK*/offsetWidth; + const containerWidth = this.element./*OK*/ offsetWidth; + const targetPosition = this.cells_[index]./*OK*/ offsetLeft; + const targetWidth = this.cells_[index]./*OK*/ offsetWidth; return targetPosition - (containerWidth - targetWidth) / 2; } @@ -190,7 +203,7 @@ export class AmpScrollableCarousel extends BaseCarousel { * @private */ scrollHandler_() { - const currentScrollLeft = this.container_./*OK*/scrollLeft; + const currentScrollLeft = this.container_./*OK*/ scrollLeft; this.pos_ = currentScrollLeft; if (this.scrollTimerId_ === null) { @@ -203,20 +216,29 @@ export class AmpScrollableCarousel extends BaseCarousel { * @private */ waitForScroll_(startingScrollLeft) { - this.scrollTimerId_ = /** @type {number} */ ( - Services.timerFor(this.win).delay(() => { - // TODO(yuxichen): test out the threshold for identifying fast scrolling - if (Math.abs(startingScrollLeft - this.pos_) < 30) { - dev().fine(TAG, 'slow scrolling: %s - %s', - startingScrollLeft, this.pos_); - this.scrollTimerId_ = null; - this.commitSwitch_(this.pos_); - } else { - dev().fine(TAG, 'fast scrolling: %s - %s', - startingScrollLeft, this.pos_); - this.waitForScroll_(this.pos_); - } - }, 100)); + this.scrollTimerId_ = /** @type {number} */ (Services.timerFor( + this.win + ).delay(() => { + // TODO(yuxichen): test out the threshold for identifying fast scrolling + if (Math.abs(startingScrollLeft - this.pos_) < 30) { + dev().fine( + TAG, + 'slow scrolling: %s - %s', + startingScrollLeft, + this.pos_ + ); + this.scrollTimerId_ = null; + this.commitSwitch_(this.pos_); + } else { + dev().fine( + TAG, + 'fast scrolling: %s - %s', + startingScrollLeft, + this.pos_ + ); + this.waitForScroll_(this.pos_); + } + }, 100)); } /** @@ -244,14 +266,13 @@ export class AmpScrollableCarousel extends BaseCarousel { */ nextPos_(pos, dir) { // TODO(jridgewell): this could be using cached values from Layers. - const containerWidth = this.element./*OK*/offsetWidth; - const fullWidth = this.container_./*OK*/scrollWidth; + const containerWidth = this.element./*OK*/ offsetWidth; + const fullWidth = this.container_./*OK*/ scrollWidth; const newPos = pos + dir * containerWidth; if (newPos < 0) { return 0; } - if (fullWidth >= containerWidth && - newPos > fullWidth - containerWidth) { + if (fullWidth >= containerWidth && newPos > fullWidth - containerWidth) { return fullWidth - containerWidth; } return newPos; @@ -266,8 +287,10 @@ export class AmpScrollableCarousel extends BaseCarousel { const containerWidth = this.getLayoutWidth(); for (let i = 0; i < this.cells_.length; i++) { const cell = this.cells_[i]; - if (cell./*OK*/offsetLeft + cell./*OK*/offsetWidth >= pos && - cell./*OK*/offsetLeft <= pos + containerWidth) { + if ( + cell./*OK*/ offsetLeft + cell./*OK*/ offsetWidth >= pos && + cell./*OK*/ offsetLeft <= pos + containerWidth + ) { callback(cell); } } @@ -327,7 +350,7 @@ export class AmpScrollableCarousel extends BaseCarousel { hasNext() { // TODO(jridgewell): this could be using cached values from Layers. const containerWidth = this.getLayoutWidth(); - const scrollWidth = this.container_./*OK*/scrollWidth; + const scrollWidth = this.container_./*OK*/ scrollWidth; const maxPos = Math.max(scrollWidth - containerWidth, 0); return this.pos_ != maxPos; } diff --git a/extensions/amp-carousel/0.1/slidescroll.js b/extensions/amp-carousel/0.1/slidescroll.js index 682941da970e..6b44a35f3b20 100644 --- a/extensions/amp-carousel/0.1/slidescroll.js +++ b/extensions/amp-carousel/0.1/slidescroll.js @@ -52,7 +52,6 @@ const CUSTOM_SNAP_TIMEOUT = 100; const TAG = 'AMP-CAROUSEL'; export class AmpSlideScroll extends BaseSlides { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -132,9 +131,13 @@ export class AmpSlideScroll extends BaseSlides { // - Non iOS devices with the flag turned off. /** @private {boolean} */ this.shouldDisableCssSnap_ = startsWith( - Services.platformFor(this.win).getIosVersionString(), - '10.3') ? true : this.isIos_ ? false : !isExperimentOn( - this.win, 'amp-carousel-chrome-scroll-snap'); + Services.platformFor(this.win).getIosVersionString(), + '10.3' + ) + ? true + : this.isIos_ + ? false + : !isExperimentOn(this.win, 'amp-carousel-chrome-scroll-snap'); } /** @override */ @@ -147,8 +150,8 @@ export class AmpSlideScroll extends BaseSlides { this.vsync_ = this.getVsync(); this.action_ = Services.actionServiceForDoc(this.element); - this.hasNativeSnapPoints_ = ( - getStyle(this.element, 'scrollSnapType') != undefined); + this.hasNativeSnapPoints_ = + getStyle(this.element, 'scrollSnapType') != undefined; if (this.shouldDisableCssSnap_) { this.hasNativeSnapPoints_ = false; @@ -185,7 +188,8 @@ export class AmpSlideScroll extends BaseSlides { this.slides_.forEach((slide, index) => { this.dataSlideIdArr_.push( - slide.getAttribute('data-slide-id') || index.toString()); + slide.getAttribute('data-slide-id') || index.toString() + ); this.setAsOwner(slide); slide.classList.add('amp-carousel-slide'); @@ -201,20 +205,30 @@ export class AmpSlideScroll extends BaseSlides { this.cancelTouchEvents_(); this.slidesContainer_.addEventListener( - 'scroll', this.scrollHandler_.bind(this)); + 'scroll', + this.scrollHandler_.bind(this) + ); this.slidesContainer_.addEventListener( - 'touchmove', this.touchMoveHandler_.bind(this)); + 'touchmove', + this.touchMoveHandler_.bind(this) + ); this.slidesContainer_.addEventListener( - 'touchend', this.touchEndHandler_.bind(this)); - - this.registerAction('goToSlide', invocation => { - const {args} = invocation; - if (args) { - this.goToSlide(args['index']); - } - }, ActionTrust.LOW); + 'touchend', + this.touchEndHandler_.bind(this) + ); + + this.registerAction( + 'goToSlide', + invocation => { + const {args} = invocation; + if (args) { + this.goToSlide(args['index']); + } + }, + ActionTrust.LOW + ); } /** @override */ @@ -254,19 +268,21 @@ export class AmpSlideScroll extends BaseSlides { if (this.scrollTimeout_) { Services.timerFor(this.win).cancel(this.scrollTimeout_); } - const timeout = this.shouldDisableCssSnap_ ? IOS_TOUCH_TIMEOUT + const timeout = this.shouldDisableCssSnap_ + ? IOS_TOUCH_TIMEOUT : NATIVE_TOUCH_TIMEOUT; // Timer that detects scroll end and/or end of snap scroll. - this.touchEndTimeout_ = /** @type {number} */ ( - Services.timerFor(this.win).delay(() => { - const currentScrollLeft = this.slidesContainer_./*OK*/scrollLeft; + this.touchEndTimeout_ = /** @type {number} */ (Services.timerFor( + this.win + ).delay(() => { + const currentScrollLeft = this.slidesContainer_./*OK*/ scrollLeft; - if (this.snappingInProgress_) { - return; - } - this.updateOnScroll_(currentScrollLeft); - this.touchEndTimeout_ = null; - }, timeout)); + if (this.snappingInProgress_) { + return; + } + this.updateOnScroll_(currentScrollLeft); + this.touchEndTimeout_ = null; + }, timeout)); } this.hasTouchMoved_ = false; } @@ -281,7 +297,9 @@ export class AmpSlideScroll extends BaseSlides { // TODO(sparhami) #19259 Tracks a more generic way to do this. Remove once // we have something better. const isScaled = closestAncestorElementBySelector( - this.element, '[i-amphtml-scale-animation]'); + this.element, + '[i-amphtml-scale-animation]' + ); if (isScaled) { return Promise.resolve(); } @@ -290,7 +308,9 @@ export class AmpSlideScroll extends BaseSlides { this.showSlide_(this.initialSlideIndex_); } else { const index = user().assertNumber( - this.slideIndex_, 'E#19457 this.slideIndex_'); + this.slideIndex_, + 'E#19457 this.slideIndex_' + ); const scrollLeft = this.getScrollLeftForIndex_(index); // When display is toggled on a partcular media or element resizes, // it will need to be re-laid-out. This is only needed when the slide @@ -299,7 +319,7 @@ export class AmpSlideScroll extends BaseSlides { this.scheduleLayout(this.slides_[index]); // Reset scrollLeft on orientationChange or anything that changes the // size of the carousel. - this.slidesContainer_./*OK*/scrollLeft = scrollLeft; + this.slidesContainer_./*OK*/ scrollLeft = scrollLeft; this.previousScrollLeft_ = scrollLeft; } return Promise.resolve(); @@ -315,8 +335,11 @@ export class AmpSlideScroll extends BaseSlides { updateViewportState(inViewport) { if (this.slideIndex_ !== null) { this.updateInViewport( - this.slides_[user().assertNumber(this.slideIndex_, - 'E#19457 this.slideIndex_')], inViewport); + this.slides_[ + user().assertNumber(this.slideIndex_, 'E#19457 this.slideIndex_') + ], + inViewport + ); } } @@ -335,17 +358,15 @@ export class AmpSlideScroll extends BaseSlides { if (this.slideIndex_ !== null) { const hasNext = this.hasNext(); const hasPrev = this.hasPrev(); - if ((dir == 1 && hasNext) || - (dir == -1 && hasPrev)) { - let newIndex = (dev().assertNumber(this.slideIndex_)) + dir; + if ((dir == 1 && hasNext) || (dir == -1 && hasPrev)) { + let newIndex = dev().assertNumber(this.slideIndex_) + dir; if (newIndex == -1) { newIndex = this.noOfSlides_ - 1; } else if (newIndex >= this.noOfSlides_) { newIndex = 0; } if (animate) { - const currentScrollLeft = - (dir == 1 && !hasPrev) ? 0 : this.slideWidth_; + const currentScrollLeft = dir == 1 && !hasPrev ? 0 : this.slideWidth_; this.customSnap_(currentScrollLeft, dir); } else { this.showSlideAndTriggerAction_(newIndex); @@ -364,27 +385,31 @@ export class AmpSlideScroll extends BaseSlides { Services.timerFor(this.win).cancel(this.scrollTimeout_); } - const currentScrollLeft = this.slidesContainer_./*OK*/scrollLeft; + const currentScrollLeft = this.slidesContainer_./*OK*/ scrollLeft; if (!this.isIos_) { this.handleCustomElasticScroll_(currentScrollLeft); } if (!this.touchEndTimeout_) { - const timeout = this.hasNativeSnapPoints_ ? NATIVE_SNAP_TIMEOUT : ( - this.isIos_ ? IOS_CUSTOM_SNAP_TIMEOUT : CUSTOM_SNAP_TIMEOUT); + const timeout = this.hasNativeSnapPoints_ + ? NATIVE_SNAP_TIMEOUT + : this.isIos_ + ? IOS_CUSTOM_SNAP_TIMEOUT + : CUSTOM_SNAP_TIMEOUT; // Timer that detects scroll end and/or end of snap scroll. - this.scrollTimeout_ = /** @type {number} */ ( - Services.timerFor(this.win).delay(() => { - if (this.snappingInProgress_) { - return; - } - if (this.hasNativeSnapPoints_) { - this.updateOnScroll_(currentScrollLeft); - } else { - this.customSnap_(currentScrollLeft); - } - }, timeout)); + this.scrollTimeout_ = /** @type {number} */ (Services.timerFor( + this.win + ).delay(() => { + if (this.snappingInProgress_) { + return; + } + if (this.hasNativeSnapPoints_) { + this.updateOnScroll_(currentScrollLeft); + } else { + this.customSnap_(currentScrollLeft); + } + }, timeout)); } this.previousScrollLeft_ = currentScrollLeft; } @@ -394,15 +419,19 @@ export class AmpSlideScroll extends BaseSlides { * @param {number} currentScrollLeft scrollLeft value of the slides container. */ handleCustomElasticScroll_(currentScrollLeft) { - const scrollWidth = this.slidesContainer_./*OK*/scrollWidth; - if (this.elasticScrollState_ == -1 && - currentScrollLeft >= this.previousScrollLeft_) { + const scrollWidth = this.slidesContainer_./*OK*/ scrollWidth; + if ( + this.elasticScrollState_ == -1 && + currentScrollLeft >= this.previousScrollLeft_ + ) { // Elastic Scroll is reversing direction take control. this.customSnap_(currentScrollLeft).then(() => { this.elasticScrollState_ = 0; }); - } else if (this.elasticScrollState_ == 1 && - currentScrollLeft <= this.previousScrollLeft_) { + } else if ( + this.elasticScrollState_ == 1 && + currentScrollLeft <= this.previousScrollLeft_ + ) { // Elastic Scroll is reversing direction take control. this.customSnap_(currentScrollLeft).then(() => { this.elasticScrollState_ = 0; @@ -410,7 +439,7 @@ export class AmpSlideScroll extends BaseSlides { } else if (currentScrollLeft < 0) { // Direction = -1. this.elasticScrollState_ = -1; - } else if ((currentScrollLeft + this.slideWidth_) > scrollWidth) { + } else if (currentScrollLeft + this.slideWidth_ > scrollWidth) { // Direction = +1. this.elasticScrollState_ = 1; } else { @@ -437,8 +466,7 @@ export class AmpSlideScroll extends BaseSlides { diff = opt_forceDir; } - if (diff == 1 || - (diff != -1 && diff == -1 * (this.noOfSlides_ - 1))) { + if (diff == 1 || (diff != -1 && diff == -1 * (this.noOfSlides_ - 1))) { // Move fwd. toScrollLeft = hasPrev ? this.slideWidth_ * 2 : this.slideWidth_; } else if (diff == -1 || diff == this.noOfSlides_ - 1) { @@ -480,11 +508,19 @@ export class AmpSlideScroll extends BaseSlides { let newIndex = this.slideIndex_ + updateValue; if (this.shouldLoop) { - newIndex = (newIndex < 0) ? this.noOfSlides_ - 1 : - (newIndex >= this.noOfSlides_) ? 0 : newIndex; + newIndex = + newIndex < 0 + ? this.noOfSlides_ - 1 + : newIndex >= this.noOfSlides_ + ? 0 + : newIndex; } else { - newIndex = (newIndex < 0) ? 0 : - (newIndex >= this.noOfSlides_) ? this.noOfSlides_ - 1 : newIndex; + newIndex = + newIndex < 0 + ? 0 + : newIndex >= this.noOfSlides_ + ? this.noOfSlides_ - 1 + : newIndex; } return newIndex; } @@ -496,8 +532,9 @@ export class AmpSlideScroll extends BaseSlides { * @private */ getButtonSuffixFormat_() { - return this.element.getAttribute('data-button-count-format') || - '(%s of %s)'; + return ( + this.element.getAttribute('data-button-count-format') || '(%s of %s)' + ); } /** @@ -508,8 +545,12 @@ export class AmpSlideScroll extends BaseSlides { getButtonTitleSuffix_(buttonIndex) { const index = String(buttonIndex + 1); const count = String(this.noOfSlides_); - return ' ' + this.getButtonSuffixFormat_().replace('%s', index) - .replace('%s', count); + return ( + ' ' + + this.getButtonSuffixFormat_() + .replace('%s', index) + .replace('%s', count) + ); } /** @@ -588,8 +629,11 @@ export class AmpSlideScroll extends BaseSlides { * @private */ getPrevIndex_(currentIndex) { - return (currentIndex - 1 >= 0) ? currentIndex - 1 : - (this.shouldLoop) ? this.noOfSlides_ - 1 : null; + return currentIndex - 1 >= 0 + ? currentIndex - 1 + : this.shouldLoop + ? this.noOfSlides_ - 1 + : null; } /** @@ -599,8 +643,11 @@ export class AmpSlideScroll extends BaseSlides { * @private */ getNextIndex_(currentIndex) { - return (currentIndex + 1 < this.noOfSlides_) ? currentIndex + 1 : - (this.shouldLoop) ? 0 : null; + return currentIndex + 1 < this.noOfSlides_ + ? currentIndex + 1 + : this.shouldLoop + ? 0 + : null; } /** @@ -614,9 +661,11 @@ export class AmpSlideScroll extends BaseSlides { showSlide_(newIndex) { const {noOfSlides_} = this; newIndex = dev().assertNumber(newIndex); - if (newIndex < 0 || - newIndex >= noOfSlides_ || - this.slideIndex_ == newIndex) { + if ( + newIndex < 0 || + newIndex >= noOfSlides_ || + this.slideIndex_ == newIndex + ) { return false; } const prevIndex = this.getPrevIndex_(newIndex); @@ -631,15 +680,22 @@ export class AmpSlideScroll extends BaseSlides { showIndexArr.push(nextIndex); } if (this.slideIndex_ !== null) { - this.updateInViewport(this.slides_[ - user().assertNumber(this.slideIndex_, 'E#19457 this.slideIndex_')], - false); + this.updateInViewport( + this.slides_[ + user().assertNumber(this.slideIndex_, 'E#19457 this.slideIndex_') + ], + false + ); } const newSlideInView = this.slides_[newIndex]; if (newSlideInView === undefined) { - dev().error(TAG, 'Attempting to access a non-existant slide %s / %s', - newIndex, noOfSlides_); + dev().error( + TAG, + 'Attempting to access a non-existant slide %s / %s', + newIndex, + noOfSlides_ + ); return false; } this.updateInViewport(newSlideInView, true); @@ -657,8 +713,9 @@ export class AmpSlideScroll extends BaseSlides { this.slides_[showIndex].setAttribute('aria-hidden', 'true'); } }); - this.slidesContainer_./*OK*/scrollLeft = - this.getScrollLeftForIndex_(newIndex); + this.slidesContainer_./*OK*/ scrollLeft = this.getScrollLeftForIndex_( + newIndex + ); this.triggerAnalyticsEvent_(newIndex); this.slideIndex_ = newIndex; // If we have a specified number of autoplay loops and @@ -687,9 +744,11 @@ export class AmpSlideScroll extends BaseSlides { if (slideChanged) { const name = 'slideChange'; - const event = - createCustomEvent(this.win, `slidescroll.${name}`, - dict({'index': newIndex})); + const event = createCustomEvent( + this.win, + `slidescroll.${name}`, + dict({'index': newIndex}) + ); this.action_.trigger(this.element, name, event, ActionTrust.HIGH); this.element.dispatchCustomEvent(name, {index: newIndex}); @@ -731,8 +790,9 @@ export class AmpSlideScroll extends BaseSlides { if (this.shouldLoop) { setStyle(this.slideWrappers_[i], 'order', ''); } - dev().assertElement(this.slideWrappers_[i]).classList - .remove(SHOWN_CSS_CLASS); + dev() + .assertElement(this.slideWrappers_[i]) + .classList.remove(SHOWN_CSS_CLASS); this.slides_[i].removeAttribute('aria-hidden'); } // Pause if not the current slide @@ -758,9 +818,14 @@ export class AmpSlideScroll extends BaseSlides { const curve = bezierCurve(0.8, 0, 0.6, 1); // ease-in const duration = 80; const slidesContainer = dev().assertElement(this.slidesContainer_); - return Animation.animate(slidesContainer, pos => { - this.slidesContainer_./*OK*/scrollLeft = interpolate(pos); - }, duration, curve).thenAlways(); + return Animation.animate( + slidesContainer, + pos => { + this.slidesContainer_./*OK*/ scrollLeft = interpolate(pos); + }, + duration, + curve + ).thenAlways(); } /** @@ -794,8 +859,9 @@ export class AmpSlideScroll extends BaseSlides { } } const fromSlide = - this.slideIndex_ === null ? - 'null' : this.dataSlideIdArr_[dev().assertNumber(this.slideIndex_)]; + this.slideIndex_ === null + ? 'null' + : this.dataSlideIdArr_[dev().assertNumber(this.slideIndex_)]; const vars = dict({ 'fromSlide': fromSlide, diff --git a/extensions/amp-carousel/0.1/test/test-base-slide.js b/extensions/amp-carousel/0.1/test/test-base-slide.js index 433c1cab9fdf..f9f303e00fd9 100644 --- a/extensions/amp-carousel/0.1/test/test-base-slide.js +++ b/extensions/amp-carousel/0.1/test/test-base-slide.js @@ -48,7 +48,6 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { let autoplaySpy; let clearAutoplaySpy; - beforeEach(() => { win = env.win; doc = win.document; @@ -60,16 +59,16 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { setupAutoplaySpy = sandbox.spy(BaseSlides.prototype, 'setupAutoplay_'); buildButtonsSpy = sandbox.spy(BaseSlides.prototype, 'buildButtons'); setupGesturesSpy = sandbox.spy(BaseSlides.prototype, 'setupGestures'); - setControlsStateSpy = - sandbox.spy(BaseSlides.prototype, 'setControlsState'); + setControlsStateSpy = sandbox.spy(BaseSlides.prototype, 'setControlsState'); hintControlsSpy = sandbox.spy(BaseSlides.prototype, 'hintControls'); autoplaySpy = sandbox.spy(BaseSlides.prototype, 'autoplay_'); clearAutoplaySpy = sandbox.spy(BaseSlides.prototype, 'clearAutoplay'); - onViewportCallbackSpy = - sandbox.spy(BaseSlides.prototype, 'onViewportCallback'); + onViewportCallbackSpy = sandbox.spy( + BaseSlides.prototype, + 'onViewportCallback' + ); }); - function setElement(options) { const element = doc.createElement('div'); if (options.loop) { @@ -89,9 +88,7 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { return element; } - class TestCarousel extends BaseSlides { - /** @override */ buildSlides() { buildSlidesSpy(); @@ -119,9 +116,11 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { } it('should do the right buildCallback processing', () => { - const carouselLoopOnly = new TestCarousel(setElement({ - loop: true, - })); + const carouselLoopOnly = new TestCarousel( + setElement({ + loop: true, + }) + ); carouselLoopOnly.buildCallback(); @@ -132,9 +131,11 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { expect(setupGesturesSpy).to.be.calledOnce; expect(setControlsStateSpy).to.be.calledOnce; - const carouselAutoplayOnly = new TestCarousel(setElement({ - autoplay: true, - })); + const carouselAutoplayOnly = new TestCarousel( + setElement({ + autoplay: true, + }) + ); carouselAutoplayOnly.buildCallback(); @@ -145,10 +146,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { expect(setupGesturesSpy).to.have.callCount(2); expect(setControlsStateSpy).to.have.callCount(2); - const carouselAutoplayWithLoop = new TestCarousel(setElement({ - loop: true, - autoplay: true, - })); + const carouselAutoplayWithLoop = new TestCarousel( + setElement({ + loop: true, + autoplay: true, + }) + ); carouselAutoplayWithLoop.buildCallback(); @@ -161,10 +164,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should handle viewportCallback when in viewport', () => { - const carousel = new TestCarousel(setElement({ - loop: true, - autoplay: true, - })); + const carousel = new TestCarousel( + setElement({ + loop: true, + autoplay: true, + }) + ); carousel.viewportCallback(true); expect(onViewportCallbackSpy).to.have.been.calledWith(true); @@ -174,10 +179,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should handle viewportCallback when not in viewport', () => { - const carousel = new TestCarousel(setElement({ - loop: true, - autoplay: true, - })); + const carousel = new TestCarousel( + setElement({ + loop: true, + autoplay: true, + }) + ); carousel.viewportCallback(false); expect(onViewportCallbackSpy).to.have.been.calledWith(false); @@ -187,9 +194,11 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should setup autoplay with no delay set', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + }) + ); carousel.autoplayDelay_ = 5000; expect(carousel.element.hasAttribute('loop')).to.be.false; carousel.setupAutoplay_(); @@ -200,10 +209,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should setup autoplay with specified number of loops', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - autoplayLoops: 5, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + autoplayLoops: 5, + }) + ); expect(carousel.element.hasAttribute('loop')).to.be.false; carousel.buildCallback(); expect(carousel.element.hasAttribute('loop')).to.be.true; @@ -212,12 +223,13 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { expect(carousel.shouldLoop).to.be.true; }); - it('should setup autoplay with delay set', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 3000, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 3000, + }) + ); carousel.autoplayDelay_ = 5000; expect(carousel.element.hasAttribute('loop')).to.be.false; carousel.setupAutoplay_(); @@ -228,10 +240,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should setup autoplay with delay set lower', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.autoplayDelay_ = 5000; expect(carousel.element.hasAttribute('loop')).to.be.false; carousel.setupAutoplay_(); @@ -242,10 +256,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should start timer on autoplay', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); @@ -254,9 +270,11 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should not start timer on when there is no autoplay', () => { - const carousel = new TestCarousel(setElement({ - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); @@ -265,10 +283,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should clear timeout', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); @@ -280,62 +300,83 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('toggle autoPlay status using speficied value & autoplay=true', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); expect(carousel.shouldAutoplay_).to.be.true; const args = {'toggleOn': false}; - carousel.executeAction( - {method: 'toggleAutoplay', args, satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + args, + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.false; args['toggleOn'] = true; - carousel.executeAction( - {method: 'toggleAutoplay', args, satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + args, + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.true; }); it('toggle autoPlay status using speficied value & autoplay=false', () => { - const carousel = new TestCarousel(setElement({ - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + delay: 300, + }) + ); carousel.buildCallback(); expect(carousel.shouldAutoplay_).to.be.false; const args = {'toggleOn': true}; - carousel.executeAction( - {method: 'toggleAutoplay', args, satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + args, + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.true; args['toggleOn'] = false; - carousel.executeAction( - {method: 'toggleAutoplay', args, satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + args, + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.false; }); it('toggle autoPlay status without speficied value & autoplay=true', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); expect(carousel.shouldAutoplay_).to.be.true; - carousel.executeAction( - {method: 'toggleAutoplay', satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.false; - carousel.executeAction( - {method: 'toggleAutoplay', satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.true; }); - }); diff --git a/extensions/amp-carousel/0.1/test/test-scrollable-carousel.js b/extensions/amp-carousel/0.1/test/test-scrollable-carousel.js index 8a43fa6b2ee3..dc923e8ef8a1 100644 --- a/extensions/amp-carousel/0.1/test/test-scrollable-carousel.js +++ b/extensions/amp-carousel/0.1/test/test-scrollable-carousel.js @@ -16,289 +16,387 @@ import '../amp-carousel'; - -describes.realWin('test-scrollable-carousel', { - amp: { - extensions: ['amp-carousel'], +describes.realWin( + 'test-scrollable-carousel', + { + amp: { + extensions: ['amp-carousel'], + }, }, -}, env => { - let win, doc; - - beforeEach(() => { - win = env.win; - doc = win.document; - env.iframe.width = '300'; - env.iframe.height = '200'; - }); - - function getAmpScrollableCarousel() { - const imgUrl = 'https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-' + + env => { + let win, doc; + + beforeEach(() => { + win = env.win; + doc = win.document; + env.iframe.width = '300'; + env.iframe.height = '200'; + }); + + function getAmpScrollableCarousel() { + const imgUrl = + 'https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-' + 'Rf78IofLb9QjS5_0mqsY1zEFc=w300-h200-no'; - const carouselElement = doc.createElement('amp-carousel'); - carouselElement.setAttribute('width', '300'); - carouselElement.setAttribute('height', '100'); - - const slideCount = 7; - for (let i = 0; i < slideCount; i++) { - const img = document.createElement('amp-img'); - img.setAttribute('src', imgUrl); - img.setAttribute('width', '120'); - img.setAttribute('height', '100'); - img.style.width = '120px'; - img.style.height = '100px'; - img.id = 'img-' + i; - carouselElement.appendChild(img); + const carouselElement = doc.createElement('amp-carousel'); + carouselElement.setAttribute('width', '300'); + carouselElement.setAttribute('height', '100'); + + const slideCount = 7; + for (let i = 0; i < slideCount; i++) { + const img = document.createElement('amp-img'); + img.setAttribute('src', imgUrl); + img.setAttribute('width', '120'); + img.setAttribute('height', '100'); + img.style.width = '120px'; + img.style.height = '100px'; + img.id = 'img-' + i; + carouselElement.appendChild(img); + } + + doc.body.appendChild(carouselElement); + return carouselElement + .build() + .then(() => { + carouselElement.updateLayoutBox({ + top: 0, + left: 0, + width: 300, + height: 100, + }); + return carouselElement.layoutCallback(); + }) + .then(() => carouselElement); } - doc.body.appendChild(carouselElement); - return carouselElement.build().then(() => { - carouselElement.updateLayoutBox( - {top: 0, left: 0, width: 300, height: 100}); - return carouselElement.layoutCallback(); - }).then(() => carouselElement); + it( + 'should initialize correctly: create container, build initial slides ' + + 'and show control buttons', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + + // create container + expect( + carousel.getElementsByClassName( + 'i-amphtml-scrollable-carousel-container' + ).length + ).to.equal(1); + const container = carousel.getElementsByClassName( + 'i-amphtml-scrollable-carousel-container' + )[0]; + const containerStyle = win.getComputedStyle(container, null); + + expect(containerStyle.getPropertyValue('overflow-x')).to.equal( + 'auto' + ); + expect(containerStyle.getPropertyValue('overflow-y')).to.equal( + 'hidden' + ); + expect(containerStyle.getPropertyValue('white-space')).to.equal( + 'nowrap' + ); + + // build child slides + const carouselSlideEls = container.getElementsByClassName( + 'amp-carousel-slide' + ); + expect(carouselSlideEls.length).to.equal(7); + expect(carouselSlideEls[0]).to.have.display('inline-block'); + + // show control buttons correctly + expect(impl.hasPrev()).to.be.false; + expect(impl.hasNext()).to.be.true; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + }); + } + ); + + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip( + 'should behave correctly when clicking on next button and the ' + + 'space to the right is MORE than containerWidth', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + + // click on the next button + impl.goCallback(1, /*animate*/ false); + + // scroll to the correct position + expect(impl.container_./*OK*/ scrollLeft).to.equal(300); + + // load new slides in viewport + expect(updateInViewportSpy).to.have.callCount(5); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[2], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[3], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[4], + true + ); + + // unload and pause old slides in viewport + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[0], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[1], + false + ); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[0]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[1]); + + // schedule layout for new slides + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[3]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); + + // preload slides in viewport + expect(schedulePreloadSpy).to.have.callCount(3); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[4]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[5]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[6]); + + // set control buttons correctly + expect(impl.hasPrev()).to.be.true; + expect(impl.hasNext()).to.be.true; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + }); + } + ); + + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip( + 'should behave correctly when clicking on next button and the ' + + 'space to the right is LESS than containerWidth', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + + // click on the next button the first time + impl.goCallback(1, /*animate*/ false); + + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + + // click on the next button the second time + impl.goCallback(1, /*animate*/ false); + + // scroll to the correct position + // note the correct scrollLeft is not 600 (300 * 2) but 588 (888 - 300) + expect(impl.container_./*OK*/ scrollLeft).to.equal(588); + + // load new slides in viewport + expect(updateInViewportSpy).to.have.callCount(5); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[4], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[5], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[6], + true + ); + + // unload and pause old slides in viewport + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[2], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[3], + false + ); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[2]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[3]); + + // schedule layout for new slides + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[5]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[6]); + + // preload slides in viewport + expect(schedulePreloadSpy).to.have.not.been.called; + + // set control buttons correctly + expect(impl.hasPrev()).to.be.true; + expect(impl.hasNext()).to.be.false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .true; + }); + } + ); + + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip( + 'should behave correctly when clicking on previous button and the ' + + 'space to the left is MORE than containerWidth', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + + // click on the next button twice to reach the right end + // scrollLeft after second click is 588 + impl.goCallback(1, /*animate*/ false); + impl.goCallback(1, /*animate*/ false); + + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + + // click on the previous button + impl.goCallback(-1, /*animate*/ false); + + // scroll to the correct position + expect(impl.container_./*OK*/ scrollLeft).to.equal(288); + + // load new slides in viewport + expect(updateInViewportSpy).to.have.callCount(5); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[2], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[3], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[4], + true + ); + + // unload and pause old slides in viewport + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[5], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[6], + false + ); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[5]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[6]); + + // schedule layout for new slides + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[3]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); + + // preload slides in viewport + expect(schedulePreloadSpy).to.have.callCount(3); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[0]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[1]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[2]); + + // set control buttons correctly + expect(impl.hasPrev()).to.be.true; + expect(impl.hasNext()).to.be.true; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + }); + } + ); + + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip( + 'should behave correctly when clicking on previous button and the ' + + 'space to the left is LESS than containerWidth', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + + // click on the next button twice to reach the right end and click on + // the previous button once, scrollLeft after third click is 288 + impl.goCallback(1, /*animate*/ false); + impl.goCallback(1, /*animate*/ false); + impl.goCallback(-1, /*animate*/ false); + + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + + // click on the previous button + impl.goCallback(-1, /*animate*/ false); + + // scroll to the correct position + expect(impl.container_./*OK*/ scrollLeft).to.equal(0); + + // load new slides in viewport + expect(updateInViewportSpy).to.have.callCount(5); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[0], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[1], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[2], + true + ); + + // unload and pause old slides in viewport + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[3], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[4], + false + ); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[3]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[4]); + + // schedule layout for new slides + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[0]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[1]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); + + // preload slides in viewport + expect(schedulePreloadSpy).to.have.not.been.called; + + // set control buttons correctly + expect(impl.hasPrev()).to.be.false; + expect(impl.hasNext()).to.be.true; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + }); + } + ); } - - it('should initialize correctly: create container, build initial slides ' + - 'and show control buttons', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - - // create container - expect(carousel.getElementsByClassName( - 'i-amphtml-scrollable-carousel-container').length).to.equal(1); - const container = carousel.getElementsByClassName( - 'i-amphtml-scrollable-carousel-container')[0]; - const containerStyle = win.getComputedStyle(container, null); - - expect(containerStyle.getPropertyValue('overflow-x')).to.equal('auto'); - expect(containerStyle.getPropertyValue('overflow-y')).to.equal('hidden'); - expect(containerStyle.getPropertyValue('white-space')).to.equal('nowrap'); - - // build child slides - const carouselSlideEls = - container.getElementsByClassName('amp-carousel-slide'); - expect(carouselSlideEls.length).to.equal(7); - expect(carouselSlideEls[0]).to.have.display('inline-block'); - - // show control buttons correctly - expect(impl.hasPrev()).to.be.false; - expect(impl.hasNext()).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should behave correctly when clicking on next button and the ' + - 'space to the right is MORE than containerWidth', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - - // click on the next button - impl.goCallback(1, /*animate*/ false); - - // scroll to the correct position - expect(impl.container_./*OK*/scrollLeft).to.equal(300); - - // load new slides in viewport - expect(updateInViewportSpy).to.have.callCount(5); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[2], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[3], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[4], true); - - // unload and pause old slides in viewport - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[0], false); - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[1], false); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[0]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[1]); - - // schedule layout for new slides - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[3]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); - - // preload slides in viewport - expect(schedulePreloadSpy).to.have.callCount(3); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[4]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[5]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[6]); - - // set control buttons correctly - expect(impl.hasPrev()).to.be.true; - expect(impl.hasNext()).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should behave correctly when clicking on next button and the ' + - 'space to the right is LESS than containerWidth', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - - // click on the next button the first time - impl.goCallback(1, /*animate*/ false); - - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - - // click on the next button the second time - impl.goCallback(1, /*animate*/ false); - - // scroll to the correct position - // note the correct scrollLeft is not 600 (300 * 2) but 588 (888 - 300) - expect(impl.container_./*OK*/scrollLeft).to.equal(588); - - // load new slides in viewport - expect(updateInViewportSpy).to.have.callCount(5); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[4], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[5], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[6], true); - - // unload and pause old slides in viewport - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[2], false); - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[3], false); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[2]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[3]); - - // schedule layout for new slides - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[5]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[6]); - - // preload slides in viewport - expect(schedulePreloadSpy).to.have.not.been.called; - - // set control buttons correctly - expect(impl.hasPrev()).to.be.true; - expect(impl.hasNext()).to.be.false; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.true; - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should behave correctly when clicking on previous button and the ' + - 'space to the left is MORE than containerWidth', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - - // click on the next button twice to reach the right end - // scrollLeft after second click is 588 - impl.goCallback(1, /*animate*/ false); - impl.goCallback(1, /*animate*/ false); - - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - - // click on the previous button - impl.goCallback(-1, /*animate*/ false); - - // scroll to the correct position - expect(impl.container_./*OK*/scrollLeft).to.equal(288); - - // load new slides in viewport - expect(updateInViewportSpy).to.have.callCount(5); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[2], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[3], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[4], true); - - // unload and pause old slides in viewport - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[5], false); - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[6], false); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[5]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[6]); - - // schedule layout for new slides - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[3]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); - - // preload slides in viewport - expect(schedulePreloadSpy).to.have.callCount(3); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[0]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[1]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[2]); - - // set control buttons correctly - expect(impl.hasPrev()).to.be.true; - expect(impl.hasNext()).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should behave correctly when clicking on previous button and the ' + - 'space to the left is LESS than containerWidth', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - - // click on the next button twice to reach the right end and click on - // the previous button once, scrollLeft after third click is 288 - impl.goCallback(1, /*animate*/ false); - impl.goCallback(1, /*animate*/ false); - impl.goCallback(-1, /*animate*/ false); - - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - - // click on the previous button - impl.goCallback(-1, /*animate*/ false); - - // scroll to the correct position - expect(impl.container_./*OK*/scrollLeft).to.equal(0); - - // load new slides in viewport - expect(updateInViewportSpy).to.have.callCount(5); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[0], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[1], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[2], true); - - // unload and pause old slides in viewport - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[3], false); - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[4], false); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[3]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[4]); - - // schedule layout for new slides - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[0]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[1]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); - - // preload slides in viewport - expect(schedulePreloadSpy).to.have.not.been.called; - - // set control buttons correctly - expect(impl.hasPrev()).to.be.false; - expect(impl.hasNext()).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - }); - }); -}); +); diff --git a/extensions/amp-carousel/0.1/test/test-slidescroll.js b/extensions/amp-carousel/0.1/test/test-slidescroll.js index 3cedf1aedd56..dc9902802bfb 100644 --- a/extensions/amp-carousel/0.1/test/test-slidescroll.js +++ b/extensions/amp-carousel/0.1/test/test-slidescroll.js @@ -16,846 +16,352 @@ import '../amp-carousel'; - -describes.realWin('SlideScroll', { - amp: { - extensions: ['amp-carousel'], +describes.realWin( + 'SlideScroll', + { + amp: { + extensions: ['amp-carousel'], + }, }, -}, env => { - const SHOW_CLASS = 'i-amphtml-slide-item-show'; - let win, doc; - - beforeEach(() => { - win = env.win; - doc = win.document; - env.iframe.width = '1000'; - env.iframe.height = '1000'; - }); - - function getAmpSlideScroll( - opt_hasLooping, opt_slideCount = 5, opt_attachToDom = true, - opt_hasAutoplay = false, opt_autoplayLoops) { - const imgUrl = 'https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-' + + env => { + const SHOW_CLASS = 'i-amphtml-slide-item-show'; + let win, doc; + + beforeEach(() => { + win = env.win; + doc = win.document; + env.iframe.width = '1000'; + env.iframe.height = '1000'; + }); + + function getAmpSlideScroll( + opt_hasLooping, + opt_slideCount = 5, + opt_attachToDom = true, + opt_hasAutoplay = false, + opt_autoplayLoops + ) { + const imgUrl = + 'https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-' + 'Rf78IofLb9QjS5_0mqsY1zEFc=w300-h200-no'; - const ampSlideScroll = doc.createElement('amp-carousel'); - ampSlideScroll.setAttribute('type', 'slides'); - ampSlideScroll.setAttribute('width', '400'); - ampSlideScroll.setAttribute('height', '300'); - ampSlideScroll.style.position = 'relative'; - ampSlideScroll.setAttribute('controls', ''); - if (opt_hasLooping) { - ampSlideScroll.setAttribute('loop', ''); - } - if (opt_hasAutoplay) { - if (!opt_autoplayLoops) { - ampSlideScroll.setAttribute('autoplay', ''); - } else { - ampSlideScroll.setAttribute('autoplay', opt_autoplayLoops); + const ampSlideScroll = doc.createElement('amp-carousel'); + ampSlideScroll.setAttribute('type', 'slides'); + ampSlideScroll.setAttribute('width', '400'); + ampSlideScroll.setAttribute('height', '300'); + ampSlideScroll.style.position = 'relative'; + ampSlideScroll.setAttribute('controls', ''); + if (opt_hasLooping) { + ampSlideScroll.setAttribute('loop', ''); + } + if (opt_hasAutoplay) { + if (!opt_autoplayLoops) { + ampSlideScroll.setAttribute('autoplay', ''); + } else { + ampSlideScroll.setAttribute('autoplay', opt_autoplayLoops); + } } - } - for (let i = 0; i < opt_slideCount; i++) { - const img = doc.createElement('amp-img'); - img.setAttribute('src', imgUrl); - img.setAttribute('width', '400'); - img.setAttribute('height', '300'); - // See https://github.com/ampproject/amphtml/issues/3989 - img.style.display = 'inline'; - if (i == 0) { - img.setAttribute('data-slide-id', 'slide-id'); + for (let i = 0; i < opt_slideCount; i++) { + const img = doc.createElement('amp-img'); + img.setAttribute('src', imgUrl); + img.setAttribute('width', '400'); + img.setAttribute('height', '300'); + // See https://github.com/ampproject/amphtml/issues/3989 + img.style.display = 'inline'; + if (i == 0) { + img.setAttribute('data-slide-id', 'slide-id'); + } + ampSlideScroll.appendChild(img); } - ampSlideScroll.appendChild(img); - } - if (opt_attachToDom) { - doc.body.appendChild(ampSlideScroll); - return ampSlideScroll.build().then(() => { - ampSlideScroll.updateLayoutBox( - {top: 0, left: 0, width: 400, height: 300}); - return ampSlideScroll.layoutCallback(); - }).then(() => ampSlideScroll); + if (opt_attachToDom) { + doc.body.appendChild(ampSlideScroll); + return ampSlideScroll + .build() + .then(() => { + ampSlideScroll.updateLayoutBox({ + top: 0, + left: 0, + width: 400, + height: 300, + }); + return ampSlideScroll.layoutCallback(); + }) + .then(() => ampSlideScroll); + } + return Promise.resolve(ampSlideScroll); } - return Promise.resolve(ampSlideScroll); - } - it('should create container and wrappers and show initial slides', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - expect( + it('should create container and wrappers and show initial slides', () => { + return getAmpSlideScroll().then(ampSlideScroll => { + expect( ampSlideScroll.getElementsByClassName('i-amphtml-slides-container') - .length).to.equal(1); - expect( + .length + ).to.equal(1); + expect( ampSlideScroll.querySelectorAll( - '.i-amphtml-slides-container > .i-amphtml-slide-item').length) - .to.equal(5); - expect( - ampSlideScroll.getElementsByClassName('amp-carousel-slide').length) - .to.equal(5); - expect(ampSlideScroll.querySelector('.i-amphtml-slides-container') - .getAttribute('aria-live')).to.equal('polite'); - const impl = ampSlideScroll.implementation_; - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - }); - }); - - it('should go to the correct slide on button click', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - - impl.goCallback(1); - expect(showSlideSpy).to.have.been.calledWith(1); - expect(showSlideSpy).to.be.calledOnce; - - impl.goCallback(-1); - expect(showSlideSpy).to.have.been.calledWith(0); - expect(showSlideSpy).to.have.callCount(2); - - impl.goCallback(0); - expect(showSlideSpy).to.have.callCount(2); - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should show the correct slide', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - const hideRestOfTheSlidesSpy = sandbox.spy(impl, 'hideRestOfTheSlides_'); - const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); - const analyticsEventSpy = sandbox.spy(impl, 'analyticsEvent_'); - - expect(impl.showSlide_(-1)).to.be.false; - expect(updateInViewportSpy).to.not.have.been.called; - expect(scheduleLayoutSpy).to.not.have.been.called; - expect(schedulePreloadSpy).to.not.have.been.called; - expect(hideRestOfTheSlidesSpy).to.not.have.been.called; - expect(setControlsStateSpy).to.not.have.been.called; - expect(analyticsEventSpy).to.not.have.been.called; - - expect(impl.showSlide_(5)).to.be.false; - expect(updateInViewportSpy).to.not.have.been.called; - expect(scheduleLayoutSpy).to.not.have.been.called; - expect(schedulePreloadSpy).to.not.have.been.called; - expect(hideRestOfTheSlidesSpy).to.not.have.been.called; - expect(setControlsStateSpy).to.not.have.been.called; - expect(analyticsEventSpy).to.not.have.been.called; - - expect(impl.showSlide_(impl.slideIndex_)).to.be.false; - expect(updateInViewportSpy).to.not.have.been.called; - expect(scheduleLayoutSpy).to.not.have.been.called; - expect(schedulePreloadSpy).to.not.have.been.called; - expect(hideRestOfTheSlidesSpy).to.not.have.been.called; - expect(setControlsStateSpy).to.not.have.been.called; - expect(analyticsEventSpy).to.not.have.been.called; - - expect(impl.showSlide_(1)).to.be.true; - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], true); - expect(updateInViewportSpy).to.have.callCount(2); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[2]); - expect(scheduleLayoutSpy).to.be.calledOnce; - expect(schedulePreloadSpy).to.have.callCount(2); - expect(impl.slideIndex_).to.equal(1); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(impl.slideWidth_); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); - expect(hideRestOfTheSlidesSpy).to.be.calledOnce; - expect(setControlsStateSpy).to.be.calledOnce; - expect(analyticsEventSpy).to.have.callCount(2); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-next', {'fromSlide': 'slide-id', 'toSlide': '1'}); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-change', {'fromSlide': 'slide-id', 'toSlide': '1'}); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[2].getAttribute('aria-hidden')).to.equal('true'); - - expect(impl.showSlide_(0)).to.be.true; - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], true); - expect(updateInViewportSpy).to.have.callCount(4); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); - expect(scheduleLayoutSpy).to.have.callCount(2); - expect(schedulePreloadSpy).to.have.callCount(3); - expect(impl.slideIndex_).to.equal(0); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(0); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); - expect(hideRestOfTheSlidesSpy).to.have.callCount(2); - expect(setControlsStateSpy).to.have.callCount(2); - expect(analyticsEventSpy).to.have.callCount(4); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-prev', {'fromSlide': '1', 'toSlide': 'slide-id'}); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-change', {'fromSlide': '1', 'toSlide': 'slide-id'}); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - - expect(impl.showSlide_(4)).to.be.true; - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[4], true); - expect(updateInViewportSpy).to.have.callCount(6); - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[3]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[4]); - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(schedulePreloadSpy).to.have.callCount(4); - expect(impl.slideIndex_).to.equal(4); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(impl.slideWidth_); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4]); - expect(hideRestOfTheSlidesSpy).to.have.callCount(3); - expect(setControlsStateSpy).to.have.callCount(3); - expect(analyticsEventSpy).to.have.callCount(6); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-prev', {'fromSlide': 'slide-id', 'toSlide': '4'}); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-change', {'fromSlide': 'slide-id', 'toSlide': '4'}); - expect(impl.slides_[3].getAttribute('aria-hidden')).to.equal('true'); - expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal(null); - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should hide the unwanted slides', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const hideRestOfTheSlidesSpy = sandbox.spy(impl, 'hideRestOfTheSlides_'); - - impl.showSlide_(1); - - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); - expect(schedulePauseSpy).to.have.callCount(2); - - impl.showSlide_(0); - - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0,1]); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); - expect(schedulePauseSpy).to.have.callCount(4); - - impl.showSlide_(4); - - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4]); - - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[3]); - expect(schedulePauseSpy).to.have.callCount(7); - }); - }); - - it('should show/hide the correct controls', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - - impl.showSlide_(1); - expect(impl.hasNext()).to.be.true; - expect(impl.hasPrev()).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - - impl.showSlide_(0); - expect(impl.hasNext()).to.be.true; - expect(impl.hasPrev()).to.be.false; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.true; - - impl.showSlide_(4); - expect(impl.hasNext()).to.be.false; - expect(impl.hasPrev()).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - }); - }); - - it('should set the correct scrollLeft when there is only one slide', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - - impl.noOfSlides_ = 1; - impl.showSlide_(0); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(0); - }); - }); - - it('should update to the right slide on scroll', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - - impl.vsync_ = { - mutatePromise: cb => { - cb(); - return { - then: cb2 => { - cb2(); - }, - }; - }, - mutate: cb => { - cb(); - }, - }; - - // Move to slide 1 (from slide 0). - impl.showSlide_(1); - expect(showSlideSpy).to.be.calledWith(1); - expect(impl.snappingInProgress_).to.be.false; - - //Move to slide 0 - via scrolling back. - impl.updateOnScroll_(1); - expect(showSlideSpy).to.be.calledWith(0); - expect(impl.slideIndex_).to.equal(0); - - // Try scrolling Fwd and move to slide 1. - impl.updateOnScroll_(401); - expect(showSlideSpy).to.be.calledWith(1); - expect(impl.slideIndex_).to.equal(1); - - - impl.updateOnScroll_(700); - expect(showSlideSpy).to.be.calledWith(2); - expect(impl.slideIndex_).to.equal(2); - - impl.showSlide_(4); - impl.updateOnScroll_(700); - expect(showSlideSpy).to.be.calledWith(4); - expect(impl.slideIndex_).to.equal(4); - }); - }); - - it('should get the correct next slide index for a scrollLeft' , () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - - // Already at slide 0; - expect(impl.getNextSlideIndex_(0)).to.equal(0); - expect(impl.getNextSlideIndex_(100)).to.equal(0); - expect(impl.getNextSlideIndex_(200)).to.equal(1); - expect(impl.getNextSlideIndex_(400)).to.equal(1); - - impl.showSlide_(3); - - expect(impl.getNextSlideIndex_(0)).to.equal(2); - expect(impl.getNextSlideIndex_(100)).to.equal(2); - expect(impl.getNextSlideIndex_(200)).to.equal(3); - expect(impl.getNextSlideIndex_(400)).to.equal(3); - expect(impl.getNextSlideIndex_(500)).to.equal(3); - expect(impl.getNextSlideIndex_(600)).to.equal(4); - expect(impl.getNextSlideIndex_(800)).to.equal(4); - - impl.showSlide_(4); - expect(impl.getNextSlideIndex_(0)).to.equal(3); - expect(impl.getNextSlideIndex_(100)).to.equal(3); - expect(impl.getNextSlideIndex_(200)).to.equal(4); - expect(impl.getNextSlideIndex_(400)).to.equal(4); - }); - }); - - it('should custom snap to the correct slide', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); - - impl.customSnap_(0); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); - impl.customSnap_(100); - expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); - impl.customSnap_(200); - expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); - impl.customSnap_(400); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); - - impl.showSlide_(3); - - impl.customSnap_(0); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); - impl.customSnap_(100); - expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); - impl.customSnap_(200); - expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); - impl.customSnap_(400); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); - impl.customSnap_(500); - expect(animateScrollLeftSpy).to.have.been.calledWith(500, 400); - impl.customSnap_(600); - expect(animateScrollLeftSpy).to.have.been.calledWith(600, 800); - impl.customSnap_(800); - expect(animateScrollLeftSpy).to.have.been.calledWith(800, 800); - - impl.showSlide_(4); - - impl.customSnap_(0); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); - impl.customSnap_(100); - expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); - impl.customSnap_(200); - expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); - impl.customSnap_(400); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); - - impl.showSlide_(0); - - impl.customSnap_(0, -1); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); - impl.customSnap_(0, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); - - impl.showSlide_(3); - - impl.customSnap_(400, -1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); - impl.customSnap_(400, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); - - impl.showSlide_(4); - - impl.customSnap_(400, -1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); - impl.customSnap_(400, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); - }); - }); - - it('should custom snap to the correct slide - special case', () => { - return getAmpSlideScroll(null, 2).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); - - impl.customSnap_(0, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); - - impl.showSlide_(1); - - impl.customSnap_(400, -1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); - }); - }); - - it('should handle custom elastic scroll', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const customSnapSpy = sandbox.stub(impl, 'customSnap_').callsFake(() => { - return { - then: cb => { - cb(); - }, - }; + '.i-amphtml-slides-container > .i-amphtml-slide-item' + ).length + ).to.equal(5); + expect( + ampSlideScroll.getElementsByClassName('amp-carousel-slide').length + ).to.equal(5); + expect( + ampSlideScroll + .querySelector('.i-amphtml-slides-container') + .getAttribute('aria-live') + ).to.equal('polite'); + const impl = ampSlideScroll.implementation_; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); }); - - impl.handleCustomElasticScroll_(-10); - expect(impl.elasticScrollState_).to.equal(-1); - impl.previousScrollLeft_ = -10; - impl.handleCustomElasticScroll_(-5); - expect(customSnapSpy).to.have.been.calledWith(-5); - - impl.previousScrollLeft_ = null; - - impl.handleCustomElasticScroll_(410); - expect(impl.elasticScrollState_).to.equal(1); - impl.previousScrollLeft_ = 410; - impl.handleCustomElasticScroll_(405); - expect(customSnapSpy).to.have.been.calledWith(405); }); - }); - - it('should handle layout measures (orientation changes)', async() => { - const ampSlideScroll = await getAmpSlideScroll(); - const impl = ampSlideScroll.implementation_; - const getLayoutWidthStub = sandbox.stub(impl, 'getLayoutWidth'); - - getLayoutWidthStub.returns(200); - impl.onLayoutMeasure(); - expect(getLayoutWidthStub).to.have.been.calledOnce; - expect(impl.slideWidth_).to.equal(200); - - // Show the first slide, make sure the scroll position is correct. - impl.showSlide_(1); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(200); - - // Now do a layout measure letting the component know it changed size. - getLayoutWidthStub.returns(400); - impl.onLayoutMeasure(); - expect(getLayoutWidthStub).to.have.callCount(2); - expect(impl.slideWidth_).to.equal(400); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(200); - - // Make sure the scroll position is correct after layoutCallback. - await impl.layoutCallback(); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(400); - }); - - it('should relayout the current slide on layoutCallback', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const scheduleLayoutSpy_ = sandbox.spy(impl, 'scheduleLayout'); - impl.slideIndex_ = null; - impl.layoutCallback(); - expect(scheduleLayoutSpy_).to.have.been.calledWith(impl.slides_[0]); - impl.showSlide_(1); - impl.layoutCallback(); - expect(scheduleLayoutSpy_).to.have.been.calledWith(impl.slides_[1]); - }); - }); + it('should go to the correct slide on button click', () => { + return getAmpSlideScroll().then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - describe('Looping', () => { - beforeEach(() => { - sandbox = sinon.sandbox; - }); + impl.goCallback(1); + expect(showSlideSpy).to.have.been.calledWith(1); + expect(showSlideSpy).to.be.calledOnce; - afterEach(() => { - sandbox.restore(); - }); + impl.goCallback(-1); + expect(showSlideSpy).to.have.been.calledWith(0); + expect(showSlideSpy).to.have.callCount(2); - it('should create container and wrappers and show initial slides', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; + impl.goCallback(0); + expect(showSlideSpy).to.have.callCount(2); }); }); // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should show the correct slides when looping', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it.skip('should show the correct slide', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - const hideRestOfTheSlidesSpy = - sandbox.spy(impl, 'hideRestOfTheSlides_'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); - - expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('true'); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - - impl.showSlide_(1); - + const analyticsEventSpy = sandbox.spy(impl, 'analyticsEvent_'); + + expect(impl.showSlide_(-1)).to.be.false; + expect(updateInViewportSpy).to.not.have.been.called; + expect(scheduleLayoutSpy).to.not.have.been.called; + expect(schedulePreloadSpy).to.not.have.been.called; + expect(hideRestOfTheSlidesSpy).to.not.have.been.called; + expect(setControlsStateSpy).to.not.have.been.called; + expect(analyticsEventSpy).to.not.have.been.called; + + expect(impl.showSlide_(5)).to.be.false; + expect(updateInViewportSpy).to.not.have.been.called; + expect(scheduleLayoutSpy).to.not.have.been.called; + expect(schedulePreloadSpy).to.not.have.been.called; + expect(hideRestOfTheSlidesSpy).to.not.have.been.called; + expect(setControlsStateSpy).to.not.have.been.called; + expect(analyticsEventSpy).to.not.have.been.called; + + expect(impl.showSlide_(impl.slideIndex_)).to.be.false; + expect(updateInViewportSpy).to.not.have.been.called; + expect(scheduleLayoutSpy).to.not.have.been.called; + expect(schedulePreloadSpy).to.not.have.been.called; + expect(hideRestOfTheSlidesSpy).to.not.have.been.called; + expect(setControlsStateSpy).to.not.have.been.called; + expect(analyticsEventSpy).to.not.have.been.called; + + expect(impl.showSlide_(1)).to.be.true; expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); + impl.slides_[0], + false + ); expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], true); + impl.slides_[1], + true + ); expect(updateInViewportSpy).to.have.callCount(2); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.true; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .true; expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[2]); expect(scheduleLayoutSpy).to.be.calledOnce; expect(schedulePreloadSpy).to.have.callCount(2); expect(impl.slideIndex_).to.equal(1); - expect(impl.slidesContainer_./*OK*/scrollLeft) - .to.equal(impl.slideWidth_); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); expect(hideRestOfTheSlidesSpy).to.be.calledOnce; expect(setControlsStateSpy).to.be.calledOnce; + expect(analyticsEventSpy).to.have.callCount(2); + expect(analyticsEventSpy).to.have.been.calledWith('amp-carousel-next', { + 'fromSlide': 'slide-id', + 'toSlide': '1', + }); + expect(analyticsEventSpy).to.have.been.calledWith( + 'amp-carousel-change', + {'fromSlide': 'slide-id', 'toSlide': '1'} + ); expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); expect(impl.slides_[2].getAttribute('aria-hidden')).to.equal('true'); - impl.showSlide_(0); - + expect(impl.showSlide_(0)).to.be.true; expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], false); + impl.slides_[1], + false + ); expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], true); + impl.slides_[0], + true + ); expect(updateInViewportSpy).to.have.callCount(4); - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[4]); expect(scheduleLayoutSpy).to.have.callCount(2); - expect(schedulePreloadSpy).to.have.callCount(4); + expect(schedulePreloadSpy).to.have.callCount(3); expect(impl.slideIndex_).to.equal(0); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(400); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([4, 0, 1]); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(0); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); expect(hideRestOfTheSlidesSpy).to.have.callCount(2); expect(setControlsStateSpy).to.have.callCount(2); - expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('true'); + expect(analyticsEventSpy).to.have.callCount(4); + expect(analyticsEventSpy).to.have.been.calledWith('amp-carousel-prev', { + 'fromSlide': '1', + 'toSlide': 'slide-id', + }); + expect(analyticsEventSpy).to.have.been.calledWith( + 'amp-carousel-change', + {'fromSlide': '1', 'toSlide': 'slide-id'} + ); expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - impl.showSlide_(4); - + expect(impl.showSlide_(4)).to.be.true; expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); + impl.slides_[0], + false + ); expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[4], true); + impl.slides_[4], + true + ); expect(updateInViewportSpy).to.have.callCount(6); - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[3]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[4]); expect(scheduleLayoutSpy).to.have.callCount(3); - expect(schedulePreloadSpy).to.have.callCount(6); + expect(schedulePreloadSpy).to.have.callCount(4); expect(impl.slideIndex_).to.equal(4); - expect(impl.slidesContainer_./*OK*/scrollLeft) - .to.equal(impl.slideWidth_); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4, 0]); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4]); expect(hideRestOfTheSlidesSpy).to.have.callCount(3); expect(setControlsStateSpy).to.have.callCount(3); + expect(analyticsEventSpy).to.have.callCount(6); + expect(analyticsEventSpy).to.have.been.calledWith('amp-carousel-prev', { + 'fromSlide': 'slide-id', + 'toSlide': '4', + }); + expect(analyticsEventSpy).to.have.been.calledWith( + 'amp-carousel-change', + {'fromSlide': 'slide-id', 'toSlide': '4'} + ); expect(impl.slides_[3].getAttribute('aria-hidden')).to.equal('true'); expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); - - }); - }); - - it('show correct slides when looping with `autoplay` for 2 slides', () => { - return getAmpSlideScroll(true, 2).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - const hideRestOfTheSlidesSpy = - sandbox.spy(impl, 'hideRestOfTheSlides_'); - const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); - - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - - impl.showSlide_(1); - - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], true); - expect(updateInViewportSpy).to.have.callCount(2); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); - expect(scheduleLayoutSpy).to.be.calledOnce; - expect(schedulePreloadSpy).to.have.callCount(1); - expect(impl.slideIndex_).to.equal(1); - expect(impl.slidesContainer_./*OK*/scrollLeft) - .to.equal(impl.slideWidth_); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); - expect(hideRestOfTheSlidesSpy).to.be.calledOnce; - expect(setControlsStateSpy).to.be.calledOnce; - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); - - impl.showSlide_(0); - - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], true); - expect(updateInViewportSpy).to.have.callCount(4); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); - expect(scheduleLayoutSpy).to.have.callCount(2); - expect(schedulePreloadSpy).to.have.callCount(2); - expect(impl.slideIndex_).to.equal(0); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); - expect(hideRestOfTheSlidesSpy).to.have.callCount(2); - expect(setControlsStateSpy).to.have.callCount(2); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - }); - }); - - it('do not set `autoplay` status if `autoplay=0` specified', () => { - return getAmpSlideScroll(false, 3, true, true, 0).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const setupAutoplaySpy = sandbox.spy(impl, 'setupAutoplay_'); - expect(setupAutoplaySpy).to.not.have.been.called; - }); - }); - - it('removes `autoplay` status after provided loops are made', () => { - return getAmpSlideScroll(false, 3, true, true, 2).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const removeAutoplaySpy = sandbox.spy(impl, 'removeAutoplay'); - impl.showSlide_(1); - impl.showSlide_(2); - expect(impl.loopsMade_).to.equal(1); - impl.showSlide_(0); - impl.showSlide_(1); - impl.showSlide_(2); - expect(impl.loopsMade_).to.equal(2); - expect(removeAutoplaySpy).to.have.been.called; - expect(ampSlideScroll.hasAttribute('loop')).to.be.false; + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal(null); }); }); // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should hide unwanted slides when looping', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it.skip('should hide the unwanted slides', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const hideRestOfTheSlidesSpy = - sandbox.spy(impl, 'hideRestOfTheSlides_'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); impl.showSlide_(1); expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.false; - - expect(impl.slideWrappers_[0].style.order).to.equal('1'); - expect(impl.slideWrappers_[1].style.order).to.equal('2'); - expect(impl.slideWrappers_[2].style.order).to.equal('3'); - expect(impl.slideWrappers_[3].style.order).to.equal(''); - expect(impl.slideWrappers_[4].style.order).to.equal(''); - - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[4]); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .false; expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); - expect(schedulePauseSpy).to.have.callCount(3); + expect(schedulePauseSpy).to.have.callCount(2); impl.showSlide_(0); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([4, 0, 1]); - - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].style.order).to.equal('2'); - expect(impl.slideWrappers_[1].style.order).to.equal('3'); - expect(impl.slideWrappers_[2].style.order).to.equal(''); - expect(impl.slideWrappers_[3].style.order).to.equal(''); - expect(impl.slideWrappers_[4].style.order).to.equal('1'); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[4]); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .false; expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePauseSpy).to.have.callCount(6); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); + expect(schedulePauseSpy).to.have.callCount(4); impl.showSlide_(4); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4, 0]); - - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].style.order).to.equal('3'); - expect(impl.slideWrappers_[1].style.order).to.equal(''); - expect(impl.slideWrappers_[2].style.order).to.equal(''); - expect(impl.slideWrappers_[3].style.order).to.equal('1'); - expect(impl.slideWrappers_[4].style.order).to.equal('2'); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[3]); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4]); + + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePauseSpy).to.have.callCount(9); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[3]); + expect(schedulePauseSpy).to.have.callCount(7); }); }); - it('should show/hide the correct controls when looping', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it('should show/hide the correct controls', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; impl.showSlide_(1); @@ -866,34 +372,42 @@ describes.realWin('SlideScroll', { impl.showSlide_(0); expect(impl.hasNext()).to.be.true; - expect(impl.hasPrev()).to.be.true; + expect(impl.hasPrev()).to.be.false; expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.true; impl.showSlide_(4); - expect(impl.hasNext()).to.be.true; + expect(impl.hasNext()).to.be.false; expect(impl.hasPrev()).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.true; expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; }); }); it('should set the correct scrollLeft when there is only one slide', () => { - return getAmpSlideScroll(true, 1).then(ampSlideScroll => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; impl.noOfSlides_ = 1; impl.showSlide_(0); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(0); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(0); }); }); it('should update to the right slide on scroll', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const showSlideSpy = sandbox.spy(impl, 'showSlide_'); impl.vsync_ = { + mutatePromise: cb => { + cb(); + return { + then: cb2 => { + cb2(); + }, + }; + }, mutate: cb => { cb(); }, @@ -909,39 +423,31 @@ describes.realWin('SlideScroll', { expect(showSlideSpy).to.be.calledWith(0); expect(impl.slideIndex_).to.equal(0); - // Try scrolling Fwd and move a little fwd to stay in the same slide. + // Try scrolling Fwd and move to slide 1. impl.updateOnScroll_(401); - expect(showSlideSpy).to.be.calledWith(0); - expect(impl.slideIndex_).to.equal(0); - - impl.updateOnScroll_(700); expect(showSlideSpy).to.be.calledWith(1); expect(impl.slideIndex_).to.equal(1); - impl.showSlide_(4); impl.updateOnScroll_(700); - expect(showSlideSpy).to.be.calledWith(0); - expect(impl.slideIndex_).to.equal(0); + expect(showSlideSpy).to.be.calledWith(2); + expect(impl.slideIndex_).to.equal(2); - impl.updateOnScroll_(1); + impl.showSlide_(4); + impl.updateOnScroll_(700); expect(showSlideSpy).to.be.calledWith(4); expect(impl.slideIndex_).to.equal(4); }); }); - it('should get the correct next slide index for a scrollLeft' , () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it('should get the correct next slide index for a scrollLeft', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; // Already at slide 0; - - expect(impl.getNextSlideIndex_(0)).to.equal(4); - expect(impl.getNextSlideIndex_(100)).to.equal(4); - expect(impl.getNextSlideIndex_(200)).to.equal(0); - expect(impl.getNextSlideIndex_(400)).to.equal(0); - expect(impl.getNextSlideIndex_(500)).to.equal(0); - expect(impl.getNextSlideIndex_(600)).to.equal(1); - expect(impl.getNextSlideIndex_(800)).to.equal(1); + expect(impl.getNextSlideIndex_(0)).to.equal(0); + expect(impl.getNextSlideIndex_(100)).to.equal(0); + expect(impl.getNextSlideIndex_(200)).to.equal(1); + expect(impl.getNextSlideIndex_(400)).to.equal(1); impl.showSlide_(3); @@ -958,17 +464,25 @@ describes.realWin('SlideScroll', { expect(impl.getNextSlideIndex_(100)).to.equal(3); expect(impl.getNextSlideIndex_(200)).to.equal(4); expect(impl.getNextSlideIndex_(400)).to.equal(4); - expect(impl.getNextSlideIndex_(500)).to.equal(4); - expect(impl.getNextSlideIndex_(600)).to.equal(0); - expect(impl.getNextSlideIndex_(800)).to.equal(0); }); }); it('should custom snap to the correct slide', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); + impl.customSnap_(0); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); + impl.customSnap_(100); + expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); + impl.customSnap_(200); + expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); + impl.customSnap_(400); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); + + impl.showSlide_(3); + impl.customSnap_(0); expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); impl.customSnap_(100); @@ -984,217 +498,806 @@ describes.realWin('SlideScroll', { impl.customSnap_(800); expect(animateScrollLeftSpy).to.have.been.calledWith(800, 800); + impl.showSlide_(4); + + impl.customSnap_(0); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); + impl.customSnap_(100); + expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); + impl.customSnap_(200); + expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); + impl.customSnap_(400); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); + + impl.showSlide_(0); + + impl.customSnap_(0, -1); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); + impl.customSnap_(0, 1); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); + + impl.showSlide_(3); + impl.customSnap_(400, -1); expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); impl.customSnap_(400, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 800); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); + + impl.showSlide_(4); + + impl.customSnap_(400, -1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); + impl.customSnap_(400, 1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); }); }); - it('should go to the correct slide on button click', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it('should custom snap to the correct slide - special case', () => { + return getAmpSlideScroll(null, 2).then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); - impl.goCallback(-1); - expect(showSlideSpy).to.have.been.calledWith(4); - expect(showSlideSpy).to.be.calledOnce; + impl.customSnap_(0, 1); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); - impl.goCallback(1); - expect(showSlideSpy).to.have.been.calledWith(0); - expect(showSlideSpy).to.have.callCount(2); + impl.showSlide_(1); - impl.goCallback(1); - expect(showSlideSpy).to.have.been.calledWith(1); - expect(showSlideSpy).to.have.callCount(3); + impl.customSnap_(400, -1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); }); }); - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should update slide when `slide` attribute is mutated', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { - expectAsyncConsoleError(/Invalid \[slide\] value:/, 1); - + it('should handle custom elastic scroll', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + const customSnapSpy = sandbox + .stub(impl, 'customSnap_') + .callsFake(() => { + return { + then: cb => { + cb(); + }, + }; + }); + + impl.handleCustomElasticScroll_(-10); + expect(impl.elasticScrollState_).to.equal(-1); + impl.previousScrollLeft_ = -10; + impl.handleCustomElasticScroll_(-5); + expect(customSnapSpy).to.have.been.calledWith(-5); + + impl.previousScrollLeft_ = null; + + impl.handleCustomElasticScroll_(410); + expect(impl.elasticScrollState_).to.equal(1); + impl.previousScrollLeft_ = 410; + impl.handleCustomElasticScroll_(405); + expect(customSnapSpy).to.have.been.calledWith(405); + }); + }); - impl.mutatedAttributesCallback({slide: 2}); - expect(showSlideSpy).to.have.been.calledWith(2); + it('should handle layout measures (orientation changes)', async () => { + const ampSlideScroll = await getAmpSlideScroll(); + const impl = ampSlideScroll.implementation_; + const getLayoutWidthStub = sandbox.stub(impl, 'getLayoutWidth'); - impl.mutatedAttributesCallback({slide: 0}); - expect(showSlideSpy).to.have.been.calledWith(0); + getLayoutWidthStub.returns(200); + impl.onLayoutMeasure(); + expect(getLayoutWidthStub).to.have.been.calledOnce; + expect(impl.slideWidth_).to.equal(200); - // Don't call showSlide_() if slide is not finite. - showSlideSpy.resetHistory(); - impl.mutatedAttributesCallback({slide: Number.POSITIVE_INFINITY}); - expect(showSlideSpy.called).to.be.false; - }); + // Show the first slide, make sure the scroll position is correct. + impl.showSlide_(1); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(200); + + // Now do a layout measure letting the component know it changed size. + getLayoutWidthStub.returns(400); + impl.onLayoutMeasure(); + expect(getLayoutWidthStub).to.have.callCount(2); + expect(impl.slideWidth_).to.equal(400); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(200); + + // Make sure the scroll position is correct after layoutCallback. + await impl.layoutCallback(); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(400); }); - it('should trigger `slideChange` action when user changes slides', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it('should relayout the current slide on layoutCallback', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; - const triggerSpy = sandbox.spy(impl.action_, 'trigger'); - - impl.goCallback(-1, /* animate */ false); - expect(triggerSpy).to.have.been.calledWith( - ampSlideScroll, - 'slideChange', - /* CustomEvent */ sinon.match.has('detail', {index: 4})); + const scheduleLayoutSpy_ = sandbox.spy(impl, 'scheduleLayout'); + impl.slideIndex_ = null; + impl.layoutCallback(); + expect(scheduleLayoutSpy_).to.have.been.calledWith(impl.slides_[0]); - impl.goCallback(1, /* animate */ false); - expect(triggerSpy).to.have.been.calledWith( - ampSlideScroll, - 'slideChange', - /* CustomEvent */ sinon.match.has('detail', {index: 0})); + impl.showSlide_(1); + impl.layoutCallback(); + expect(scheduleLayoutSpy_).to.have.been.calledWith(impl.slides_[1]); }); }); - it('should goToSlide on action', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { - expectAsyncConsoleError(/Invalid \[slide\] value:/, 4); + describe('Looping', () => { + beforeEach(() => { + sandbox = sinon.sandbox; + }); - const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - const satisfiesTrust = () => true; + afterEach(() => { + sandbox.restore(); + }); - let args = {'index': '123'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + it('should create container and wrappers and show initial slides', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + }); + }); - args = {'index': '5'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip('should show the correct slides when looping', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); + const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); + + expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); + + impl.showSlide_(1); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[1], + true + ); + expect(updateInViewportSpy).to.have.callCount(2); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .true; + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[2]); + expect(scheduleLayoutSpy).to.be.calledOnce; + expect(schedulePreloadSpy).to.have.callCount(2); + expect(impl.slideIndex_).to.equal(1); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); + expect(hideRestOfTheSlidesSpy).to.be.calledOnce; + expect(setControlsStateSpy).to.be.calledOnce; + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[2].getAttribute('aria-hidden')).to.equal('true'); + + impl.showSlide_(0); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[1], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + true + ); + expect(updateInViewportSpy).to.have.callCount(4); + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[4]); + expect(scheduleLayoutSpy).to.have.callCount(2); + expect(schedulePreloadSpy).to.have.callCount(4); + expect(impl.slideIndex_).to.equal(0); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(400); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([4, 0, 1]); + expect(hideRestOfTheSlidesSpy).to.have.callCount(2); + expect(setControlsStateSpy).to.have.callCount(2); + expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); + + impl.showSlide_(4); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[4], + true + ); + expect(updateInViewportSpy).to.have.callCount(6); + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[3]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[4]); + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(schedulePreloadSpy).to.have.callCount(6); + expect(impl.slideIndex_).to.equal(4); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4, 0]); + expect(hideRestOfTheSlidesSpy).to.have.callCount(3); + expect(setControlsStateSpy).to.have.callCount(3); + expect(impl.slides_[3].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); + }); + }); - args = {'index': 'ssds11'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + it('show correct slides when looping with `autoplay` for 2 slides', () => { + return getAmpSlideScroll(true, 2).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); + const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); + + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); + + impl.showSlide_(1); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[1], + true + ); + expect(updateInViewportSpy).to.have.callCount(2); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); + expect(scheduleLayoutSpy).to.be.calledOnce; + expect(schedulePreloadSpy).to.have.callCount(1); + expect(impl.slideIndex_).to.equal(1); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); + expect(hideRestOfTheSlidesSpy).to.be.calledOnce; + expect(setControlsStateSpy).to.be.calledOnce; + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); + + impl.showSlide_(0); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[1], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + true + ); + expect(updateInViewportSpy).to.have.callCount(4); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); + expect(scheduleLayoutSpy).to.have.callCount(2); + expect(schedulePreloadSpy).to.have.callCount(2); + expect(impl.slideIndex_).to.equal(0); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); + expect(hideRestOfTheSlidesSpy).to.have.callCount(2); + expect(setControlsStateSpy).to.have.callCount(2); + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); + }); + }); - args = {'index': '-1'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + it('do not set `autoplay` status if `autoplay=0` specified', () => { + return getAmpSlideScroll(false, 3, true, true, 0).then( + ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const setupAutoplaySpy = sandbox.spy(impl, 'setupAutoplay_'); + expect(setupAutoplaySpy).to.not.have.been.called; + } + ); + }); - args = {'index': '0'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.have.been.calledWith(0); + it('removes `autoplay` status after provided loops are made', () => { + return getAmpSlideScroll(false, 3, true, true, 2).then( + ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const removeAutoplaySpy = sandbox.spy(impl, 'removeAutoplay'); + impl.showSlide_(1); + impl.showSlide_(2); + expect(impl.loopsMade_).to.equal(1); + impl.showSlide_(0); + impl.showSlide_(1); + impl.showSlide_(2); + expect(impl.loopsMade_).to.equal(2); + expect(removeAutoplaySpy).to.have.been.called; + expect(ampSlideScroll.hasAttribute('loop')).to.be.false; + } + ); + }); - args = {'index': '4'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.have.been.calledWith(4); + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip('should hide unwanted slides when looping', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); + + impl.showSlide_(1); + + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .false; + + expect(impl.slideWrappers_[0].style.order).to.equal('1'); + expect(impl.slideWrappers_[1].style.order).to.equal('2'); + expect(impl.slideWrappers_[2].style.order).to.equal('3'); + expect(impl.slideWrappers_[3].style.order).to.equal(''); + expect(impl.slideWrappers_[4].style.order).to.equal(''); + + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[4]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); + expect(schedulePauseSpy).to.have.callCount(3); + + impl.showSlide_(0); + + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([4, 0, 1]); + + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].style.order).to.equal('2'); + expect(impl.slideWrappers_[1].style.order).to.equal('3'); + expect(impl.slideWrappers_[2].style.order).to.equal(''); + expect(impl.slideWrappers_[3].style.order).to.equal(''); + expect(impl.slideWrappers_[4].style.order).to.equal('1'); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[4]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); + expect(schedulePauseSpy).to.have.callCount(6); + + impl.showSlide_(4); + + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4, 0]); + + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].style.order).to.equal('3'); + expect(impl.slideWrappers_[1].style.order).to.equal(''); + expect(impl.slideWrappers_[2].style.order).to.equal(''); + expect(impl.slideWrappers_[3].style.order).to.equal('1'); + expect(impl.slideWrappers_[4].style.order).to.equal('2'); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[3]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); + expect(schedulePauseSpy).to.have.callCount(9); + }); }); - }); - it('should NOT call showSlide_ before layout', () => { - const promise = getAmpSlideScroll(true, 5, /* opt_attachToDom */ false); - return promise.then(ampSlideScroll => { + it('should show/hide the correct controls when looping', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; - // Layout happens asynchronously after attaching to DOM, so we can - // test pre-layoutCallback logic now. - doc.body.appendChild(ampSlideScroll); - return ampSlideScroll.build().then(() => { + impl.showSlide_(1); + expect(impl.hasNext()).to.be.true; + expect(impl.hasPrev()).to.be.true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + + impl.showSlide_(0); + expect(impl.hasNext()).to.be.true; + expect(impl.hasPrev()).to.be.true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + + impl.showSlide_(4); + expect(impl.hasNext()).to.be.true; + expect(impl.hasPrev()).to.be.true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + }); + }); + + it('should set the correct scrollLeft when there is only one slide', () => { + return getAmpSlideScroll(true, 1).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + + impl.noOfSlides_ = 1; + impl.showSlide_(0); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(0); + }); + }); + + it('should update to the right slide on scroll', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - const satisfiesTrust = () => true; - const args = {'index': '3'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + impl.vsync_ = { + mutate: cb => { + cb(); + }, + }; - impl.mutatedAttributesCallback({slide: 2}); - expect(showSlideSpy).to.not.have.been.called; + // Move to slide 1 (from slide 0). + impl.showSlide_(1); + expect(showSlideSpy).to.be.calledWith(1); + expect(impl.snappingInProgress_).to.be.false; + + //Move to slide 0 - via scrolling back. + impl.updateOnScroll_(1); + expect(showSlideSpy).to.be.calledWith(0); + expect(impl.slideIndex_).to.equal(0); + + // Try scrolling Fwd and move a little fwd to stay in the same slide. + impl.updateOnScroll_(401); + expect(showSlideSpy).to.be.calledWith(0); + expect(impl.slideIndex_).to.equal(0); + + impl.updateOnScroll_(700); + expect(showSlideSpy).to.be.calledWith(1); + expect(impl.slideIndex_).to.equal(1); + + impl.showSlide_(4); + impl.updateOnScroll_(700); + expect(showSlideSpy).to.be.calledWith(0); + expect(impl.slideIndex_).to.equal(0); + + impl.updateOnScroll_(1); + expect(showSlideSpy).to.be.calledWith(4); + expect(impl.slideIndex_).to.equal(4); + }); + }); - impl.onLayoutMeasure(); - ampSlideScroll.layoutCallback(); + it('should get the correct next slide index for a scrollLeft', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; - // Should show the last slide index requested before layout. - expect(showSlideSpy).to.have.been.calledWith(2); + // Already at slide 0; + + expect(impl.getNextSlideIndex_(0)).to.equal(4); + expect(impl.getNextSlideIndex_(100)).to.equal(4); + expect(impl.getNextSlideIndex_(200)).to.equal(0); + expect(impl.getNextSlideIndex_(400)).to.equal(0); + expect(impl.getNextSlideIndex_(500)).to.equal(0); + expect(impl.getNextSlideIndex_(600)).to.equal(1); + expect(impl.getNextSlideIndex_(800)).to.equal(1); + + impl.showSlide_(3); + + expect(impl.getNextSlideIndex_(0)).to.equal(2); + expect(impl.getNextSlideIndex_(100)).to.equal(2); + expect(impl.getNextSlideIndex_(200)).to.equal(3); + expect(impl.getNextSlideIndex_(400)).to.equal(3); + expect(impl.getNextSlideIndex_(500)).to.equal(3); + expect(impl.getNextSlideIndex_(600)).to.equal(4); + expect(impl.getNextSlideIndex_(800)).to.equal(4); + + impl.showSlide_(4); + expect(impl.getNextSlideIndex_(0)).to.equal(3); + expect(impl.getNextSlideIndex_(100)).to.equal(3); + expect(impl.getNextSlideIndex_(200)).to.equal(4); + expect(impl.getNextSlideIndex_(400)).to.equal(4); + expect(impl.getNextSlideIndex_(500)).to.equal(4); + expect(impl.getNextSlideIndex_(600)).to.equal(0); + expect(impl.getNextSlideIndex_(800)).to.equal(0); + }); + }); + + it('should custom snap to the correct slide', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); + + impl.customSnap_(0); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); + impl.customSnap_(100); + expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); + impl.customSnap_(200); + expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); + impl.customSnap_(400); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); + impl.customSnap_(500); + expect(animateScrollLeftSpy).to.have.been.calledWith(500, 400); + impl.customSnap_(600); + expect(animateScrollLeftSpy).to.have.been.calledWith(600, 800); + impl.customSnap_(800); + expect(animateScrollLeftSpy).to.have.been.calledWith(800, 800); + + impl.customSnap_(400, -1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); + impl.customSnap_(400, 1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 800); + }); + }); + + it('should go to the correct slide on button click', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + + impl.goCallback(-1); + expect(showSlideSpy).to.have.been.calledWith(4); expect(showSlideSpy).to.be.calledOnce; + + impl.goCallback(1); + expect(showSlideSpy).to.have.been.calledWith(0); + expect(showSlideSpy).to.have.callCount(2); + + impl.goCallback(1); + expect(showSlideSpy).to.have.been.calledWith(1); + expect(showSlideSpy).to.have.callCount(3); }); }); - }); - it('should NOT call showSlide_ before re-layout', () => { - return getAmpSlideScroll(false, 5, false).then(ampSlideScroll => { + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip('should update slide when `slide` attribute is mutated', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + expectAsyncConsoleError(/Invalid \[slide\] value:/, 1); + + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + + impl.mutatedAttributesCallback({slide: 2}); + expect(showSlideSpy).to.have.been.calledWith(2); + + impl.mutatedAttributesCallback({slide: 0}); + expect(showSlideSpy).to.have.been.calledWith(0); + + // Don't call showSlide_() if slide is not finite. + showSlideSpy.resetHistory(); + impl.mutatedAttributesCallback({slide: Number.POSITIVE_INFINITY}); + expect(showSlideSpy.called).to.be.false; + }); + }); + + it('should trigger `slideChange` action when user changes slides', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const triggerSpy = sandbox.spy(impl.action_, 'trigger'); + + impl.goCallback(-1, /* animate */ false); + expect(triggerSpy).to.have.been.calledWith( + ampSlideScroll, + 'slideChange', + /* CustomEvent */ sinon.match.has('detail', {index: 4}) + ); + + impl.goCallback(1, /* animate */ false); + expect(triggerSpy).to.have.been.calledWith( + ampSlideScroll, + 'slideChange', + /* CustomEvent */ sinon.match.has('detail', {index: 0}) + ); + }); + }); + + it('should goToSlide on action', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + expectAsyncConsoleError(/Invalid \[slide\] value:/, 4); - doc.body.appendChild(ampSlideScroll); - return ampSlideScroll.build().then(() => { const impl = ampSlideScroll.implementation_; const showSlideSpy = sandbox.spy(impl, 'showSlide_'); const satisfiesTrust = () => true; - // Test that showSlide_ due to goToSlide(index=1) is not called before - // layout. - let args = {'index': '1'}; + let args = {'index': '123'}; impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); expect(showSlideSpy).to.not.have.been.called; - // Test that showSlide_ is called after layout. - impl.onLayoutMeasure(); - ampSlideScroll.layoutCallback(); - - expect(showSlideSpy).to.have.been.calledWith(1); - expect(showSlideSpy).to.be.calledOnce; + args = {'index': '5'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; - // Unlayout - showSlideSpy.resetHistory(); - impl.unlayoutCallback(); + args = {'index': 'ssds11'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; - // Test that showSlide_ due to goToSlide(index=4) is not called before - // layout. - args = {'index': '4'}; + args = {'index': '-1'}; impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); expect(showSlideSpy).to.not.have.been.called; - // Test that showSlide_ is called after layout. - impl.onLayoutMeasure(); - ampSlideScroll.layoutCallback(); + args = {'index': '0'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.have.been.calledWith(0); + args = {'index': '4'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); expect(showSlideSpy).to.have.been.calledWith(4); - expect(showSlideSpy).to.be.calledOnce; + }); + }); + + it('should NOT call showSlide_ before layout', () => { + const promise = getAmpSlideScroll(true, 5, /* opt_attachToDom */ false); + return promise.then(ampSlideScroll => { + // Layout happens asynchronously after attaching to DOM, so we can + // test pre-layoutCallback logic now. + doc.body.appendChild(ampSlideScroll); + return ampSlideScroll.build().then(() => { + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + const satisfiesTrust = () => true; + + const args = {'index': '3'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; + + impl.mutatedAttributesCallback({slide: 2}); + expect(showSlideSpy).to.not.have.been.called; + + impl.onLayoutMeasure(); + ampSlideScroll.layoutCallback(); + + // Should show the last slide index requested before layout. + expect(showSlideSpy).to.have.been.calledWith(2); + expect(showSlideSpy).to.be.calledOnce; + }); + }); + }); + + it('should NOT call showSlide_ before re-layout', () => { + return getAmpSlideScroll(false, 5, false).then(ampSlideScroll => { + doc.body.appendChild(ampSlideScroll); + return ampSlideScroll.build().then(() => { + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + const satisfiesTrust = () => true; + + // Test that showSlide_ due to goToSlide(index=1) is not called before + // layout. + let args = {'index': '1'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; + + // Test that showSlide_ is called after layout. + impl.onLayoutMeasure(); + ampSlideScroll.layoutCallback(); + + expect(showSlideSpy).to.have.been.calledWith(1); + expect(showSlideSpy).to.be.calledOnce; + + // Unlayout + showSlideSpy.resetHistory(); + impl.unlayoutCallback(); + + // Test that showSlide_ due to goToSlide(index=4) is not called before + // layout. + args = {'index': '4'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; + + // Test that showSlide_ is called after layout. + impl.onLayoutMeasure(); + ampSlideScroll.layoutCallback(); + + expect(showSlideSpy).to.have.been.calledWith(4); + expect(showSlideSpy).to.be.calledOnce; + }); }); }); }); - }); - describe('button titles', () => { - function getNextTitle(el) { - return el.querySelector('.amp-carousel-button-next') + describe('button titles', () => { + function getNextTitle(el) { + return el + .querySelector('.amp-carousel-button-next') .getAttribute('title'); - } + } - function getPrevTitle(el) { - return el.querySelector('.amp-carousel-button-prev') + function getPrevTitle(el) { + return el + .querySelector('.amp-carousel-button-prev') .getAttribute('title'); - } + } - describe('when not looping', () => { - it('should have the correct values on the first index', function* () { - const el = yield getAmpSlideScroll(false, 3); - expect(getPrevTitle(el)).to.equal('Previous item in carousel (1 of 3)'); - expect(getNextTitle(el)).to.equal('Next item in carousel (2 of 3)'); - }); + describe('when not looping', () => { + it('should have the correct values on the first index', function*() { + const el = yield getAmpSlideScroll(false, 3); + expect(getPrevTitle(el)).to.equal( + 'Previous item in carousel (1 of 3)' + ); + expect(getNextTitle(el)).to.equal('Next item in carousel (2 of 3)'); + }); - it('should have the correct values on the last index', function* () { - const el = yield getAmpSlideScroll(false, 3); - el.implementation_.showSlide_(2); - expect(getPrevTitle(el)).to.equal('Previous item in carousel (2 of 3)'); - expect(getNextTitle(el)).to.equal('Next item in carousel (3 of 3)'); + it('should have the correct values on the last index', function*() { + const el = yield getAmpSlideScroll(false, 3); + el.implementation_.showSlide_(2); + expect(getPrevTitle(el)).to.equal( + 'Previous item in carousel (2 of 3)' + ); + expect(getNextTitle(el)).to.equal('Next item in carousel (3 of 3)'); + }); }); - }); - describe('when looping', () => { - it('should have the correct values on the first index', function* () { - const el = yield getAmpSlideScroll(true, 3); - expect(getPrevTitle(el)).to.equal('Previous item in carousel (3 of 3)'); - expect(getNextTitle(el)).to.equal('Next item in carousel (2 of 3)'); - }); + describe('when looping', () => { + it('should have the correct values on the first index', function*() { + const el = yield getAmpSlideScroll(true, 3); + expect(getPrevTitle(el)).to.equal( + 'Previous item in carousel (3 of 3)' + ); + expect(getNextTitle(el)).to.equal('Next item in carousel (2 of 3)'); + }); - it('should have the correct values on the last index', function* () { - const el = yield getAmpSlideScroll(true, 3); - el.implementation_.showSlide_(2); - expect(getPrevTitle(el)).to.equal('Previous item in carousel (2 of 3)'); - expect(getNextTitle(el)).to.equal('Next item in carousel (1 of 3)'); + it('should have the correct values on the last index', function*() { + const el = yield getAmpSlideScroll(true, 3); + el.implementation_.showSlide_(2); + expect(getPrevTitle(el)).to.equal( + 'Previous item in carousel (2 of 3)' + ); + expect(getNextTitle(el)).to.equal('Next item in carousel (1 of 3)'); + }); }); }); - }); -}); + } +); diff --git a/extensions/amp-connatix-player/0.1/amp-connatix-player.js b/extensions/amp-connatix-player/0.1/amp-connatix-player.js index 18a16dc34edc..4b37053ff934 100644 --- a/extensions/amp-connatix-player/0.1/amp-connatix-player.js +++ b/extensions/amp-connatix-player/0.1/amp-connatix-player.js @@ -22,7 +22,6 @@ import {removeElement} from '../../../src/dom'; import {userAssert} from '../../../src/log'; export class AmpConnatixPlayer extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -50,8 +49,10 @@ export class AmpConnatixPlayer extends AMP.BaseElement { sendCommand_(command) { if (this.iframe_ && this.iframe_.contentWindow) { // Send message to the player - this.iframe_.contentWindow./*OK*/postMessage(command, - this.iframeDomain_); + this.iframe_.contentWindow./*OK*/ postMessage( + command, + this.iframeDomain_ + ); } } @@ -93,13 +94,13 @@ export class AmpConnatixPlayer extends AMP.BaseElement { // Player id is mandatory this.playerId_ = userAssert( - element.getAttribute('data-player-id'), - 'The data-player-id attribute is required for %s', - element); + element.getAttribute('data-player-id'), + 'The data-player-id attribute is required for %s', + element + ); // Media id is optional - this.mediaId_ = element.getAttribute('data-media-id') || - ''; + this.mediaId_ = element.getAttribute('data-media-id') || ''; this.bindToPlayerCommands_(); } diff --git a/extensions/amp-connatix-player/0.1/test/test-amp-connatix-player.js b/extensions/amp-connatix-player/0.1/test/test-amp-connatix-player.js index bce547030c0a..facd1a5c6970 100644 --- a/extensions/amp-connatix-player/0.1/test/test-amp-connatix-player.js +++ b/extensions/amp-connatix-player/0.1/test/test-amp-connatix-player.js @@ -16,75 +16,84 @@ import '../amp-connatix-player'; -describes.realWin('amp-connatix-player', { - amp: { - extensions: ['amp-connatix-player'], +describes.realWin( + 'amp-connatix-player', + { + amp: { + extensions: ['amp-connatix-player'], + }, }, -}, env => { + env => { + let win; + let doc; - let win; - let doc; + beforeEach(() => { + win = env.win; + doc = win.document; + }); - beforeEach(() => { - win = env.win; - doc = win.document; - }); + function getConnatixPlayer(attributes) { + const cnx = doc.createElement('amp-connatix-player'); + for (const key in attributes) { + cnx.setAttribute(key, attributes[key]); + } + cnx.setAttribute('width', '480'); + cnx.setAttribute('height', '270'); + cnx.setAttribute('layout', 'responsive'); - function getConnatixPlayer(attributes) { - const cnx = doc.createElement('amp-connatix-player'); - for (const key in attributes) { - cnx.setAttribute(key, attributes[key]); + doc.body.appendChild(cnx); + return cnx.build().then(() => { + cnx.layoutCallback(); + return cnx; + }); } - cnx.setAttribute('width', '480'); - cnx.setAttribute('height', '270'); - cnx.setAttribute('layout', 'responsive'); - - doc.body.appendChild(cnx); - return cnx.build().then(() => { cnx.layoutCallback(); return cnx; }); - } - it('renders', async() => { - const cnx = await getConnatixPlayer({ - 'data-player-id': 'f721b0d8-7a79-42b6-b637-fa4e86138ed9', + it('renders', async () => { + const cnx = await getConnatixPlayer({ + 'data-player-id': 'f721b0d8-7a79-42b6-b637-fa4e86138ed9', + }); + const iframe = cnx.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.tagName).to.equal('IFRAME'); + expect(iframe.src).to.equal( + 'https://cds.connatix.com/embed/index.html?playerId=f721b0d8-7a79-42b6-b637-fa4e86138ed9' + ); + expect(iframe.className).to.match(/i-amphtml-fill-content/); }); - const iframe = cnx.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.tagName).to.equal('IFRAME'); - expect(iframe.src).to.equal( - 'https://cds.connatix.com/embed/index.html?playerId=f721b0d8-7a79-42b6-b637-fa4e86138ed9'); - expect(iframe.className).to.match(/i-amphtml-fill-content/); - }); - it('renders with a mediaId', async() => { - const cnx = await getConnatixPlayer({ - 'data-player-id': 'f721b0d8-7a79-42b6-b637-fa4e86138ed9', - 'data-media-id': '527207df-2007-43c4-b87a-f90814bafd2e', + it('renders with a mediaId', async () => { + const cnx = await getConnatixPlayer({ + 'data-player-id': 'f721b0d8-7a79-42b6-b637-fa4e86138ed9', + 'data-media-id': '527207df-2007-43c4-b87a-f90814bafd2e', + }); + const iframe = cnx.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.tagName).to.equal('IFRAME'); + expect(iframe.src).to.equal( + 'https://cds.connatix.com/embed/index.html?playerId=f721b0d8-7a79-42b6-b637-fa4e86138ed9&mediaId=527207df-2007-43c4-b87a-f90814bafd2e' + ); }); - const iframe = cnx.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.tagName).to.equal('IFRAME'); - expect(iframe.src).to.equal( - 'https://cds.connatix.com/embed/index.html?playerId=f721b0d8-7a79-42b6-b637-fa4e86138ed9&mediaId=527207df-2007-43c4-b87a-f90814bafd2e'); - }); - it('fails if no playerId is specified', () => { - return allowConsoleError(() => { return getConnatixPlayer({ - 'data-media-id': '527207df-2007-43c4-b87a-f90814bafd2e', - }).should.eventually.be.rejectedWith( - /The data-player-id attribute is required for/); + it('fails if no playerId is specified', () => { + return allowConsoleError(() => { + return getConnatixPlayer({ + 'data-media-id': '527207df-2007-43c4-b87a-f90814bafd2e', + }).should.eventually.be.rejectedWith( + /The data-player-id attribute is required for/ + ); + }); }); - }); - it('removes iframe after unlayoutCallback', async() => { - const cnx = await getConnatixPlayer({ - 'data-player-id': 'f721b0d8-7a79-42b6-b637-fa4e86138ed9', + it('removes iframe after unlayoutCallback', async () => { + const cnx = await getConnatixPlayer({ + 'data-player-id': 'f721b0d8-7a79-42b6-b637-fa4e86138ed9', + }); + const iframe = cnx.querySelector('iframe'); + expect(iframe).to.not.be.null; + const obj = cnx.implementation_; + obj.unlayoutCallback(); + expect(cnx.querySelector('iframe')).to.be.null; + expect(obj.iframe_).to.be.null; }); - const iframe = cnx.querySelector('iframe'); - expect(iframe).to.not.be.null; - const obj = cnx.implementation_; - obj.unlayoutCallback(); - expect(cnx.querySelector('iframe')).to.be.null; - expect(obj.iframe_).to.be.null; - }); - -}); + } +); diff --git a/extensions/amp-consent/0.1/amp-consent.js b/extensions/amp-consent/0.1/amp-consent.js index 52e1c9adc322..17720370366e 100644 --- a/extensions/amp-consent/0.1/amp-consent.js +++ b/extensions/amp-consent/0.1/amp-consent.js @@ -110,8 +110,10 @@ export class AmpConsent extends AMP.BaseElement { /** @override */ buildCallback() { - userAssert(this.element.getAttribute('id'), - 'amp-consent should have an id'); + userAssert( + this.element.getAttribute('id'), + 'amp-consent should have an id' + ); const config = new ConsentConfig(this.element); @@ -121,8 +123,11 @@ export class AmpConsent extends AMP.BaseElement { this.consentId_ = this.consentConfig_['consentInstanceId']; if (this.consentConfig_['postPromptUI']) { - this.postPromptUI_ = - new ConsentUI(this, dict({}), this.consentConfig_['postPromptUI']); + this.postPromptUI_ = new ConsentUI( + this, + dict({}), + this.consentConfig_['postPromptUI'] + ); } /** @@ -145,7 +150,9 @@ export class AmpConsent extends AMP.BaseElement { const policyConfig = this.consentConfig_['policy'] || dict({}); this.policyConfig_ = expandPolicyConfig( - policyConfig, /** @type {string} */ (this.consentId_)); + policyConfig, + /** @type {string} */ (this.consentId_) + ); const children = this.getRealChildren(); for (let i = 0; i < children.length; i++) { @@ -155,44 +162,47 @@ export class AmpConsent extends AMP.BaseElement { this.setAsOwner(child); } - const consentPolicyManagerPromise = - getServicePromiseForDoc(this.getAmpDoc(), CONSENT_POLICY_MANAGER) - .then(manager => { - this.consentPolicyManager_ = /** @type {!ConsentPolicyManager} */ ( - manager); - this.consentPolicyManager_.setLegacyConsentInstanceId( - /** @type {string} */ (this.consentId_)); - const policyKeys = - Object.keys(/** @type {!Object} */ (this.policyConfig_)); - for (let i = 0; i < policyKeys.length; i++) { - this.consentPolicyManager_.registerConsentPolicyInstance( - policyKeys[i], this.policyConfig_[policyKeys[i]]); - } - }); - - const consentStateManagerPromise = - getServicePromiseForDoc(this.getAmpDoc(), CONSENT_STATE_MANAGER) - .then(manager => { - manager.registerConsentInstance( - this.consentId_, this.consentConfig_); - this.consentStateManager_ = /** @type {!ConsentStateManager} */ ( - manager); - }); - - const notificationUiManagerPromise = - getServicePromiseForDoc(this.getAmpDoc(), NOTIFICATION_UI_MANAGER) - .then(manager => { - this.notificationUiManager_ = /** @type {!NotificationUiManager} */ ( - manager); - }); + const consentPolicyManagerPromise = getServicePromiseForDoc( + this.getAmpDoc(), + CONSENT_POLICY_MANAGER + ).then(manager => { + this.consentPolicyManager_ = /** @type {!ConsentPolicyManager} */ (manager); + this.consentPolicyManager_.setLegacyConsentInstanceId( + /** @type {string} */ (this.consentId_) + ); + const policyKeys = Object.keys( + /** @type {!Object} */ (this.policyConfig_) + ); + for (let i = 0; i < policyKeys.length; i++) { + this.consentPolicyManager_.registerConsentPolicyInstance( + policyKeys[i], + this.policyConfig_[policyKeys[i]] + ); + } + }); + + const consentStateManagerPromise = getServicePromiseForDoc( + this.getAmpDoc(), + CONSENT_STATE_MANAGER + ).then(manager => { + manager.registerConsentInstance(this.consentId_, this.consentConfig_); + this.consentStateManager_ = /** @type {!ConsentStateManager} */ (manager); + }); + + const notificationUiManagerPromise = getServicePromiseForDoc( + this.getAmpDoc(), + NOTIFICATION_UI_MANAGER + ).then(manager => { + this.notificationUiManager_ = /** @type {!NotificationUiManager} */ (manager); + }); Promise.all([ consentStateManagerPromise, notificationUiManagerPromise, - consentPolicyManagerPromise]) - .then(() => { - this.init_(); - }); + consentPolicyManagerPromise, + ]).then(() => { + this.init_(); + }); } /** @@ -238,18 +248,27 @@ export class AmpConsent extends AMP.BaseElement { user().error(TAG, 'consent-response message missing required info'); return; } - if (isExperimentOn(this.win, 'amp-consent-v2') && - data['info'] !== undefined) { + if ( + isExperimentOn(this.win, 'amp-consent-v2') && + data['info'] !== undefined + ) { if (typeof data['info'] != 'string') { - user().error(TAG, 'consent-response info only supports string, ' + - '%s, treated as undefined', data['info']); + user().error( + TAG, + 'consent-response info only supports string, ' + + '%s, treated as undefined', + data['info'] + ); data['info'] = undefined; } if (data['action'] === ACTION_TYPE.DISMISS) { if (data['info']) { - this.user().error(TAG, - 'Consent string value %s not applicable on user dismiss, ' + - 'stored value will be kept and used', consentString); + this.user().error( + TAG, + 'Consent string value %s not applicable on user dismiss, ' + + 'stored value will be kept and used', + consentString + ); } data['info'] = undefined; } @@ -293,7 +312,7 @@ export class AmpConsent extends AMP.BaseElement { this.consentUIPending_ = true; this.notificationUiManager_.registerUI( - this.show_.bind(this, isActionPromptTrigger) + this.show_.bind(this, isActionPromptTrigger) ); } @@ -305,8 +324,7 @@ export class AmpConsent extends AMP.BaseElement { */ show_(isActionPromptTrigger) { if (this.isPromptUIOn_) { - dev().error(TAG, - 'Attempt to show an already displayed prompt UI'); + dev().error(TAG, 'Attempt to show an already displayed prompt UI'); } this.vsync_.mutate(() => { @@ -362,16 +380,19 @@ export class AmpConsent extends AMP.BaseElement { if (action == ACTION_TYPE.ACCEPT) { //accept this.consentStateManager_.updateConsentInstanceState( - CONSENT_ITEM_STATE.ACCEPTED, - consentString); + CONSENT_ITEM_STATE.ACCEPTED, + consentString + ); } else if (action == ACTION_TYPE.REJECT) { // reject this.consentStateManager_.updateConsentInstanceState( - CONSENT_ITEM_STATE.REJECTED, - consentString); + CONSENT_ITEM_STATE.REJECTED, + consentString + ); } else if (action == ACTION_TYPE.DISMISS) { this.consentStateManager_.updateConsentInstanceState( - CONSENT_ITEM_STATE.DISMISSED); + CONSENT_ITEM_STATE.DISMISSED + ); } // Hide current dialog @@ -385,16 +406,19 @@ export class AmpConsent extends AMP.BaseElement { this.passSharedData_(); this.maybeSetDirtyBit_(); - this.getConsentRequiredPromise_().then(isConsentRequired => { - return this.initPromptUI_(isConsentRequired); - }).then(isPostPromptUIRequired => { - if (isPostPromptUIRequired) { - this.handlePostPromptUI_(); - } - this.consentPolicyManager_.enableTimeout(); - }).catch(unusedError => { - // TODO: Handle errors - }); + this.getConsentRequiredPromise_() + .then(isConsentRequired => { + return this.initPromptUI_(isConsentRequired); + }) + .then(isPostPromptUIRequired => { + if (isPostPromptUIRequired) { + this.handlePostPromptUI_(); + } + this.consentPolicyManager_.enableTimeout(); + }) + .catch(unusedError => { + // TODO: Handle errors + }); this.enableInteractions_(); } @@ -405,27 +429,34 @@ export class AmpConsent extends AMP.BaseElement { * @return {!Promise} */ getConsentRequiredPromise_() { - userAssert(this.consentConfig_['checkConsentHref'] || + userAssert( + this.consentConfig_['checkConsentHref'] || this.consentConfig_['promptIfUnknownForGeoGroup'], - 'neither checkConsentHref nor ' + - 'promptIfUnknownForGeoGroup is defined'); + 'neither checkConsentHref nor promptIfUnknownForGeoGroup is defined' + ); let consentRequiredPromise = null; if (this.consentConfig_['promptIfUnknownForGeoGroup']) { const geoGroup = this.consentConfig_['promptIfUnknownForGeoGroup']; consentRequiredPromise = this.isConsentRequiredGeo_(geoGroup); } else { - consentRequiredPromise = - this.getConsentRemote_().then(remoteConfigResponse => { - if (!remoteConfigResponse || - !hasOwn(remoteConfigResponse, 'promptIfUnknown')) { - this.user().error(TAG, 'Expecting promptIfUnknown from ' + + consentRequiredPromise = this.getConsentRemote_().then( + remoteConfigResponse => { + if ( + !remoteConfigResponse || + !hasOwn(remoteConfigResponse, 'promptIfUnknown') + ) { + this.user().error( + TAG, + 'Expecting promptIfUnknown from ' + 'checkConsentHref when promptIfUnknownForGeoGroup is not ' + - 'specified'); - // Set to false if not defined - return false; - } - return !!remoteConfigResponse['promptIfUnknown']; - }); + 'specified' + ); + // Set to false if not defined + return false; + } + return !!remoteConfigResponse['promptIfUnknown']; + } + ); } return consentRequiredPromise.then(required => { return !!required; @@ -466,9 +497,8 @@ export class AmpConsent extends AMP.BaseElement { */ isConsentRequiredGeo_(geoGroup) { return Services.geoForDocOrNull(this.element).then(geo => { - userAssert(geo, - 'requires to use promptIfUnknownForGeoGroup'); - return (geo.isInCountryGroup(geoGroup) == GEO_IN_GROUP.IN); + userAssert(geo, 'requires to use promptIfUnknownForGeoGroup'); + return geo.isInCountryGroup(geoGroup) == GEO_IN_GROUP.IN; }); } @@ -484,8 +514,7 @@ export class AmpConsent extends AMP.BaseElement { if (!this.consentConfig_['checkConsentHref']) { this.remoteConfigPromise_ = Promise.resolve(null); } else { - const storeConsentPromise = - this.consentStateManager_.getLastConsentInstanceInfo(); + const storeConsentPromise = this.consentStateManager_.getLastConsentInstanceInfo(); this.remoteConfigPromise_ = storeConsentPromise.then(storedInfo => { // Note: Expect the request to look different in following versions. const request = /** @type {!JsonObject} */ ({ @@ -503,8 +532,7 @@ export class AmpConsent extends AMP.BaseElement { body: request, requireAmpResponseSourceOrigin: false, }; - const href = - this.consentConfig_['checkConsentHref']; + const href = this.consentConfig_['checkConsentHref']; assertHttpsUrl(href, this.element); const ampdoc = this.getAmpDoc(); const sourceBase = getSourceUrl(ampdoc.getUrl()); @@ -512,8 +540,8 @@ export class AmpConsent extends AMP.BaseElement { const viewer = Services.viewerForDoc(ampdoc); return viewer.whenFirstVisible().then(() => { return Services.xhrFor(this.win) - .fetchJson(resolvedHref, init) - .then(res => res.json()); + .fetchJson(resolvedHref, init) + .then(res => res.json()); }); }); } @@ -526,31 +554,35 @@ export class AmpConsent extends AMP.BaseElement { * @return {Promise} */ initPromptUI_(isConsentRequired) { - this.consentUI_ = new ConsentUI(this, - /** @type {!JsonObject} */ ( - devAssert(this.consentConfig_, 'consent config not found'))); + this.consentUI_ = new ConsentUI( + this, + /** @type {!JsonObject} */ (devAssert( + this.consentConfig_, + 'consent config not found' + )) + ); // Get current consent state - return this.consentStateManager_.getConsentInstanceInfo() - .then(info => { - if (hasStoredValue(info)) { - // Has user stored value, no need to prompt - return true; - } - if (!isConsentRequired) { - // no need to prompt if remote reponse say so - // Also no need to display postPromptUI - this.consentStateManager_.updateConsentInstanceState( - CONSENT_ITEM_STATE.NOT_REQUIRED); - return false; - } - // Prompt - this.scheduleDisplay_(false); - return true; - // TODO(@zhouyx): - // Race condition on consent state change between schedule to - // display and display. Add one more check before display - }); + return this.consentStateManager_.getConsentInstanceInfo().then(info => { + if (hasStoredValue(info)) { + // Has user stored value, no need to prompt + return true; + } + if (!isConsentRequired) { + // no need to prompt if remote reponse say so + // Also no need to display postPromptUI + this.consentStateManager_.updateConsentInstanceState( + CONSENT_ITEM_STATE.NOT_REQUIRED + ); + return false; + } + // Prompt + this.scheduleDisplay_(false); + return true; + // TODO(@zhouyx): + // Race condition on consent state change between schedule to + // display and display. Add one more check before display + }); } /** @@ -577,7 +609,6 @@ export class AmpConsent extends AMP.BaseElement { } } - AMP.extension('amp-consent', '0.1', AMP => { AMP.registerElement('amp-consent', AmpConsent, CSS); AMP.registerServiceForDoc(NOTIFICATION_UI_MANAGER, NotificationUiManager); diff --git a/extensions/amp-consent/0.1/cmps.js b/extensions/amp-consent/0.1/cmps.js index f4e03e4ec8f5..1460e2a1100c 100644 --- a/extensions/amp-consent/0.1/cmps.js +++ b/extensions/amp-consent/0.1/cmps.js @@ -25,7 +25,7 @@ import {getMode} from '../../../src/mode'; * } */ -export const CMP_CONFIG = ({}); +export const CMP_CONFIG = {}; if (getMode().test || getMode().localDev) { CMP_CONFIG['_ping_'] = { diff --git a/extensions/amp-consent/0.1/consent-config.js b/extensions/amp-consent/0.1/consent-config.js index 6de1562210f7..adfb44b6f610 100644 --- a/extensions/amp-consent/0.1/consent-config.js +++ b/extensions/amp-consent/0.1/consent-config.js @@ -32,7 +32,6 @@ const ALLOWED_DEPR_CONSENTINSTANCE_ATTRS = { }; export class ConsentConfig { - /** @param {!Element} element */ constructor(element) { /** @private {!Element} */ @@ -64,8 +63,11 @@ export class ConsentConfig { const consentsConfigDepr = config['consents']; if (!isExperimentOn(this.win_, 'amp-consent-v2')) { userAssert(consentsConfigDepr, '%s: consents config is required', TAG); - userAssert(Object.keys(consentsConfigDepr).length != 0, - '%s: can\'t find consent instance', TAG); + userAssert( + Object.keys(consentsConfigDepr).length != 0, + "%s: can't find consent instance", + TAG + ); } if (!config['consents']) { @@ -75,8 +77,11 @@ export class ConsentConfig { // Assert single consent instance const keys = Object.keys(consentsConfigDepr); - userAssert(keys.length <= 1, - '%s: only single consent instance is supported', TAG); + userAssert( + keys.length <= 1, + '%s: only single consent instance is supported', + TAG + ); if (keys.length > 0) { config['consentInstanceId'] = keys[0]; @@ -108,16 +113,25 @@ export class ConsentConfig { */ validateAndParseConfig_() { const inlineConfig = this.convertInlineConfigFormat_( - /** @type {!JsonObject} */ ( - userAssert(this.getInlineConfig_(), '%s: Inline config not found'))); + /** @type {!JsonObject} */ (userAssert( + this.getInlineConfig_(), + '%s: Inline config not found' + )) + ); const cmpConfig = this.getCMPConfig_(); - const config = /** @type {!JsonObject} */ - (deepMerge(cmpConfig || {}, inlineConfig || {}, 1)); + const config = /** @type {!JsonObject} */ (deepMerge( + cmpConfig || {}, + inlineConfig || {}, + 1 + )); - userAssert(config['consentInstanceId'], - '%s: consentInstanceId to store consent info is required', TAG); + userAssert( + config['consentInstanceId'], + '%s: consentInstanceId to store consent info is required', + TAG + ); if (config['policy']) { // Only respect 'default' consent policy; @@ -125,8 +139,11 @@ export class ConsentConfig { // TODO (@zhouyx): Validate waitFor value for (let i = 0; i < keys.length; i++) { if (keys[i] != 'default') { - user().warn(TAG, 'policy %s is currently not supported ' + - 'and will be ignored', keys[i]); + user().warn( + TAG, + 'policy %s is currently not supported and will be ignored', + keys[i] + ); delete config['policy'][keys[i]]; } } @@ -182,8 +199,11 @@ export class ConsentConfig { * @param {!JsonObject} config */ validateCMPConfig_(config) { - const assertValues = - ['consentInstanceId', 'checkConsentHref', 'promptUISrc']; + const assertValues = [ + 'consentInstanceId', + 'checkConsentHref', + 'promptUISrc', + ]; for (let i = 0; i < assertValues.length; i++) { const attribute = assertValues[i]; devAssert(config[attribute], 'CMP config must specify %s', attribute); diff --git a/extensions/amp-consent/0.1/consent-info.js b/extensions/amp-consent/0.1/consent-info.js index 2cbb834da425..78bb073fca9a 100644 --- a/extensions/amp-consent/0.1/consent-info.js +++ b/extensions/amp-consent/0.1/consent-info.js @@ -18,7 +18,6 @@ import {dev} from '../../../src/log'; import {hasOwn, map} from '../../../src/utils/object'; import {isEnumValue, isObject} from '../../../src/types'; - /** * Key values for retriving/storing consent info object. * STATE: Set when user accept or reject consent. @@ -64,7 +63,10 @@ export let ConsentInfoDef; export function getStoredConsentInfo(value) { if (value === undefined) { return constructConsentInfo( - CONSENT_ITEM_STATE.UNKNOWN, undefined, undefined); + CONSENT_ITEM_STATE.UNKNOWN, + undefined, + undefined + ); } if (typeof value === 'boolean') { // legacy format @@ -75,9 +77,11 @@ export function getStoredConsentInfo(value) { } const consentState = convertValueToState(value[STORAGE_KEY.STATE]); - return constructConsentInfo(consentState, - value[STORAGE_KEY.STRING], - (value[STORAGE_KEY.IS_DIRTY] && value[STORAGE_KEY.IS_DIRTY] === 1)); + return constructConsentInfo( + consentState, + value[STORAGE_KEY.STRING], + value[STORAGE_KEY.IS_DIRTY] && value[STORAGE_KEY.IS_DIRTY] === 1 + ); } /** @@ -104,8 +108,10 @@ export function recalculateConsentStateValue(newState, previousState) { if (!isEnumValue(CONSENT_ITEM_STATE, newState)) { newState = CONSENT_ITEM_STATE.UNKNOWN; } - if (newState == CONSENT_ITEM_STATE.DISMISSED || - newState == CONSENT_ITEM_STATE.UNKNOWN) { + if ( + newState == CONSENT_ITEM_STATE.DISMISSED || + newState == CONSENT_ITEM_STATE.UNKNOWN + ) { return previousState || CONSENT_ITEM_STATE.UNKNOWN; } if (newState == CONSENT_ITEM_STATE.NOT_REQUIRED) { @@ -123,9 +129,11 @@ export function recalculateConsentStateValue(newState, previousState) { * @return {?boolean|Object} */ export function composeStoreValue(consentInfo, opt_forceNew) { - if (!opt_forceNew && - !consentInfo['consentString'] && - consentInfo['isDirty'] === undefined) { + if ( + !opt_forceNew && + !consentInfo['consentString'] && + consentInfo['isDirty'] === undefined + ) { // TODO: Remove after turn on amp-consent-v2 return calculateLegacyStateValue(consentInfo['consentState']); } @@ -183,10 +191,11 @@ export function isConsentInfoStoredValueSame(infoA, infoB, opt_isDirty) { return true; } if (infoA && infoB) { - const stateEqual = calculateLegacyStateValue(infoA['consentState']) === - calculateLegacyStateValue(infoB['consentState']); + const stateEqual = + calculateLegacyStateValue(infoA['consentState']) === + calculateLegacyStateValue(infoB['consentState']); const stringEqual = - ((infoA['consentString'] || '') === (infoB['consentString'] || '')); + (infoA['consentString'] || '') === (infoB['consentString'] || ''); let isDirtyEqual; if (opt_isDirty) { isDirtyEqual = !!infoA['isDirty'] === !!opt_isDirty; @@ -215,8 +224,11 @@ function getLegacyStoredConsentInfo(value) { * @param {boolean=} opt_isDirty * @return {!ConsentInfoDef} */ -export function constructConsentInfo(consentState, - opt_consentString, opt_isDirty) { +export function constructConsentInfo( + consentState, + opt_consentString, + opt_isDirty +) { return { 'consentState': consentState, 'consentString': opt_consentString, @@ -246,8 +258,10 @@ export function hasStoredValue(info) { if (info['consentString']) { return true; } - return info['consentState'] === CONSENT_ITEM_STATE.ACCEPTED || - info['consentState'] === CONSENT_ITEM_STATE.REJECTED; + return ( + info['consentState'] === CONSENT_ITEM_STATE.ACCEPTED || + info['consentState'] === CONSENT_ITEM_STATE.REJECTED + ); } /** diff --git a/extensions/amp-consent/0.1/consent-policy-manager.js b/extensions/amp-consent/0.1/consent-policy-manager.js index f11345efc53a..3bc528fff3eb 100644 --- a/extensions/amp-consent/0.1/consent-policy-manager.js +++ b/extensions/amp-consent/0.1/consent-policy-manager.js @@ -24,8 +24,6 @@ import {isFiniteNumber, isObject} from '../../../src/types'; import {map} from '../../../src/utils/object'; import {user, userAssert} from '../../../src/log'; - - const CONSENT_STATE_MANAGER = 'consentStateManager'; const TAG = 'consent-policy-manager'; @@ -36,7 +34,6 @@ const WHITELIST_POLICY = { '_auto_reject': true, }; - export class ConsentPolicyManager { /** * Creates an instance of ConsentPolicyManager. @@ -53,8 +50,10 @@ export class ConsentPolicyManager { this.instances_ = map(); /** @private {!Promise} */ - this.ConsentStateManagerPromise_ = - getServicePromiseForDoc(this.ampdoc_, CONSENT_STATE_MANAGER); + this.ConsentStateManagerPromise_ = getServicePromiseForDoc( + this.ampdoc_, + CONSENT_STATE_MANAGER + ); /** @private {!Deferred} */ this.consentPromptInitiated_ = new Deferred(); @@ -127,8 +126,10 @@ export class ConsentPolicyManager { const waitFor = Object.keys(config['waitFor'] || {}); if (waitFor.length !== 1 || waitFor[0] !== this.consentInstanceIdDepr_) { - user().error(TAG, - 'invalid waitFor value, consent policy will never resolve'); + user().error( + TAG, + 'invalid waitFor value, consent policy will never resolve' + ); return; } @@ -198,8 +199,8 @@ export class ConsentPolicyManager { if (state == CONSENT_ITEM_STATE.NOT_REQUIRED) { const shouldOverwrite = - this.consentState_ != CONSENT_ITEM_STATE.ACCEPTED && - this.consentState_ != CONSENT_ITEM_STATE.REJECTED; + this.consentState_ != CONSENT_ITEM_STATE.ACCEPTED && + this.consentState_ != CONSENT_ITEM_STATE.REJECTED; // Ignore the consent item state and overwrite state value. if (shouldOverwrite) { this.consentState_ = CONSENT_ITEM_STATE.NOT_REQUIRED; @@ -225,8 +226,11 @@ export class ConsentPolicyManager { whenPolicyResolved(policyId) { // If customized policy is not supported if (!WHITELIST_POLICY[policyId]) { - user().error(TAG, 'can not find policy %s, ' + - 'only predefined policies are supported', policyId); + user().error( + TAG, + 'can not find policy %s, only predefined policies are supported', + policyId + ); return Promise.resolve(CONSENT_POLICY_STATE.UNKNOWN); } return this.whenPolicyInstanceRegistered_(policyId).then(() => { @@ -244,8 +248,11 @@ export class ConsentPolicyManager { whenPolicyUnblock(policyId) { // If customized policy is not supported if (!WHITELIST_POLICY[policyId]) { - user().error(TAG, 'can not find policy %s, ' + - 'only predefined policies are supported', policyId); + user().error( + TAG, + 'can not find policy %s, only predefined policies are supported', + policyId + ); return Promise.resolve(false); } return this.whenPolicyInstanceRegistered_(policyId).then(() => { @@ -266,10 +273,10 @@ export class ConsentPolicyManager { */ getMergedSharedData(policyId) { return this.whenPolicyResolved(policyId) - .then(() => this.ConsentStateManagerPromise_) - .then(manager => { - return manager.getConsentInstanceSharedData(); - }); + .then(() => this.ConsentStateManagerPromise_) + .then(manager => { + return manager.getConsentInstanceSharedData(); + }); } /** @@ -296,8 +303,8 @@ export class ConsentPolicyManager { if (!this.policyInstancesDeferred_[policyId]) { this.policyInstancesDeferred_[policyId] = new Deferred(); } - return /** @type {!Promise} */ ( - this.policyInstancesDeferred_[policyId].promise); + return /** @type {!Promise} */ (this.policyInstancesDeferred_[policyId] + .promise); } } @@ -322,9 +329,10 @@ export class ConsentPolicyInstance { this.status_ = CONSENT_POLICY_STATE.UNKNOWN; /** @private {!Array} */ - this.unblockStateLists_ = config['unblockOn'] || - [CONSENT_POLICY_STATE.SUFFICIENT, - CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED]; + this.unblockStateLists_ = config['unblockOn'] || [ + CONSENT_POLICY_STATE.SUFFICIENT, + CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED, + ]; } /** @@ -345,20 +353,30 @@ export class ConsentPolicyInstance { * "fallbackAction": "reject" * } */ - if (timeoutConfig['fallbackAction'] && - timeoutConfig['fallbackAction'] == 'reject') { + if ( + timeoutConfig['fallbackAction'] && + timeoutConfig['fallbackAction'] == 'reject' + ) { fallbackState = CONSENT_ITEM_STATE.REJECTED; - } else if (timeoutConfig['fallbackAction'] && - timeoutConfig['fallbackAction'] != 'dismiss') { - user().error(TAG, 'unsupported fallbackAction %s', - timeoutConfig['fallbackAction']); + } else if ( + timeoutConfig['fallbackAction'] && + timeoutConfig['fallbackAction'] != 'dismiss' + ) { + user().error( + TAG, + 'unsupported fallbackAction %s', + timeoutConfig['fallbackAction'] + ); } timeoutSecond = timeoutConfig['seconds']; } else { timeoutSecond = timeoutConfig; } - userAssert(isFiniteNumber(timeoutSecond), - 'invalid timeout value %s', timeoutSecond); + userAssert( + isFiniteNumber(timeoutSecond), + 'invalid timeout value %s', + timeoutSecond + ); } if (timeoutSecond != null) { @@ -368,7 +386,6 @@ export class ConsentPolicyInstance { this.evaluate(fallbackState, true); }, timeoutSecond * 1000); } - } /** @@ -427,6 +444,6 @@ export class ConsentPolicyInstance { * @return {boolean} */ shouldUnblock() { - return (this.unblockStateLists_.indexOf(this.status_) > -1); + return this.unblockStateLists_.indexOf(this.status_) > -1; } } diff --git a/extensions/amp-consent/0.1/consent-state-manager.js b/extensions/amp-consent/0.1/consent-state-manager.js index 2e70431b1ab0..2a9bfb71a32a 100644 --- a/extensions/amp-consent/0.1/consent-state-manager.js +++ b/extensions/amp-consent/0.1/consent-state-manager.js @@ -32,7 +32,6 @@ import {assertHttpsUrl} from '../../../src/url'; import {dev, devAssert, user} from '../../../src/log'; import {isExperimentOn} from '../../../src/experiments'; - const TAG = 'CONSENT-STATE-MANAGER'; const CID_SCOPE = 'AMP-CONSENT'; @@ -68,9 +67,13 @@ export class ConsentStateManager { */ registerConsentInstance(instanceId, config) { if (this.instance_) { - dev().error(TAG, 'Cannot register consent instance %s, ' + + dev().error( + TAG, + 'Cannot register consent instance %s, ' + 'instance %s has already been registered.', - instanceId, this.instanceId_); + instanceId, + this.instanceId_ + ); return; } @@ -107,8 +110,7 @@ export class ConsentStateManager { * @return {Promise} */ getLastConsentInstanceInfo() { - devAssert(this.instance_, - '%s: cannot find the instance', TAG); + devAssert(this.instance_, '%s: cannot find the instance', TAG); return this.instance_.get(); } @@ -117,8 +119,7 @@ export class ConsentStateManager { * @return {Promise} */ getConsentInstanceInfo() { - devAssert(this.instance_, - '%s: cannot find the instance', TAG); + devAssert(this.instance_, '%s: cannot find the instance', TAG); return this.instance_.get().then(info => { if (hasDirtyBit(info)) { return constructConsentInfo(CONSENT_ITEM_STATE.UNKNOWN); @@ -132,11 +133,13 @@ export class ConsentStateManager { * @param {function(!ConsentInfoDef)} handler */ onConsentStateChange(handler) { - devAssert(this.instance_, - '%s: cannot find the instance', TAG); + devAssert(this.instance_, '%s: cannot find the instance', TAG); - devAssert(!this.consentChangeHandler_, - '%s: Duplicate consent change handler, will be ignored', TAG); + devAssert( + !this.consentChangeHandler_, + '%s: Duplicate consent change handler, will be ignored', + TAG + ); this.consentChangeHandler_ = handler; @@ -146,7 +149,6 @@ export class ConsentStateManager { }); } - /** * Sets a promise which resolves to a shareData object that is to be returned * from the remote endpoint. @@ -154,8 +156,7 @@ export class ConsentStateManager { * @param {Promise} sharedDataPromise */ setConsentInstanceSharedData(sharedDataPromise) { - devAssert(this.instance_, - '%s: cannot find the instance', TAG); + devAssert(this.instance_, '%s: cannot find the instance', TAG); this.instance_.sharedDataPromise = sharedDataPromise; } @@ -174,8 +175,7 @@ export class ConsentStateManager { * @return {?Promise} */ getConsentInstanceSharedData() { - devAssert(this.instance_, - '%s: cannot find the instance', TAG); + devAssert(this.instance_, '%s: cannot find the instance', TAG); return this.instance_.sharedDataPromise; } @@ -210,8 +210,10 @@ export class ConsentInstance { this.ampdoc_ = ampdoc; /** @private {boolean} */ - this.isAmpConsentV2ExperimentOn_ = - isExperimentOn(ampdoc.win, 'amp-consent-v2'); + this.isAmpConsentV2ExperimentOn_ = isExperimentOn( + ampdoc.win, + 'amp-consent-v2' + ); /** @private {string} */ this.id_ = id; @@ -267,16 +269,17 @@ export class ConsentInstance { */ update(state, consentString, opt_systemUpdate) { const localState = - this.localConsentInfo_ && this.localConsentInfo_['consentState']; + this.localConsentInfo_ && this.localConsentInfo_['consentState']; const localConsentStr = - this.localConsentInfo_ && this.localConsentInfo_['consentString']; - const calculatedState = - recalculateConsentStateValue(state, localState); + this.localConsentInfo_ && this.localConsentInfo_['consentString']; + const calculatedState = recalculateConsentStateValue(state, localState); if (state === CONSENT_ITEM_STATE.DISMISSED) { // If state is dismissed, use the old consent string. - this.localConsentInfo_ = - constructConsentInfo(calculatedState, localConsentStr); + this.localConsentInfo_ = constructConsentInfo( + calculatedState, + localConsentStr + ); return; } @@ -285,16 +288,24 @@ export class ConsentInstance { const oldValue = this.localConsentInfo_; if (opt_systemUpdate && hasDirtyBit(oldValue)) { this.localConsentInfo_ = constructConsentInfo( - calculatedState, consentString, true); + calculatedState, + consentString, + true + ); } else { // Any user update makes the current state valid, thus remove dirtyBit // from localConsentInfo_ this.localConsentInfo_ = constructConsentInfo( - calculatedState, consentString); + calculatedState, + consentString + ); } const newConsentInfo = constructConsentInfo( - calculatedState, consentString, this.hasDirtyBitNext_); + calculatedState, + consentString, + this.hasDirtyBitNext_ + ); if (isConsentInfoStoredValueSame(newConsentInfo, this.savedConsentInfo_)) { // Only update/save to localstorage if it's not dismiss @@ -312,8 +323,13 @@ export class ConsentInstance { */ updateStoredValue_(consentInfo) { this.storagePromise_.then(storage => { - if (!isConsentInfoStoredValueSame( - consentInfo, this.localConsentInfo_, this.hasDirtyBitNext_)) { + if ( + !isConsentInfoStoredValueSame( + consentInfo, + this.localConsentInfo_, + this.hasDirtyBitNext_ + ) + ) { // If state has changed. do not store outdated value. return; } @@ -323,9 +339,11 @@ export class ConsentInstance { // Verify the length of consentString. // 150 * 2 (utf8Encode) * 4/3 (base64) = 400 bytes. // TODO: Need utf8Encode if necessary. - user().error(TAG, - 'Cannot store consentString which length exceeds 150 ' + - 'Previous stored consentInfo will be cleared'); + user().error( + TAG, + 'Cannot store consentString which length exceeds 150 ' + + 'Previous stored consentInfo will be cleared' + ); // If new consentInfo value cannot be stored, need to remove previous // value storage.remove(this.storageKey_); @@ -334,7 +352,9 @@ export class ConsentInstance { } const value = composeStoreValue( - consentInfo, this.isAmpConsentV2ExperimentOn_); + consentInfo, + this.isAmpConsentV2ExperimentOn_ + ); if (value == null) { // Value can be false, do not use !value check // Nothing to store to localStorage @@ -357,34 +377,38 @@ export class ConsentInstance { } let storage; - return this.storagePromise_.then(s => { - storage = s; - return storage.get(this.storageKey_); - }).then(storedValue => { - if (this.localConsentInfo_) { - // If local value has been updated, return most updated value; + return this.storagePromise_ + .then(s => { + storage = s; + return storage.get(this.storageKey_); + }) + .then(storedValue => { + if (this.localConsentInfo_) { + // If local value has been updated, return most updated value; + return this.localConsentInfo_; + } + + const consentInfo = getStoredConsentInfo(storedValue); + this.savedConsentInfo_ = consentInfo; + + if (hasDirtyBit(consentInfo)) { + // clear stored value. + this.sendUpdateHrefRequest_( + constructConsentInfo(CONSENT_ITEM_STATE.UNKNOWN) + ); + storage.remove(this.storageKey_); + this.savedConsentInfo_ = null; + } + // Note: this.localConsentInfo dirtyBit can only be set to false + // if the stored value has dirtyBit. + // Any local update reset the value to true. + this.localConsentInfo_ = consentInfo; return this.localConsentInfo_; - } - - const consentInfo = getStoredConsentInfo(storedValue); - this.savedConsentInfo_ = consentInfo; - - if (hasDirtyBit(consentInfo)) { - // clear stored value. - this.sendUpdateHrefRequest_( - constructConsentInfo(CONSENT_ITEM_STATE.UNKNOWN)); - storage.remove(this.storageKey_); - this.savedConsentInfo_ = null; - } - // Note: this.localConsentInfo dirtyBit can only be set to false - // if the stored value has dirtyBit. - // Any local update reset the value to true. - this.localConsentInfo_ = consentInfo; - return this.localConsentInfo_; - }).catch(e => { - dev().error(TAG, 'Failed to read storage', e); - return constructConsentInfo(CONSENT_ITEM_STATE.UNKNOWN); - }); + }) + .catch(e => { + dev().error(TAG, 'Failed to read storage', e); + return constructConsentInfo(CONSENT_ITEM_STATE.UNKNOWN); + }); } /** @@ -400,11 +424,14 @@ export class ConsentInstance { // No need to send update request if the stored consent info is dirty return; } - const legacyConsentState = - calculateLegacyStateValue(consentInfo['consentState']); + const legacyConsentState = calculateLegacyStateValue( + consentInfo['consentState'] + ); const cidPromise = Services.cidForDoc(this.ampdoc_).then(cid => { - return cid.get({scope: CID_SCOPE, createCookieIfNotPresent: true}, - Promise.resolve()); + return cid.get( + {scope: CID_SCOPE, createCookieIfNotPresent: true}, + Promise.resolve() + ); }); cidPromise.then(userId => { const request = /** @type {!JsonObject} */ ({ @@ -415,8 +442,9 @@ export class ConsentInstance { if (legacyConsentState != null) { request['consentState'] = legacyConsentState; } - request['consentStateValue'] = - getConsentStateValue(consentInfo['consentState']); + request['consentStateValue'] = getConsentStateValue( + consentInfo['consentState'] + ); if (consentInfo['consentString']) { request['consentString'] = consentInfo['consentString']; } @@ -426,10 +454,14 @@ export class ConsentInstance { body: request, ampCors: false, }; - Services.viewerForDoc(this.ampdoc_).whenFirstVisible().then(() => { - Services.xhrFor(this.ampdoc_.win).fetchJson( - /** @type {string} */ (this.onUpdateHref_), init); - }); + Services.viewerForDoc(this.ampdoc_) + .whenFirstVisible() + .then(() => { + Services.xhrFor(this.ampdoc_.win).fetchJson( + /** @type {string} */ (this.onUpdateHref_), + init + ); + }); }); } } diff --git a/extensions/amp-consent/0.1/consent-ui.js b/extensions/amp-consent/0.1/consent-ui.js index b1e2c5b1e250..20b27267496b 100644 --- a/extensions/amp-consent/0.1/consent-ui.js +++ b/extensions/amp-consent/0.1/consent-ui.js @@ -16,9 +16,7 @@ import {Deferred} from '../../../src/utils/promise'; import {Services} from '../../../src/services'; -import { - assertHttpsUrl, -} from '../../../src/url'; +import {assertHttpsUrl} from '../../../src/url'; import {dev, user} from '../../../src/log'; import {dict} from '../../../src/utils/object'; import { @@ -52,14 +50,12 @@ export const consentUiClasses = { }; export class ConsentUI { - /** * @param {!AMP.BaseElement} baseInstance * @param {!JsonObject} config * @param {string=} opt_postPromptUI */ constructor(baseInstance, config, opt_postPromptUI) { - /** @private {!AMP.BaseElement} */ this.baseInstance_ = baseInstance; @@ -82,7 +78,8 @@ export class ConsentUI { this.ui_ = null; /** @private {boolean} */ - this.overlayEnabled_ = isExperimentOn(baseInstance.win, 'amp-consent-v2') && + this.overlayEnabled_ = + isExperimentOn(baseInstance.win, 'amp-consent-v2') && config['uiConfig'] && config['uiConfig']['overlay'] === true; @@ -137,11 +134,13 @@ export class ConsentUI { */ init_(config, opt_postPromptUI) { if (opt_postPromptUI) { - const postPromptUI = - this.ampdoc_.getElementById(opt_postPromptUI); + const postPromptUI = this.ampdoc_.getElementById(opt_postPromptUI); if (!postPromptUI) { - user().error(TAG, 'postPromptUI element with ' + - 'id=%s not found', opt_postPromptUI); + user().error( + TAG, + 'postPromptUI element with id=%s not found', + opt_postPromptUI + ); } this.ui_ = dev().assertElement(postPromptUI); this.isPostPrompt_ = true; @@ -153,15 +152,17 @@ export class ConsentUI { // Always respect promptUI first const promptElement = this.ampdoc_.getElementById(promptUI); if (!promptElement || !this.parent_.contains(promptElement)) { - user().error(TAG, 'child element of with ' + - 'promptUI id %s not found', promptUI); + user().error( + TAG, + 'child element of with promptUI id %s not found', + promptUI + ); } this.ui_ = dev().assertElement(promptElement); } else if (promptUISrc && isExperimentOn(this.win_, 'amp-consent-v2')) { // Create an iframe element with the provided src this.isCreatedIframe_ = true; - this.ui_ = - this.createPromptIframeFromSrc_(promptUISrc); + this.ui_ = this.createPromptIframeFromSrc_(promptUISrc); this.placeholder_ = this.createPlaceholder_(); this.clientConfig_ = config['clientConfig'] || null; } @@ -189,7 +190,6 @@ export class ConsentUI { // being hidden. CMP iframe is responsible to call consent-iframe-ready // API before consent-response API. this.baseInstance_.mutateElement(() => { - if (!this.isPostPrompt_) { this.elementWithFocusBeforeShowing_ = this.document_.activeElement; } @@ -199,7 +199,7 @@ export class ConsentUI { this.showIframe_(); if (!this.isPostPrompt_) { - this.ui_./*OK*/focus(); + this.ui_./*OK*/ focus(); } }); }); @@ -212,7 +212,6 @@ export class ConsentUI { toggle(this.ui_, true); if (!this.isPostPrompt_) { - this.elementWithFocusBeforeShowing_ = this.document_.activeElement; this.maybeShowOverlay_(); @@ -222,16 +221,14 @@ export class ConsentUI { // for example this.baseInstance_.scheduleLayout(this.ui_); - this.ui_./*OK*/focus(); + this.ui_./*OK*/ focus(); } }; // If the UI is an AMP Element, wait until it's built before showing it, // to avoid race conditions where the UI would be hidden by the runtime // at build time. (see #18841). - isAmpElement(this.ui_) ? - this.ui_.whenBuilt().then(() => show()) : - show(); + isAmpElement(this.ui_) ? this.ui_.whenBuilt().then(() => show()) : show(); } this.isVisible_ = true; @@ -241,7 +238,6 @@ export class ConsentUI { * Hide the UI */ hide() { - if (!this.ui_) { // Nothing to hide from; return; @@ -276,12 +272,12 @@ export class ConsentUI { this.isVisible_ = false; if (this.elementWithFocusBeforeShowing_) { - this.elementWithFocusBeforeShowing_./*OK*/focus(); + this.elementWithFocusBeforeShowing_./*OK*/ focus(); this.elementWithFocusBeforeShowing_ = null; } else if (this.win_.document.body.children.length > 0) { // TODO (torch2424): Find if the first child can not be // focusable due to styling. - this.win_.document.body.children[0]./*OK*/focus(); + this.win_.document.body.children[0]./*OK*/ focus(); } }); } @@ -291,31 +287,31 @@ export class ConsentUI { * @param {!JsonObject} data */ handleReady_(data) { - this.initialHeight_ = DEFAULT_INITIAL_HEIGHT; this.enableBorder_ = DEFAULT_ENABLE_BORDER; // Set our initial height if (data['initialHeight']) { - if (typeof data['initialHeight'] === 'string' && - data['initialHeight'].indexOf('vh') >= 0) { - + if ( + typeof data['initialHeight'] === 'string' && + data['initialHeight'].indexOf('vh') >= 0 + ) { const dataHeight = parseInt(data['initialHeight'], 10); if (dataHeight >= 10 && dataHeight <= 60) { this.initialHeight_ = `${dataHeight}vh`; } else { user().error( - TAG, - `Inavlid initial height: ${data['initialHeight']}.` + - 'Minimum: 10vh. Maximum: 60vh.' + TAG, + `Inavlid initial height: ${data['initialHeight']}.` + + 'Minimum: 10vh. Maximum: 60vh.' ); } } else { user().error( - TAG, - `Inavlid initial height: ${data['initialHeight']}.` + - 'Must be a string in "vh" units.' + TAG, + `Inavlid initial height: ${data['initialHeight']}.` + + 'Must be a string in "vh" units.' ); } } @@ -411,22 +407,26 @@ export class ConsentUI { * @return {!Promise} */ getClientInfoPromise_(isActionPromptTrigger) { - const consentStatePromise = - getServicePromiseForDoc(this.ampdoc_, CONSENT_STATE_MANAGER); + const consentStatePromise = getServicePromiseForDoc( + this.ampdoc_, + CONSENT_STATE_MANAGER + ); return consentStatePromise.then(consentStateManager => { - return consentStateManager.getLastConsentInstanceInfo().then( - consentInfo => { - return dict({ - 'clientConfig': this.clientConfig_, - // consentState to be deprecated - 'consentState': getConsentStateValue(consentInfo['consentState']), - 'consentStateValue': - getConsentStateValue(consentInfo['consentState']), - 'consentString': consentInfo['consentString'], - 'promptTrigger': isActionPromptTrigger ? 'action' : 'load', - 'isDirty': !!consentInfo['isDirty'], - }); + return consentStateManager + .getLastConsentInstanceInfo() + .then(consentInfo => { + return dict({ + 'clientConfig': this.clientConfig_, + // consentState to be deprecated + 'consentState': getConsentStateValue(consentInfo['consentState']), + 'consentStateValue': getConsentStateValue( + consentInfo['consentState'] + ), + 'consentString': consentInfo['consentString'], + 'promptTrigger': isActionPromptTrigger ? 'action' : 'load', + 'isDirty': !!consentInfo['isDirty'], }); + }); }); } @@ -440,28 +440,22 @@ export class ConsentUI { this.iframeReady_ = new Deferred(); const {classList} = this.parent_; if (!elementByTag(this.parent_, 'placeholder')) { - insertAfterOrAtStart(this.parent_, - dev().assertElement(this.placeholder_), null); + insertAfterOrAtStart( + this.parent_, + dev().assertElement(this.placeholder_), + null + ); } classList.add(consentUiClasses.loading); toggle(dev().assertElement(this.ui_), false); - const iframePromise = this.getClientInfoPromise_(isActionPromptTrigger) - .then(clientInfo => { - this.ui_.setAttribute( - 'name', - JSON.stringify(clientInfo) - ); - this.win_.addEventListener( - 'message', - this.boundHandleIframeMessages_ - ); - insertAfterOrAtStart( - this.parent_, - dev().assertElement(this.ui_), - null - ); - }); + const iframePromise = this.getClientInfoPromise_( + isActionPromptTrigger + ).then(clientInfo => { + this.ui_.setAttribute('name', JSON.stringify(clientInfo)); + this.win_.addEventListener('message', this.boundHandleIframeMessages_); + insertAfterOrAtStart(this.parent_, dev().assertElement(this.ui_), null); + }); return Promise.all([ iframePromise, @@ -571,7 +565,7 @@ export class ConsentUI { this.parent_.ownerDocument.body.appendChild(mask); this.maskElement_ = mask; } - toggle(this.maskElement_, /* display */true); + toggle(this.maskElement_, /* display */ true); this.disableScroll_(); } @@ -586,7 +580,7 @@ export class ConsentUI { } if (this.maskElement_) { - toggle(this.maskElement_, /* display */false); + toggle(this.maskElement_, /* display */ false); } this.enableScroll_(); } @@ -645,11 +639,10 @@ export class ConsentUI { } if (data['action'] === 'ready') { - this.handleReady_(/** @type {!JsonObject} */(data)); + this.handleReady_(/** @type {!JsonObject} */ (data)); } if (data['action'] === 'enter-fullscreen') { - // TODO (@torch2424) Send response back if enter fullscreen was succesful if (!this.isIframeVisible_) { return; diff --git a/extensions/amp-consent/0.1/test/test-amp-consent.js b/extensions/amp-consent/0.1/test/test-amp-consent.js index 42ef617ff138..4568cdfecf1e 100644 --- a/extensions/amp-consent/0.1/test/test-amp-consent.js +++ b/extensions/amp-consent/0.1/test/test-amp-consent.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - ACTION_TYPE, - AmpConsent, -} from '../amp-consent'; +import {ACTION_TYPE, AmpConsent} from '../amp-consent'; import {CONSENT_ITEM_STATE} from '../consent-info'; import {GEO_IN_GROUP} from '../../../amp-geo/0.1/amp-geo-in-group'; import {dict} from '../../../../src/utils/object'; @@ -28,489 +25,510 @@ import { } from '../../../../src/service'; import {toggleExperiment} from '../../../../src/experiments'; -describes.realWin('amp-consent', { - amp: { - extensions: ['amp-consent'], - ampdoc: 'single', +describes.realWin( + 'amp-consent', + { + amp: { + extensions: ['amp-consent'], + ampdoc: 'single', + }, }, -}, env => { - let win; - let doc; - let ampdoc; - let jsonMockResponses; - let storageValue; - let requestBody; - let ISOCountryGroups; - let xhrServiceMock; - - beforeEach(() => { - doc = env.win.document; - ampdoc = env.ampdoc; - win = env.win; - toggleExperiment(win, 'amp-consent-v2', true); - - - storageValue = {}; - jsonMockResponses = { - 'https://response1/': '{"promptIfUnknown": true}', - 'https://response2/': '{}', - 'https://response3/': '{"promptIfUnknown": false}', - }; - - xhrServiceMock = {fetchJson: (url, init) => { - requestBody = init.body; - expect(init.credentials).to.equal('include'); - expect(init.method).to.equal('POST'); - return Promise.resolve({ - json() { - return Promise.resolve(JSON.parse(jsonMockResponses[url])); + env => { + let win; + let doc; + let ampdoc; + let jsonMockResponses; + let storageValue; + let requestBody; + let ISOCountryGroups; + let xhrServiceMock; + + beforeEach(() => { + doc = env.win.document; + ampdoc = env.ampdoc; + win = env.win; + toggleExperiment(win, 'amp-consent-v2', true); + + storageValue = {}; + jsonMockResponses = { + 'https://response1/': '{"promptIfUnknown": true}', + 'https://response2/': '{}', + 'https://response3/': '{"promptIfUnknown": false}', + }; + + xhrServiceMock = { + fetchJson: (url, init) => { + requestBody = init.body; + expect(init.credentials).to.equal('include'); + expect(init.method).to.equal('POST'); + return Promise.resolve({ + json() { + return Promise.resolve(JSON.parse(jsonMockResponses[url])); + }, + }); }, + }; + + resetServiceForTesting(win, 'xhr'); + registerServiceBuilder(win, 'xhr', function() { + return xhrServiceMock; }); - }}; - resetServiceForTesting(win, 'xhr'); - registerServiceBuilder(win, 'xhr', function() { - return xhrServiceMock; - }); + resetServiceForTesting(win, 'geo'); + registerServiceBuilder(win, 'geo', function() { + return Promise.resolve({ + isInCountryGroup: group => + ISOCountryGroups.indexOf(group) >= 0 + ? GEO_IN_GROUP.IN + : GEO_IN_GROUP.NOT_IN, + }); + }); - resetServiceForTesting(win, 'geo'); - registerServiceBuilder(win, 'geo', function() { - return Promise.resolve({ - isInCountryGroup: group => - ISOCountryGroups.indexOf(group) >= 0 ? - GEO_IN_GROUP.IN : GEO_IN_GROUP.NOT_IN, + resetServiceForTesting(win, 'storage'); + registerServiceBuilder(win, 'storage', function() { + return Promise.resolve({ + get: name => { + return Promise.resolve(storageValue[name]); + }, + set: (name, value) => { + storageValue[name] = value; + return Promise.resolve(); + }, + }); }); }); - resetServiceForTesting(win, 'storage'); - registerServiceBuilder(win, 'storage', function() { - return Promise.resolve({ - get: name => { - return Promise.resolve(storageValue[name]); - }, - set: (name, value) => { - storageValue[name] = value; - return Promise.resolve(); - }, + describe('amp-consent', () => { + describe('consent config', () => { + let consentElement; + + it('get consent/policy/postPromptUI config', () => { + consentElement = createConsentElement( + doc, + dict({ + 'consents': { + 'test': { + 'checkConsentHref': '/override', + }, + }, + 'clientConfig': { + 'test': 'ABC', + }, + 'postPromptUI': 'test', + }) + ); + const postPromptUI = document.createElement('div'); + postPromptUI.setAttribute('id', 'test'); + consentElement.appendChild(postPromptUI); + doc.body.appendChild(consentElement); + const ampConsent = new AmpConsent(consentElement); + ampConsent.buildCallback(); + + expect(ampConsent.postPromptUI_).to.not.be.null; + expect(ampConsent.consentId_).to.equal('test'); + expect(ampConsent.consentConfig_).to.deep.equal( + dict({ + 'consentInstanceId': 'test', + 'checkConsentHref': '/override', + 'postPromptUI': 'test', + 'clientConfig': { + 'test': 'ABC', + }, + }) + ); + + expect(Object.keys(ampConsent.policyConfig_)).to.have.length(4); + expect(ampConsent.policyConfig_['default']).to.be.ok; + expect(ampConsent.policyConfig_['_till_responded']).to.be.ok; + expect(ampConsent.policyConfig_['_till_accepted']).to.be.ok; + expect(ampConsent.policyConfig_['_auto_reject']).to.be.ok; + }); + + it('relative checkConsentHref is resolved', function*() { + const fetchSpy = sandbox.spy(xhrServiceMock, 'fetchJson'); + consentElement = createConsentElement( + doc, + dict({ + 'consents': { + 'XYZ': { + 'checkConsentHref': '/r/1', + }, + }, + }) + ); + const ampConsent = new AmpConsent(consentElement); + doc.body.appendChild(consentElement); + const getUrlStub = sandbox.stub(ampdoc, 'getUrl'); + // return a cache Url to test origin source being used to resolve. + getUrlStub.callsFake(() => { + return 'https://cdn.ampproject.org/v/www.origin.com/foo/?f=0#h'; + }); + ampConsent.buildCallback(); + yield macroTask(); + expect(fetchSpy).to.be.calledOnce; + expect(win.testLocation.origin).not.to.be.empty; + expect(fetchSpy).to.be.calledWith('http://www.origin.com/r/1'); + }); }); }); - }); - describe('amp-consent', () => { - describe('consent config', () => { + describe('server communication', () => { + let defaultConfig; + let ampConsent; let consentElement; - - it('get consent/policy/postPromptUI config', () => { - consentElement = createConsentElement(doc, dict({ + beforeEach(() => { + defaultConfig = dict({ 'consents': { - 'test': { - 'checkConsentHref': '/override', + 'ABC': { + 'checkConsentHref': 'https://response1', }, }, - 'clientConfig': { - 'test': 'ABC', - }, - 'postPromptUI': 'test', - })); - const postPromptUI = document.createElement('div'); - postPromptUI.setAttribute('id', 'test'); - consentElement.appendChild(postPromptUI); + }); + consentElement = createConsentElement(doc, defaultConfig); doc.body.appendChild(consentElement); - const ampConsent = new AmpConsent(consentElement); - ampConsent.buildCallback(); + ampConsent = new AmpConsent(consentElement); + }); - expect(ampConsent.postPromptUI_).to.not.be.null; - expect(ampConsent.consentId_).to.equal('test'); - expect(ampConsent.consentConfig_).to.deep.equal(dict({ - 'consentInstanceId': 'test', - 'checkConsentHref': '/override', - 'postPromptUI': 'test', - 'clientConfig': { - 'test': 'ABC', - }, - })); + it('send post request to server', function*() { + ampConsent.buildCallback(); + yield macroTask(); + expect(requestBody).to.deep.equal({ + 'consentInstanceId': 'ABC', + 'consentStateValue': 'unknown', + 'consentString': undefined, + 'isDirty': false, + }); + }); - expect(Object.keys(ampConsent.policyConfig_)).to.have.length(4); - expect(ampConsent.policyConfig_['default']).to.be.ok; - expect(ampConsent.policyConfig_['_till_responded']).to.be.ok; - expect(ampConsent.policyConfig_['_till_accepted']).to.be.ok; - expect(ampConsent.policyConfig_['_auto_reject']).to.be.ok; + it('read promptIfUnknown from server response', function*() { + ampConsent.buildCallback(); + yield macroTask(); + return ampConsent.getConsentRequiredPromise_().then(isRequired => { + expect(isRequired).to.be.true; + }); }); + }); - it('relative checkConsentHref is resolved', function* () { - const fetchSpy = sandbox.spy(xhrServiceMock, 'fetchJson'); - consentElement = createConsentElement(doc, dict({ + describe('amp-geo integration', () => { + let defaultConfig; + let ampConsent; + let consentElement; + beforeEach(() => { + defaultConfig = dict({ 'consents': { - 'XYZ': { - 'checkConsentHref': '/r/1', + 'ABC': { + 'promptIfUnknownForGeoGroup': 'testGroup', }, }, - })); - const ampConsent = new AmpConsent(consentElement); - doc.body.appendChild(consentElement); - const getUrlStub = sandbox.stub(ampdoc, 'getUrl'); - // return a cache Url to test origin source being used to resolve. - getUrlStub.callsFake(() => { - return 'https://cdn.ampproject.org/v/www.origin.com/foo/?f=0#h'; }); - ampConsent.buildCallback(); - yield macroTask(); - expect(fetchSpy).to.be.calledOnce; - expect(win.testLocation.origin).not.to.be.empty; - expect(fetchSpy).to.be.calledWith('http://www.origin.com/r/1'); + consentElement = createConsentElement(doc, defaultConfig); }); - }); - }); - describe('server communication', () => { - let defaultConfig; - let ampConsent; - let consentElement; - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response1', - }, - }, + it('in geo group', function*() { + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + ISOCountryGroups = ['unknown', 'testGroup']; + ampConsent.buildCallback(); + return ampConsent.getConsentRequiredPromise_().then(isRequired => { + expect(isRequired).to.be.true; + }); }); - consentElement = createConsentElement(doc, defaultConfig); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - }); - it('send post request to server', function* () { - ampConsent.buildCallback(); - yield macroTask(); - expect(requestBody).to.deep.equal({ - 'consentInstanceId': 'ABC', - 'consentStateValue': 'unknown', - 'consentString': undefined, - 'isDirty': false, + it('not in geo group', function*() { + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + ISOCountryGroups = ['unknown']; + ampConsent.buildCallback(); + return ampConsent.getConsentRequiredPromise_().then(isRequired => { + expect(isRequired).to.be.false; + }); }); - }); - it('read promptIfUnknown from server response', function* () { - ampConsent.buildCallback(); - yield macroTask(); - return ampConsent.getConsentRequiredPromise_().then(isRequired => { - expect(isRequired).to.be.true; + it('geo override promptIfUnknown', function*() { + ISOCountryGroups = ['unknown']; + consentElement = createConsentElement( + doc, + dict({ + 'consents': { + 'ABC': { + 'checkConsentHref': 'https://response1', + 'promptIfUnknownForGeoGroup': 'testGroup', + }, + }, + }) + ); + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + ampConsent.buildCallback(); + return ampConsent.getConsentRequiredPromise_().then(isRequired => { + expect(isRequired).to.be.false; + }); }); }); - }); - describe('amp-geo integration', () => { - let defaultConfig; - let ampConsent; - let consentElement; - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'promptIfUnknownForGeoGroup': 'testGroup', + describe('external consent action', () => { + let defaultConfig; + let ampConsent; + let actionSpy; + let event; + let ampIframe; + let iframe; + let consentElement; + beforeEach(() => { + defaultConfig = dict({ + 'consents': { + 'ABC': { + 'checkConsentHref': 'https://response1', + }, }, - }, + }); + consentElement = createConsentElement(doc, defaultConfig); + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + actionSpy = sandbox.stub(ampConsent, 'handleAction_'); + ampConsent.enableInteractions_(); + ampIframe = document.createElement('amp-iframe'); + iframe = doc.createElement('iframe'); + ampIframe.appendChild(iframe); + ampConsent.element.appendChild(ampIframe); + ampConsent.isPromptUIOn_ = true; + event = new Event('message'); }); - consentElement = createConsentElement(doc, defaultConfig); - }); - it('in geo group', function* () { - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - ISOCountryGroups = ['unknown', 'testGroup']; - ampConsent.buildCallback(); - return ampConsent.getConsentRequiredPromise_().then(isRequired => { - expect(isRequired).to.be.true; + it('listen to external consent response msg', () => { + event.data = { + 'type': 'consent-response', + 'action': 'accept', + 'info': 'accept-string', + }; + event.source = iframe.contentWindow; + win.dispatchEvent(event); + expect(actionSpy).to.be.calledWith(ACTION_TYPE.ACCEPT, 'accept-string'); }); - }); - it('not in geo group', function* () { - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - ISOCountryGroups = ['unknown']; - ampConsent.buildCallback(); - return ampConsent.getConsentRequiredPromise_().then(isRequired => { - expect(isRequired).to.be.false; + it('ignore info when prompt UI is not displayed', () => { + ampConsent.isPromptUIOn_ = false; + event.data = { + 'type': 'consent-response', + 'action': 'accept', + 'info': 'accept-string', + }; + event.source = iframe.contentWindow; + win.dispatchEvent(event); + expect(actionSpy).to.not.be.called; }); - }); - it('geo override promptIfUnknown', function* () { - ISOCountryGroups = ['unknown']; - consentElement = createConsentElement(doc, dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response1', - 'promptIfUnknownForGeoGroup': 'testGroup', - }, - }, - })); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - ampConsent.buildCallback(); - return ampConsent.getConsentRequiredPromise_().then(isRequired => { - expect(isRequired).to.be.false; - }); - }); - }); - - describe('external consent action', () => { - let defaultConfig; - let ampConsent; - let actionSpy; - let event; - let ampIframe; - let iframe; - let consentElement; - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response1', - }, - }, + it('ignore info w/o amp-consent-v2 flag', () => { + // TODO(@zhouyx): Remove with amp-consent-v2 flag + toggleExperiment(win, 'amp-consent-v2', false); + event.data = { + 'type': 'consent-response', + 'action': 'accept', + 'info': 'accept-string', + }; + event.source = iframe.contentWindow; + win.dispatchEvent(event); + expect(actionSpy).to.be.calledWith(ACTION_TYPE.ACCEPT, undefined); }); - consentElement = createConsentElement(doc, defaultConfig); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - actionSpy = sandbox.stub(ampConsent, 'handleAction_'); - ampConsent.enableInteractions_(); - ampIframe = document.createElement('amp-iframe'); - iframe = doc.createElement('iframe'); - ampIframe.appendChild(iframe); - ampConsent.element.appendChild(ampIframe); - ampConsent.isPromptUIOn_ = true; - event = new Event('message'); - }); - - it('listen to external consent response msg', () => { - event.data = { - 'type': 'consent-response', - 'action': 'accept', - 'info': 'accept-string', - }; - event.source = iframe.contentWindow; - win.dispatchEvent(event); - expect(actionSpy).to.be.calledWith(ACTION_TYPE.ACCEPT, - 'accept-string'); - }); - - it('ignore info when prompt UI is not displayed', () => { - ampConsent.isPromptUIOn_ = false; - event.data = { - 'type': 'consent-response', - 'action': 'accept', - 'info': 'accept-string', - }; - event.source = iframe.contentWindow; - win.dispatchEvent(event); - expect(actionSpy).to.not.be.called; - }); - - it('ignore info w/o amp-consent-v2 flag', () => { - // TODO(@zhouyx): Remove with amp-consent-v2 flag - toggleExperiment(win, 'amp-consent-v2', false); - event.data = { - 'type': 'consent-response', - 'action': 'accept', - 'info': 'accept-string', - }; - event.source = iframe.contentWindow; - win.dispatchEvent(event); - expect(actionSpy).to.be.calledWith(ACTION_TYPE.ACCEPT, - undefined); - }); - it('ignore msg from incorrect source', () => { - event.data = { - 'type': 'consent-response', - 'action': 'accept', - }; - event.source = null; - win.dispatchEvent(event); - expect(actionSpy).to.not.be.called; - }); + it('ignore msg from incorrect source', () => { + event.data = { + 'type': 'consent-response', + 'action': 'accept', + }; + event.source = null; + win.dispatchEvent(event); + expect(actionSpy).to.not.be.called; + }); - it('ignore info with action dismiss', () => { - expectAsyncConsoleError('[amp-consent] ' + - 'Consent string value %s not applicable on user dismiss, ' + - 'stored value will be kept and used '); - event.data = { - 'type': 'consent-response', - 'action': 'dismiss', - 'info': 'test', - }; - event.source = iframe.contentWindow; - win.dispatchEvent(event); - expect(actionSpy).to.be.calledWith(ACTION_TYPE.DISMISS); + it('ignore info with action dismiss', () => { + expectAsyncConsoleError( + '[amp-consent] ' + + 'Consent string value %s not applicable on user dismiss, ' + + 'stored value will be kept and used ' + ); + event.data = { + 'type': 'consent-response', + 'action': 'dismiss', + 'info': 'test', + }; + event.source = iframe.contentWindow; + win.dispatchEvent(event); + expect(actionSpy).to.be.calledWith(ACTION_TYPE.DISMISS); + }); }); - }); - describe('UI', () => { - let uiElement; - let defaultConfig; - let ampConsent; - let updateConsentInstanceStateSpy; - let consentElement; - let postPromptUI; + describe('UI', () => { + let uiElement; + let defaultConfig; + let ampConsent; + let updateConsentInstanceStateSpy; + let consentElement; + let postPromptUI; - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response1', - 'promptUI': '123', + beforeEach(() => { + defaultConfig = dict({ + 'consents': { + 'ABC': { + 'checkConsentHref': 'https://response1', + 'promptUI': '123', + }, }, - }, - 'postPromptUI': 'test', - }); - consentElement = createConsentElement(doc, defaultConfig); - uiElement = document.createElement('div'); - uiElement.setAttribute('id', '123'); - consentElement.appendChild(uiElement); - postPromptUI = document.createElement('div'); - postPromptUI.setAttribute('id', 'test'); - consentElement.appendChild(postPromptUI); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - sandbox.stub(ampConsent.vsync_, 'mutate').callsFake(fn => { - fn(); - }); - sandbox.stub(ampConsent, 'mutateElement').callsFake(fn => { - fn(); + 'postPromptUI': 'test', + }); + consentElement = createConsentElement(doc, defaultConfig); + uiElement = document.createElement('div'); + uiElement.setAttribute('id', '123'); + consentElement.appendChild(uiElement); + postPromptUI = document.createElement('div'); + postPromptUI.setAttribute('id', 'test'); + consentElement.appendChild(postPromptUI); + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + sandbox.stub(ampConsent.vsync_, 'mutate').callsFake(fn => { + fn(); + }); + sandbox.stub(ampConsent, 'mutateElement').callsFake(fn => { + fn(); + }); }); - }); - - it('update current displaying status', function* () { - ampConsent.buildCallback(); - yield macroTask(); - updateConsentInstanceStateSpy = - sandbox.spy(ampConsent.consentStateManager_, - 'updateConsentInstanceState'); - yield macroTask(); - expect(ampConsent.isPromptUIOn_).to.be.true; - yield macroTask(); - ampConsent.handleAction_(ACTION_TYPE.ACCEPT); - expect(updateConsentInstanceStateSpy).to.be.calledWith( - CONSENT_ITEM_STATE.ACCEPTED); - yield macroTask(); - expect(ampConsent.isPromptUIOn_).to.be.false; - }); - - it('ignore action when no consent prompt is displaying', function* () { - ampConsent.buildCallback(); - yield macroTask(); - updateConsentInstanceStateSpy = - sandbox.spy(ampConsent.consentStateManager_, - 'updateConsentInstanceState'); - ampConsent.handleAction_(ACTION_TYPE.DISMISS); - yield macroTask(); - expect(updateConsentInstanceStateSpy).to.be.calledOnce; - updateConsentInstanceStateSpy.resetHistory(); - expect(ampConsent.isPromptUIOn_).to.be.false; - ampConsent.handleAction_(ACTION_TYPE.DISMISS); - yield macroTask(); - expect(updateConsentInstanceStateSpy).to.not.be.called; - }); - describe('schedule display', () => { - it('should check for pending consent UI', function* () { + it('update current displaying status', function*() { ampConsent.buildCallback(); yield macroTask(); - expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); - ampConsent.scheduleDisplay_(); - expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); - ampConsent.hide_(); + updateConsentInstanceStateSpy = sandbox.spy( + ampConsent.consentStateManager_, + 'updateConsentInstanceState' + ); yield macroTask(); - expect(ampConsent.notificationUiManager_.queueSize_).to.equal(0); - ampConsent.scheduleDisplay_(); - ampConsent.scheduleDisplay_(); - ampConsent.scheduleDisplay_(); - expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); - }); - }); - - describe('postPromptUI', () => { - let postPromptUI; - - beforeEach(() => { - postPromptUI = doc.getElementById('test'); + expect(ampConsent.isPromptUIOn_).to.be.true; + yield macroTask(); + ampConsent.handleAction_(ACTION_TYPE.ACCEPT); + expect(updateConsentInstanceStateSpy).to.be.calledWith( + CONSENT_ITEM_STATE.ACCEPTED + ); + yield macroTask(); + expect(ampConsent.isPromptUIOn_).to.be.false; }); - it('handle postPromptUI', function* () { - storageValue = { - 'amp-consent:ABC': true, - }; - - // Build the amp consent, and check that everything is - // initialized correctly + it('ignore action when no consent prompt is displaying', function*() { ampConsent.buildCallback(); - ampConsent.element.classList.remove('i-amphtml-notbuilt'); - expect(ampConsent.postPromptUI_).to.not.be.null; - expect(ampConsent.element).to.have.display('none'); - expect(postPromptUI).to.have.display('none'); - - // Wait for all modifications to the element to be applied. - // Then make more assertions. yield macroTask(); - expect(ampConsent.element).to.not.have.display('none'); - expect(ampConsent.element.classList.contains('amp-active')).to.be.true; - expect(ampConsent.element.classList.contains('amp-hidden')).to.be.false; - expect(postPromptUI).to.not.have.display('none'); - - // Schedule the display of the element - ampConsent.scheduleDisplay_(); - - // Wait for the element to be displayed, - // And the postPrompt to be hidden. + updateConsentInstanceStateSpy = sandbox.spy( + ampConsent.consentStateManager_, + 'updateConsentInstanceState' + ); + ampConsent.handleAction_(ACTION_TYPE.DISMISS); + yield macroTask(); + expect(updateConsentInstanceStateSpy).to.be.calledOnce; + updateConsentInstanceStateSpy.resetHistory(); + expect(ampConsent.isPromptUIOn_).to.be.false; + ampConsent.handleAction_(ACTION_TYPE.DISMISS); yield macroTask(); - expect(postPromptUI).to.have.display('none'); + expect(updateConsentInstanceStateSpy).to.not.be.called; }); - describe('hide/show postPromptUI', () => { - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response3', - }, - }, - // There's already an amp-consent from a parent beforeEach with a - // test postPromptUI - 'postPromptUI': 'test2', - }); - consentElement = createConsentElement(doc, defaultConfig); - postPromptUI = doc.createElement('div'); - postPromptUI.setAttribute('id', 'test2'); - consentElement.appendChild(postPromptUI); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - }); - - it('hide postPromptUI', function* () { + describe('schedule display', () => { + it('should check for pending consent UI', function*() { ampConsent.buildCallback(); - ampConsent.element.classList.remove('i-amphtml-notbuilt'); yield macroTask(); + expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); + ampConsent.scheduleDisplay_(); + expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); + ampConsent.hide_(); + yield macroTask(); + expect(ampConsent.notificationUiManager_.queueSize_).to.equal(0); + ampConsent.scheduleDisplay_(); + ampConsent.scheduleDisplay_(); + ampConsent.scheduleDisplay_(); + expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); + }); + }); - expect(postPromptUI).to.not.be.null; - expect(postPromptUI).to.have.display('none'); + describe('postPromptUI', () => { + let postPromptUI; + + beforeEach(() => { + postPromptUI = doc.getElementById('test'); }); - it('show postPromptUI', function* () { + it('handle postPromptUI', function*() { storageValue = { 'amp-consent:ABC': true, }; + + // Build the amp consent, and check that everything is + // initialized correctly ampConsent.buildCallback(); ampConsent.element.classList.remove('i-amphtml-notbuilt'); - yield macroTask(); + expect(ampConsent.postPromptUI_).to.not.be.null; + expect(ampConsent.element).to.have.display('none'); + expect(postPromptUI).to.have.display('none'); - expect(postPromptUI).to.not.be.null; + // Wait for all modifications to the element to be applied. + // Then make more assertions. + yield macroTask(); + expect(ampConsent.element).to.not.have.display('none'); + expect(ampConsent.element.classList.contains('amp-active')).to.be + .true; + expect(ampConsent.element.classList.contains('amp-hidden')).to.be + .false; expect(postPromptUI).to.not.have.display('none'); + + // Schedule the display of the element + ampConsent.scheduleDisplay_(); + + // Wait for the element to be displayed, + // And the postPrompt to be hidden. + yield macroTask(); + expect(postPromptUI).to.have.display('none'); + }); + + describe('hide/show postPromptUI', () => { + beforeEach(() => { + defaultConfig = dict({ + 'consents': { + 'ABC': { + 'checkConsentHref': 'https://response3', + }, + }, + // There's already an amp-consent from a parent beforeEach with a + // test postPromptUI + 'postPromptUI': 'test2', + }); + consentElement = createConsentElement(doc, defaultConfig); + postPromptUI = doc.createElement('div'); + postPromptUI.setAttribute('id', 'test2'); + consentElement.appendChild(postPromptUI); + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + }); + + it('hide postPromptUI', function*() { + ampConsent.buildCallback(); + ampConsent.element.classList.remove('i-amphtml-notbuilt'); + yield macroTask(); + + expect(postPromptUI).to.not.be.null; + expect(postPromptUI).to.have.display('none'); + }); + + it('show postPromptUI', function*() { + storageValue = { + 'amp-consent:ABC': true, + }; + ampConsent.buildCallback(); + ampConsent.element.classList.remove('i-amphtml-notbuilt'); + yield macroTask(); + + expect(postPromptUI).to.not.be.null; + expect(postPromptUI).to.not.have.display('none'); + }); }); }); }); - }); -}); - + } +); /** * Create an element from config for testing diff --git a/extensions/amp-consent/0.1/test/test-consent-config.js b/extensions/amp-consent/0.1/test/test-consent-config.js index ac6513e46087..78b549e9933a 100644 --- a/extensions/amp-consent/0.1/test/test-consent-config.js +++ b/extensions/amp-consent/0.1/test/test-consent-config.js @@ -14,7 +14,6 @@ * limitations under the License. */ - import {CONSENT_POLICY_STATE} from '../../../../src/consent-state'; import {ConsentConfig, expandPolicyConfig} from '../consent-config'; import {dict} from '../../../../src/utils/object'; @@ -47,111 +46,129 @@ describes.realWin('ConsentConfig', {amp: 1}, env => { it('read inline config', () => { appendConfigScriptElement(doc, element, defaultConfig); const consentConfig = new ConsentConfig(element); - expect(consentConfig.getConsentConfig()).to.deep.equal(dict({ - 'consentInstanceId': 'ABC', - 'checkConsentHref': 'https://response1', - })); + expect(consentConfig.getConsentConfig()).to.deep.equal( + dict({ + 'consentInstanceId': 'ABC', + 'checkConsentHref': 'https://response1', + }) + ); }); it('read cmp config', () => { appendConfigScriptElement(doc, element, dict({})); element.setAttribute('type', '_ping_'); const consentConfig = new ConsentConfig(element); - expect(consentConfig.getConsentConfig()).to.deep.equal(dict({ - 'consentInstanceId': '_ping_', - 'checkConsentHref': '/get-consent-v1', - 'promptUISrc': - '/test/manual/diy-consent.html', - })); + expect(consentConfig.getConsentConfig()).to.deep.equal( + dict({ + 'consentInstanceId': '_ping_', + 'checkConsentHref': '/get-consent-v1', + 'promptUISrc': '/test/manual/diy-consent.html', + }) + ); }); it('support deprecated config format', () => { - appendConfigScriptElement(doc, element, dict({ - 'consents': { - 'ABC': { - 'promptIfUnknownForGeoGroup': 'eea', - 'checkConsentHref': '/href', - 'clientConfig': { - 'test': 'error', + appendConfigScriptElement( + doc, + element, + dict({ + 'consents': { + 'ABC': { + 'promptIfUnknownForGeoGroup': 'eea', + 'checkConsentHref': '/href', + 'clientConfig': { + 'test': 'error', + }, }, }, - }, - 'clientConfig': { - 'test': 'ABC', - }, - 'uiConfig': { - 'overlay': true, - }, - 'postPromptUI': 'test', - })); + 'clientConfig': { + 'test': 'ABC', + }, + 'uiConfig': { + 'overlay': true, + }, + 'postPromptUI': 'test', + }) + ); const consentConfig = new ConsentConfig(element); - expect(consentConfig.getConsentConfig()).to.deep.equal(dict({ - 'consentInstanceId': 'ABC', - 'promptIfUnknownForGeoGroup': 'eea', - 'checkConsentHref': '/href', - 'clientConfig': { - 'test': 'ABC', - }, - 'uiConfig': { - 'overlay': true, - }, - 'postPromptUI': 'test', - })); + expect(consentConfig.getConsentConfig()).to.deep.equal( + dict({ + 'consentInstanceId': 'ABC', + 'promptIfUnknownForGeoGroup': 'eea', + 'checkConsentHref': '/href', + 'clientConfig': { + 'test': 'ABC', + }, + 'uiConfig': { + 'overlay': true, + }, + 'postPromptUI': 'test', + }) + ); }); it('merge inline config w/ cmp config', () => { - appendConfigScriptElement(doc, element, dict({ - 'consentInstanceId': '_ping_', - 'promptIfUnknownForGeoGroup': 'eea', - 'checkConsentHref': '/override', - 'clientConfig': { - 'test': 'ABC', - }, - 'uiConfig': { - 'overlay': true, - }, - 'policy': { - 'default': { - 'waitFor': {}, + appendConfigScriptElement( + doc, + element, + dict({ + 'consentInstanceId': '_ping_', + 'promptIfUnknownForGeoGroup': 'eea', + 'checkConsentHref': '/override', + 'clientConfig': { + 'test': 'ABC', }, - }, - 'postPromptUI': 'test', - })); + 'uiConfig': { + 'overlay': true, + }, + 'policy': { + 'default': { + 'waitFor': {}, + }, + }, + 'postPromptUI': 'test', + }) + ); element.setAttribute('type', '_ping_'); const consentConfig = new ConsentConfig(element); - expect(consentConfig.getConsentConfig()).to.deep.equal(dict({ - 'consentInstanceId': '_ping_', - 'checkConsentHref': '/override', - 'promptUISrc': - '/test/manual/diy-consent.html', - 'promptIfUnknownForGeoGroup': 'eea', - 'postPromptUI': 'test', - 'clientConfig': { - 'test': 'ABC', - }, - 'uiConfig': { - 'overlay': true, - }, - 'policy': { - 'default': { - 'waitFor': {}, + expect(consentConfig.getConsentConfig()).to.deep.equal( + dict({ + 'consentInstanceId': '_ping_', + 'checkConsentHref': '/override', + 'promptUISrc': '/test/manual/diy-consent.html', + 'promptIfUnknownForGeoGroup': 'eea', + 'postPromptUI': 'test', + 'clientConfig': { + 'test': 'ABC', }, - }, - })); + 'uiConfig': { + 'overlay': true, + }, + 'policy': { + 'default': { + 'waitFor': {}, + }, + }, + }) + ); }); it('assert valid config', () => { - const scriptTypeError = 'amp-consent/consent-config: `, - extensions: ['amp-analytics'], - }, env => { - let browser; + extensions: ['amp-analytics'], + }, + env => { + let browser; - beforeEach(() => { - browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + beforeEach(() => { + browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - it('should send request', () => { - const reqPromise = RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/?f=hello%20world&b=2'); + it('should send request', () => { + const reqPromise = RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/?f=hello%20world&b=2'); + }); + browser.click('a'); + return reqPromise; }); - browser.click('a'); - return reqPromise; - }); - }); + } + ); - describes.integration('scroll trigger', { - body: ` + describes.integration( + 'scroll trigger', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - it('should trigger 1s after amp-analytics starts', () => { - const startTime = Date.now(); - return RequestBank.withdraw().then(req => { - const q = parseQueryString(req.url.substr(1)); - const timerStart = parseFloat(q['timerStart']); - expect(timerStart + 1000).to.be.at.most(Date.now()); - expect(timerStart + 1000).to.be.at.most(parseInt(q['timestamp'], 10)); - // Verify that timerStart is about current time - expect(timerStart - startTime).to.be.above(-1000).and.below(1000); - expect(parseFloat(q['timerDuration'])).to.be.at.least(950).below(1100); + it('should trigger 1s after amp-analytics starts', () => { + const startTime = Date.now(); + return RequestBank.withdraw().then(req => { + const q = parseQueryString(req.url.substr(1)); + const timerStart = parseFloat(q['timerStart']); + expect(timerStart + 1000).to.be.at.most(Date.now()); + expect(timerStart + 1000).to.be.at.most(parseInt(q['timestamp'], 10)); + // Verify that timerStart is about current time + expect(timerStart - startTime) + .to.be.above(-1000) + .and.below(1000); + expect(parseFloat(q['timerDuration'])) + .to.be.at.least(950) + .below(1100); + }); }); - }); - }); + } + ); - describes.integration('CLIENT_ID new user', { - body: ` + describes.integration( + 'CLIENT_ID new user', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - afterEach(() => { - // clean up written _cid cookie - document.cookie = '_cid=;expires=' + new Date(0).toUTCString(); - }); + afterEach(() => { + // clean up written _cid cookie + document.cookie = '_cid=;expires=' + new Date(0).toUTCString(); + }); - it('should assign new cid', () => { - return Promise.all([ - RequestBank.withdraw(1), - RequestBank.withdraw(2), - ]).then(reqs => { - const req1 = reqs[0]; - const req2 = reqs[1]; - expect(req1.url).to.match(/^\/\?cid=/); - expect(req2.url).to.match(/^\/\?cid=/); - const cid1 = req1.url.substr('/?cid='.length); - const cid2 = req2.url.substr('/?cid='.length); - expect(cid1).to.match(/^amp-/); - expect(cid2).to.equal(cid1); - expect(document.cookie).to.contain('_cid=' + cid1); + it('should assign new cid', () => { + return Promise.all([ + RequestBank.withdraw(1), + RequestBank.withdraw(2), + ]).then(reqs => { + const req1 = reqs[0]; + const req2 = reqs[1]; + expect(req1.url).to.match(/^\/\?cid=/); + expect(req2.url).to.match(/^\/\?cid=/); + const cid1 = req1.url.substr('/?cid='.length); + const cid2 = req2.url.substr('/?cid='.length); + expect(cid1).to.match(/^amp-/); + expect(cid2).to.equal(cid1); + expect(document.cookie).to.contain('_cid=' + cid1); + }); }); - }); - }); + } + ); - describes.integration('batch', { - body: - ` + describes.integration( + 'batch', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - it('should send request in batch', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/?a=1&b=AMP%20TEST&a=1&b=AMP%20TEST'); + it('should send request in batch', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/?a=1&b=AMP%20TEST&a=1&b=AMP%20TEST'); + }); }); - }); - }); + } + ); - describes.integration('useBody', { - body: - ` + describes.integration( + 'useBody', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - it('should send request use POST body payload', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/'); - expect(JSON.parse(req.body)).to.deep.equal({ - a: 2, - b: 'AMP TEST', - c: { - d: 'AMP TEST', - e: { - f: ['AMP TEST', 'AMP TEST'], + it('should send request use POST body payload', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/'); + expect(JSON.parse(req.body)).to.deep.equal({ + a: 2, + b: 'AMP TEST', + c: { + d: 'AMP TEST', + e: { + f: ['AMP TEST', 'AMP TEST'], + }, }, - }, - g: ['AMP TEST', 'AMP TEST'], - '_c_a': 1, - '_c_b': { - 'context.c': 'AMP TEST', - 'context.d': { - 'context.e': ['AMP TEST', 'AMP TEST'], + g: ['AMP TEST', 'AMP TEST'], + '_c_a': 1, + '_c_b': { + 'context.c': 'AMP TEST', + 'context.d': { + 'context.e': ['AMP TEST', 'AMP TEST'], + }, }, - }, + }); }); }); - }); - }); + } + ); - describes.integration('batch useBody', { - body: - ` + describes.integration( + 'batch useBody', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - it('should send batch request use POST body payload', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/'); - expect(JSON.parse(req.body)).to.deep.equal([{ - a: 1, b: 'AMP TEST', - }, { - a: 1, b: 'AMP TEST', - }]); + it('should send batch request use POST body payload', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/'); + expect(JSON.parse(req.body)).to.deep.equal([ + { + a: 1, + b: 'AMP TEST', + }, + { + a: 1, + b: 'AMP TEST', + }, + ]); + }); }); - }); - }); + } + ); - describes.integration('referrerPolicy', { - body: - ` + describes.integration( + 'referrerPolicy', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - it('should remove referrer if referrerpolicy=no-referrer', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/'); - expect(req.headers.referer).to.not.be.ok; + it('should remove referrer if referrerpolicy=no-referrer', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/'); + expect(req.headers.referer).to.not.be.ok; + }); }); - }); - }); + } + ); - describes.integration('configRewriter', { - body: - ` + describes.integration( + 'configRewriter', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - it('should use config from server', () => { - return RequestBank.withdraw().then(req => { - // The config here should have been rewritten by the /analytics/rewriter - // endpoint. This logic is located in the file - // /build-system/routes/analytics.js - const body = JSON.parse(req.body); - expect(body.reqBody.configRewriter.vars).to.deep.equal({ - name: 'cats', - title: 'AMP TEST', - title2: 'AMP TEST', + it('should use config from server', () => { + return RequestBank.withdraw().then(req => { + // The config here should have been rewritten by the /analytics/rewriter + // endpoint. This logic is located in the file + // /build-system/routes/analytics.js + const body = JSON.parse(req.body); + expect(body.reqBody.configRewriter.vars).to.deep.equal({ + name: 'cats', + title: 'AMP TEST', + title2: 'AMP TEST', + }); + expect(body.rewritten).to.be.true; + expect(body.testId).to.equal(12358); }); - expect(body.rewritten).to.be.true; - expect(body.testId).to.equal(12358); }); - }); - }); + } + ); - describes.integration('configRewriter without publisher config', { - body: - ` + describes.integration( + 'configRewriter without publisher config', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - it('should use config from server', () => { - return RequestBank.withdraw().then(req => { - // The config here should have been rewritten by the /analytics/rewriter - // endpoint. This logic is located in the file - // /build-system/routes/analytics.js - const body = JSON.parse(req.body); - expect(body.reqBody.configRewriter.vars).to.deep.equal({ - title2: 'AMP TEST', + it('should use config from server', () => { + return RequestBank.withdraw().then(req => { + // The config here should have been rewritten by the /analytics/rewriter + // endpoint. This logic is located in the file + // /build-system/routes/analytics.js + const body = JSON.parse(req.body); + expect(body.reqBody.configRewriter.vars).to.deep.equal({ + title2: 'AMP TEST', + }); + expect(body.rewritten).to.be.true; + expect(body.testId).to.equal(12358); }); - expect(body.rewritten).to.be.true; - expect(body.testId).to.equal(12358); }); - }); - }); + } + ); - describes.integration('type=googleanalytics', { - body: ` + describes.integration( + 'type=googleanalytics', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); - afterEach(() => { - // clean up written _ga cookie - document.cookie = '_ga=;expires=' + new Date(0).toUTCString(); - }); + afterEach(() => { + // clean up written _ga cookie + document.cookie = '_ga=;expires=' + new Date(0).toUTCString(); + }); - it('should send request', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.match(/^\/r\/collect\?/); - const queries = parseQueryString(req.url.substr('/r/collect'.length)); - // see vendors/googleanalytics.js "pageview" request for config - expect(queries).to.include({ - _v: 'a1', - _r: '1', - v: '1', - cid: '1427830804.1524174812', - dr: '', - ds: 'AMP', - dt: 'AMP TEST', - tid: 'UA-67833617-1', - t: 'pageview', + it('should send request', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.match(/^\/r\/collect\?/); + const queries = parseQueryString(req.url.substr('/r/collect'.length)); + // see vendors/googleanalytics.js "pageview" request for config + expect(queries).to.include({ + _v: 'a1', + _r: '1', + v: '1', + cid: '1427830804.1524174812', + dr: '', + ds: 'AMP', + dt: 'AMP TEST', + tid: 'UA-67833617-1', + t: 'pageview', + }); + const isNumber = /^\d+$/; + const isRandomNumber = /^0\.\d+$/; + expect(queries['dl']).to.contain('/amp4test/compose-doc?'); // ${documentLocation} + expect(queries['_s']).to.match(isNumber); // ${requestCount} + expect(queries['_utmht']).to.match(isNumber); // ${timestamp} + expect(queries['sr']).to.match(/^\d+x\d+$/); // ${screenWidth}x${screenHeight} + expect(queries['sd']).to.match(isNumber); // ${screenColorDepth} + expect(queries['ul']).to.be.ok; // ${browserLanguage} + expect(queries['de']).to.be.ok; // ${documentCharset} + expect(queries['jid']).to.match(isRandomNumber); // ${random} + expect(queries['a']).to.match(isNumber); // ${pageViewId} + expect(queries['z']).to.match(isRandomNumber); // ${random} }); - const isNumber = /^\d+$/; - const isRandomNumber = /^0\.\d+$/; - expect(queries['dl']).to.contain('/amp4test/compose-doc?'); // ${documentLocation} - expect(queries['_s']).to.match(isNumber); // ${requestCount} - expect(queries['_utmht']).to.match(isNumber); // ${timestamp} - expect(queries['sr']).to.match(/^\d+x\d+$/); // ${screenWidth}x${screenHeight} - expect(queries['sd']).to.match(isNumber); // ${screenColorDepth} - expect(queries['ul']).to.be.ok; // ${browserLanguage} - expect(queries['de']).to.be.ok; // ${documentCharset} - expect(queries['jid']).to.match(isRandomNumber); // ${random} - expect(queries['a']).to.match(isNumber); // ${pageViewId} - expect(queries['z']).to.match(isRandomNumber); // ${random} }); - }); - }); + } + ); // TODO: Find source of test failure on edge. - describe.configure().skipEdge().run('amp-analytics:shadow mode', function() { - describes.integration('basic pageview', { - // TODO(ccordry): Figure out how to write cookie in shadow case, so that - // we can verify CLIENT_ID() is reading the right value. - body: ` + describe + .configure() + .skipEdge() + .run('amp-analytics:shadow mode', function() { + describes.integration( + 'basic pageview', + { + // TODO(ccordry): Figure out how to write cookie in shadow case, so that + // we can verify CLIENT_ID() is reading the right value. + body: `
    @@ -748,17 +813,20 @@ describe('amp-analytics', function() { } `, - extensions: ['amp-analytics'], - ampdoc: 'shadow', - }, () => { - - it('should send request', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.match(/\/?a=2&b=Shadow%20Viewer&cid=amp-.*/); - expect(req.headers.referer, - 'should keep referrer if no referrerpolicy specified').to.be.ok; - }); - }); + extensions: ['amp-analytics'], + ampdoc: 'shadow', + }, + () => { + it('should send request', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.match(/\/?a=2&b=Shadow%20Viewer&cid=amp-.*/); + expect( + req.headers.referer, + 'should keep referrer if no referrerpolicy specified' + ).to.be.ok; + }); + }); + } + ); }); - }); }); diff --git a/test/integration/test-amp-bind.js b/test/integration/test-amp-bind.js index 0512d2cc5c4e..523e52aa81a1 100644 --- a/test/integration/test-amp-bind.js +++ b/test/integration/test-amp-bind.js @@ -20,51 +20,60 @@ const TIMEOUT = 15000; // Skip Edge, which throws "Permission denied" errors when inspecting // element properties in the testing iframe (Edge 17, Windows 10). -describe.configure().skipEdge().run('amp-bind', function() { - this.timeout(TIMEOUT); - - // Helper that sets the poll timeout. - function poll(desc, condition, onError) { - return classicPoll(desc, condition, onError, TIMEOUT); - } - - describes.integration('basic', { - /* eslint-disable max-len */ - body: ` +describe + .configure() + .skipEdge() + .run('amp-bind', function() { + this.timeout(TIMEOUT); + + // Helper that sets the poll timeout. + function poll(desc, condition, onError) { + return classicPoll(desc, condition, onError, TIMEOUT); + } + + describes.integration( + 'basic', + { + /* eslint-disable max-len */ + body: `

    before_text

    `, - /* eslint-enable max-len */ - extensions: ['amp-bind'], - }, env => { - let browser; - let doc; - let text; - - beforeEach(() => { - doc = env.win.document; - text = doc.querySelector('p'); - browser = new BrowserController(env.win); - }); - - it('[text]', function*() { - expect(text.textContent).to.equal('before_text'); - yield browser.wait(200); - browser.click('#changeText'); - yield poll('[text]', () => text.textContent === 'after_text'); - }); - - it('[class]', function*() { - expect(text.className).to.equal('before_class'); - yield browser.wait(200); - browser.click('#changeClass'); - yield poll('[class]', () => text.className === 'after_class'); - }); - }); - - describes.integration('+ amp-img', { - body: ` + /* eslint-enable max-len */ + extensions: ['amp-bind'], + }, + env => { + let browser; + let doc; + let text; + + beforeEach(() => { + doc = env.win.document; + text = doc.querySelector('p'); + browser = new BrowserController(env.win); + }); + + it('[text]', function*() { + expect(text.textContent).to.equal('before_text'); + yield browser.wait(200); + browser.click('#changeText'); + yield poll('[text]', () => text.textContent === 'after_text'); + }); + + it('[class]', function*() { + expect(text.className).to.equal('before_class'); + yield browser.wait(200); + browser.click('#changeClass'); + yield poll('[class]', () => text.className === 'after_class'); + }); + } + ); + + describes.integration( + '+ amp-img', + { + body: ` `, - extensions: ['amp-bind'], - }, env => { - let doc, img; - - beforeEach(() => { - doc = env.win.document; - img = doc.querySelector('amp-img'); - }); - - it('[src] with valid URL', () => { - const button = doc.getElementById('changeSrc'); - expect(img.getAttribute('src')).to.equal('http://example.com/before.jpg'); - button.click(); - return poll('[src]', - () => img.getAttribute('src') === 'http://example.com/after.jpg'); - }); - - it('[alt]', () => { - const button = doc.getElementById('changeAlt'); - expect(img.getAttribute('alt')).to.equal('before_alt'); - button.click(); - return poll('[src]', () => img.getAttribute('alt') === 'after_alt'); - }); - - it('[width] and [height]', () => { - const button = doc.getElementById('changeSize'); - expect(img.getAttribute('width')).to.equal('1'); - expect(img.getAttribute('height')).to.equal('1'); - button.click(); - return Promise.all([ - poll('[width]', () => img.getAttribute('width') === '2'), - poll('[height]', () => img.getAttribute('height') === '2'), - ]); - }); - }); - - describes.integration('+ forms', { - /* eslint-disable max-len */ - body: ` + extensions: ['amp-bind'], + }, + env => { + let doc, img; + + beforeEach(() => { + doc = env.win.document; + img = doc.querySelector('amp-img'); + }); + + it('[src] with valid URL', () => { + const button = doc.getElementById('changeSrc'); + expect(img.getAttribute('src')).to.equal( + 'http://example.com/before.jpg' + ); + button.click(); + return poll( + '[src]', + () => img.getAttribute('src') === 'http://example.com/after.jpg' + ); + }); + + it('[alt]', () => { + const button = doc.getElementById('changeAlt'); + expect(img.getAttribute('alt')).to.equal('before_alt'); + button.click(); + return poll('[src]', () => img.getAttribute('alt') === 'after_alt'); + }); + + it('[width] and [height]', () => { + const button = doc.getElementById('changeSize'); + expect(img.getAttribute('width')).to.equal('1'); + expect(img.getAttribute('height')).to.equal('1'); + button.click(); + return Promise.all([ + poll('[width]', () => img.getAttribute('width') === '2'), + poll('[height]', () => img.getAttribute('height') === '2'), + ]); + }); + } + ); + + describes.integration( + '+ forms', + { + /* eslint-disable max-len */ + body: `

    before_range

    @@ -124,65 +141,69 @@ describe.configure().skipEdge().run('amp-bind', function() {

    before_radio

    `, - /* eslint-enable max-len */ - extensions: ['amp-bind'], - }, env => { - let doc; - - beforeEach(() => { - doc = env.win.document; - }); - - it('input[type=range] on:change', () => { - const rangeText = doc.getElementById('range'); - const range = doc.querySelector('input[type="range"]'); - expect(rangeText.textContent).to.equal('before_range'); - // Calling #click() on the range element will not generate a change event, - // so it must be generated manually. - range.value = 47; - range.dispatchEvent(new Event('change', {bubbles: true})); - poll('[text]', () => rangeText.textContent === '0 <= 47 <= 100'); - }); - - it('input[type=checkbox] on:change', () => { - const checkboxText = doc.getElementById('checkbox'); - const checkbox = doc.querySelector('input[type="checkbox"]'); - expect(checkboxText.textContent).to.equal('before_check'); - checkbox.click(); - poll('[text]', () => checkboxText.textContent === 'checked: true'); - }); - - it('[checked]', function*() { - const checkbox = doc.querySelector('input[type="checkbox"]'); - const button = doc.querySelector('button'); - - checkbox.click(); - // Note that attributes are initial values, properties are current values. - expect(checkbox.hasAttribute('checked')).to.be.false; - expect(checkbox.checked).to.be.true; - - button.click(); - yield poll('[checked]', () => !checkbox.checked); - expect(checkbox.hasAttribute('checked')).to.be.false; - - button.click(); - yield poll('[checked]', () => checkbox.checked); - // amp-bind sets both the attribute and property. - expect(checkbox.hasAttribute('checked')).to.be.true; - }); - - it('input[type=radio] on:change', () => { - const radioText = doc.getElementById('radio'); - const radio = doc.querySelector('input[type="radio"]'); - expect(radioText.textContent).to.equal('before_radio'); - radio.click(); - poll('[text]', () => radioText.textContent === 'checked: true'); - }); - }); - - describes.integration('+ amp-carousel', { - /* eslint-disable max-len */ - body: ` + /* eslint-enable max-len */ + extensions: ['amp-bind'], + }, + env => { + let doc; + + beforeEach(() => { + doc = env.win.document; + }); + + it('input[type=range] on:change', () => { + const rangeText = doc.getElementById('range'); + const range = doc.querySelector('input[type="range"]'); + expect(rangeText.textContent).to.equal('before_range'); + // Calling #click() on the range element will not generate a change event, + // so it must be generated manually. + range.value = 47; + range.dispatchEvent(new Event('change', {bubbles: true})); + poll('[text]', () => rangeText.textContent === '0 <= 47 <= 100'); + }); + + it('input[type=checkbox] on:change', () => { + const checkboxText = doc.getElementById('checkbox'); + const checkbox = doc.querySelector('input[type="checkbox"]'); + expect(checkboxText.textContent).to.equal('before_check'); + checkbox.click(); + poll('[text]', () => checkboxText.textContent === 'checked: true'); + }); + + it('[checked]', function*() { + const checkbox = doc.querySelector('input[type="checkbox"]'); + const button = doc.querySelector('button'); + + checkbox.click(); + // Note that attributes are initial values, properties are current values. + expect(checkbox.hasAttribute('checked')).to.be.false; + expect(checkbox.checked).to.be.true; + + button.click(); + yield poll('[checked]', () => !checkbox.checked); + expect(checkbox.hasAttribute('checked')).to.be.false; + + button.click(); + yield poll('[checked]', () => checkbox.checked); + // amp-bind sets both the attribute and property. + expect(checkbox.hasAttribute('checked')).to.be.true; + }); + + it('input[type=radio] on:change', () => { + const radioText = doc.getElementById('radio'); + const radio = doc.querySelector('input[type="radio"]'); + expect(radioText.textContent).to.equal('before_radio'); + radio.click(); + poll('[text]', () => radioText.textContent === 'checked: true'); + }); + } + ); + + describes.integration( + '+ amp-carousel', + { + /* eslint-disable max-len */ + body: `

    0

    `, - /* eslint-enable max-len */ - extensions: ['amp-bind', 'amp-carousel'], - }, env => { - let doc, carousel, slideText; - - beforeEach(() => { - doc = env.win.document; - carousel = doc.querySelector('amp-carousel'); - slideText = doc.querySelector('p'); - - const browserController = new BrowserController(env.win); - return browserController.waitForElementLayout('amp-carousel'); - }); - - it('on:slideChange', () => { - expect(slideText.textContent).to.equal('0'); - - const nextSlide = carousel.querySelector('div.amp-carousel-button-next'); - nextSlide.click(); - return poll('[slide]', () => slideText.textContent === '1'); - }); - - it('[slide]', function*() { - const slides = carousel.querySelectorAll( - '.i-amphtml-slide-item > amp-img'); - const first = slides[0]; - const second = slides[1]; - - expect(first.getAttribute('aria-hidden')).to.equal('false'); - expect(second.getAttribute('aria-hidden')).to.be.equal('true'); - - const button = doc.getElementById('goToSlideOne'); - button.click(); - - yield poll('[slide]', () => - first.getAttribute('aria-hidden') === 'true'); - yield poll('[slide]', () => - second.getAttribute('aria-hidden') === 'false'); - }); - }); + /* eslint-enable max-len */ + extensions: ['amp-bind', 'amp-carousel'], + }, + env => { + let doc, carousel, slideText; + + beforeEach(() => { + doc = env.win.document; + carousel = doc.querySelector('amp-carousel'); + slideText = doc.querySelector('p'); + + const browserController = new BrowserController(env.win); + return browserController.waitForElementLayout('amp-carousel'); + }); + + it('on:slideChange', () => { + expect(slideText.textContent).to.equal('0'); + + const nextSlide = carousel.querySelector( + 'div.amp-carousel-button-next' + ); + nextSlide.click(); + return poll('[slide]', () => slideText.textContent === '1'); + }); + + it('[slide]', function*() { + const slides = carousel.querySelectorAll( + '.i-amphtml-slide-item > amp-img' + ); + const first = slides[0]; + const second = slides[1]; + + expect(first.getAttribute('aria-hidden')).to.equal('false'); + expect(second.getAttribute('aria-hidden')).to.be.equal('true'); + + const button = doc.getElementById('goToSlideOne'); + button.click(); + + yield poll( + '[slide]', + () => first.getAttribute('aria-hidden') === 'true' + ); + yield poll( + '[slide]', + () => second.getAttribute('aria-hidden') === 'false' + ); + }); + } + ); - /* eslint-disable max-len */ - const list = ` + /* eslint-disable max-len */ + const list = ` `, - }, env => { - it('should layout amp-img, amp-pixel, amp-analytics', () => { - // See amp4test.js for creative content - return testAmpComponents(); - }); - - afterEach(() => { - unregisterIframe(env.win.document.getElementById('inabox')); - }); - }); - - describes.integration('AMPHTML ads rendered on non-AMP page BTF', { - amp: false, - body: ` + }, + env => { + it('should layout amp-img, amp-pixel, amp-analytics', () => { + // See amp4test.js for creative content + return testAmpComponents(); + }); + + afterEach(() => { + unregisterIframe(env.win.document.getElementById('inabox')); + }); + } + ); + + describes.integration( + 'AMPHTML ads rendered on non-AMP page BTF', + { + amp: false, + body: `
    c')) - .to.be.equal('ac'); - expect(purify('ac')) - .to.be.equal('ac'); + expect(purify('ac')).to.be.equal('ac'); + expect(purify('ac')).to.be.equal('ac'); + expect(purify('ac')).to.be.equal('ac'); }); it('should NOT output security-sensitive markup when broken', () => { @@ -220,41 +241,44 @@ function runSanitizerTests() { it('should output "on" attribute', () => { expect(purify('a
    b')).to.be.equal( - 'ab'); + 'ab' + ); }); it('should output "data-, aria-, and role" attributes', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - purify('b') + purify('b') ); const expected = serialize( - 'b'); + 'b' + ); expectEqualNodeLists(actual, expected); }); it('should output "href" attribute', () => { // Can't use string equality since DOMPurify will reorder attributes. - const actual = serialize( - purify('ab') - ); + const actual = serialize(purify('ab')); const expected = serialize( - 'ab'); + 'ab' + ); expectEqualNodeLists(actual, expected); }); it('should allow arbitrary protocols', () => { expect(purify('link')).to.be.equal( - 'link'); + 'link' + ); }); it('should output "rel" attribute', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - purify('ab') + purify('ab') ); const expected = serialize( - 'ab'); + 'ab' + ); expectEqualNodeLists(actual, expected); }); @@ -281,167 +305,202 @@ function runSanitizerTests() { it('should default target to _top with href', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - purify('ac') + purify('ac') ); const expected = serialize( - 'ac'); + 'ac' + ); expectEqualNodeLists(actual, expected); }); it('should NOT default target to _top w/o href', () => { - expect(purify( - 'b' - + 'd' - )).to.equal( - 'b' - + 'd'); + expect(purify('bd')).to.equal( + 'bd' + ); }); it('should output a valid target', () => { - expect(purify('ab')) - .to.equal('ab'); + expect(purify('ab')).to.equal( + 'ab' + ); }); it('should output a valid target in different case', () => { - expect(purify('ab')) - .to.equal('ab'); + expect(purify('ab')).to.equal( + 'ab' + ); }); it('should override a unallowed target', () => { - expect(purify( - '_self' - + '_parent' - + '_other' - + '_OTHER' - + 'other' - )).to.equal( - '_self' - + '_parent' - + '_other' - + '_OTHER' - + 'other'); + expect( + purify( + '_self' + + '_parent' + + '_other' + + '_OTHER' + + 'other' + ) + ).to.equal( + '_self' + + '_parent' + + '_other' + + '_OTHER' + + 'other' + ); }); it('should NOT output security-sensitive attributes', () => { - expect(purify('ab')).to.be.equal( - 'ab'); + expect(purify('ab')).to.be.equal('ab'); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); it('should NOT output blacklisted values for class attributes', () => { allowConsoleError(() => { - expect(purify('

    hello

    ')).to.be - .equal('

    hello

    '); - expect(purify('

    hello

    ')).to.be - .equal('

    hello

    '); - expect(purify('

    hello

    ')).to.be - .equal('

    hello

    '); + expect(purify('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); + expect(purify('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); + expect(purify('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); }); }); it('should allow amp-subscriptions attributes', () => { - expect(purify('
    link
    ')) - .to.equal('
    link
    '); - expect(purify('
    link
    ')) - .to.equal('
    link
    '); - expect(purify('
    link
    ')) - .to.equal('
    link
    '); - expect(purify('
    link
    ')) - .to.equal('
    link
    '); - expect(purify('
    link
    ')) - .to.equal('
    link
    '); + expect(purify('
    link
    ')).to.equal( + '
    link
    ' + ); + expect( + purify('
    link
    ') + ).to.equal('
    link
    '); + expect(purify('
    link
    ')).to.equal( + '
    link
    ' + ); + expect(purify('
    link
    ')).to.equal( + '
    link
    ' + ); + expect(purify('
    link
    ')).to.equal( + '
    link
    ' + ); }); it('should allow source::src with valid protocol', () => { - expect(purify('')) - .to.equal(''); + expect(purify('')).to.equal( + '' + ); }); // TODO(choumx): HTTPS-only URI attributes are not enforced consistently // in the sanitizer yet. E.g. amp-video requires HTTPS, amp-img does not. // Unskip when this is fixed. it.skip('should not allow source::src with invalid protocol', () => { - expect(purify('')) - .to.equal(''); - expect(purify('')) - .to.equal(''); + expect(purify('')).to.equal( + '' + ); + expect(purify('')).to.equal( + '' + ); }); it('should allow div::template', () => { - expect(purify('
    ')) - .to.equal('
    '); + expect(purify('
    ')).to.equal( + '
    ' + ); }); it('should allow form::action-xhr', () => { - expect(purify('
    ')) - .to.equal('
    '); + expect(purify('
    ')).to.equal( + '
    ' + ); }); it('should allow input::mask-output', () => { - expect(purify('')) - .to.equal(''); + expect(purify('')).to.equal( + '' + ); }); // Need to test this since DOMPurify doesn't offer a API for tag-specific // attribute whitelists. Instead, we hack around it with custom hooks. it('should not allow unsupported attributes after a valid one', () => { - const html = '
    ' + - '

    '; - expect(purify(html)) - .to.equal('

    '); + const html = + '
    ' + + '

    '; + expect(purify(html)).to.equal( + '

    ' + ); }); it('should allow -related attributes', () => { - expect(purify('
    ')) - .to.equal('
    '); - expect(purify('
    ')) - .to.equal('
    '); - expect(purify('
    ')) - .to.equal('
    '); - expect(purify('
    ')) - .to.equal('
    '); - expect(purify('')) - .to.equal(''); - expect(purify('')) - .to.equal(''); + expect(purify('
    ')).to.equal( + '
    ' + ); + expect(purify('
    ')).to.equal( + '
    ' + ); + expect(purify('
    ')).to.equal( + '
    ' + ); + expect(purify('
    ')).to.equal( + '
    ' + ); + expect( + purify('') + ).to.equal(''); + expect(purify('')).to.equal( + '' + ); }); it('should avoid disallowing default-supported attributes', () => { // We whitelist all attributes of AMP elements, but make sure we don't // remove default-supported attributes from the whitelist afterwards. const html = - '

    '; + '

    '; expect(purify(html)).to.equal(html); }); it('should allow attributes', () => { - expect(purify('')) - .to.equal(''); + expect(purify('')).to.equal( + '' + ); }); it('should output "i-amphtml-key" attribute if diffing is enabled', () => { // Elements with bindings should have i-amphtml-key="". expect(purify('

    ', true)).to.match( - /

    <\/p>/); + /

    <\/p>/ + ); // AMP elements should have i-amphtml-key="". expect(purify('', true)).to.match( - /<\/amp-img>/); + /<\/amp-img>/ + ); // AMP elements with bindings should have i-amphtml-key="". expect(purify('', true)).to.match( - /<\/amp-img>/); + /<\/amp-img>/ + ); // Other elements should NOT have i-amphtml-key-set. expect(purify('

    ', true)).to.equal('

    '); }); @@ -449,21 +508,19 @@ function runSanitizerTests() { it('should resolve URLs', () => { expect(purify('')).to.match(/http/); expect(purify('')).to.match(/http/); - expect(purify('')) - .to.match(/http/); + expect(purify('')).to.match(/http/); }); }); describe('purify based on AMP format type', () => { - it('should blacklist input[type="image"] and input[type="button"] in AMP', - () => { - // Given the AMP format type. - html.setAttribute('amp', ''); - allowConsoleError(() => { - expect(purify('')).to.equal(''); - expect(purify('')).to.equal(''); - }); - }); + it('should blacklist input[type="image"] and input[type="button"] in AMP', () => { + // Given the AMP format type. + html.setAttribute('amp', ''); + allowConsoleError(() => { + expect(purify('')).to.equal(''); + expect(purify('')).to.equal(''); + }); + }); it('should allow input[type="file"] and input[type="password"]', () => { // Given that the AMP format does not blacklist input types file and @@ -471,8 +528,9 @@ function runSanitizerTests() { html.setAttribute('amp', ''); allowConsoleError(() => { expect(purify('')).to.equal(''); - expect(purify('')) - .to.equal(''); + expect(purify('')).to.equal( + '' + ); }); }); @@ -481,10 +539,12 @@ function runSanitizerTests() { allowConsoleError(() => { expect(purify('')).to.equal(''); expect(purify('')).to.equal(''); - expect(purify('
    ')) - .to.equal('
    '); - expect(purify('')) - .to.equal(''); + expect(purify('
    ')).to.equal( + '
    ' + ); + expect(purify('')).to.equal( + '' + ); }); }); }); @@ -498,39 +558,47 @@ function runSanitizerTests() { const entity = '<tag>'; expect(purifyTagsForTripleMustache(entity)).to.be.equal(entity); // DOMPurify short-circuits when there are no '<' characters. - expect(purifyTagsForTripleMustache(`

    ${entity}

    `)) - .to.be.equal(`

    ${entity}

    `); + expect(purifyTagsForTripleMustache(`

    ${entity}

    `)).to.be.equal( + `

    ${entity}

    ` + ); }); it('should output valid markup', () => { - expect(purifyTagsForTripleMustache('abc')) - .to.be.equal('abc'); + expect(purifyTagsForTripleMustache('abc')).to.be.equal( + 'abc' + ); expect(purifyTagsForTripleMustache('ab
    c
    ')).to.be.equal( - 'ab
    c
    '); + 'ab
    c
    ' + ); expect(purifyTagsForTripleMustache('abc')).to.be.equal( - 'abc'); + 'abc' + ); const markupWithClassAttribute = '

    heading

    '; - expect(purifyTagsForTripleMustache(markupWithClassAttribute)) - .to.be.equal(markupWithClassAttribute); + expect(purifyTagsForTripleMustache(markupWithClassAttribute)).to.be.equal( + markupWithClassAttribute + ); const markupWithClassesAttribute = - '
    heading
    '; - expect(purifyTagsForTripleMustache(markupWithClassesAttribute)) - .to.be.equal(markupWithClassesAttribute); + '
    heading
    '; + expect( + purifyTagsForTripleMustache(markupWithClassesAttribute) + ).to.be.equal(markupWithClassesAttribute); const markupParagraph = '

    paragraph

    '; - expect(purifyTagsForTripleMustache(markupParagraph)) - .to.be.equal(markupParagraph); + expect(purifyTagsForTripleMustache(markupParagraph)).to.be.equal( + markupParagraph + ); }); it('should NOT output non-whitelisted markup', () => { - expect(purifyTagsForTripleMustache('ac')) - .to.be.equal('ac'); - expect(purifyTagsForTripleMustache('ac')) - .to.be.equal('ac'); + expect(purifyTagsForTripleMustache('ac')).to.be.equal( + 'ac' + ); + expect(purifyTagsForTripleMustache('ac')).to.be.equal('ac'); }); it('should compensate for broken markup', () => { expect(purifyTagsForTripleMustache('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); it('should support list tags', () => { @@ -540,62 +608,70 @@ function runSanitizerTests() { it('should whitelist formatting related elements', () => { const nonWhiteListedTag = ''; - const whiteListedFormattingTags = 'abc
    def
    ' - + '
    ' - + '' - + '' - + '
    '; + const whiteListedFormattingTags = + 'abc
    def
    ' + + '
    ' + + '' + + '' + + '
    '; const html = `${whiteListedFormattingTags}${nonWhiteListedTag}`; // Expect the purifier to unescape the whitelisted tags and to sanitize // and remove the img tag. - expect(purifyTagsForTripleMustache(html)) - .to.be.equal(whiteListedFormattingTags); + expect(purifyTagsForTripleMustache(html)).to.be.equal( + whiteListedFormattingTags + ); }); it('should whitelist table related elements and anchor tags', () => { - const html = '' - + '' - + '' - + '' - + '' - + '' - + '' - + '
    caption
    header
    ' - + 'google' - + '
    footer
    '; + const html = + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
    caption
    header
    ' + + 'google' + + '
    footer
    '; expect(purifyTagsForTripleMustache(html)).to.be.equal(html); }); it('should sanitize tags, removing unsafe attributes', () => { - const html = 'test' - + ''; + const html = + 'test' + + ''; expect(purifyTagsForTripleMustache(html)).to.be.equal('test'); }); describe('should sanitize `style` attribute', () => { - it('should allow valid styles',() => { - expect(purify('
    Test
    ')) - .to.equal('
    Test
    '); + it('should allow valid styles', () => { + expect(purify('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); - it('should ignore styles containing `!important`',() => { + it('should ignore styles containing `!important`', () => { allowConsoleError(() => { - expect(purify('
    Test
    ')) - .to.equal('
    Test
    '); + expect( + purify('
    Test
    ') + ).to.equal('
    Test
    '); }); }); it('should ignore styles containing `position:fixed`', () => { allowConsoleError(() => { - expect(purify('
    Test
    ')) - .to.equal('
    Test
    '); + expect(purify('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); }); it('should ignore styles containing `position:sticky`', () => { allowConsoleError(() => { - expect(purify('
    Test
    ')) - .to.equal('
    Test
    '); + expect(purify('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); }); }); @@ -613,7 +689,11 @@ describe('validateAttributeChange', () => { vac = (type, attr, value) => validateAttributeChange( - purifier, document.createElement(type), attr, value); + purifier, + document.createElement(type), + attr, + value + ); }); it('should validate script[type]', () => { diff --git a/test/unit/test-render-delaying-services.js b/test/unit/test-render-delaying-services.js index de58a9caf8fd..afc58a2ac9ee 100644 --- a/test/unit/test-render-delaying-services.js +++ b/test/unit/test-render-delaying-services.js @@ -24,7 +24,6 @@ import { import {macroTask} from '../../testing/yield'; describe('waitForServices', () => { - let win; let sandbox; let clock; @@ -46,10 +45,9 @@ describe('waitForServices', () => { }, }; variantResolve = waitForService(getService, 'variant', variantService); - variantStub = sandbox.stub( - variantService, - 'whenReady' - ).returns(Promise.resolve()); + variantStub = sandbox + .stub(variantService, 'whenReady') + .returns(Promise.resolve()); return createIframePromise().then(iframe => { win = iframe.win; @@ -69,7 +67,7 @@ describe('waitForServices', () => { return expect(waitForServices(win)).to.eventually.have.lengthOf(0); }); - it('should timeout if some blocking services are missing', function* () { + it('should timeout if some blocking services are missing', function*() { addExtensionScript(win, 'amp-dynamic-css-classes'); win.document.body.appendChild(win.document.createElement('amp-experiment')); expect(hasRenderDelayingServices(win)).to.be.true; @@ -130,9 +128,11 @@ describe('waitForServices', () => { function waitForService(getService, serviceId, service) { let resolve = null; - getService.withArgs(sinon.match.any, serviceId).returns(new Promise(r => { - resolve = r.bind(this, service); - })); + getService.withArgs(sinon.match.any, serviceId).returns( + new Promise(r => { + resolve = r.bind(this, service); + }) + ); return resolve; } diff --git a/test/unit/test-resource.js b/test/unit/test-resource.js index 84e0395a725e..25d494046a67 100644 --- a/test/unit/test-resource.js +++ b/test/unit/test-resource.js @@ -21,7 +21,6 @@ import {Resources} from '../../src/service/resources-impl'; import {Services} from '../../src/services'; import {layoutRectLtwh} from '../../src/layout-rect'; - describes.realWin('Resource', {amp: true}, env => { let win, doc; let element; @@ -35,14 +34,16 @@ describes.realWin('Resource', {amp: true}, env => { doc = win.document; element = env.createAmpElement('amp-ad'); - sandbox.stub(element, 'getLayoutPriority').callsFake( - () => LayoutPriority.ADS); + sandbox + .stub(element, 'getLayoutPriority') + .callsFake(() => LayoutPriority.ADS); elementMock = sandbox.mock(element); const viewer = Services.viewerForDoc(document); sandbox.stub(viewer, 'isRuntimeOn').callsFake(() => false); - sandbox.stub(Resources.prototype, 'rebuildDomWhenReady') - .callsFake(() => {}); + sandbox + .stub(Resources.prototype, 'rebuildDomWhenReady') + .callsFake(() => {}); resources = new Resources(new AmpDocSingle(window)); resource = new Resource(1, element, resources); viewportMock = sandbox.mock(resources.viewport_); @@ -55,8 +56,9 @@ describes.realWin('Resource', {amp: true}, env => { resources.win = { document, getComputedStyle: el => { - return el.fakeComputedStyle ? - el.fakeComputedStyle : window.getComputedStyle(el); + return el.fakeComputedStyle + ? el.fakeComputedStyle + : window.getComputedStyle(el); }, }; }); @@ -77,13 +79,20 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should initialize correctly when already built', () => { - elementMock.expects('isBuilt').returns(true).once(); + elementMock + .expects('isBuilt') + .returns(true) + .once(); expect(new Resource(1, element).getState()).to.equal( - ResourceState.NOT_LAID_OUT); + ResourceState.NOT_LAID_OUT + ); }); it('should not build before upgraded', () => { - elementMock.expects('isUpgraded').returns(false).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(false) + .atLeast(1); elementMock.expects('build').never(); elementMock.expects('updateLayoutBox').never(); @@ -91,11 +100,16 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); }); - it('should build after upgraded', () => { const buildPromise = Promise.resolve(); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(buildPromise).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(buildPromise) + .once(); elementMock.expects('updateLayoutBox').never(); return resource.build().then(() => { expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); @@ -104,43 +118,65 @@ describes.realWin('Resource', {amp: true}, env => { it('should not build if permission is not granted', () => { let permission = false; - elementMock.expects('isUpgraded').returns(true).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); sandbox.stub(resources, 'grantBuildPermission').callsFake(() => permission); elementMock.expects('updateLayoutBox').never(); expect(resource.build()).to.be.null; expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); permission = true; - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); }); }); it('should blacklist on build failure', () => { - sandbox.stub(resource, 'maybeReportErrorOnBuildFailure') - .callsFake(() => {}); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build') - .returns(Promise.reject(new Error('intentional'))).once(); + sandbox + .stub(resource, 'maybeReportErrorOnBuildFailure') + .callsFake(() => {}); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.reject(new Error('intentional'))) + .once(); elementMock.expects('updateLayoutBox').never(); const buildPromise = resource.build(); expect(resource.isBuilding()).to.be.true; - return buildPromise.then(() => { - throw new Error('must have failed'); - }, () => { - expect(resource.isBuilding()).to.be.false; - expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); - }); + return buildPromise.then( + () => { + throw new Error('must have failed'); + }, + () => { + expect(resource.isBuilding()).to.be.false; + expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); + } + ); }); it('should mark as ready for layout if already measured', () => { const box = layoutRectLtwh(0, 0, 100, 200); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); - elementMock.expects('updateLayoutBox') - .withExactArgs(box, true) - .once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); + elementMock + .expects('updateLayoutBox') + .withExactArgs(box, true) + .once(); const stub = sandbox.stub(resource, 'hasBeenMeasured').returns(true); resource.layoutBox_ = box; return resource.build().then(() => { @@ -150,8 +186,14 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should mark as not laid out if not yet measured', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); const stub = sandbox.stub(resource, 'hasBeenMeasured').returns(false); return resource.build().then(() => { expect(stub.calledOnce).to.be.true; @@ -160,40 +202,65 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should track size changes on measure', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { - elementMock.expects('getBoundingClientRect') - .returns({left: 11, top: 12, width: 111, height: 222}) - .once(); - elementMock.expects('updateLayoutBox') - .withExactArgs(sinon.match(data => { + elementMock + .expects('getBoundingClientRect') + .returns({left: 11, top: 12, width: 111, height: 222}) + .once(); + elementMock + .expects('updateLayoutBox') + .withExactArgs( + sinon.match(data => { return data.width == 111 && data.height == 222; - }), true) - .once(); + }), + true + ) + .once(); resource.measure(); }); }); it('should track no size changes on measure', () => { layoutRectLtwh(0, 0, 0, 0); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { - elementMock.expects('getBoundingClientRect') - .returns({left: 0, top: 0, width: 0, height: 0}) - .once(); - elementMock.expects('updateLayoutBox') - .withExactArgs(sinon.match(data => { + elementMock + .expects('getBoundingClientRect') + .returns({left: 0, top: 0, width: 0, height: 0}) + .once(); + elementMock + .expects('updateLayoutBox') + .withExactArgs( + sinon.match(data => { return data.width == 0 && data.height == 0; - }), false) - .once(); + }), + false + ) + .once(); resource.measure(); }); }); it('should allow to measure when not upgraded', () => { - elementMock.expects('isUpgraded').returns(false).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(false) + .atLeast(1); const viewport = { getLayoutRect() { return layoutRectLtwh(0, 100, 300, 100); @@ -212,30 +279,47 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.getLayoutBox()).to.eql(layoutRectLtwh(0, 100, 300, 100)); // pageLayoutBox == layoutBox expect(resource.getPageLayoutBox()).to.eql( - layoutRectLtwh(0, 100, 300, 100)); + layoutRectLtwh(0, 100, 300, 100) + ); }); it('should allow measure even when not built', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect').returns( - layoutRectLtwh(0, 0, 0, 0)).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns(layoutRectLtwh(0, 0, 0, 0)) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); expect(resource.isFixed()).to.be.false; }); it('should measure and update state', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { - elementMock.expects('getBoundingClientRect') - .returns({left: 11, top: 12, width: 111, height: 222}) - .once(); - elementMock.expects('updateLayoutBox') - .withExactArgs(sinon.match(data => { + elementMock + .expects('getBoundingClientRect') + .returns({left: 11, top: 12, width: 111, height: 222}) + .once(); + elementMock + .expects('updateLayoutBox') + .withExactArgs( + sinon.match(data => { return data.width == 111 && data.height == 222; - }), true) - .once(); + }), + true + ) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); expect(resource.getLayoutBox().left).to.equal(11); @@ -247,17 +331,31 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should update initial box only on first measure', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { - element.getBoundingClientRect = () => - ({left: 11, top: 12, width: 111, height: 222}); + element.getBoundingClientRect = () => ({ + left: 11, + top: 12, + width: 111, + height: 222, + }); resource.measure(); expect(resource.getLayoutBox().top).to.equal(12); expect(resource.getInitialLayoutBox().top).to.equal(12); - element.getBoundingClientRect = () => - ({left: 11, top: 22, width: 111, height: 222}); + element.getBoundingClientRect = () => ({ + left: 11, + top: 22, + width: 111, + height: 222, + }); resource.measure(); expect(resource.getLayoutBox().top).to.equal(22); expect(resource.getInitialLayoutBox().top).to.equal(12); @@ -280,12 +378,17 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should always layout if has not been laid out before', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); resource.state_ = ResourceState.NOT_LAID_OUT; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; - elementMock.expects('getBoundingClientRect') - .returns(resource.layoutBox_).once(); + elementMock + .expects('getBoundingClientRect') + .returns(resource.layoutBox_) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); }); @@ -295,55 +398,86 @@ describes.realWin('Resource', {amp: true}, env => { resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Left is not part of validation. - elementMock.expects('getBoundingClientRect') - .returns({left: 11 + 10, top: 12, width: 111, height: 222}).once(); + elementMock + .expects('getBoundingClientRect') + .returns({left: 11 + 10, top: 12, width: 111, height: 222}) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); expect(resource.getLayoutBox().left).to.equal(11 + 10); }); - it('should not relayout if box changed but element didn\'t opt in', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); + it("should not relayout if box changed but element didn't opt in", () => { + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); resource.state_ = ResourceState.LAYOUT_COMPLETE; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Width changed. - elementMock.expects('getBoundingClientRect') - .returns({left: 11, top: 12, width: 111 + 10, height: 222}).once(); - elementMock.expects('isRelayoutNeeded').returns(false).atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns({left: 11, top: 12, width: 111 + 10, height: 222}) + .once(); + elementMock + .expects('isRelayoutNeeded') + .returns(false) + .atLeast(1); resource.measure(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); expect(resource.getLayoutBox().width).to.equal(111 + 10); }); it('should relayout if box changed when element opted in', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); resource.state_ = ResourceState.LAYOUT_COMPLETE; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Width changed. - elementMock.expects('getBoundingClientRect') - .returns({left: 11, top: 12, width: 111 + 10, height: 222}).once(); - elementMock.expects('isRelayoutNeeded').returns(true).atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns({left: 11, top: 12, width: 111 + 10, height: 222}) + .once(); + elementMock + .expects('isRelayoutNeeded') + .returns(true) + .atLeast(1); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); expect(resource.getLayoutBox().width).to.equal(111 + 10); }); it('should calculate NOT fixed for non-displayed elements', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect').returns( - layoutRectLtwh(0, 0, 0, 0)).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns(layoutRectLtwh(0, 0, 0, 0)) + .once(); element.isAlwaysFixed = () => true; resource.measure(); expect(resource.isFixed()).to.be.false; }); it('should calculate fixed for always-fixed parent', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect').returns( - layoutRectLtwh(0, 0, 10, 10)).once(); - viewportMock.expects('getScrollTop').returns(11).atLeast(0); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns(layoutRectLtwh(0, 0, 10, 10)) + .once(); + viewportMock + .expects('getScrollTop') + .returns(11) + .atLeast(0); Object.defineProperty(element, 'offsetParent', { value: { isAlwaysFixed: () => true, @@ -357,22 +491,32 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should calculate fixed for fixed-style parent', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect').returns( - layoutRectLtwh(0, 0, 10, 10)).once(); - viewportMock.expects('getScrollTop').returns(11).atLeast(0); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns(layoutRectLtwh(0, 0, 10, 10)) + .once(); + viewportMock + .expects('getScrollTop') + .returns(11) + .atLeast(0); const fixedParent = doc.createElement('div'); fixedParent.style.position = 'fixed'; doc.body.appendChild(fixedParent); fixedParent.appendChild(element); - viewportMock.expects('isDeclaredFixed') - .withExactArgs(element) - .returns(false) - .once(); - viewportMock.expects('isDeclaredFixed') - .withExactArgs(fixedParent) - .returns(true) - .once(); + viewportMock + .expects('isDeclaredFixed') + .withExactArgs(element) + .returns(false) + .once(); + viewportMock + .expects('isDeclaredFixed') + .withExactArgs(fixedParent) + .returns(true) + .once(); resource.measure(); expect(resource.isFixed()).to.be.true; // layoutBox != pageLayoutBox @@ -391,15 +535,24 @@ describes.realWin('Resource', {amp: true}, env => { writable: true, }); element.parentElement.__AMP__RESOURCE = {}; - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); rect = {left: 11, top: 12, width: 111, height: 222}; resource = new Resource(1, element, resources); return resource.build(); }); it('should measure placeholder with stubbed parent', () => { - elementMock.expects('getBoundingClientRect').returns(rect).once(); + elementMock + .expects('getBoundingClientRect') + .returns(rect) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); @@ -420,7 +573,10 @@ describes.realWin('Resource', {amp: true}, env => { it('should support abnormal case with no parent', () => { delete element.parentElement; - elementMock.expects('getBoundingClientRect').returns(rect).once(); + elementMock + .expects('getBoundingClientRect') + .returns(rect) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); @@ -430,7 +586,10 @@ describes.realWin('Resource', {amp: true}, env => { it('should support abnormal case with non-AMP parent', () => { element.parentElement = document.createElement('div'); - elementMock.expects('getBoundingClientRect').returns(rect).once(); + elementMock + .expects('getBoundingClientRect') + .returns(rect) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); @@ -441,11 +600,14 @@ describes.realWin('Resource', {amp: true}, env => { it('should hide and update layout box on collapse', () => { resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; resource.isFixed_ = true; - elementMock.expects('updateLayoutBox') - .withExactArgs(sinon.match(data => { + elementMock + .expects('updateLayoutBox') + .withExactArgs( + sinon.match(data => { return data.width == 0 && data.height == 0; - })) - .once(); + }) + ) + .once(); const owner = { collapsedCallback: sandbox.spy(), }; @@ -471,41 +633,47 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.requestMeasure).to.be.calledOnce; }); - it('should ignore startLayout if already completed or failed or going', - () => { - elementMock.expects('layoutCallback').never(); + it('should ignore startLayout if already completed or failed or going', () => { + elementMock.expects('layoutCallback').never(); - resource.state_ = ResourceState.LAYOUT_COMPLETE; - resource.startLayout(); + resource.state_ = ResourceState.LAYOUT_COMPLETE; + resource.startLayout(); - resource.state_ = ResourceState.LAYOUT_FAILED; - resource.startLayout(); + resource.state_ = ResourceState.LAYOUT_FAILED; + resource.startLayout(); - resource.state_ = ResourceState.READY_FOR_LAYOUT; - resource.layoutPromise_ = {}; - resource.startLayout(); - }); + resource.state_ = ResourceState.READY_FOR_LAYOUT; + resource.layoutPromise_ = {}; + resource.startLayout(); + }); it('should fail startLayout if not built', () => { elementMock.expects('layoutCallback').never(); resource.state_ = ResourceState.NOT_BUILT; - allowConsoleError(() => { expect(() => { - resource.startLayout(); - }).to.throw(/Not ready to start layout/); }); + allowConsoleError(() => { + expect(() => { + resource.startLayout(); + }).to.throw(/Not ready to start layout/); + }); }); it('should ignore startLayout if not visible', () => { elementMock.expects('layoutCallback').never(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 0, height: 0}; - allowConsoleError(() => { expect(() => { - resource.startLayout(); - }).to.throw(/Not displayed/); }); + allowConsoleError(() => { + expect(() => { + resource.startLayout(); + }).to.throw(/Not displayed/); + }); }); it('should force startLayout for first layout', () => { - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; @@ -519,24 +687,36 @@ describes.realWin('Resource', {amp: true}, env => { resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; resource.layoutCount_ = 1; - elementMock.expects('isRelayoutNeeded').returns(false).atLeast(1); + elementMock + .expects('isRelayoutNeeded') + .returns(false) + .atLeast(1); resource.startLayout(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); }); it('should force startLayout for re-layout when opt-in', () => { - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; resource.layoutCount_ = 1; - elementMock.expects('isRelayoutNeeded').returns(true).atLeast(1); + elementMock + .expects('isRelayoutNeeded') + .returns(true) + .atLeast(1); resource.startLayout(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_SCHEDULED); }); it('should complete startLayout', () => { - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; @@ -553,8 +733,14 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should complete startLayout with height == 0', () => { - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); - elementMock.expects('getLayout').returns('fluid').once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); + elementMock + .expects('getLayout') + .returns('fluid') + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 0}; @@ -572,8 +758,10 @@ describes.realWin('Resource', {amp: true}, env => { it('should fail startLayout', () => { const error = new Error('intentional'); - elementMock.expects('layoutCallback') - .returns(Promise.reject(error)).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.reject(error)) + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; @@ -581,22 +769,30 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.layoutPromise_).to.not.equal(null); expect(resource.getState()).to.equal(ResourceState.LAYOUT_SCHEDULED); - return promise.then(() => { - /* global fail: false */ - fail('should not be here'); - }, () => { - expect(resource.getState()).to.equal(ResourceState.LAYOUT_FAILED); - expect(resource.layoutPromise_).to.equal(null); - expect(resource.lastLayoutError_).to.equal(error); - - // Should fail with the same error again. - return resource.startLayout(); - }).then(() => { - /* global fail: false */ - fail('should not be here'); - }, reason => { - expect(reason).to.equal(error); - }); + return promise + .then( + () => { + /* global fail: false */ + fail('should not be here'); + }, + () => { + expect(resource.getState()).to.equal(ResourceState.LAYOUT_FAILED); + expect(resource.layoutPromise_).to.equal(null); + expect(resource.lastLayoutError_).to.equal(error); + + // Should fail with the same error again. + return resource.startLayout(); + } + ) + .then( + () => { + /* global fail: false */ + fail('should not be here'); + }, + reason => { + expect(reason).to.equal(error); + } + ); }); it('should record layout schedule time', () => { @@ -622,16 +818,20 @@ describes.realWin('Resource', {amp: true}, env => { it('should change size and update state', () => { expect(resource.isMeasureRequested()).to.be.false; resource.state_ = ResourceState.READY_FOR_LAYOUT; - elementMock.expects('changeSize').withExactArgs(111, 222, - {top: 1, right: 2, bottom: 3, left: 4}).once(); + elementMock + .expects('changeSize') + .withExactArgs(111, 222, {top: 1, right: 2, bottom: 3, left: 4}) + .once(); resource.changeSize(111, 222, {top: 1, right: 2, bottom: 3, left: 4}); expect(resource.isMeasureRequested()).to.be.true; }); it('should change size but not state', () => { resource.state_ = ResourceState.NOT_BUILT; - elementMock.expects('changeSize').withExactArgs(111, 222, - {top: 1, right: 2, bottom: 3, left: 4}).once(); + elementMock + .expects('changeSize') + .withExactArgs(111, 222, {top: 1, right: 2, bottom: 3, left: 4}) + .once(); resource.changeSize(111, 222, {top: 1, right: 2, bottom: 3, left: 4}); expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); }); @@ -652,11 +852,15 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.getLayoutPriority()).to.equal(LayoutPriority.CONTENT); }); - describe('setInViewport', () => { let resolveWithinViewportSpy; - beforeEach(() => resolveWithinViewportSpy = - sandbox.spy(resource, 'resolveDeferredsWhenWithinViewports_')); + beforeEach( + () => + (resolveWithinViewportSpy = sandbox.spy( + resource, + 'resolveDeferredsWhenWithinViewports_' + )) + ); it('should call viewportCallback when not built', () => { resource.state_ = ResourceState.NOT_BUILT; @@ -700,11 +904,17 @@ describes.realWin('Resource', {amp: true}, env => { hasAttribute: () => false, isBuilt: () => false, contains: () => true, - getElementsByClassName: () => {return [];}, + getElementsByClassName: () => { + return []; + }, parentElement: child, }; - parent.getElementsByClassName = () => {return [child, grandChild];}; - child.getElementsByClassName = () => {return [grandChild];}; + parent.getElementsByClassName = () => { + return [child, grandChild]; + }; + child.getElementsByClassName = () => { + return [grandChild]; + }; resources = new Resources(new AmpDocSingle(window)); parentResource = new Resource(1, parent, resources); }); @@ -751,55 +961,91 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); }); - it('should call unlayoutCallback on built element and update state', - () => { - resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('unlayoutCallback').returns(true).once(); - elementMock.expects('togglePlaceholder').withArgs(true).once(); - resource.unlayout(); - expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); - }); + it('should call unlayoutCallback on built element and update state', () => { + resource.state_ = ResourceState.LAYOUT_COMPLETE; + elementMock + .expects('unlayoutCallback') + .returns(true) + .once(); + elementMock + .expects('togglePlaceholder') + .withArgs(true) + .once(); + resource.unlayout(); + expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); + }); it('updated state should bypass isRelayoutNeeded', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('unlayoutCallback').returns(true).once(); - elementMock.expects('togglePlaceholder').withArgs(true).once(); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect') - .returns({left: 1, top: 1, width: 1, height: 1}).once(); + elementMock + .expects('unlayoutCallback') + .returns(true) + .once(); + elementMock + .expects('togglePlaceholder') + .withArgs(true) + .once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns({left: 1, top: 1, width: 1, height: 1}) + .once(); resource.unlayout(); - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); resource.measure(); resource.startLayout(); }); - it('should call unlayoutCallback on built element' + - ' but NOT update state', () => { + it('should call unlayoutCallback on built element but NOT update state', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('unlayoutCallback').returns(false).once(); - elementMock.expects('togglePlaceholder').withArgs(true).never(); + elementMock + .expects('unlayoutCallback') + .returns(false) + .once(); + elementMock + .expects('togglePlaceholder') + .withArgs(true) + .never(); resource.unlayout(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); }); it('should call viewportCallback when resource not in viewport', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('viewportCallback').withExactArgs(false).once(); + elementMock + .expects('viewportCallback') + .withExactArgs(false) + .once(); resource.unlayout(); }); it('should call viewportCallback when resource in viewport', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('viewportCallback').withExactArgs(false).once(); + elementMock + .expects('viewportCallback') + .withExactArgs(false) + .once(); resource.unlayout(); }); it('should delegate unload to unlayoutCallback', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('unlayoutCallback').returns(false).once(); - elementMock.expects('togglePlaceholder').withArgs(true).never(); + elementMock + .expects('unlayoutCallback') + .returns(false) + .once(); + elementMock + .expects('togglePlaceholder') + .withArgs(true) + .never(); resource.unload(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); }); @@ -827,13 +1073,19 @@ describes.realWin('Resource', {amp: true}, env => { describe('when unlayoutOnPause', () => { beforeEach(() => { - elementMock.expects('unlayoutOnPause').returns(true).once(); + elementMock + .expects('unlayoutOnPause') + .returns(true) + .once(); }); it('should call unlayoutCallback and update state', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; elementMock.expects('pauseCallback').once(); - elementMock.expects('unlayoutCallback').returns(true).once(); + elementMock + .expects('unlayoutCallback') + .returns(true) + .once(); resource.pause(); expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); }); @@ -841,7 +1093,10 @@ describes.realWin('Resource', {amp: true}, env => { it('should call unlayoutCallback but NOT update state', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; elementMock.expects('pauseCallback').once(); - elementMock.expects('unlayoutCallback').returns(false).once(); + elementMock + .expects('unlayoutCallback') + .returns(false) + .once(); resource.pause(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); }); @@ -938,8 +1193,7 @@ describe('Resource idleRenderOutsideViewport', () => { }; resources = new Resources(new AmpDocSingle(window)); resource = new Resource(1, element, resources); - isWithinViewportRatio = - sandbox.stub(resource, 'isWithinViewportRatio'); + isWithinViewportRatio = sandbox.stub(resource, 'isWithinViewportRatio'); }); afterEach(() => { @@ -998,15 +1252,16 @@ describe('Resource renderOutsideViewport', () => { viewport = resources.viewport_; renderOutsideViewport = sandbox.stub(element, 'renderOutsideViewport'); sandbox.stub(viewport, 'getRect').returns(layoutRectLtwh(0, 0, 100, 100)); - resolveWithinViewportSpy = - sandbox.spy(resource, 'resolveDeferredsWhenWithinViewports_'); + resolveWithinViewportSpy = sandbox.spy( + resource, + 'resolveDeferredsWhenWithinViewports_' + ); }); afterEach(() => { sandbox.restore(); }); - describe('boolean API', () => { describe('when element returns true', () => { beforeEach(() => { @@ -2017,13 +2272,19 @@ describe('Resource renderOutsideViewport', () => { describe('whenWithinViewport', () => { it('should resolve correctly', () => { - sandbox.stub(resource, 'isWithinViewportRatio').withArgs(3) - .onCall(0).returns(false) - .onCall(1).returns(false) - .onCall(2).returns(true) - .onCall(3).callsFake(() => { - throw new Error('should not call!'); - }); + sandbox + .stub(resource, 'isWithinViewportRatio') + .withArgs(3) + .onCall(0) + .returns(false) + .onCall(1) + .returns(false) + .onCall(2) + .returns(true) + .onCall(3) + .callsFake(() => { + throw new Error('should not call!'); + }); const promise = resource.whenWithinViewport(3); // Multiple calls should return the same promise. expect(resource.whenWithinViewport(3)).to.equal(promise); @@ -2040,8 +2301,10 @@ describe('Resource renderOutsideViewport', () => { }); it('should resolve correctly with float', () => { - const isWithinViewportRatioStub = - sandbox.stub(resource, 'isWithinViewportRatio'); + const isWithinViewportRatioStub = sandbox.stub( + resource, + 'isWithinViewportRatio' + ); const ratio = {}; sandbox.stub(resource, 'getDistanceViewportRatio').returns(ratio); isWithinViewportRatioStub.withArgs(1.25).returns(false); diff --git a/test/unit/test-resources.js b/test/unit/test-resources.js index 029df6b0e3a5..9217c0f1575f 100644 --- a/test/unit/test-resources.js +++ b/test/unit/test-resources.js @@ -26,7 +26,6 @@ import {loadPromise} from '../../src/event-helper'; /*eslint "google-camelcase/google-camelcase": 0*/ describe('Resources', () => { - let sandbox; let clock; let resources; @@ -278,28 +277,30 @@ describe('Resources', () => { expect(resources.calcTaskTimeout_(task_p1)).to.equal(1000); }); - it('should not schedule non-prerenderable resource when' + - ' document is in prerender', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => true, - prerenderAllowed: () => false, - renderOutsideViewport: () => false, - startLayout: () => {}, - applySizesAndMediaQuery: () => {}, - }; - resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(0); - }); + it( + 'should not schedule non-prerenderable resource when' + + ' document is in prerender', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => true, + prerenderAllowed: () => false, + renderOutsideViewport: () => false, + startLayout: () => {}, + applySizesAndMediaQuery: () => {}, + }; + resources.visible_ = false; + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(0); + } + ); - it('should schedule prerenderable resource when' + - ' document is in prerender', () => { + it('should schedule prerenderable resource when document is in prerender', () => { const resource = { getState: () => ResourceState.READY_FOR_LAYOUT, isDisplayed: () => true, @@ -314,16 +315,15 @@ describe('Resources', () => { applySizesAndMediaQuery: () => {}, }; resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); resources.scheduleLayoutOrPreload_(resource, true); expect(resources.queue_.getSize()).to.equal(1); expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; }); - it('should not schedule prerenderable resource when' + - ' document is hidden', () => { + it('should not schedule prerenderable resource when document is hidden', () => { const resource = { getState: () => ResourceState.READY_FOR_LAYOUT, isDisplayed: () => true, @@ -338,102 +338,115 @@ describe('Resources', () => { applySizesAndMediaQuery: () => {}, }; resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.HIDDEN - ); - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(0); - }); - - it('should not schedule non-renderOutsideViewport resource when' + - ' resource is not visible', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => false, - prerenderAllowed: () => true, - renderOutsideViewport: () => false, - idleRenderOutsideViewport: () => false, - startLayout: () => {}, - applySizesAndMediaQuery: () => {}, - }; + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.HIDDEN); resources.scheduleLayoutOrPreload_(resource, true); expect(resources.queue_.getSize()).to.equal(0); }); - it('should force schedule non-renderOutsideViewport resource when' + - ' resource is not visible', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => false, - prerenderAllowed: () => true, - renderOutsideViewport: () => false, - idleRenderOutsideViewport: () => false, - getLayoutPriority: () => LayoutPriority.METADATA, - startLayout: () => {}, - layoutScheduled: () => {}, - getTaskId: () => 'resource#L', - applySizesAndMediaQuery: () => {}, - }; - resources.scheduleLayoutOrPreload_(resource, true, 0, /* force */ true); - expect(resources.queue_.getSize()).to.equal(1); - expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.true; - }); - - it('should schedule renderOutsideViewport resource when' + - ' resource is not visible', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => false, - prerenderAllowed: () => true, - renderOutsideViewport: () => true, - idleRenderOutsideViewport: () => false, - getLayoutPriority: () => LayoutPriority.METADATA, - startLayout: () => {}, - layoutScheduled: () => {}, - getTaskId: () => 'resource#L', - applySizesAndMediaQuery: () => {}, - }; - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(1); - expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; - }); - - it('should schedule idleRenderOutsideViewport resource when' + - ' resource is not visible', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => false, - prerenderAllowed: () => true, - renderOutsideViewport: () => false, - idleRenderOutsideViewport: () => true, - getLayoutPriority: () => LayoutPriority.METADATA, - startLayout: () => {}, - layoutScheduled: () => {}, - getTaskId: () => 'resource#L', - applySizesAndMediaQuery: () => {}, - }; - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(1); - expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; - }); + it( + 'should not schedule non-renderOutsideViewport resource when' + + ' resource is not visible', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => false, + prerenderAllowed: () => true, + renderOutsideViewport: () => false, + idleRenderOutsideViewport: () => false, + startLayout: () => {}, + applySizesAndMediaQuery: () => {}, + }; + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(0); + } + ); + + it( + 'should force schedule non-renderOutsideViewport resource when' + + ' resource is not visible', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => false, + prerenderAllowed: () => true, + renderOutsideViewport: () => false, + idleRenderOutsideViewport: () => false, + getLayoutPriority: () => LayoutPriority.METADATA, + startLayout: () => {}, + layoutScheduled: () => {}, + getTaskId: () => 'resource#L', + applySizesAndMediaQuery: () => {}, + }; + resources.scheduleLayoutOrPreload_(resource, true, 0, /* force */ true); + expect(resources.queue_.getSize()).to.equal(1); + expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.true; + } + ); + + it( + 'should schedule renderOutsideViewport resource when' + + ' resource is not visible', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => false, + prerenderAllowed: () => true, + renderOutsideViewport: () => true, + idleRenderOutsideViewport: () => false, + getLayoutPriority: () => LayoutPriority.METADATA, + startLayout: () => {}, + layoutScheduled: () => {}, + getTaskId: () => 'resource#L', + applySizesAndMediaQuery: () => {}, + }; + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(1); + expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; + } + ); + + it( + 'should schedule idleRenderOutsideViewport resource when' + + ' resource is not visible', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => false, + prerenderAllowed: () => true, + renderOutsideViewport: () => false, + idleRenderOutsideViewport: () => true, + getLayoutPriority: () => LayoutPriority.METADATA, + startLayout: () => {}, + layoutScheduled: () => {}, + getTaskId: () => 'resource#L', + applySizesAndMediaQuery: () => {}, + }; + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(1); + expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; + } + ); it('should require layout for non-scheduled element', () => { const element = createAmpElement(); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 100, 100)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 100, 100)); const resource = new Resource(1, element, resources); const measureSpy = sandbox.spy(resource, 'measure'); - const scheduleStub = sandbox.stub( - resources, 'scheduleLayoutOrPreload_').callsFake( - () => resource.loadPromiseResolve_()); + const scheduleStub = sandbox + .stub(resources, 'scheduleLayoutOrPreload_') + .callsFake(() => resource.loadPromiseResolve_()); const promise = resources.requireLayout(resource.element); resource.build(); return Promise.all([promise, resource.whenBuilt()]).then(() => { @@ -444,8 +457,9 @@ describe('Resources', () => { it('should require layout for scheduled element', () => { const element = createAmpElement(); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 100, 100)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 100, 100)); const resource = new Resource(1, element, resources); resource.layoutScheduled(); const measureSpy = sandbox.spy(resource, 'measure'); @@ -461,8 +475,9 @@ describe('Resources', () => { it('should not require layout for undisplayed element', () => { const element = createAmpElement(); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 0, 0)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 0, 0)); const resource = new Resource(1, element, resources); const measureSpy = sandbox.spy(resource, 'measure'); const scheduleStub = sandbox.stub(resources, 'scheduleLayoutOrPreload_'); @@ -476,8 +491,9 @@ describe('Resources', () => { it('should not require layout for already completed element', () => { const element = createAmpElement(); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 0, 0)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 0, 0)); const resource = new Resource(1, element, resources); resource.layoutComplete_(true); const measureSpy = sandbox.spy(resource, 'measure'); @@ -494,15 +510,17 @@ describe('Resources', () => { const parentElement = createAmpElement(); const element = createAmpElement(); parentElement.appendChild(element); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 10, 10)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 10, 10)); sandbox.stub(element, 'isBuilt').callsFake(() => true); const parentResource = new Resource(1, parentElement, resources); const resource = new Resource(2, element, resources); const measureSpy = sandbox.spy(resource, 'measure'); const scheduleStub = sandbox.stub(resources, 'scheduleLayoutOrPreload_'); - resources.scheduleLayoutOrPreloadForSubresources_( - parentResource, true, [element]); + resources.scheduleLayoutOrPreloadForSubresources_(parentResource, true, [ + element, + ]); expect(measureSpy).to.be.calledOnce; expect(scheduleStub).to.be.calledOnce; }); @@ -511,23 +529,28 @@ describe('Resources', () => { const parentElement = createAmpElement(); const element = createAmpElement(); parentElement.appendChild(element); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 10, 10)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 10, 10)); sandbox.stub(element, 'isBuilt').callsFake(() => false); const parentResource = new Resource(1, parentElement, resources); const resource = new Resource(2, element, resources); const measureSpy = sandbox.spy(resource, 'measure'); const scheduleStub = sandbox.stub(resources, 'scheduleLayoutOrPreload_'); - resources.scheduleLayoutOrPreloadForSubresources_( - parentResource, true, [element]); + resources.scheduleLayoutOrPreloadForSubresources_(parentResource, true, [ + element, + ]); expect(measureSpy).to.not.be.called; expect(scheduleStub).to.not.be.called; - return resource.build().then(() => { - return element.whenBuilt(); - }).then(() => { - expect(measureSpy).to.be.calledOnce; - expect(scheduleStub).to.be.calledOnce; - }); + return resource + .build() + .then(() => { + return element.whenBuilt(); + }) + .then(() => { + expect(measureSpy).to.be.calledOnce; + expect(scheduleStub).to.be.calledOnce; + }); }); it('should update priority and schedule pass', () => { @@ -573,116 +596,137 @@ describe('Resources', () => { }); }); - -describes.fakeWin('Resources startup', { - win: { - readyState: 'loading', +describes.fakeWin( + 'Resources startup', + { + win: { + readyState: 'loading', + }, + amp: true, }, - amp: true, -}, env => { - let win; - let clock; - let sandbox; - let resources; - let schedulePassStub; - - beforeEach(() => { - win = env.win; - sandbox = sinon.sandbox; - clock = sandbox.useFakeTimers(); - resources = Services.resourcesForDoc(win.document.body); - resources.relayoutAll_ = false; - schedulePassStub = sandbox.stub(resources, 'schedulePass'); - }); + env => { + let win; + let clock; + let sandbox; + let resources; + let schedulePassStub; - afterEach(() => { - sandbox.restore(); - }); + beforeEach(() => { + win = env.win; + sandbox = sinon.sandbox; + clock = sandbox.useFakeTimers(); + resources = Services.resourcesForDoc(win.document.body); + resources.relayoutAll_ = false; + schedulePassStub = sandbox.stub(resources, 'schedulePass'); + }); - it('should run a full reload pass on window.onload', () => { - expect(resources.relayoutAll_).to.be.false; - expect(schedulePassStub).to.not.be.called; - win.readyState = 'complete'; - win.eventListeners.fire({type: 'load'}); - win.document.eventListeners.fire({type: 'readystatechange'}); - return resources.ampdoc.whenReady().then(() => { - return loadPromise(win); - }).then(() => { - expect(resources.relayoutAll_).to.be.true; - expect(schedulePassStub).to.have.been.called; + afterEach(() => { + sandbox.restore(); }); - }); - it('should run a full reload pass on fonts timeout', () => { - win.readyState = 'complete'; - win.document.eventListeners.fire({type: 'readystatechange'}); - return resources.ampdoc.whenReady().then(() => { + it('should run a full reload pass on window.onload', () => { expect(resources.relayoutAll_).to.be.false; expect(schedulePassStub).to.not.be.called; - clock.tick(3100); - }).then(() => { - expect(resources.relayoutAll_).to.be.true; - expect(schedulePassStub).to.have.been.called; + win.readyState = 'complete'; + win.eventListeners.fire({type: 'load'}); + win.document.eventListeners.fire({type: 'readystatechange'}); + return resources.ampdoc + .whenReady() + .then(() => { + return loadPromise(win); + }) + .then(() => { + expect(resources.relayoutAll_).to.be.true; + expect(schedulePassStub).to.have.been.called; + }); }); - }); - it('should run a full reload pass on document.fonts.ready', () => { - win.readyState = 'interactive'; - win.document.eventListeners.fire({type: 'readystatechange'}); - win.document.fonts.status = 'loading'; - return resources.ampdoc.whenReady().then(() => { + it('should run a full reload pass on fonts timeout', () => { + win.readyState = 'complete'; + win.document.eventListeners.fire({type: 'readystatechange'}); + return resources.ampdoc + .whenReady() + .then(() => { + expect(resources.relayoutAll_).to.be.false; + expect(schedulePassStub).to.not.be.called; + clock.tick(3100); + }) + .then(() => { + expect(resources.relayoutAll_).to.be.true; + expect(schedulePassStub).to.have.been.called; + }); + }); - }).then(() => { - // This is the regular remeasure on doc-ready. - expect(resources.relayoutAll_).to.be.true; - resources.relayoutAll_ = false; - return win.document.fonts.ready; - }).then(() => { - // Wait one micro task. - return Promise.resolve(); - }).then(() => { - expect(resources.relayoutAll_).to.be.true; - // Remeasure on doc-ready and fonts-ready. - expect(schedulePassStub).to.have.been.calledTwice; + it('should run a full reload pass on document.fonts.ready', () => { + win.readyState = 'interactive'; + win.document.eventListeners.fire({type: 'readystatechange'}); + win.document.fonts.status = 'loading'; + return resources.ampdoc + .whenReady() + .then(() => {}) + .then(() => { + // This is the regular remeasure on doc-ready. + expect(resources.relayoutAll_).to.be.true; + resources.relayoutAll_ = false; + return win.document.fonts.ready; + }) + .then(() => { + // Wait one micro task. + return Promise.resolve(); + }) + .then(() => { + expect(resources.relayoutAll_).to.be.true; + // Remeasure on doc-ready and fonts-ready. + expect(schedulePassStub).to.have.been.calledTwice; + }); }); - }); - it('should not remeasure if fonts load before doc-ready', () => { - win.readyState = 'interactive'; - win.document.eventListeners.fire({type: 'readystatechange'}); - win.document.fonts.status = 'loaded'; - return resources.ampdoc.whenReady().then(() => { + it('should not remeasure if fonts load before doc-ready', () => { + win.readyState = 'interactive'; + win.document.eventListeners.fire({type: 'readystatechange'}); + win.document.fonts.status = 'loaded'; + return resources.ampdoc + .whenReady() + .then(() => {}) + .then(() => { + // This is the regular remeasure on doc-ready. + expect(resources.relayoutAll_).to.be.true; + resources.relayoutAll_ = false; + return win.document.fonts.ready; + }) + .then(() => { + // Wait one micro task. + return Promise.resolve(); + }) + .then(() => { + expect(resources.relayoutAll_).to.be.false; + // Only remeasure on doc-ready. + expect(schedulePassStub).to.have.been.calledOnce; + }); + }); - }).then(() => { - // This is the regular remeasure on doc-ready. - expect(resources.relayoutAll_).to.be.true; - resources.relayoutAll_ = false; - return win.document.fonts.ready; - }).then(() => { - // Wait one micro task. - return Promise.resolve(); - }).then(() => { + it('should run a full reload when a new element is connected', () => { + expect(resources.relayoutAll_).to.be.false; + expect(schedulePassStub).to.not.be.called; + const el = win.document.createElement('amp-img'); + el.isBuilt = () => { + return true; + }; + el.isUpgraded = () => { + return true; + }; + el.isRelayoutNeeded = () => { + return true; + }; + el.updateLayoutBox = () => {}; + win.document.body.appendChild(el); + resources.add(el); expect(resources.relayoutAll_).to.be.false; - // Only remeasure on doc-ready. - expect(schedulePassStub).to.have.been.calledOnce; + clock.tick(1000); + expect(resources.relayoutAll_).to.be.true; }); - }); - - it('should run a full reload when a new element is connected', () => { - expect(resources.relayoutAll_).to.be.false; - expect(schedulePassStub).to.not.be.called; - const el = win.document.createElement('amp-img'); - el.isBuilt = () => { return true; }; - el.isUpgraded = () => { return true; }; - el.isRelayoutNeeded = () => { return true; }; - el.updateLayoutBox = () => {}; - win.document.body.appendChild(el); - resources.add(el); - expect(resources.relayoutAll_).to.be.false; - clock.tick(1000); - expect(resources.relayoutAll_).to.be.true; - }); -}); + } +); describes.realWin('getElementLayoutBox', {}, env => { let win; @@ -691,9 +735,15 @@ describes.realWin('getElementLayoutBox', {}, env => { let vsyncSpy; function addResourceForElement(id, element) { - element.isBuilt = () => { return true; }; - element.isUpgraded = () => { return true; }; - element.isRelayoutNeeded = () => { return true; }; + element.isBuilt = () => { + return true; + }; + element.isUpgraded = () => { + return true; + }; + element.isRelayoutNeeded = () => { + return true; + }; element.updateLayoutBox = () => {}; const resource = new Resource(id, element, resources); resource.state_ = ResourceState.LAYOUT_COMPLETE; @@ -782,157 +832,159 @@ describes.realWin('getElementLayoutBox', {}, env => { }); }); +describes.realWin( + 'Resources pause/resume/unlayout scheduling', + { + amp: true, + }, + env => { + let win, doc; + let resources; + let parent; + let children; + let child0; + let child1; + let child2; -describes.realWin('Resources pause/resume/unlayout scheduling', { - amp: true, -}, env => { - let win, doc; - let resources; - let parent; - let children; - let child0; - let child1; - let child2; - - beforeEach(() => { - win = env.win; - doc = win.document; - resources = new Resources(env.ampdoc); - resources.isRuntimeOn_ = false; - const parentTuple = createElementWithResource(1); - parent = parentTuple[0]; - child0 = doc.createElement('div'); - child1 = createElementWithResource(2)[0]; - child2 = createElementWithResource(3)[0]; - children = [child0, child1, child2]; - children.forEach(child => { - parent.appendChild(child); + beforeEach(() => { + win = env.win; + doc = win.document; + resources = new Resources(env.ampdoc); + resources.isRuntimeOn_ = false; + const parentTuple = createElementWithResource(1); + parent = parentTuple[0]; + child0 = doc.createElement('div'); + child1 = createElementWithResource(2)[0]; + child2 = createElementWithResource(3)[0]; + children = [child0, child1, child2]; + children.forEach(child => { + parent.appendChild(child); + }); }); - }); - function createElement() { - const element = env.createAmpElement('amp-test'); - sandbox.stub(element, 'isBuilt').callsFake(() => true); - return element; - } + function createElement() { + const element = env.createAmpElement('amp-test'); + sandbox.stub(element, 'isBuilt').callsFake(() => true); + return element; + } - function createElementWithResource(id) { - const element = createElement(); - const resource = new Resource(id, element, resources); - resource.state_ = ResourceState.LAYOUT_COMPLETE; - resource.element['__AMP__RESOURCE'] = resource; - return [element, resource]; - } + function createElementWithResource(id) { + const element = createElement(); + const resource = new Resource(id, element, resources); + resource.state_ = ResourceState.LAYOUT_COMPLETE; + resource.element['__AMP__RESOURCE'] = resource; + return [element, resource]; + } - describe('schedulePause', () => { - it('should not throw with a single element', () => { - expect(() => { - resources.schedulePause(parent, child1); - }).to.not.throw(); - }); + describe('schedulePause', () => { + it('should not throw with a single element', () => { + expect(() => { + resources.schedulePause(parent, child1); + }).to.not.throw(); + }); - it('should not throw with an array of elements', () => { - expect(() => { - resources.schedulePause(parent, [child1, child2]); - }).to.not.throw(); - }); + it('should not throw with an array of elements', () => { + expect(() => { + resources.schedulePause(parent, [child1, child2]); + }).to.not.throw(); + }); + + it('should be ok with non amp children', () => { + expect(() => { + resources.schedulePause(parent, children); + resources.schedulePause(parent, child0); + }).to.not.throw(); + }); + + it('should call pauseCallback on custom element', () => { + const stub1 = sandbox.stub(child1, 'pauseCallback'); + const stub2 = sandbox.stub(child2, 'pauseCallback'); - it('should be ok with non amp children', () => { - expect(() => { resources.schedulePause(parent, children); - resources.schedulePause(parent, child0); - }).to.not.throw(); - }); + expect(stub1.calledOnce).to.be.true; + expect(stub2.calledOnce).to.be.true; + }); - it('should call pauseCallback on custom element', () => { - const stub1 = sandbox.stub(child1, 'pauseCallback'); - const stub2 = sandbox.stub(child2, 'pauseCallback'); + it('should call unlayoutCallback when unlayoutOnPause', () => { + const stub1 = sandbox.stub(child1, 'unlayoutCallback'); + const stub2 = sandbox.stub(child2, 'unlayoutCallback'); + sandbox.stub(child1, 'unlayoutOnPause').returns(true); - resources.schedulePause(parent, children); - expect(stub1.calledOnce).to.be.true; - expect(stub2.calledOnce).to.be.true; + resources.schedulePause(parent, children); + expect(stub1.calledOnce).to.be.true; + expect(stub2.calledOnce).to.be.false; + }); }); - it('should call unlayoutCallback when unlayoutOnPause', () => { - const stub1 = sandbox.stub(child1, 'unlayoutCallback'); - const stub2 = sandbox.stub(child2, 'unlayoutCallback'); - sandbox.stub(child1, 'unlayoutOnPause').returns(true); + describe('scheduleResume', () => { + beforeEach(() => { + // Pause one child. + resources.schedulePause(parent, child1); + }); - resources.schedulePause(parent, children); - expect(stub1.calledOnce).to.be.true; - expect(stub2.calledOnce).to.be.false; - }); - }); + it('should not throw with a single element', () => { + expect(() => { + resources.scheduleResume(parent, child1); + }).to.not.throw(); + }); - describe('scheduleResume', () => { - beforeEach(() => { - // Pause one child. - resources.schedulePause(parent, child1); - }); + it('should not throw with an array of elements', () => { + expect(() => { + resources.scheduleResume(parent, [child1, child2]); + }).to.not.throw(); + }); - it('should not throw with a single element', () => { - expect(() => { - resources.scheduleResume(parent, child1); - }).to.not.throw(); - }); + it('should be ok with non amp children', () => { + expect(() => { + resources.scheduleResume(parent, children); + resources.scheduleResume(parent, child0); + }).to.not.throw(); + }); - it('should not throw with an array of elements', () => { - expect(() => { - resources.scheduleResume(parent, [child1, child2]); - }).to.not.throw(); - }); + it('should call resumeCallback on paused custom elements', () => { + const stub1 = sandbox.stub(child1, 'resumeCallback'); - it('should be ok with non amp children', () => { - expect(() => { resources.scheduleResume(parent, children); - resources.scheduleResume(parent, child0); - }).to.not.throw(); - }); + expect(stub1.calledOnce).to.be.true; + }); - it('should call resumeCallback on paused custom elements', () => { - const stub1 = sandbox.stub(child1, 'resumeCallback'); + it('should call resumeCallback on non-paused custom elements', () => { + const stub2 = sandbox.stub(child2, 'resumeCallback'); - resources.scheduleResume(parent, children); - expect(stub1.calledOnce).to.be.true; + resources.scheduleResume(parent, children); + expect(stub2.calledOnce).to.be.true; + }); }); - it('should call resumeCallback on non-paused custom elements', () => { - const stub2 = sandbox.stub(child2, 'resumeCallback'); - - resources.scheduleResume(parent, children); - expect(stub2.calledOnce).to.be.true; - }); - }); + describe('scheduleUnlayout', () => { + it('should not throw with a single element', () => { + expect(() => { + resources.scheduleUnlayout(parent, child1); + }).to.not.throw(); + }); - describe('scheduleUnlayout', () => { - it('should not throw with a single element', () => { - expect(() => { - resources.scheduleUnlayout(parent, child1); - }).to.not.throw(); - }); + it('should not throw with an array of elements', () => { + expect(() => { + resources.scheduleUnlayout(parent, [child1, child2]); + }).to.not.throw(); + }); - it('should not throw with an array of elements', () => { - expect(() => { - resources.scheduleUnlayout(parent, [child1, child2]); - }).to.not.throw(); - }); + it('should be ok with non amp children', () => { + expect(() => { + resources.scheduleUnlayout(parent, children); + }).to.not.throw(); + }); - it('should be ok with non amp children', () => { - expect(() => { + it('should schedule on custom element with multiple children', () => { + const stub1 = sandbox.stub(child1, 'unlayoutCallback'); + const stub2 = sandbox.stub(child2, 'unlayoutCallback'); resources.scheduleUnlayout(parent, children); - }).to.not.throw(); - }); - - it('should schedule on custom element with multiple children', () => { - const stub1 = sandbox.stub(child1, 'unlayoutCallback'); - const stub2 = sandbox.stub(child2, 'unlayoutCallback'); - resources.scheduleUnlayout(parent, children); - expect(stub1.called).to.be.true; - expect(stub2.called).to.be.true; + expect(stub1.called).to.be.true; + expect(stub2.called).to.be.true; + }); }); - }); -}); - + } +); describes.realWin('Resources schedulePreload', {amp: true}, env => { let win, doc; @@ -1030,9 +1082,7 @@ describes.realWin('Resources schedulePreload', {amp: true}, env => { }); }); - describe('Resources discoverWork', () => { - function createElement(rect) { const signals = new Signals(); return { @@ -1097,8 +1147,9 @@ describe('Resources discoverWork', () => { resources.win = { document, getComputedStyle: el => { - return el.fakeComputedStyle ? - el.fakeComputedStyle : window.getComputedStyle(el); + return el.fakeComputedStyle + ? el.fakeComputedStyle + : window.getComputedStyle(el); }, }; @@ -1131,11 +1182,10 @@ describe('Resources discoverWork', () => { it('should measure unbuilt elements', () => { resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resource1.isBuilt = () => false; const mediaSpy = sandbox.stub(resource1.element, 'applySizesAndMediaQuery'); expect(resource1.hasBeenMeasured()).to.be.false; @@ -1149,11 +1199,10 @@ describe('Resources discoverWork', () => { it('should render two screens when visible', () => { resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resources.discoverWork_(); @@ -1166,11 +1215,10 @@ describe('Resources discoverWork', () => { resource1.state_ = ResourceState.LAYOUT_COMPLETE; resource2.state_ = ResourceState.LAYOUT_COMPLETE; resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resources.discoverWork_(); @@ -1182,18 +1230,17 @@ describe('Resources discoverWork', () => { resource2.state_ = ResourceState.LAYOUT_COMPLETE; resource1.hasBeenMeasured = () => true; resource2.hasBeenMeasured = () => true; - resource1.element.getBoundingClientRect = - () => layoutRectLtwh(10, 10, 100, 101); - resource2.element.getBoundingClientRect = - () => layoutRectLtwh(10, 1010, 100, 101); + resource1.element.getBoundingClientRect = () => + layoutRectLtwh(10, 10, 100, 101); + resource2.element.getBoundingClientRect = () => + layoutRectLtwh(10, 1010, 100, 101); resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); resources.relayoutAll_ = false; resources.relayoutTop_ = 1000; - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resources.discoverWork_(); @@ -1206,12 +1253,11 @@ describe('Resources discoverWork', () => { it('should prerender only one screen with prerenderSize = 1', () => { resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); resources.prerenderSize_ = 1; - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 1009)); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 1009)); resources.discoverWork_(); @@ -1221,12 +1267,11 @@ describe('Resources discoverWork', () => { it('should NOT prerender anything with prerenderSize = 0', () => { resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); resources.prerenderSize_ = 0; - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resources.discoverWork_(); @@ -1238,17 +1283,18 @@ describe('Resources discoverWork', () => { resource1.state_ = ResourceState.LAYOUT_COMPLETE; resource2.state_ = ResourceState.LAYOUT_COMPLETE; resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); - - const resource1MeasureStub = sandbox.stub(resource1, 'measure').callsFake( - resource1.measure.bind(resource1)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); + + const resource1MeasureStub = sandbox + .stub(resource1, 'measure') + .callsFake(resource1.measure.bind(resource1)); const resource1UnloadStub = sandbox.stub(resource1, 'unload'); - const resource2MeasureStub = sandbox.stub(resource2, 'measure').callsFake( - resource2.measure.bind(resource2)); + const resource2MeasureStub = sandbox + .stub(resource2, 'measure') + .callsFake(resource2.measure.bind(resource2)); const resource2UnloadStub = sandbox.stub(resource2, 'unload'); // 1st pass: measure for the first time. @@ -1270,8 +1316,7 @@ describe('Resources discoverWork', () => { resource2.requestMeasure(); expect(resource1.isMeasureRequested()).to.be.true; expect(resource2.isMeasureRequested()).to.be.true; - resource2.element.getBoundingClientRect = - () => layoutRectLtwh(0, 0, 0, 0); // Equiv to display:none. + resource2.element.getBoundingClientRect = () => layoutRectLtwh(0, 0, 0, 0); // Equiv to display:none. resources.discoverWork_(); expect(resource1MeasureStub).to.have.callCount(2); expect(resource1UnloadStub).to.have.not.been.called; @@ -1293,11 +1338,13 @@ describe('Resources discoverWork', () => { resource1.layoutCallback = new Promise(unusedResolve => {}); resource1.unlayoutCallback = () => true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)).atLeast(1); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock + .expects('getRect') + .returns(layoutRectLtwh(0, 0, 300, 400)) + .atLeast(1); resources.discoverWork_(); expect(resources.queue_.getSize()).to.equal(2); @@ -1391,8 +1438,9 @@ describe('Resources discoverWork', () => { expect(resources.queue_.tasks_[0].resource).to.equal(resource1); resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').callsFake( - () => 'prerender'); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .callsFake(() => 'prerender'); sandbox.stub(resource1, 'isInViewport').callsFake(() => true); sandbox.stub(resource1, 'prerenderAllowed').callsFake(() => true); @@ -1411,8 +1459,9 @@ describe('Resources discoverWork', () => { expect(resources.queue_.tasks_[0].resource).to.equal(resource1); resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').callsFake( - () => 'prerender'); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .callsFake(() => 'prerender'); sandbox.stub(resource1, 'isInViewport').callsFake(() => true); sandbox.stub(resource1, 'prerenderAllowed').callsFake(() => false); @@ -1431,8 +1480,9 @@ describe('Resources discoverWork', () => { expect(resources.queue_.tasks_[0].resource).to.equal(resource1); resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').callsFake( - () => 'hidden'); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .callsFake(() => 'hidden'); sandbox.stub(resource1, 'isInViewport').callsFake(() => true); sandbox.stub(resource1, 'prerenderAllowed').callsFake(() => true); @@ -1449,11 +1499,10 @@ describe('Resources discoverWork', () => { // it.configure().skipSafari().run( it.skip('should update inViewport before scheduling layouts', () => { resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); const setInViewport = sandbox.spy(resource1, 'setInViewport'); const schedule = sandbox.spy(resources, 'scheduleLayoutOrPreload_'); @@ -1465,8 +1514,9 @@ describe('Resources discoverWork', () => { it('should not grant permission to build when threshold reached', () => { let hasBeenVisible = false; - sandbox.stub(resources.viewer_, 'hasBeenVisible').callsFake( - () => hasBeenVisible); + sandbox + .stub(resources.viewer_, 'hasBeenVisible') + .callsFake(() => hasBeenVisible); for (let i = 0; i < 20; i++) { expect(resources.grantBuildPermission()).to.be.true; @@ -1489,7 +1539,9 @@ describe('Resources discoverWork', () => { expect(resource1.build).to.be.calledOnce; expect(buildResourceSpy).calledWithExactly( - resource1, /* schedulePass */ true); + resource1, + /* schedulePass */ true + ); }); it('should build resource when not built and before doc ready', () => { @@ -1506,12 +1558,15 @@ describe('Resources discoverWork', () => { expect(resource1.build).to.be.calledOnce; expect(buildResourceSpy).calledWithExactly( - resource1, /* schedulePass */ true); + resource1, + /* schedulePass */ true + ); }); it('should NOT build non-prerenderable resources in prerender', () => { - sandbox.stub(resources.viewer_, 'getVisibilityState') - .returns(VisibilityState.PRERENDER); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); sandbox.stub(resources, 'schedule_'); resources.documentReady_ = true; @@ -1543,7 +1598,6 @@ describe('Resources discoverWork', () => { }); describe('getResourcesInRect', () => { - beforeEach(() => { resources.isRuntimeOn_ = false; resources.ampdoc.signals().signal('ready-scan'); @@ -1600,8 +1654,8 @@ describe('Resources discoverWork', () => { it('should ignore invisible elements', () => { const rect = layoutRectLtwh(0, 0, 100, 1500); - resource2.element.getBoundingClientRect = - () => layoutRectLtwh(0, 0, 0, 0); + resource2.element.getBoundingClientRect = () => + layoutRectLtwh(0, 0, 0, 0); return resources.getResourcesInRect(window, rect).then(res => { expect(res).to.have.length(1); expect(res[0]).to.equal(resource1); @@ -1637,7 +1691,6 @@ describe('Resources discoverWork', () => { }); describe('onNextPass', () => { - it('should only run callbacks once.', () => { resources.isRuntimeOn_ = true; resources.documentReady_ = true; @@ -1655,82 +1708,86 @@ describe('Resources discoverWork', () => { }); }); -describes.realWin('Resources contentHeight', { - amp: { - runtimeOn: true, +describes.realWin( + 'Resources contentHeight', + { + amp: { + runtimeOn: true, + }, }, -}, env => { - let win; - let resources; - let viewerSendMessageStub, viewportContentHeightChangedStub; + env => { + let win; + let resources; + let viewerSendMessageStub, viewportContentHeightChangedStub; - beforeEach(() => { - win = env.win; - resources = win.services.resources.obj; - viewerSendMessageStub = sandbox.stub(resources.viewer_, 'sendMessage'); - viewportContentHeightChangedStub = - sandbox.stub(resources.viewport_, 'contentHeightChanged'); - sandbox.stub(resources.vsync_, 'run').callsFake(task => { - task.measure({}); + beforeEach(() => { + win = env.win; + resources = win.services.resources.obj; + viewerSendMessageStub = sandbox.stub(resources.viewer_, 'sendMessage'); + viewportContentHeightChangedStub = sandbox.stub( + resources.viewport_, + 'contentHeightChanged' + ); + sandbox.stub(resources.vsync_, 'run').callsFake(task => { + task.measure({}); + }); }); - }); - - it('should measure initial contentHeight', () => { - const contentHeight = resources.viewport_.getContentHeight(); - expect(resources.maybeChangeHeight_).to.equal(false); - expect(resources.documentReady_).to.equal(true); - expect(resources.contentHeight_).to.equal(contentHeight); - }); - it('should send contentHeight to viewer if height was changed', () => { - sandbox.stub(resources.viewport_, 'getContentHeight').callsFake(() => { - return 200; + it('should measure initial contentHeight', () => { + const contentHeight = resources.viewport_.getContentHeight(); + expect(resources.maybeChangeHeight_).to.equal(false); + expect(resources.documentReady_).to.equal(true); + expect(resources.contentHeight_).to.equal(contentHeight); }); - resources.maybeChangeHeight_ = true; - resources.doPass(); + it('should send contentHeight to viewer if height was changed', () => { + sandbox.stub(resources.viewport_, 'getContentHeight').callsFake(() => { + return 200; + }); + resources.maybeChangeHeight_ = true; - expect(resources.maybeChangeHeight_).to.equal(false); - expect(resources.contentHeight_).to.equal(200); - expect(viewerSendMessageStub).to.be.calledOnce; - expect(viewerSendMessageStub.lastCall.args[0]).to.equal('documentHeight'); - expect(viewerSendMessageStub.lastCall.args[1].height).to.equal(200); - expect(viewerSendMessageStub.lastCall.args[2]).to.equal(true); - expect(viewportContentHeightChangedStub).to.be.calledOnce; - }); + resources.doPass(); - it('should not send contentHeight to viewer if height is not changed', () => { - const contentHeight = resources.viewport_.getContentHeight(); - resources.maybeChangeHeight_ = true; + expect(resources.maybeChangeHeight_).to.equal(false); + expect(resources.contentHeight_).to.equal(200); + expect(viewerSendMessageStub).to.be.calledOnce; + expect(viewerSendMessageStub.lastCall.args[0]).to.equal('documentHeight'); + expect(viewerSendMessageStub.lastCall.args[1].height).to.equal(200); + expect(viewerSendMessageStub.lastCall.args[2]).to.equal(true); + expect(viewportContentHeightChangedStub).to.be.calledOnce; + }); - resources.doPass(); + it('should not send contentHeight to viewer if height is not changed', () => { + const contentHeight = resources.viewport_.getContentHeight(); + resources.maybeChangeHeight_ = true; - expect(resources.maybeChangeHeight_).to.equal(false); - expect(resources.contentHeight_).to.equal(contentHeight); - expect(viewerSendMessageStub).to.not.be.called; - expect(viewportContentHeightChangedStub).to.not.be.called; - }); + resources.doPass(); - it('should send contentHeight to viewer if viewport resizes', () => { - sandbox.stub(resources.viewport_, 'getContentHeight').callsFake(() => { - return 200; + expect(resources.maybeChangeHeight_).to.equal(false); + expect(resources.contentHeight_).to.equal(contentHeight); + expect(viewerSendMessageStub).to.not.be.called; + expect(viewportContentHeightChangedStub).to.not.be.called; }); - resources.viewport_.changed_(/* relayoutAll */ true, /* velocity */ 0); - resources.doPass(); - expect(resources.maybeChangeHeight_).to.equal(false); - expect(resources.contentHeight_).to.equal(200); - expect(viewerSendMessageStub).to.be.calledOnce; - expect(viewerSendMessageStub.lastCall.args[0]).to.equal('documentHeight'); - expect(viewerSendMessageStub.lastCall.args[1].height).to.equal(200); - expect(viewerSendMessageStub.lastCall.args[2]).to.equal(true); - expect(viewportContentHeightChangedStub).to.be.calledOnce; - }); + it('should send contentHeight to viewer if viewport resizes', () => { + sandbox.stub(resources.viewport_, 'getContentHeight').callsFake(() => { + return 200; + }); + resources.viewport_.changed_(/* relayoutAll */ true, /* velocity */ 0); + resources.doPass(); -}); + expect(resources.maybeChangeHeight_).to.equal(false); + expect(resources.contentHeight_).to.equal(200); + expect(viewerSendMessageStub).to.be.calledOnce; + expect(viewerSendMessageStub.lastCall.args[0]).to.equal('documentHeight'); + expect(viewerSendMessageStub.lastCall.args[1].height).to.equal(200); + expect(viewerSendMessageStub.lastCall.args[2]).to.equal(true); + expect(viewportContentHeightChangedStub).to.be.calledOnce; + }); + } +); describe('Resources changeSize', () => { - function createElement(rect) { const signals = new Signals(); return { @@ -1759,9 +1816,11 @@ describe('Resources changeSize', () => { contains: unused_otherElement => false, updateLayoutBox: () => {}, togglePlaceholder: () => sandbox.spy(), - overflowCallback: - (unused_overflown, unused_requestedHeight, unused_requestedWidth) => { - }, + overflowCallback: ( + unused_overflown, + unused_requestedHeight, + unused_requestedWidth + ) => {}, getLayoutPriority: () => LayoutPriority.CONTENT, signals: () => signals, fakeComputedStyle: { @@ -1795,8 +1854,9 @@ describe('Resources changeSize', () => { resources.isRuntimeOn_ = false; resources.win = { getComputedStyle: el => { - return el.fakeComputedStyle ? - el.fakeComputedStyle : window.getComputedStyle(el); + return el.fakeComputedStyle + ? el.fakeComputedStyle + : window.getComputedStyle(el); }, }; viewportMock = sandbox.mock(resources.viewport_); @@ -1858,8 +1918,13 @@ describe('Resources changeSize', () => { }); it('should schedule margin only size change', () => { - resources.scheduleChangeSize_(resource1, undefined, undefined, - {top: 1, right: 2, bottom: 3, left: 4}, false); + resources.scheduleChangeSize_( + resource1, + undefined, + undefined, + {top: 1, right: 2, bottom: 3, left: 4}, + false + ); resources.vsync_.runScheduledTasks_(); expect(resources.requestsChangeSize_.length).to.equal(1); expect(resources.requestsChangeSize_[0].resource).to.equal(resource1); @@ -1883,7 +1948,7 @@ describe('Resources changeSize', () => { expect(resources.requestsChangeSize_[0].force).to.equal(true); }); - it('should NOT change size if it didn\'t change', () => { + it("should NOT change size if it didn't change", () => { resources.scheduleChangeSize_(resource1, 100, 100, undefined, true); resources.mutateWork_(); expect(resources.relayoutTop_).to.equal(-1); @@ -1957,19 +2022,38 @@ describe('Resources changeSize', () => { resource1.element.overflowCallback = overflowCallbackSpy; viewportRect = {top: 2, left: 0, right: 100, bottom: 200, height: 200}; - viewportMock.expects('getRect').returns(viewportRect).atLeast(1); - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 50, - height: 50}; + viewportMock + .expects('getRect') + .returns(viewportRect) + .atLeast(1); + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 50, + height: 50, + }; vsyncSpy = sandbox.stub(resources.vsync_, 'run'); resources.visible_ = true; }); it('should NOT change size when height is unchanged', () => { const callback = sandbox.spy(); - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 210, - height: 50}; - resources.scheduleChangeSize_(resource1, 50, /* width */ undefined, - undefined, false, callback); + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 210, + height: 50, + }; + resources.scheduleChangeSize_( + resource1, + 50, + /* width */ undefined, + undefined, + false, + callback + ); resources.mutateWork_(); expect(resource1.changeSize).to.not.been.called; expect(overflowCallbackSpy).to.not.been.called; @@ -1979,16 +2063,27 @@ describe('Resources changeSize', () => { it('should NOT change size when height and margins are unchanged', () => { const callback = sandbox.spy(); - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 210, - height: 50}; + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 210, + height: 50, + }; resource1.element.fakeComputedStyle = { marginTop: '1px', marginRight: '2px', marginBottom: '3px', marginLeft: '4px', }; - resources.scheduleChangeSize_(resource1, 50, /* width */ undefined, - {top: 1, right: 2, bottom: 3, left: 4}, false, callback); + resources.scheduleChangeSize_( + resource1, + 50, + /* width */ undefined, + {top: 1, right: 2, bottom: 3, left: 4}, + false, + callback + ); expect(vsyncSpy).to.be.calledOnce; const task = vsyncSpy.lastCall.args[0]; @@ -2003,16 +2098,27 @@ describe('Resources changeSize', () => { it('should change size when margins but not height changed', () => { const callback = sandbox.spy(); - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 210, - height: 50}; + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 210, + height: 50, + }; resource1.element.fakeComputedStyle = { marginTop: '1px', marginRight: '2px', marginBottom: '3px', marginLeft: '4px', }; - resources.scheduleChangeSize_(resource1, 50, /* width */ undefined, - {top: 1, right: 2, bottom: 4, left: 4}, false, callback); + resources.scheduleChangeSize_( + resource1, + 50, + /* width */ undefined, + {top: 1, right: 2, bottom: 4, left: 4}, + false, + callback + ); expect(vsyncSpy).to.be.calledOnce; const task = vsyncSpy.lastCall.args[0]; @@ -2032,19 +2138,20 @@ describe('Resources changeSize', () => { }); // TODO (#16156): duplicate stub for getVisibilityState on Safari - it.configure().skipSafari() - .run('should change size when document is invisible', () => { - resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); - resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); - resources.mutateWork_(); - expect(resources.requestsChangeSize_).to.be.empty; - expect(resource1.changeSize).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); - }); + it.configure() + .skipSafari() + .run('should change size when document is invisible', () => { + resources.visible_ = false; + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); + resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); + resources.mutateWork_(); + expect(resources.requestsChangeSize_).to.be.empty; + expect(resource1.changeSize).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); + }); it('should change size when active', () => { resource1.element.contains = () => true; @@ -2057,8 +2164,13 @@ describe('Resources changeSize', () => { }); it('should change size when below the viewport', () => { - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 1050, - height: 50}; + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 1050, + height: 50, + }; resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); expect(resources.requestsChangeSize_).to.be.empty; @@ -2067,34 +2179,15 @@ describe('Resources changeSize', () => { expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); }); - it('should change size when below the viewport and top margin also changed', - () => { - resource1.layoutBox_ = {top: 200, left: 0, right: 100, bottom: 300, - height: 100}; - resources.scheduleChangeSize_(resource1, 111, 222, {top: 20}, false); - - expect(vsyncSpy).to.be.calledOnce; - const marginsTask = vsyncSpy.lastCall.args[0]; - marginsTask.measure({}); - - resources.mutateWork_(); - expect(resources.requestsChangeSize_).to.be.empty; - expect(resource1.changeSize).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); - }); - - it('should change size when box top below the viewport but top margin ' + - 'boundary is above viewport but top margin in unchanged', () => { - resource1.layoutBox_ = {top: 200, left: 0, right: 100, bottom: 300, - height: 100}; - resource1.element.fakeComputedStyle = { - marginTop: '100px', - marginRight: '0px', - marginBottom: '0px', - marginLeft: '0px', + it('should change size when below the viewport and top margin also changed', () => { + resource1.layoutBox_ = { + top: 200, + left: 0, + right: 100, + bottom: 300, + height: 100, }; - resources.scheduleChangeSize_(resource1, 111, 222, {top: 100}, false); + resources.scheduleChangeSize_(resource1, 111, 222, {top: 20}, false); expect(vsyncSpy).to.be.calledOnce; const marginsTask = vsyncSpy.lastCall.args[0]; @@ -2107,30 +2200,83 @@ describe('Resources changeSize', () => { expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); }); - it('should NOT change size when top margin boundary within viewport ' + - 'and top margin changed', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - - const callback = sandbox.spy(); - resource1.layoutBox_ = {top: 100, left: 0, right: 100, bottom: 300, - height: 200}; - resources.scheduleChangeSize_( - resource1, 111, 222, {top: 20}, false, callback); - - expect(vsyncSpy).to.be.calledOnce; - const task = vsyncSpy.lastCall.args[0]; - task.measure({}); + it( + 'should change size when box top below the viewport but top margin ' + + 'boundary is above viewport but top margin in unchanged', + () => { + resource1.layoutBox_ = { + top: 200, + left: 0, + right: 100, + bottom: 300, + height: 100, + }; + resource1.element.fakeComputedStyle = { + marginTop: '100px', + marginRight: '0px', + marginBottom: '0px', + marginLeft: '0px', + }; + resources.scheduleChangeSize_(resource1, 111, 222, {top: 100}, false); + + expect(vsyncSpy).to.be.calledOnce; + const marginsTask = vsyncSpy.lastCall.args[0]; + marginsTask.measure({}); + + resources.mutateWork_(); + expect(resources.requestsChangeSize_).to.be.empty; + expect(resource1.changeSize).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); + } + ); - resources.mutateWork_(); - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.not.been.called; - expect(callback).to.be.calledOnce; - expect(callback.args[0][0]).to.be.false; - }); + it( + 'should NOT change size when top margin boundary within viewport ' + + 'and top margin changed', + () => { + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + + const callback = sandbox.spy(); + resource1.layoutBox_ = { + top: 100, + left: 0, + right: 100, + bottom: 300, + height: 200, + }; + resources.scheduleChangeSize_( + resource1, + 111, + 222, + {top: 20}, + false, + callback + ); + + expect(vsyncSpy).to.be.calledOnce; + const task = vsyncSpy.lastCall.args[0]; + task.measure({}); + + resources.mutateWork_(); + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.not.been.called; + expect(callback).to.be.calledOnce; + expect(callback.args[0][0]).to.be.false; + } + ); it('should defer when above the viewport and scrolling on', () => { - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050, - height: 50}; + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1050, + height: 50, + }; resources.lastVelocity_ = 10; resources.lastScrollTime_ = Date.now(); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); @@ -2140,41 +2286,71 @@ describe('Resources changeSize', () => { expect(overflowCallbackSpy).to.not.been.called; }); - it('should defer change size if just inside viewport and viewport ' + - 'scrolled by user.', () => { - viewportRect.top = 2; - resource1.layoutBox_ = {top: -50, left: 0, right: 100, bottom: 1, - height: 51}; - resources.lastVelocity_ = 10; - resources.lastScrollTime_ = Date.now(); - resources.scheduleChangeSize_(resource1, 111, 222, false); - resources.mutateWork_(); - expect(resources.requestsChangeSize_.length).to.equal(1); - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.not.been.called; - }); + it( + 'should defer change size if just inside viewport and viewport ' + + 'scrolled by user.', + () => { + viewportRect.top = 2; + resource1.layoutBox_ = { + top: -50, + left: 0, + right: 100, + bottom: 1, + height: 51, + }; + resources.lastVelocity_ = 10; + resources.lastScrollTime_ = Date.now(); + resources.scheduleChangeSize_(resource1, 111, 222, false); + resources.mutateWork_(); + expect(resources.requestsChangeSize_.length).to.equal(1); + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.not.been.called; + } + ); - it('should NOT change size and call overflow callback if viewport not ' + - 'scrolled by user.', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - viewportRect.top = 1; - resource1.layoutBox_ = {top: -50, left: 0, right: 100, bottom: 0, - height: 51}; - resources.lastVelocity_ = 10; - resources.lastScrollTime_ = Date.now(); - resources.scheduleChangeSize_(resource1, 111, 222, false); - resources.mutateWork_(); - expect(resources.requestsChangeSize_.length).to.equal(0); - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222); - }); + it( + 'should NOT change size and call overflow callback if viewport not ' + + 'scrolled by user.', + () => { + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + viewportRect.top = 1; + resource1.layoutBox_ = { + top: -50, + left: 0, + right: 100, + bottom: 0, + height: 51, + }; + resources.lastVelocity_ = 10; + resources.lastScrollTime_ = Date.now(); + resources.scheduleChangeSize_(resource1, 111, 222, false); + resources.mutateWork_(); + expect(resources.requestsChangeSize_.length).to.equal(0); + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222); + } + ); it('should change size when above the vp and adjust scrolling', () => { - viewportMock.expects('getScrollHeight').returns(2999).once(); - viewportMock.expects('getScrollTop').returns(1777).once(); - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050, - height: 50}; + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); + viewportMock + .expects('getScrollTop') + .returns(1777) + .once(); + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1050, + height: 50, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); @@ -2189,8 +2365,14 @@ describe('Resources changeSize', () => { expect(state.scrollTop).to.equal(1777); expect(state.scrollHeight).to.equal(2999); - viewportMock.expects('getScrollHeight').returns(3999).once(); - viewportMock.expects('setScrollTop').withExactArgs(2777).once(); + viewportMock + .expects('getScrollHeight') + .returns(3999) + .once(); + viewportMock + .expects('setScrollTop') + .withExactArgs(2777) + .once(); task.mutate(state); expect(resource1.changeSize).to.be.calledOnce; expect(resource1.changeSize).to.be.calledWith(111, 222); @@ -2198,8 +2380,13 @@ describe('Resources changeSize', () => { }); it('should NOT resize when above vp but cannot adjust scrolling', () => { - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1100, - height: 100}; + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1100, + height: 100, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 0, 222, undefined, false); @@ -2213,10 +2400,20 @@ describe('Resources changeSize', () => { }); it('should resize if multi request above vp can adjust scroll', () => { - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1100, - height: 100}; - resource2.layoutBox_ = {top: -1300, left: 0, right: 100, bottom: -1200, - height: 100}; + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1100, + height: 100, + }; + resource2.layoutBox_ = { + top: -1300, + left: 0, + right: 100, + bottom: -1200, + height: 100, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource2, 200, 222, undefined, false); @@ -2236,12 +2433,26 @@ describe('Resources changeSize', () => { resources.viewport_.getRect(); viewportMock.expects('getRect').returns({ - top: 10, left: 0, right: 100, bottom: 210, height: 200, + top: 10, + left: 0, + right: 100, + bottom: 210, + height: 200, }); - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1100, - height: 100}; - resource2.layoutBox_ = {top: -1300, left: 0, right: 100, bottom: -1200, - height: 100}; + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1100, + height: 100, + }; + resource2.layoutBox_ = { + top: -1300, + left: 0, + right: 100, + bottom: -1200, + height: 100, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 92, 222, undefined, false); @@ -2255,10 +2466,21 @@ describe('Resources changeSize', () => { }); it('should NOT adjust scrolling if height not change above vp', () => { - viewportMock.expects('getScrollHeight').returns(2999).once(); - viewportMock.expects('getScrollTop').returns(1777).once(); - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050, - height: 50}; + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); + viewportMock + .expects('getScrollTop') + .returns(1777) + .once(); + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1050, + height: 50, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); @@ -2273,7 +2495,10 @@ describe('Resources changeSize', () => { expect(state.scrollTop).to.equal(1777); expect(state.scrollHeight).to.equal(2999); - viewportMock.expects('getScrollHeight').returns(2999).once(); + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); viewportMock.expects('setScrollTop').never(); task.mutate(state); expect(resource1.changeSize).to.be.calledOnce; @@ -2282,10 +2507,21 @@ describe('Resources changeSize', () => { }); it('should adjust scrolling if height change above vp', () => { - viewportMock.expects('getScrollHeight').returns(2999).once(); - viewportMock.expects('getScrollTop').returns(1000).once(); - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050, - height: 50}; + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); + viewportMock + .expects('getScrollTop') + .returns(1000) + .once(); + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1050, + height: 50, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); @@ -2293,13 +2529,22 @@ describe('Resources changeSize', () => { const task = vsyncSpy.lastCall.args[0]; const state = {}; task.measure(state); - viewportMock.expects('getScrollHeight').returns(2000).once(); - viewportMock.expects('setScrollTop').withExactArgs(1).once(); + viewportMock + .expects('getScrollHeight') + .returns(2000) + .once(); + viewportMock + .expects('setScrollTop') + .withExactArgs(1) + .once(); task.mutate(state); }); it('in vp should NOT call overflowCallback if new height smaller', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); resources.scheduleChangeSize_(resource1, 10, 11, undefined, false); resources.mutateWork_(); expect(resources.requestsChangeSize_).to.be.empty; @@ -2307,79 +2552,93 @@ describe('Resources changeSize', () => { expect(overflowCallbackSpy).to.not.been.called; }); - it('in viewport should change size if in the last 15% and ' + - 'in the last 1000px', () => { - viewportRect.top = 9600; - viewportRect.bottom = 9800; - resource1.layoutBox_ = {top: 9650, left: 0, right: 100, bottom: 9700, - height: 50}; - resources.scheduleChangeSize_(resource1, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}, false); - - expect(vsyncSpy).to.be.calledOnce; - const marginsTask = vsyncSpy.lastCall.args[0]; - marginsTask.measure({}); - - resources.mutateWork_(); - expect(resources.requestsChangeSize_).to.be.empty; - expect(resource1.changeSize).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); - }); - - it('in viewport should NOT change size if in the last 15% but NOT ' + - 'in the last 1000px', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - viewportRect.top = 8600; - viewportRect.bottom = 8800; - resource1.layoutBox_ = {top: 8650, left: 0, right: 100, bottom: 8700, - height: 50}; - resources.scheduleChangeSize_(resource1, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}, false); - - expect(vsyncSpy).to.be.calledOnce; - const marginsTask = vsyncSpy.lastCall.args[0]; - marginsTask.measure({}); + it( + 'in viewport should change size if in the last 15% and ' + + 'in the last 1000px', + () => { + viewportRect.top = 9600; + viewportRect.bottom = 9800; + resource1.layoutBox_ = { + top: 9650, + left: 0, + right: 100, + bottom: 9700, + height: 50, + }; + resources.scheduleChangeSize_( + resource1, + 111, + 222, + {top: 1, right: 2, bottom: 3, left: 4}, + false + ); + + expect(vsyncSpy).to.be.calledOnce; + const marginsTask = vsyncSpy.lastCall.args[0]; + marginsTask.measure({}); + + resources.mutateWork_(); + expect(resources.requestsChangeSize_).to.be.empty; + expect(resource1.changeSize).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); + } + ); - resources.mutateWork_(); - expect(resources.requestsChangeSize_).to.be.empty; - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}); - }); + it( + 'in viewport should NOT change size if in the last 15% but NOT ' + + 'in the last 1000px', + () => { + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + viewportRect.top = 8600; + viewportRect.bottom = 8800; + resource1.layoutBox_ = { + top: 8650, + left: 0, + right: 100, + bottom: 8700, + height: 50, + }; + resources.scheduleChangeSize_( + resource1, + 111, + 222, + {top: 1, right: 2, bottom: 3, left: 4}, + false + ); + + expect(vsyncSpy).to.be.calledOnce; + const marginsTask = vsyncSpy.lastCall.args[0]; + marginsTask.measure({}); + + resources.mutateWork_(); + expect(resources.requestsChangeSize_).to.be.empty; + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222, { + top: 1, + right: 2, + bottom: 3, + left: 4, + }); + } + ); it('in viewport should NOT change size and calls overflowCallback', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - resources.scheduleChangeSize_(resource1, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}, false); - - expect(vsyncSpy).to.be.calledOnce; - const task = vsyncSpy.lastCall.args[0]; - task.measure({}); - - resources.mutateWork_(); - expect(resources.requestsChangeSize_.length).to.equal(0); - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}); - expect(resource1.getPendingChangeSize()).to.jsonEqual( - {height: 111, width: 222, - margins: {top: 1, right: 2, bottom: 3, left: 4}}); - }); - - it('should NOT change size when resized margin in viewport and should ' + - 'call overflowCallback', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - resource1.layoutBox_ = {top: -48, left: 0, right: 100, bottom: 2, - height: 50}; - resource1.element.fakeComputedStyle = { - marginBottom: '21px', - }; - - resources.scheduleChangeSize_(resource1, undefined, undefined, - {bottom: 22}, false); + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + resources.scheduleChangeSize_( + resource1, + 111, + 222, + {top: 1, right: 2, bottom: 3, left: 4}, + false + ); expect(vsyncSpy).to.be.calledOnce; const task = vsyncSpy.lastCall.args[0]; @@ -2389,25 +2648,97 @@ describe('Resources changeSize', () => { expect(resources.requestsChangeSize_.length).to.equal(0); expect(resource1.changeSize).to.not.been.called; expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledWith(true, undefined, - undefined, {bottom: 22}); - expect(resource1.getPendingChangeSize()).to.jsonEqual( - {height: undefined, width: undefined, margins: {bottom: 22}}); + expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222, { + top: 1, + right: 2, + bottom: 3, + left: 4, + }); + expect(resource1.getPendingChangeSize()).to.jsonEqual({ + height: 111, + width: 222, + margins: {top: 1, right: 2, bottom: 3, left: 4}, + }); }); + it( + 'should NOT change size when resized margin in viewport and should ' + + 'call overflowCallback', + () => { + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + resource1.layoutBox_ = { + top: -48, + left: 0, + right: 100, + bottom: 2, + height: 50, + }; + resource1.element.fakeComputedStyle = { + marginBottom: '21px', + }; + + resources.scheduleChangeSize_( + resource1, + undefined, + undefined, + {bottom: 22}, + false + ); + + expect(vsyncSpy).to.be.calledOnce; + const task = vsyncSpy.lastCall.args[0]; + task.measure({}); + + resources.mutateWork_(); + expect(resources.requestsChangeSize_.length).to.equal(0); + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledWith( + true, + undefined, + undefined, + {bottom: 22} + ); + expect(resource1.getPendingChangeSize()).to.jsonEqual({ + height: undefined, + width: undefined, + margins: {bottom: 22}, + }); + } + ); + it('should change size when resized margin above viewport', () => { - resource1.layoutBox_ = {top: -49, left: 0, right: 100, bottom: 1, - height: 50}; + resource1.layoutBox_ = { + top: -49, + left: 0, + right: 100, + bottom: 1, + height: 50, + }; resource1.element.fakeComputedStyle = { marginBottom: '21px', }; - viewportMock.expects('getScrollHeight').returns(2999).once(); - viewportMock.expects('getScrollTop').returns(1777).once(); + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); + viewportMock + .expects('getScrollTop') + .returns(1777) + .once(); resources.lastVelocity_ = 0; clock.tick(5000); - resources.scheduleChangeSize_(resource1, undefined, undefined, - {top: 1}, false); + resources.scheduleChangeSize_( + resource1, + undefined, + undefined, + {top: 1}, + false + ); expect(vsyncSpy).to.be.calledOnce; const marginsTask = vsyncSpy.lastCall.args[0]; @@ -2424,17 +2755,27 @@ describe('Resources changeSize', () => { expect(state.scrollTop).to.equal(1777); expect(state.scrollHeight).to.equal(2999); - viewportMock.expects('getScrollHeight').returns(3999).once(); - viewportMock.expects('setScrollTop').withExactArgs(2777).once(); + viewportMock + .expects('getScrollHeight') + .returns(3999) + .once(); + viewportMock + .expects('setScrollTop') + .withExactArgs(2777) + .once(); scrollAdjustTask.mutate(state); expect(resource1.changeSize).to.be.calledOnce; - expect(resource1.changeSize).to.be.calledWith(undefined, undefined, - {top: 1}); + expect(resource1.changeSize).to.be.calledWith(undefined, undefined, { + top: 1, + }); expect(resources.relayoutTop_).to.equal(resource1.layoutBox_.top); }); it('should reset pending change size when rescheduling', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); expect(resource1.getPendingChangeSize().height).to.equal(111); @@ -2445,11 +2786,16 @@ describe('Resources changeSize', () => { }); it('should force resize after focus', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); - expect(resource1.getPendingChangeSize()).to.jsonEqual( - {height: 111, width: 222}); + expect(resource1.getPendingChangeSize()).to.jsonEqual({ + height: 111, + width: 222, + }); expect(resources.requestsChangeSize_).to.be.empty; resources.checkPendingChangeSize_(resource1.element); @@ -2466,23 +2812,33 @@ describe('Resources changeSize', () => { }); describe('attemptChangeSize rules for element wrt document', () => { - beforeEach(() => { - viewportMock.expects('getRect').returns( - {top: 0, left: 0, right: 100, bottom: 10000, height: 200}); - resource1.layoutBox_ = resource1.initialLayoutBox_ = - layoutRectLtwh(0, 10, 100, 100); + viewportMock + .expects('getRect') + .returns({top: 0, left: 0, right: 100, bottom: 10000, height: 200}); + resource1.layoutBox_ = resource1.initialLayoutBox_ = layoutRectLtwh( + 0, + 10, + 100, + 100 + ); }); it('should NOT change size when far the bottom of the document', () => { - viewportMock.expects('getContentHeight').returns(10000).once(); + viewportMock + .expects('getContentHeight') + .returns(10000) + .once(); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); expect(resource1.changeSize).to.not.been.called; }); it('should change size when close to the bottom of the document', () => { - viewportMock.expects('getContentHeight').returns(110).once(); + viewportMock + .expects('getContentHeight') + .returns(110) + .once(); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); expect(resource1.changeSize).to.be.calledOnce; @@ -2490,9 +2846,7 @@ describe('Resources changeSize', () => { }); }); - describe('Resources mutateElement and collapse', () => { - function createElement(rect, isAmp) { const signals = new Signals(); return { @@ -2533,9 +2887,10 @@ describe('Resources mutateElement and collapse', () => { function createResource(id, rect) { const resource = new Resource( - id, - createElement(rect, /* isAmp */ true), - resources); + id, + createElement(rect, /* isAmp */ true), + resources + ); resource.element['__AMP__RESOURCE'] = resource; resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = rect; @@ -2590,10 +2945,14 @@ describe('Resources mutateElement and collapse', () => { resource1RequestMeasureStub = sandbox.stub(resource1, 'requestMeasure'); resource2RequestMeasureStub = sandbox.stub(resource2, 'requestMeasure'); - parent1 = createElement(layoutRectLtwh(10, 10, 100, 100), - /* isAmp */ false); - parent2 = createElement(layoutRectLtwh(10, 1010, 100, 100), - /* isAmp */ false); + parent1 = createElement( + layoutRectLtwh(10, 10, 100, 100), + /* isAmp */ false + ); + parent2 = createElement( + layoutRectLtwh(10, 1010, 100, 100), + /* isAmp */ false + ); parent1.getElementsByClassName = className => { if (className == 'i-amphtml-element') { @@ -2630,8 +2989,8 @@ describe('Resources mutateElement and collapse', () => { it('should mutate from visible to invisible on itself', () => { const mutateSpy = sandbox.spy(); const promise = resources.mutateElement(resource1.element, () => { - resource1.element.getBoundingClientRect = - () => layoutRectLtwh(0, 0, 0, 0); + resource1.element.getBoundingClientRect = () => + layoutRectLtwh(0, 0, 0, 0); mutateSpy(); }); return promise.then(() => { @@ -2711,8 +3070,13 @@ describe('Resources mutateElement and collapse', () => { index++; }); - resource1.layoutBox_ = {top: 1000, left: 0, right: 100, bottom: 1050, - height: 50}; + resource1.layoutBox_ = { + top: 1000, + left: 0, + right: 100, + bottom: 1050, + height: 50, + }; resources.lastVelocity_ = 0; resources.attemptCollapse(resource1.element); resources.mutateWork_(); @@ -2720,19 +3084,25 @@ describe('Resources mutateElement and collapse', () => { }); it('attemptCollapse should complete collapse if resize succeed', () => { - sandbox.stub(resources, 'scheduleChangeSize_').callsFake( + sandbox + .stub(resources, 'scheduleChangeSize_') + .callsFake( (resource, newHeight, newWidth, newMargins, force, callback) => { callback(true); - }); + } + ); resources.attemptCollapse(resource1.element); expect(resource1.completeCollapse).to.be.calledOnce; }); it('attemptCollapse should NOT complete collapse if resize fail', () => { - sandbox.stub(resources, 'scheduleChangeSize_').callsFake( + sandbox + .stub(resources, 'scheduleChangeSize_') + .callsFake( (resource, newHeight, newWidth, newMargins, force, callback) => { callback(false); - }); + } + ); resources.attemptCollapse(resource1.element); expect(resource1.completeCollapse).to.not.been.called; }); @@ -2754,7 +3124,6 @@ describe('Resources mutateElement and collapse', () => { }); }); - describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { let resources; let parent; @@ -2828,12 +3197,13 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { resource2 = child2['__AMP__RESOURCE']; }); - afterEach(() => { - }); + afterEach(() => {}); it('should enforce that viewport is ready for first add', () => { - const ensureViewportReady = sandbox.stub(resources.viewport_, - 'ensureReadyForElements'); + const ensureViewportReady = sandbox.stub( + resources.viewport_, + 'ensureReadyForElements' + ); resources.add(child1); expect(ensureViewportReady).to.be.calledOnce; @@ -2862,53 +3232,60 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { }); // TODO(jridgewell, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should not schedule pass when immediate ' + - 'build fails', () => { - const schedulePassStub = sandbox.stub(resources, 'schedulePass'); - child1.isBuilt = () => false; - const child1BuildSpy = sandbox.spy(); - child1.build = () => { - // Emulate an error happening during an element build. - child1BuildSpy(); - return Promise.reject(new Error('child1-build-error')); - }; - resources.documentReady_ = true; - resources.add(child1); - const resource1 = stubBuild(Resource.forElementOptional(child1)); - resources.upgraded(child1); - expect(resources.get()).to.contain(resource1); - return resource1.buildPromise.then(() => { - throw new Error('must have failed'); - }, () => { - expect(child1BuildSpy).to.be.calledOnce; - expect(schedulePassStub).to.not.be.called; - expect(resources.get()).to.not.contain(resource1); - }); - }); + it.configure().skipSafari( + 'should not schedule pass when immediate build fails', + () => { + const schedulePassStub = sandbox.stub(resources, 'schedulePass'); + child1.isBuilt = () => false; + const child1BuildSpy = sandbox.spy(); + child1.build = () => { + // Emulate an error happening during an element build. + child1BuildSpy(); + return Promise.reject(new Error('child1-build-error')); + }; + resources.documentReady_ = true; + resources.add(child1); + const resource1 = stubBuild(Resource.forElementOptional(child1)); + resources.upgraded(child1); + expect(resources.get()).to.contain(resource1); + return resource1.buildPromise.then( + () => { + throw new Error('must have failed'); + }, + () => { + expect(child1BuildSpy).to.be.calledOnce; + expect(schedulePassStub).to.not.be.called; + expect(resources.get()).to.not.contain(resource1); + } + ); + } + ); // TODO(amphtml, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should add element to pending build when ' + - 'document is not ready', () => { - child1.isBuilt = () => false; - child2.isBuilt = () => false; - resources.buildReadyResources_ = sandbox.spy(); - resources.documentReady_ = false; - resources.add(child1); - resources.upgraded(child1); - expect(child1.build.called).to.be.false; - expect(resources.pendingBuildResources_.length).to.be.equal(1); - resources.add(child2); - resources.upgraded(child2); - expect(child2.build.called).to.be.false; - expect(resources.pendingBuildResources_.length).to.be.equal(2); - expect(resources.buildReadyResources_.calledTwice).to.be.true; - const resource1 = Resource.forElementOptional(child1); - const resource2 = Resource.forElementOptional(child2); - expect(resources.get()).to.contain(resource1); - expect(resources.get()).to.contain(resource2); - expect(resource1.isBuilding()).to.be.false; - expect(resource2.isBuilding()).to.be.false; - }); + it.configure().skipSafari( + 'should add element to pending build when document is not ready', + () => { + child1.isBuilt = () => false; + child2.isBuilt = () => false; + resources.buildReadyResources_ = sandbox.spy(); + resources.documentReady_ = false; + resources.add(child1); + resources.upgraded(child1); + expect(child1.build.called).to.be.false; + expect(resources.pendingBuildResources_.length).to.be.equal(1); + resources.add(child2); + resources.upgraded(child2); + expect(child2.build.called).to.be.false; + expect(resources.pendingBuildResources_.length).to.be.equal(2); + expect(resources.buildReadyResources_.calledTwice).to.be.true; + const resource1 = Resource.forElementOptional(child1); + const resource2 = Resource.forElementOptional(child2); + expect(resources.get()).to.contain(resource1); + expect(resources.get()).to.contain(resource2); + expect(resource1.isBuilding()).to.be.false; + expect(resource2.isBuilding()).to.be.false; + } + ); describe('buildReadyResources_', () => { let schedulePassStub; @@ -2924,42 +3301,46 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { }); // TODO(amphtml, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should build ready resources and remove ' + - 'them from pending', () => { - resources.pendingBuildResources_ = [resource1, resource2]; - resources.buildReadyResources_(); - expect(child1.build.called).to.be.false; - expect(child2.build.called).to.be.false; - expect(resources.pendingBuildResources_.length).to.be.equal(2); - expect(resources.schedulePass.called).to.be.false; - - child1.nextSibling = child2; - resources.buildReadyResources_(); - expect(child1.build.called).to.be.true; - expect(child2.build.called).to.be.false; - expect(resources.pendingBuildResources_.length).to.be.equal(1); - expect(resources.pendingBuildResources_[0]).to.be.equal(resource2); - expect(resource1.isBuilding()).to.be.true; - expect(resource2.isBuilding()).to.be.false; - return resource1.buildPromise.then(() => { - expect(resources.schedulePass.calledOnce).to.be.true; + it.configure().skipSafari( + 'should build ready resources and remove them from pending', + () => { + resources.pendingBuildResources_ = [resource1, resource2]; + resources.buildReadyResources_(); + expect(child1.build.called).to.be.false; + expect(child2.build.called).to.be.false; + expect(resources.pendingBuildResources_.length).to.be.equal(2); + expect(resources.schedulePass.called).to.be.false; - child2.parentNode = parent; - parent.nextSibling = true; + child1.nextSibling = child2; resources.buildReadyResources_(); - expect(child1.build).to.be.calledOnce; - expect(child2.build.called).to.be.true; - expect(resources.pendingBuildResources_.length).to.be.equal(0); - expect(resource2.isBuilding()).to.be.true; - return resource2.buildPromise; - }).then(() => { - expect(resources.get()).to.contain(resource1); - expect(resources.get()).to.contain(resource2); - expect(resource1.isBuilding()).to.be.false; + expect(child1.build.called).to.be.true; + expect(child2.build.called).to.be.false; + expect(resources.pendingBuildResources_.length).to.be.equal(1); + expect(resources.pendingBuildResources_[0]).to.be.equal(resource2); + expect(resource1.isBuilding()).to.be.true; expect(resource2.isBuilding()).to.be.false; - expect(resources.schedulePass.calledTwice).to.be.true; - }); - }); + return resource1.buildPromise + .then(() => { + expect(resources.schedulePass.calledOnce).to.be.true; + + child2.parentNode = parent; + parent.nextSibling = true; + resources.buildReadyResources_(); + expect(child1.build).to.be.calledOnce; + expect(child2.build.called).to.be.true; + expect(resources.pendingBuildResources_.length).to.be.equal(0); + expect(resource2.isBuilding()).to.be.true; + return resource2.buildPromise; + }) + .then(() => { + expect(resources.get()).to.contain(resource1); + expect(resources.get()).to.contain(resource2); + expect(resource1.isBuilding()).to.be.false; + expect(resource2.isBuilding()).to.be.false; + expect(resources.schedulePass.calledTwice).to.be.true; + }); + } + ); it('should NOT build past the root node when pending', () => { resources.pendingBuildResources_ = [resource1]; @@ -3015,62 +3396,76 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { }); // TODO(amphtml, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should build everything pending when ' + - 'document is ready', () => { - resources.documentReady_ = true; - resources.pendingBuildResources_ = [parentResource, resource1, resource2]; - const child1BuildSpy = sandbox.spy(); - child1.build = () => { - // Emulate an error happening during an element build. - child1BuildSpy(); - return Promise.reject(new Error('child1-build-error')); - }; - resources.buildReadyResources_(); - expect(child1BuildSpy.called).to.be.true; - expect(child2.build.called).to.be.true; - expect(parent.build.called).to.be.true; - expect(resources.pendingBuildResources_.length).to.be.equal(0); - return Promise.all([ - parentResource.buildPromise, - resource2.buildPromise, - resource1.buildPromise.then(() => { - throw new Error('must have failed'); - }, () => { - // Ignore error. - }), - ]).then(() => { - expect(schedulePassStub).to.be.calledTwice; - // Failed build. - expect(resources.get()).to.not.contain(resource1); - expect(resource1.isBuilding()).to.be.false; - // Successful build. - expect(resources.get()).to.contain(resource2); - expect(resource2.isBuilding()).to.be.false; - }); - }); + it.configure().skipSafari( + 'should build everything pending when document is ready', + () => { + resources.documentReady_ = true; + resources.pendingBuildResources_ = [ + parentResource, + resource1, + resource2, + ]; + const child1BuildSpy = sandbox.spy(); + child1.build = () => { + // Emulate an error happening during an element build. + child1BuildSpy(); + return Promise.reject(new Error('child1-build-error')); + }; + resources.buildReadyResources_(); + expect(child1BuildSpy.called).to.be.true; + expect(child2.build.called).to.be.true; + expect(parent.build.called).to.be.true; + expect(resources.pendingBuildResources_.length).to.be.equal(0); + return Promise.all([ + parentResource.buildPromise, + resource2.buildPromise, + resource1.buildPromise.then( + () => { + throw new Error('must have failed'); + }, + () => { + // Ignore error. + } + ), + ]).then(() => { + expect(schedulePassStub).to.be.calledTwice; + // Failed build. + expect(resources.get()).to.not.contain(resource1); + expect(resource1.isBuilding()).to.be.false; + // Successful build. + expect(resources.get()).to.contain(resource2); + expect(resource2.isBuilding()).to.be.false; + }); + } + ); // TODO(amphtml, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should not schedule pass if all ' + - 'builds failed', () => { - resources.documentReady_ = true; - resources.pendingBuildResources_ = [resource1]; - const child1BuildSpy = sandbox.spy(); - child1.build = () => { - // Emulate an error happening during an element build. - child1BuildSpy(); - return Promise.reject(new Error('child1-build-error')); - }; - resources.buildReadyResources_(); - expect(child1BuildSpy.called).to.be.true; - expect(resources.pendingBuildResources_.length).to.be.equal(0); - return resource1.buildPromise.then(() => { - throw new Error('must have failed'); - }, () => { - expect(schedulePassStub).to.not.be.called; - expect(resources.get()).to.not.contain(resource1); - expect(resource1.isBuilding()).to.be.false; - }); - }); + it.configure().skipSafari( + 'should not schedule pass if all builds failed', + () => { + resources.documentReady_ = true; + resources.pendingBuildResources_ = [resource1]; + const child1BuildSpy = sandbox.spy(); + child1.build = () => { + // Emulate an error happening during an element build. + child1BuildSpy(); + return Promise.reject(new Error('child1-build-error')); + }; + resources.buildReadyResources_(); + expect(child1BuildSpy.called).to.be.true; + expect(resources.pendingBuildResources_.length).to.be.equal(0); + return resource1.buildPromise.then( + () => { + throw new Error('must have failed'); + }, + () => { + expect(schedulePassStub).to.not.be.called; + expect(resources.get()).to.not.contain(resource1); + expect(resource1.isBuilding()).to.be.false; + } + ); + } + ); }); describe('remove', () => { @@ -3108,7 +3503,9 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { beforeEach(() => { scheduleBuildStub = sandbox.stub( - resources, 'buildOrScheduleBuildForResource_'); + resources, + 'buildOrScheduleBuildForResource_' + ); child1.isBuilt = () => true; resources.add(child1); resources.upgraded(child1); diff --git a/test/unit/test-runtime.js b/test/unit/test-runtime.js index 0c846619683b..feb2a3e0399e 100644 --- a/test/unit/test-runtime.js +++ b/test/unit/test-runtime.js @@ -20,11 +20,7 @@ import * as styles from '../../src/style-installer'; import {AmpDocShadow, AmpDocSingle} from '../../src/service/ampdoc-impl'; import {ElementStub} from '../../src/element-stub'; import {Services} from '../../src/services'; -import { - adopt, - adoptShadowMode, - installAmpdocServices, -} from '../../src/runtime'; +import {adopt, adoptShadowMode, installAmpdocServices} from '../../src/runtime'; import {createShadowRoot} from '../../src/shadow-embed'; import {deactivateChunking, runChunksForTesting} from '../../src/chunk'; import { @@ -38,1697 +34,1860 @@ import {installTimerService} from '../../src/service/timer-impl'; import {toggleExperiment} from '../../src/experiments'; import {vsyncForTesting} from '../../src/service/vsync-impl'; +describes.fakeWin( + 'runtime', + { + location: 'https://cdn.ampproject.org/c/s/www.example.com/path', + }, + env => { + let win; + let clock; + let ampdocService; + let ampdocServiceMock; + let extensionElementIndex; -describes.fakeWin('runtime', { - location: 'https://cdn.ampproject.org/c/s/www.example.com/path', -}, env => { - let win; - let clock; - let ampdocService; - let ampdocServiceMock; - let extensionElementIndex; - - beforeEach(() => { - win = env.win; - clock = env.sandbox.useFakeTimers(); - extensionElementIndex = 0; - ampdocService = { - isSingleDoc: () => true, - getAmpDoc: () => null, - installShadowDoc_: () => null, - }; - ampdocServiceMock = sandbox.mock(ampdocService); - win.AMP = []; - win.services = { - ampdoc: {obj: ampdocService}, - }; - const ampdoc = new AmpDocSingle(win); - ampdocService.getAmpDoc = () => ampdoc; - installDocumentStateService(win); - installPlatformService(win); - installTimerService(win); - vsyncForTesting(win); - installAmpdocServices(ampdoc); - }); - - - function regularExtension(fn, opt_version) { - return { - n: 'amp-test-element' + extensionElementIndex++, - f: fn, - // Default version of uncompiled sources. - v: opt_version || '$internalRuntimeVersion$', - }; - } + beforeEach(() => { + win = env.win; + clock = env.sandbox.useFakeTimers(); + extensionElementIndex = 0; + ampdocService = { + isSingleDoc: () => true, + getAmpDoc: () => null, + installShadowDoc_: () => null, + }; + ampdocServiceMock = sandbox.mock(ampdocService); + win.AMP = []; + win.services = { + ampdoc: {obj: ampdocService}, + }; + const ampdoc = new AmpDocSingle(win); + ampdocService.getAmpDoc = () => ampdoc; + installDocumentStateService(win); + installPlatformService(win); + installTimerService(win); + vsyncForTesting(win); + installAmpdocServices(ampdoc); + }); + + function regularExtension(fn, opt_version) { + return { + n: 'amp-test-element' + extensionElementIndex++, + f: fn, + // Default version of uncompiled sources. + v: opt_version || '$internalRuntimeVersion$', + }; + } - afterEach(() => { - ampdocServiceMock.verify(); - }); - - it('should convert AMP from array to AMP object in single-doc', () => { - expect(win.AMP.push).to.equal([].push); - adopt(win); - expect(win.AMP.push).to.not.equal([].push); - expect(win.AMP_TAG).to.be.true; - }); - - it('should convert AMP from array to AMP object in shadow-doc', () => { - expect(win.AMP.push).to.equal([].push); - adoptShadowMode(win); - expect(win.AMP.push).to.not.equal([].push); - expect(win.AMP_TAG).to.be.true; - }); - - it('should install legacy stubs in single-doc', () => { - const initial = win.ampExtendedElements || {}; - expect(initial['amp-ad']).to.be.undefined; - expect(initial['amp-embed']).to.be.undefined; - expect(initial['amp-video']).to.be.undefined; - adopt(win); - expect(win.ampExtendedElements['amp-ad']).to.equal(ElementStub); - expect(win.ampExtendedElements['amp-embed']).to.equal(ElementStub); - expect(win.ampExtendedElements['amp-video']).to.equal(ElementStub); - }); - - it('should install legacy stubs in shadow-doc', () => { - const initial = win.ampExtendedElements || {}; - expect(initial['amp-ad']).to.be.undefined; - expect(initial['amp-embed']).to.be.undefined; - expect(initial['amp-video']).to.be.undefined; - adoptShadowMode(win); - expect(win.ampExtendedElements['amp-ad']).to.equal(ElementStub); - expect(win.ampExtendedElements['amp-embed']).to.equal(ElementStub); - expect(win.ampExtendedElements['amp-video']).to.equal(ElementStub); - }); - - it('should NOT set cursor:pointer on document element on non-IOS', () => { - const platform = Services.platformFor(win); - sandbox.stub(platform, 'isIos').returns(false); - adopt(win); - expect(win.document.documentElement.style.cursor).to.not.be.ok; - }); - - it('should set cursor:pointer on document element on IOS', () => { - const platform = Services.platformFor(win); - sandbox.stub(platform, 'isIos').returns(true); - adopt(win); - expect(win.document.documentElement.style.cursor).to.equal('pointer'); - }); - - it('should set cursor:pointer on IOS in shadow-doc', () => { - const platform = Services.platformFor(win); - sandbox.stub(platform, 'isIos').returns(true); - adoptShadowMode(win); - expect(win.document.documentElement.style.cursor).to.equal('pointer'); - }); - - function extensionRegistrationTest() { - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '2'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '3'; - })); - expect(queueExtensions).to.have.length(3); - const promise = adopt(win); - runChunksForTesting(win.document); - return promise.then(() => { - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal('123'); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '4'; - })); - runChunksForTesting(win.document); - return promise; - }).then(() => { - expect(progress).to.equal('1234'); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '5'; - })); - runChunksForTesting(win.document); - return promise; - }).then(() => { - expect(progress).to.equal('12345'); - expect(queueExtensions).to.have.length(0); + afterEach(() => { + ampdocServiceMock.verify(); }); - } - it('should execute scheduled extensions & execute new extensions', - extensionRegistrationTest); - - it('should not maybePumpEarlyFrame when body not yet present', () => { - toggleExperiment(win, 'pump-early-frame', true); - // Make document.body be null on first invocation to simulate - // JS executing before the rest of the doc has been parsed. - const {body} = win.document; - let accessedOnce = false; - Object.defineProperty(win.document, 'body', { - get: () => { - if (accessedOnce) { - return body; - } - accessedOnce = true; - return null; - }, + it('should convert AMP from array to AMP object in single-doc', () => { + expect(win.AMP.push).to.equal([].push); + adopt(win); + expect(win.AMP.push).to.not.equal([].push); + expect(win.AMP_TAG).to.be.true; }); - extensionRegistrationTest(); - }); - - it('should not maybePumpEarlyFrame ' + - 'when a renderDelayingExtension is present', () => { - toggleExperiment(win, 'pump-early-frame', true); - win.document.body.appendChild( - document.createElement('amp-experiment')); - extensionRegistrationTest(); - }); - - it('should maybePumpEarlyFrame and delay extension execution', () => { - toggleExperiment(win, 'pump-early-frame', true); - let progress = ''; - const queueExtensions = win.AMP; - const highPriority = regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += 'high'; - }); - highPriority.p = 'high'; - win.AMP.push(highPriority); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '2'; - })); - win.AMP.push(() => { - progress += 'function'; - }); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '3'; - })); - expect(queueExtensions).to.have.length(5); - const promise = adopt(win); - runChunksForTesting(win.document); - return promise.then(() => { - // Skip a microtask. - return Promise.resolve(); - }).then(() => { - expect(progress).to.equal('highfunction'); - expect(queueExtensions).to.have.length(3); - clock.tick(); - expect(queueExtensions).to.have.length(3); - expect(progress).to.equal('highfunction'); - // New extension arrives before inital ran. - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '4'; - })); - expect(queueExtensions).to.have.length(4); - clock.tick(1); - expect(queueExtensions).to.have.length(0); - runChunksForTesting(win.document); - return promise; - }).then(() => { - expect(progress).to.equal('highfunction1234'); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '5'; - })); - runChunksForTesting(win.document); - return promise; - }).then(() => { - expect(progress).to.equal('highfunction12345'); - expect(queueExtensions).to.have.length(0); - }); - }); - - it('support struct AMP.push raw functions and high priority', () => { - // New format: {n:string, f:function()}. - let progress = ''; - const queueExtensions = win.AMP; - // Queue mode. - win.AMP.push(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - }); - win.AMP.push({ - n: 'ext2', - p: 'high', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'HIGH'; - }, - }); - expect(queueExtensions).to.have.length(2); - expect(progress).to.equal(''); - const promise = adopt(win); - return promise.then(() => { - // Notice the queue is down to 0 but there is a micro task to execute - // raw and high prio functions. Also notice that no `runChunksForTesting` - // was called to process the queue. - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal(''); - // Even though raw functions and high priority don't go through chunking - // there is a micro task for its queue. - return Promise.resolve(); - }).then(() => { - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal('1HIGH'); - win.AMP.push({ - n: 'ext1', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'A'; - }, - }); - runChunksForTesting(win.document); - return promise.then(() => { - expect(progress).to.equal('1HIGHA'); - }); + it('should convert AMP from array to AMP object in shadow-doc', () => { + expect(win.AMP.push).to.equal([].push); + adoptShadowMode(win); + expect(win.AMP.push).to.not.equal([].push); + expect(win.AMP_TAG).to.be.true; }); - }); - - it('loads and waits for a single intermediate bundles', () => { - // New format: {n:string, f:function(), i: }. - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push({ - n: 'ext2', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'C'; - }, - i: 'ext1', - }); - win.AMP.push({ - n: 'ext1', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'A'; - }, - i: '_base_ext', + it('should install legacy stubs in single-doc', () => { + const initial = win.ampExtendedElements || {}; + expect(initial['amp-ad']).to.be.undefined; + expect(initial['amp-embed']).to.be.undefined; + expect(initial['amp-video']).to.be.undefined; + adopt(win); + expect(win.ampExtendedElements['amp-ad']).to.equal(ElementStub); + expect(win.ampExtendedElements['amp-embed']).to.equal(ElementStub); + expect(win.ampExtendedElements['amp-video']).to.equal(ElementStub); }); - win.AMP.push({ - n: '_base_ext', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'B'; - }, + it('should install legacy stubs in shadow-doc', () => { + const initial = win.ampExtendedElements || {}; + expect(initial['amp-ad']).to.be.undefined; + expect(initial['amp-embed']).to.be.undefined; + expect(initial['amp-video']).to.be.undefined; + adoptShadowMode(win); + expect(win.ampExtendedElements['amp-ad']).to.equal(ElementStub); + expect(win.ampExtendedElements['amp-embed']).to.equal(ElementStub); + expect(win.ampExtendedElements['amp-video']).to.equal(ElementStub); }); - let script = win.document.querySelector('[data-script=_base_ext]'); - expect(script).to.be.null; - const promise = adopt(win); - const e = Services.extensionsFor(win); - - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal(''); - runChunksForTesting(win.document); - script = win.document.querySelector('[data-script=_base_ext]'); - expect(script).to.be.not.null; - return promise.then(() => { - // ext1 should not be executed yet and needs to wait on _base_ext - expect(progress).to.equal('B'); - return e.waitForExtension(win, '_base_ext').then(() => { - return e.waitForExtension(win, 'ext1').then(() => { - expect(progress).to.equal('BA'); - return e.waitForExtension(win, 'ext2').then(() => { - expect(progress).to.equal('BAC'); - }); - }); - }); - }); - }); - - it('loads and waits for a multiple intermediate bundles', () => { - // New format: {n:string, f:function(), i: }. - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push({ - n: 'ext1', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'A'; - }, - i: ['_base_ext1', '_base_ext2'], + it('should NOT set cursor:pointer on document element on non-IOS', () => { + const platform = Services.platformFor(win); + sandbox.stub(platform, 'isIos').returns(false); + adopt(win); + expect(win.document.documentElement.style.cursor).to.not.be.ok; }); - win.AMP.push({ - n: '_base_ext2', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'B'; - }, - i: ['_base_ext1'], + it('should set cursor:pointer on document element on IOS', () => { + const platform = Services.platformFor(win); + sandbox.stub(platform, 'isIos').returns(true); + adopt(win); + expect(win.document.documentElement.style.cursor).to.equal('pointer'); }); - win.AMP.push({ - n: '_base_ext1', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'C'; - }, + it('should set cursor:pointer on IOS in shadow-doc', () => { + const platform = Services.platformFor(win); + sandbox.stub(platform, 'isIos').returns(true); + adoptShadowMode(win); + expect(win.document.documentElement.style.cursor).to.equal('pointer'); }); - let script1 = win.document.querySelector('[data-script=_base_ext1]'); - let script2 = win.document.querySelector('[data-script=_base_ext2]'); - expect(script1).to.be.null; - expect(script2).to.be.null; - const promise = adopt(win); - const e = Services.extensionsFor(win); - - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal(''); - runChunksForTesting(win.document); - script1 = win.document.querySelector('[data-script=_base_ext1]'); - script2 = win.document.querySelector('[data-script=_base_ext2]'); - expect(script1).to.not.be.null; - expect(script2).to.not.be.null; - - return promise.then(() => { - // ext1 should not be executed yet and needs to wait on _base_ext - // Notice that ext0 executes before A - expect(progress).to.equal('C'); + function extensionRegistrationTest() { + let progress = ''; + const queueExtensions = win.AMP; + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '2'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '3'; + }) + ); + expect(queueExtensions).to.have.length(3); + const promise = adopt(win); runChunksForTesting(win.document); - return e.waitForExtension(win, '_base_ext2').then(() => { - expect(progress).to.equal('CB'); - }).then(() => { - return e.waitForExtension(win, 'ext1').then(() => { - expect(progress).to.equal('CBA'); + return promise + .then(() => { + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal('123'); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '4'; + }) + ); + runChunksForTesting(win.document); + return promise; + }) + .then(() => { + expect(progress).to.equal('1234'); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '5'; + }) + ); + runChunksForTesting(win.document); + return promise; + }) + .then(() => { + expect(progress).to.equal('12345'); + expect(queueExtensions).to.have.length(0); }); - }); - }); - }); - - it('should wait for body before processing extensions', function* () { - let bodyResolver; - const bodyPromise = new Promise(resolve => { - bodyResolver = resolve; - }); - sandbox.stub(dom, 'waitForBodyPromise').callsFake(() => bodyPromise); - - function skipMicro() { - return Promise.resolve().then(() => Promise.resolve()); - } - function waitNext(promise) { - return Promise.race([promise, skipMicro()]); - } - - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '2'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '3'; - })); - const promise = adopt(win); - runChunksForTesting(win.document); - - yield waitNext(promise); - // Extensions are still unprocessed - expect(progress).to.equal(''); - - // Add one more - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '4'; - })); - runChunksForTesting(win.document); - - yield waitNext(promise); - expect(progress).to.equal(''); - - // Body is available now. - bodyResolver(); - runChunksForTesting(win.document); - - yield waitNext(promise); - expect(progress).to.equal('1234'); - expect(queueExtensions).to.have.length(0); - }); - - it('should load correct extension version', function* () { - self.AMP_MODE = { - rtvVersion: 'test-version', - }; - toggleExperiment(win, 'version-locking', true); - function addExisting(index) { - const s = document.createElement('script'); - s.setAttribute('custom-element', 'amp-test-element' + index); - win.document.head.appendChild(s); - return s; } - const s1 = addExisting(1); - const s2 = addExisting(4); - const s3 = addExisting(5); - let bodyResolver; - const bodyPromise = new Promise(resolve => { - bodyResolver = resolve; + it( + 'should execute scheduled extensions & execute new extensions', + extensionRegistrationTest + ); + + it('should not maybePumpEarlyFrame when body not yet present', () => { + toggleExperiment(win, 'pump-early-frame', true); + // Make document.body be null on first invocation to simulate + // JS executing before the rest of the doc has been parsed. + const {body} = win.document; + let accessedOnce = false; + Object.defineProperty(win.document, 'body', { + get: () => { + if (accessedOnce) { + return body; + } + accessedOnce = true; + return null; + }, + }); + extensionRegistrationTest(); }); - sandbox.stub(dom, 'waitForBodyPromise').callsFake(() => bodyPromise); - - function skipMicro() { - return Promise.resolve().then(() => Promise.resolve()); - } - function waitNext(promise) { - return Promise.race([promise, skipMicro()]); - } - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += 'not expected 1'; - }, 'version123')); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '3'; - })); - const promise = adopt(win); - runChunksForTesting(win.document); - - yield waitNext(promise); - // Extensions are still unprocessed - expect(progress).to.equal(''); - - // Add one more - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '4'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += 'not expected 2'; - }, 'version123')); - // Add legacy element (5) and eagarly ask for its load as ElementStub does. - Services.extensionsFor(win).preloadExtension('amp-test-element5', false); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '5'; - }, 'version123')); - runChunksForTesting(win.document); - - yield waitNext(promise); - expect(progress).to.equal(''); - - // Body is available now. - bodyResolver(); - runChunksForTesting(win.document); - - yield waitNext(promise); - expect(progress).to.equal('134'); - expect(queueExtensions).to.have.length(0); - expect(s1.getAttribute('custom-element')).to.be.null; - expect(s2.getAttribute('custom-element')).to.be.null; - expect(s3.getAttribute('custom-element')).to.be.null; - expect(s1.getAttribute('i-amphtml-loaded-new-version')) - .to.equal('amp-test-element1'); - expect(s2.getAttribute('i-amphtml-loaded-new-version')) - .to.equal('amp-test-element4'); - expect(s3.getAttribute('i-amphtml-loaded-new-version')) - .to.equal('amp-test-element5'); - const inserted = win.document.head.querySelectorAll( - '[i-amphtml-inserted]'); - expect(inserted).to.have.length(3); - expect(inserted[0].getAttribute('src')).to.equal( - 'https://cdn.ampproject.org/rtv/test-version' + - '/v0/amp-test-element1-0.1.js'); - expect(inserted[1].getAttribute('src')).to.equal( - 'https://cdn.ampproject.org/rtv/test-version' + - '/v0/amp-test-element4-0.1.js'); - expect(inserted[2].getAttribute('src')).to.equal( - 'https://cdn.ampproject.org/rtv/test-version' + - '/v0/amp-test-element5-0.1.js'); - }); - - it('should be robust against errors in early extensions', function* () { - let progress = ''; - win.AMP.push(regularExtension(() => { - progress += '1'; - })); - win.AMP.push(regularExtension(() => { - throw new Error('extension error'); - })); - win.AMP.push(regularExtension(() => { - progress += '3'; - })); - const promise = adopt(win); - runChunksForTesting(win.document); - yield promise; - expect(progress).to.equal('13'); - }); - - describe('single-mode', () => { - let extensions; + it( + 'should not maybePumpEarlyFrame ' + + 'when a renderDelayingExtension is present', + () => { + toggleExperiment(win, 'pump-early-frame', true); + win.document.body.appendChild(document.createElement('amp-experiment')); + extensionRegistrationTest(); + } + ); - beforeEach(() => { + it('should maybePumpEarlyFrame and delay extension execution', () => { + toggleExperiment(win, 'pump-early-frame', true); + let progress = ''; + const queueExtensions = win.AMP; + const highPriority = regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += 'high'; + }); + highPriority.p = 'high'; + win.AMP.push(highPriority); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '2'; + }) + ); + win.AMP.push(() => { + progress += 'function'; + }); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '3'; + }) + ); + expect(queueExtensions).to.have.length(5); const promise = adopt(win); - ext.installExtensionsService(win); - extensions = Services.extensionsFor(win); - return promise; - }); - - it('should export properties to global AMP object', () => { - expect(win.AMP.BaseElement).to.be.a('function'); - expect(win.AMP.BaseTemplate).to.be.a('function'); - expect(win.AMP.registerElement).to.be.a('function'); - expect(win.AMP.registerTemplate).to.be.a('function'); - expect(win.AMP.setTickFunction).to.be.a('function'); - expect(win.AMP.win).to.equal(win); - - expect(win.AMP.viewer).to.be.a('object'); - expect(win.AMP.viewport).to.be.a('object'); - // Single-doc mode does not create `attachShadowDoc`. - expect(win.AMP.attachShadowDoc).to.not.exist; - expect(win.AMP.attachShadowDocAsStream).to.not.exist; + runChunksForTesting(win.document); + return promise + .then(() => { + // Skip a microtask. + return Promise.resolve(); + }) + .then(() => { + expect(progress).to.equal('highfunction'); + expect(queueExtensions).to.have.length(3); + clock.tick(); + expect(queueExtensions).to.have.length(3); + expect(progress).to.equal('highfunction'); + // New extension arrives before inital ran. + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '4'; + }) + ); + expect(queueExtensions).to.have.length(4); + clock.tick(1); + expect(queueExtensions).to.have.length(0); + runChunksForTesting(win.document); + return promise; + }) + .then(() => { + expect(progress).to.equal('highfunction1234'); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '5'; + }) + ); + runChunksForTesting(win.document); + return promise; + }) + .then(() => { + expect(progress).to.equal('highfunction12345'); + expect(queueExtensions).to.have.length(0); + }); }); - it('should register element without CSS', function* () { - const ampdoc = ampdocService.getAmpDoc(); - const servicePromise = getServicePromise(win, 'amp-ext'); - const installStylesStub = sandbox.stub(styles, 'installStylesForDoc'); + it('support struct AMP.push raw functions and high priority', () => { + // New format: {n:string, f:function()}. + let progress = ''; + const queueExtensions = win.AMP; - ampdoc.declareExtension('amp-ext'); + // Queue mode. + win.AMP.push(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }); win.AMP.push({ - n: 'amp-ext', + n: 'ext2', + p: 'high', f: amp => { - amp.registerElement('amp-ext', win.AMP.BaseElement); + expect(amp).to.equal(win.AMP); + progress += 'HIGH'; }, }); - runChunksForTesting(win.document); - yield extensions.waitForExtension(win, 'amp-ext'); - - // Extension is added immediately. Can't find for micro-tasks here. - const ext = extensions.extensions_['amp-ext'].extension; - expect(ext.elements['amp-ext']).exist; - expect(ext.elements['amp-ext'].implementationClass) - .to.equal(win.AMP.BaseElement); - - // No installStyles calls. - expect(installStylesStub).to.have.not.been.called; - - // Register is called immediately as well. - expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); - - // Service and extensions are resolved. - yield Promise.all([ - extensions.waitForExtension(win, 'amp-ext'), - servicePromise]); + expect(queueExtensions).to.have.length(2); + expect(progress).to.equal(''); + const promise = adopt(win); + return promise + .then(() => { + // Notice the queue is down to 0 but there is a micro task to execute + // raw and high prio functions. Also notice that no `runChunksForTesting` + // was called to process the queue. + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal(''); + // Even though raw functions and high priority don't go through chunking + // there is a micro task for its queue. + return Promise.resolve(); + }) + .then(() => { + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal('1HIGH'); + win.AMP.push({ + n: 'ext1', + f: amp => { + expect(amp).to.equal(win.AMP); + progress += 'A'; + }, + }); + runChunksForTesting(win.document); + return promise.then(() => { + expect(progress).to.equal('1HIGHA'); + }); + }); }); - it('should register element with CSS', function* () { - const ampdoc = Services.ampdocServiceFor(win).getAmpDoc(); - const servicePromise = getServicePromise(win, 'amp-ext'); - let installStylesCallback; - const installStylesStub = - sandbox.stub(styles, 'installStylesForDoc').callsFake( - (doc, cssText, cb) => { - installStylesCallback = cb; - }); - - ampdoc.declareExtension('amp-ext'); + it('loads and waits for a single intermediate bundles', () => { + // New format: {n:string, f:function(), i: }. + let progress = ''; + const queueExtensions = win.AMP; + win.AMP.push({ - n: 'amp-ext', + n: 'ext2', f: amp => { - amp.registerElement('amp-ext', win.AMP.BaseElement, 'a{}'); + expect(amp).to.equal(win.AMP); + progress += 'C'; }, + i: 'ext1', }); - runChunksForTesting(win.document); - - // Extension is added immediately. Can't find for micro-tasks here. - yield extensions.waitForExtension(win, 'amp-ext'); - const ext = extensions.extensions_['amp-ext'].extension; - expect(ext.elements['amp-ext']).exist; - expect(ext.elements['amp-ext'].implementationClass) - .to.equal(win.AMP.BaseElement); - expect(ext.elements['amp-ext'].css).to.equal('a{}'); - - expect(installStylesStub).to.be.calledOnce; - expect(installStylesStub).to.be.calledWithExactly( - ampdoc, - 'a{}', - installStylesCallback, - /* isRuntimeCss */ false, - /* ext */ 'amp-ext'); - - // Element resistration is not done until callback. - expect(win.ampExtendedElements['amp-ext']).to.be.undefined; - installStylesCallback(); - expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); - - // Service and extensions are resolved. - yield Promise.all([ - extensions.waitForExtension(win, 'amp-ext'), - servicePromise]); - }); - - it('should register doc-service as ctor and install imm', function* () { - class Service1 {} - const ampdoc = new AmpDocSingle(win); - ampdoc.declareExtension('amp-ext'); - ampdocServiceMock.expects('getAmpDoc') - .returns(ampdoc) - .atLeast(1); win.AMP.push({ - n: 'amp-ext', + n: 'ext1', f: amp => { - amp.registerServiceForDoc('service1', Service1); + expect(amp).to.equal(win.AMP); + progress += 'A'; }, + i: '_base_ext', }); - runChunksForTesting(win.document); - - // No factories - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - expect(extHolder.docFactories).to.have.length(1); - - // Already installed. - expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); - - // The main top-level service is also pinged to unblock render. - yield getServicePromise(win, 'service1'); - }); - it('should register doc-service factory and install', function* () { - let count = 0; - function factory() { - count++; - return {str: 'A'}; - } - const ampdoc = new AmpDocSingle(win); - ampdoc.declareExtension('amp-ext'); - ampdocServiceMock.expects('getAmpDoc') - .returns(ampdoc) - .atLeast(1); win.AMP.push({ - n: 'amp-ext', + n: '_base_ext', f: amp => { - amp.registerServiceForDoc('service1', factory); + expect(amp).to.equal(win.AMP); + progress += 'B'; }, }); - runChunksForTesting(win.document); - - // No factories - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - expect(extHolder.docFactories).to.have.length(1); - // Already installed. - expect(count).to.equal(1); - expect(getServiceForDoc(ampdoc, 'service1')).to.deep.equal({str: 'A'}); - }); - }); - - describe('shadow-mode', () => { - let extensions; - - beforeEach(() => { - const promise = adoptShadowMode(win); - ext.installExtensionsService(win); - extensions = Services.extensionsFor(win); - return promise; - }); - - it('should export properties to global AMP object', () => { - expect(win.AMP.BaseElement).to.be.a('function'); - expect(win.AMP.BaseTemplate).to.be.a('function'); - expect(win.AMP.registerElement).to.be.a('function'); - expect(win.AMP.registerTemplate).to.be.a('function'); - expect(win.AMP.setTickFunction).to.be.a('function'); - expect(win.AMP.win).to.equal(win); - - expect(win.AMP.attachShadowDoc).to.be.a('function'); - expect(win.AMP.attachShadowDocAsStream).to.be.a('function'); + let script = win.document.querySelector('[data-script=_base_ext]'); + expect(script).to.be.null; + const promise = adopt(win); + const e = Services.extensionsFor(win); - expect(win.AMP.viewer).to.not.exist; - expect(win.AMP.viewport).to.not.exist; + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal(''); + runChunksForTesting(win.document); + script = win.document.querySelector('[data-script=_base_ext]'); + expect(script).to.be.not.null; + return promise.then(() => { + // ext1 should not be executed yet and needs to wait on _base_ext + expect(progress).to.equal('B'); + return e.waitForExtension(win, '_base_ext').then(() => { + return e.waitForExtension(win, 'ext1').then(() => { + expect(progress).to.equal('BA'); + return e.waitForExtension(win, 'ext2').then(() => { + expect(progress).to.equal('BAC'); + }); + }); + }); + }); }); - it('should register element without CSS', function* () { - const servicePromise = getServicePromise(win, 'amp-ext'); - const installStylesStub = sandbox.stub(styles, 'installStylesForDoc'); - + it('loads and waits for a multiple intermediate bundles', () => { + // New format: {n:string, f:function(), i: }. + let progress = ''; + const queueExtensions = win.AMP; win.AMP.push({ - n: 'amp-ext', + n: 'ext1', f: amp => { - amp.registerElement('amp-ext', win.AMP.BaseElement); + expect(amp).to.equal(win.AMP); + progress += 'A'; }, + i: ['_base_ext1', '_base_ext2'], }); - runChunksForTesting(win.document); - - // Extension is added immediately. Can't find for micro-tasks here. - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - const ext = extHolder.extension; - expect(ext.elements['amp-ext']).exist; - expect(ext.elements['amp-ext'].implementationClass) - .to.equal(win.AMP.BaseElement); - - // No installStyles calls and no factories. - expect(installStylesStub).to.not.be.called; - expect(extHolder.docFactories).to.have.length(1); - expect(win.ampExtendedElements['amp-ext']).to.be.undefined; - - // Execute factory to install style. - const shadowRoot = document.createDocumentFragment(); - const ampdoc = new AmpDocShadow(win, 'https://acme.org/', shadowRoot); - extHolder.docFactories[0](ampdoc); - expect(installStylesStub).to.not.be.called; - expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); - - // Service and extensions are resolved. - yield Promise.all([ - extensions.waitForExtension(win, 'amp-ext'), - servicePromise]); - }); - - it('should register element with CSS', function* () { - const servicePromise = getServicePromise(win, 'amp-ext'); - let installStylesCallback; - const installStylesStub = - sandbox.stub(styles, 'installStylesForDoc').callsFake( - (doc, cssText, cb) => { - installStylesCallback = cb; - }); win.AMP.push({ - n: 'amp-ext', + n: '_base_ext2', f: amp => { - amp.registerElement('amp-ext', win.AMP.BaseElement, 'a{}'); + expect(amp).to.equal(win.AMP); + progress += 'B'; }, + i: ['_base_ext1'], }); - runChunksForTesting(win.document); - - // Extension is added immediately. Can't find for micro-tasks here. - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - const ext = extHolder.extension; - expect(ext.elements['amp-ext']).exist; - expect(ext.elements['amp-ext'].implementationClass) - .to.equal(win.AMP.BaseElement); - expect(ext.elements['amp-ext'].css).to.equal('a{}'); - // No installations yet, but there's a factory. - expect(extHolder.docFactories).to.have.length(1); - expect(win.ampExtendedElements['amp-ext']).to.be.undefined; - expect(installStylesStub).to.have.not.been.called; - - // Execute factory to install style. - const shadowRoot = document.createDocumentFragment(); - const ampdoc = new AmpDocShadow(win, 'https://acme.org/', shadowRoot); - extHolder.docFactories[0](ampdoc); - expect(installStylesStub).to.be.calledOnce; - expect(installStylesStub).to.be.calledWithExactly( - ampdoc, - 'a{}', - installStylesCallback, - /* isRuntimeCss */ false, - /* ext */ 'amp-ext'); - - // Run install. - installStylesCallback(); - expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); - // Service and extensions are resolved. - yield Promise.all([ - extensions.waitForExtension(win, 'amp-ext'), - servicePromise]); - }); - - it('should register doc-service as ctor and defer install', function* () { - class Service1 {} win.AMP.push({ - n: 'amp-ext', + n: '_base_ext1', f: amp => { - amp.registerServiceForDoc('service1', Service1); + expect(amp).to.equal(win.AMP); + progress += 'C'; }, }); - runChunksForTesting(win.document); - // Factory recorded. - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - expect(extHolder.docFactories).to.have.length(1); - - const shadowRoot = document.createDocumentFragment(); - const ampdoc = new AmpDocShadow(win, 'https://a.org/', shadowRoot); + let script1 = win.document.querySelector('[data-script=_base_ext1]'); + let script2 = win.document.querySelector('[data-script=_base_ext2]'); + expect(script1).to.be.null; + expect(script2).to.be.null; + const promise = adopt(win); + const e = Services.extensionsFor(win); - // Not installed. - expect(getServicePromiseOrNullForDoc(ampdoc, 'service1')).to.be.null; + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal(''); + runChunksForTesting(win.document); + script1 = win.document.querySelector('[data-script=_base_ext1]'); + script2 = win.document.querySelector('[data-script=_base_ext2]'); + expect(script1).to.not.be.null; + expect(script2).to.not.be.null; - // Install. - extHolder.docFactories[0](ampdoc); - expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); + return promise.then(() => { + // ext1 should not be executed yet and needs to wait on _base_ext + // Notice that ext0 executes before A + expect(progress).to.equal('C'); + runChunksForTesting(win.document); + return e + .waitForExtension(win, '_base_ext2') + .then(() => { + expect(progress).to.equal('CB'); + }) + .then(() => { + return e.waitForExtension(win, 'ext1').then(() => { + expect(progress).to.equal('CBA'); + }); + }); + }); }); - }); -}); + it('should wait for body before processing extensions', function*() { + let bodyResolver; + const bodyPromise = new Promise(resolve => { + bodyResolver = resolve; + }); + sandbox.stub(dom, 'waitForBodyPromise').callsFake(() => bodyPromise); + + function skipMicro() { + return Promise.resolve().then(() => Promise.resolve()); + } + function waitNext(promise) { + return Promise.race([promise, skipMicro()]); + } -describes.realWin('runtime multidoc', { - amp: {ampdoc: 'multi'}, -}, env => { - let win; - let extensions; - let extensionsMock; - let ampdocServiceMock; + let progress = ''; + const queueExtensions = win.AMP; + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '2'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '3'; + }) + ); + const promise = adopt(win); + runChunksForTesting(win.document); - beforeEach(() => { - win = env.win; - extensions = env.extensions; - extensionsMock = sandbox.mock(extensions); - ampdocServiceMock = sandbox.mock(env.ampdocService); - }); + yield waitNext(promise); + // Extensions are still unprocessed + expect(progress).to.equal(''); - afterEach(() => { - extensionsMock.verify(); - ampdocServiceMock.verify(); - }); + // Add one more + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '4'; + }) + ); + runChunksForTesting(win.document); - describe('attachShadowDoc', () => { - const docUrl = 'https://example.org/doc1'; + yield waitNext(promise); + expect(progress).to.equal(''); - let clock; - let importDoc; - let hostElement; - let ampdoc; + // Body is available now. + bodyResolver(); + runChunksForTesting(win.document); - beforeEach(() => { - deactivateChunking(); - clock = sandbox.useFakeTimers(); - hostElement = win.document.createElement('div'); - importDoc = win.document.implementation.createHTMLDocument(''); - importDoc.body.appendChild(win.document.createElement('child')); - const shadowRoot = createShadowRoot(hostElement); - ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - - ampdocServiceMock.expects('installShadowDoc') - .withExactArgs( - docUrl, - sinon.match(arg => arg == getShadowRoot(hostElement))) - .returns(ampdoc) - .atLeast(0); - ampdocServiceMock.expects('getAmpDoc') - .withExactArgs(sinon.match(arg => arg == getShadowRoot(hostElement))) - .returns(ampdoc) - .atLeast(0); + yield waitNext(promise); + expect(progress).to.equal('1234'); + expect(queueExtensions).to.have.length(0); }); - it('should install services and styles', () => { - const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(ret).to.exist; - - const shadowRoot = getShadowRoot(hostElement); + it('should load correct extension version', function*() { + self.AMP_MODE = { + rtvVersion: 'test-version', + }; + toggleExperiment(win, 'version-locking', true); + function addExisting(index) { + const s = document.createElement('script'); + s.setAttribute('custom-element', 'amp-test-element' + index); + win.document.head.appendChild(s); + return s; + } + const s1 = addExisting(1); + const s2 = addExisting(4); + const s3 = addExisting(5); - // URL is set. - expect(shadowRoot.AMP.url).to.equal(docUrl); + let bodyResolver; + const bodyPromise = new Promise(resolve => { + bodyResolver = resolve; + }); + sandbox.stub(dom, 'waitForBodyPromise').callsFake(() => bodyPromise); - // Stylesheet has been installed. - expect(shadowRoot.querySelector('style[amp-runtime]')).to.exist; + function skipMicro() { + return Promise.resolve().then(() => Promise.resolve()); + } + function waitNext(promise) { + return Promise.race([promise, skipMicro()]); + } - // Doc services have been installed. - expect(ampdoc.services.action).to.exist; - expect(ampdoc.services.action.obj).to.exist; - expect(ampdoc.services.viewer).to.exist; - expect(ampdoc.services.viewer.obj).to.exist; + let progress = ''; + const queueExtensions = win.AMP; + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += 'not expected 1'; + }, 'version123') + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '3'; + }) + ); + const promise = adopt(win); + runChunksForTesting(win.document); - // Single-doc bidings have been installed. - expect(ret.ampdoc).to.equal(ampdoc); - expect(ret.viewer).to.not.exist; - }); + yield waitNext(promise); + // Extensions are still unprocessed + expect(progress).to.equal(''); - it('should install doc services', () => { - class Service1 {} - win.AMP.push({ - n: 'amp-ext', - f: amp => { - amp.registerServiceForDoc('service1', Service1); - }, - }); + // Add one more + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '4'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += 'not expected 2'; + }, 'version123') + ); + // Add legacy element (5) and eagarly ask for its load as ElementStub does. + Services.extensionsFor(win).preloadExtension('amp-test-element5', false); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '5'; + }, 'version123') + ); + runChunksForTesting(win.document); - const script = win.document.createElement('script'); - script.setAttribute('custom-element', 'amp-ext'); - script.setAttribute('src', ''); - importDoc.head.appendChild(script); + yield waitNext(promise); + expect(progress).to.equal(''); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + // Body is available now. + bodyResolver(); + runChunksForTesting(win.document); - return extensions.waitForExtension(win, 'amp-ext').then(() => { - // Factories have been applied. - expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); - }); + yield waitNext(promise); + expect(progress).to.equal('134'); + expect(queueExtensions).to.have.length(0); + expect(s1.getAttribute('custom-element')).to.be.null; + expect(s2.getAttribute('custom-element')).to.be.null; + expect(s3.getAttribute('custom-element')).to.be.null; + expect(s1.getAttribute('i-amphtml-loaded-new-version')).to.equal( + 'amp-test-element1' + ); + expect(s2.getAttribute('i-amphtml-loaded-new-version')).to.equal( + 'amp-test-element4' + ); + expect(s3.getAttribute('i-amphtml-loaded-new-version')).to.equal( + 'amp-test-element5' + ); + const inserted = win.document.head.querySelectorAll( + '[i-amphtml-inserted]' + ); + expect(inserted).to.have.length(3); + expect(inserted[0].getAttribute('src')).to.equal( + 'https://cdn.ampproject.org/rtv/test-version' + + '/v0/amp-test-element1-0.1.js' + ); + expect(inserted[1].getAttribute('src')).to.equal( + 'https://cdn.ampproject.org/rtv/test-version' + + '/v0/amp-test-element4-0.1.js' + ); + expect(inserted[2].getAttribute('src')).to.equal( + 'https://cdn.ampproject.org/rtv/test-version' + + '/v0/amp-test-element5-0.1.js' + ); + }); + + it('should be robust against errors in early extensions', function*() { + let progress = ''; + win.AMP.push( + regularExtension(() => { + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(() => { + throw new Error('extension error'); + }) + ); + win.AMP.push( + regularExtension(() => { + progress += '3'; + }) + ); + const promise = adopt(win); + runChunksForTesting(win.document); + yield promise; + expect(progress).to.equal('13'); }); - it('should pass init parameters to viewer', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl, { - 'test1': '12', - }); + describe('single-mode', () => { + let extensions; - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(viewer.getParam('test1')).to.equal('12'); - }); + beforeEach(() => { + const promise = adopt(win); + ext.installExtensionsService(win); + extensions = Services.extensionsFor(win); + return promise; + }); - it('should update host visibility', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + it('should export properties to global AMP object', () => { + expect(win.AMP.BaseElement).to.be.a('function'); + expect(win.AMP.BaseTemplate).to.be.a('function'); + expect(win.AMP.registerElement).to.be.a('function'); + expect(win.AMP.registerTemplate).to.be.a('function'); + expect(win.AMP.setTickFunction).to.be.a('function'); + expect(win.AMP.win).to.equal(win); + + expect(win.AMP.viewer).to.be.a('object'); + expect(win.AMP.viewport).to.be.a('object'); + // Single-doc mode does not create `attachShadowDoc`. + expect(win.AMP.attachShadowDoc).to.not.exist; + expect(win.AMP.attachShadowDocAsStream).to.not.exist; + }); - // Document is invisible at first. - expect(hostElement.style.visibility).to.equal('hidden'); + it('should register element without CSS', function*() { + const ampdoc = ampdocService.getAmpDoc(); + const servicePromise = getServicePromise(win, 'amp-ext'); + const installStylesStub = sandbox.stub(styles, 'installStylesForDoc'); + + ampdoc.declareExtension('amp-ext'); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerElement('amp-ext', win.AMP.BaseElement); + }, + }); + runChunksForTesting(win.document); + yield extensions.waitForExtension(win, 'amp-ext'); + + // Extension is added immediately. Can't find for micro-tasks here. + const ext = extensions.extensions_['amp-ext'].extension; + expect(ext.elements['amp-ext']).exist; + expect(ext.elements['amp-ext'].implementationClass).to.equal( + win.AMP.BaseElement + ); + + // No installStyles calls. + expect(installStylesStub).to.have.not.been.called; + + // Register is called immediately as well. + expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); + + // Service and extensions are resolved. + yield Promise.all([ + extensions.waitForExtension(win, 'amp-ext'), + servicePromise, + ]); + }); - // After timeout the doc rendered is started. - clock.tick(3000); - expect(hostElement.style.visibility).to.equal('visible'); - expect(ampdoc.signals().get('render-start')).to.be.ok; + it('should register element with CSS', function*() { + const ampdoc = Services.ampdocServiceFor(win).getAmpDoc(); + const servicePromise = getServicePromise(win, 'amp-ext'); + let installStylesCallback; + const installStylesStub = sandbox + .stub(styles, 'installStylesForDoc') + .callsFake((doc, cssText, cb) => { + installStylesCallback = cb; + }); - return ampdoc.whenReady().then(() => { - expect(ampdoc.isReady()).to.be.true; + ampdoc.declareExtension('amp-ext'); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerElement('amp-ext', win.AMP.BaseElement, 'a{}'); + }, + }); + runChunksForTesting(win.document); + + // Extension is added immediately. Can't find for micro-tasks here. + yield extensions.waitForExtension(win, 'amp-ext'); + const ext = extensions.extensions_['amp-ext'].extension; + expect(ext.elements['amp-ext']).exist; + expect(ext.elements['amp-ext'].implementationClass).to.equal( + win.AMP.BaseElement + ); + expect(ext.elements['amp-ext'].css).to.equal('a{}'); + + expect(installStylesStub).to.be.calledOnce; + expect(installStylesStub).to.be.calledWithExactly( + ampdoc, + 'a{}', + installStylesCallback, + /* isRuntimeCss */ false, + /* ext */ 'amp-ext' + ); + + // Element resistration is not done until callback. + expect(win.ampExtendedElements['amp-ext']).to.be.undefined; + installStylesCallback(); + expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); + + // Service and extensions are resolved. + yield Promise.all([ + extensions.waitForExtension(win, 'amp-ext'), + servicePromise, + ]); }); - }); - it('should import body', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const shadowRoot = getShadowRoot(hostElement); - const body = shadowRoot.querySelector('body') || - shadowRoot.querySelector('amp-body'); - expect(body).to.exist; - expect(body).to.have.class('amp-shadow'); - expect(body.style.position).to.equal('relative'); - expect(body.querySelector('child')).to.exist; - expect(ampdoc.getBody()).to.exist; - }); + it('should register doc-service as ctor and install imm', function*() { + class Service1 {} + const ampdoc = new AmpDocSingle(win); + ampdoc.declareExtension('amp-ext'); + ampdocServiceMock + .expects('getAmpDoc') + .returns(ampdoc) + .atLeast(1); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', Service1); + }, + }); + runChunksForTesting(win.document); - it('should read title element', () => { - const titleEl = win.document.createElement('title'); - titleEl.textContent = 'test title'; - importDoc.head.appendChild(titleEl); - const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(ret.title).to.equal('test title'); - expect(getShadowRoot(hostElement).AMP.title).to.equal('test title'); - }); + // No factories + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + expect(extHolder.docFactories).to.have.length(1); - it('should read canonical element', () => { - const canonicalEl = win.document.createElement('link'); - canonicalEl.setAttribute('rel', 'canonical'); - canonicalEl.setAttribute('href', 'http://example.org/canonical'); - importDoc.head.appendChild(canonicalEl); - const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(ret.canonicalUrl).to.equal('http://example.org/canonical'); - }); + // Already installed. + expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); - it('should import fonts', () => { - const fontEl1 = win.document.createElement('link'); - fontEl1.setAttribute('rel', 'stylesheet'); - fontEl1.setAttribute('href', 'http://example.org/font1'); - importDoc.head.appendChild(fontEl1); - const fontEl2 = win.document.createElement('link'); - fontEl2.setAttribute('rel', 'stylesheet'); - fontEl2.setAttribute('href', 'http://example.org/font2'); - importDoc.head.appendChild(fontEl2); - win.document.head.appendChild(fontEl2.cloneNode(true)); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(win.document.querySelector( - 'link[href="http://example.org/font1"]')).to.exist; - // Duplicates are ignored. - expect(win.document.querySelectorAll( - 'link[href="http://example.org/font2"]')).to.have.length(1); - - const fontEl = win.document.querySelector( - 'link[href="http://example.org/font1"]'); - expect(fontEl.getAttribute('type')).to.equal('text/css'); - expect(fontEl.getAttribute('rel')).to.equal('stylesheet'); - fontEl.parentElement.removeChild(fontEl); - }); + // The main top-level service is also pinged to unblock render. + yield getServicePromise(win, 'service1'); + }); - it('should ignore boilerplate style', () => { - const styleEl = win.document.createElement('style'); - styleEl.setAttribute('amp-boilerplate', ''); - importDoc.head.appendChild(styleEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const shadowRoot = getShadowRoot(hostElement); - expect(shadowRoot.querySelector('style[amp-boilerplate]')).to.not.exist; - }); + it('should register doc-service factory and install', function*() { + let count = 0; + function factory() { + count++; + return {str: 'A'}; + } + const ampdoc = new AmpDocSingle(win); + ampdoc.declareExtension('amp-ext'); + ampdocServiceMock + .expects('getAmpDoc') + .returns(ampdoc) + .atLeast(1); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', factory); + }, + }); + runChunksForTesting(win.document); - it('should import custom style', () => { - const styleEl = win.document.createElement('style'); - styleEl.setAttribute('amp-custom', ''); - styleEl.textContent = '.custom{}'; - importDoc.head.appendChild(styleEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const shadowRoot = getShadowRoot(hostElement); - expect(shadowRoot.querySelector('style[amp-custom]')).to.exist; - expect(shadowRoot.querySelector('style[amp-custom]').textContent) - .to.contain('.custom'); - }); + // No factories + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + expect(extHolder.docFactories).to.have.length(1); - it('should import keyframes style', () => { - const styleEl = win.document.createElement('style'); - styleEl.setAttribute('amp-keyframes', ''); - styleEl.textContent = '.keyframes{}'; - importDoc.head.appendChild(styleEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const shadowRoot = getShadowRoot(hostElement); - expect(shadowRoot.querySelector('style[amp-custom]')).to.not.exist; - expect(shadowRoot.querySelector('style[amp-keyframes]')).to.exist; - expect(shadowRoot.querySelector('style[amp-keyframes]').textContent) - .to.contain('.keyframes'); + // Already installed. + expect(count).to.equal(1); + expect(getServiceForDoc(ampdoc, 'service1')).to.deep.equal({str: 'A'}); + }); }); - it('should ignore runtime extension', () => { - extensionsMock.expects('preloadExtension').never(); + describe('shadow-mode', () => { + let extensions; - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('src', 'https://cdn.ampproject.org/v0.js'); - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - }); + beforeEach(() => { + const promise = adoptShadowMode(win); + ext.installExtensionsService(win); + extensions = Services.extensionsFor(win); + return promise; + }); - it('should ignore unknown script', () => { - extensionsMock.expects('preloadExtension').never(); + it('should export properties to global AMP object', () => { + expect(win.AMP.BaseElement).to.be.a('function'); + expect(win.AMP.BaseTemplate).to.be.a('function'); + expect(win.AMP.registerElement).to.be.a('function'); + expect(win.AMP.registerTemplate).to.be.a('function'); + expect(win.AMP.setTickFunction).to.be.a('function'); + expect(win.AMP.win).to.equal(win); - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('data-id', 'unknown1'); - scriptEl.setAttribute('src', 'https://cdn.ampproject.org/other.js'); - importDoc.head.appendChild(scriptEl); - allowConsoleError(() => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - }); - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="unknown1"]')).to.not.exist; - expect(win.document.querySelector('script[data-id="unknown1"]')) - .to.not.exist; - }); + expect(win.AMP.attachShadowDoc).to.be.a('function'); + expect(win.AMP.attachShadowDocAsStream).to.be.a('function'); - it('should import extension element', () => { - extensionsMock.expects('preloadExtension') - .withExactArgs('amp-ext1', '0.1') - .returns(Promise.resolve({ - elements: { - 'amp-ext1': function() {}, - }, - })) - .once(); + expect(win.AMP.viewer).to.not.exist; + expect(win.AMP.viewport).to.not.exist; + }); - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('custom-element', 'amp-ext1'); - scriptEl.setAttribute('src', ''); - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(win.document.querySelector('script[custom-element="amp-ext1"]')) - .to.not.exist; - }); + it('should register element without CSS', function*() { + const servicePromise = getServicePromise(win, 'amp-ext'); + const installStylesStub = sandbox.stub(styles, 'installStylesForDoc'); - it('should import extension element with version ≠ 0.1', () => { - extensionsMock.expects('preloadExtension') - .withExactArgs('amp-ext1', '1.0') - .returns(Promise.resolve({ - elements: { - 'amp-ext1': function() { }, - }, - })) - .once(); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerElement('amp-ext', win.AMP.BaseElement); + }, + }); + runChunksForTesting(win.document); + + // Extension is added immediately. Can't find for micro-tasks here. + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + const ext = extHolder.extension; + expect(ext.elements['amp-ext']).exist; + expect(ext.elements['amp-ext'].implementationClass).to.equal( + win.AMP.BaseElement + ); + + // No installStyles calls and no factories. + expect(installStylesStub).to.not.be.called; + expect(extHolder.docFactories).to.have.length(1); + expect(win.ampExtendedElements['amp-ext']).to.be.undefined; + + // Execute factory to install style. + const shadowRoot = document.createDocumentFragment(); + const ampdoc = new AmpDocShadow(win, 'https://acme.org/', shadowRoot); + extHolder.docFactories[0](ampdoc); + expect(installStylesStub).to.not.be.called; + expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); + + // Service and extensions are resolved. + yield Promise.all([ + extensions.waitForExtension(win, 'amp-ext'), + servicePromise, + ]); + }); - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('custom-element', 'amp-ext1'); - scriptEl.setAttribute('src', 'https://cdn.ampproject.org/v0/amp-ext1-1.0.js'); - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(win.document.querySelector('script[custom-element="amp-ext1"]')) - .to.not.exist; - }); + it('should register element with CSS', function*() { + const servicePromise = getServicePromise(win, 'amp-ext'); + let installStylesCallback; + const installStylesStub = sandbox + .stub(styles, 'installStylesForDoc') + .callsFake((doc, cssText, cb) => { + installStylesCallback = cb; + }); - it('should import extension template', () => { - extensionsMock.expects('preloadExtension') - .withExactArgs('amp-ext1', '0.1') - .returns(Promise.resolve({elements: {}})) - .once(); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerElement('amp-ext', win.AMP.BaseElement, 'a{}'); + }, + }); + runChunksForTesting(win.document); + + // Extension is added immediately. Can't find for micro-tasks here. + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + const ext = extHolder.extension; + expect(ext.elements['amp-ext']).exist; + expect(ext.elements['amp-ext'].implementationClass).to.equal( + win.AMP.BaseElement + ); + expect(ext.elements['amp-ext'].css).to.equal('a{}'); + // No installations yet, but there's a factory. + expect(extHolder.docFactories).to.have.length(1); + expect(win.ampExtendedElements['amp-ext']).to.be.undefined; + expect(installStylesStub).to.have.not.been.called; + + // Execute factory to install style. + const shadowRoot = document.createDocumentFragment(); + const ampdoc = new AmpDocShadow(win, 'https://acme.org/', shadowRoot); + extHolder.docFactories[0](ampdoc); + expect(installStylesStub).to.be.calledOnce; + expect(installStylesStub).to.be.calledWithExactly( + ampdoc, + 'a{}', + installStylesCallback, + /* isRuntimeCss */ false, + /* ext */ 'amp-ext' + ); + + // Run install. + installStylesCallback(); + expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); + + // Service and extensions are resolved. + yield Promise.all([ + extensions.waitForExtension(win, 'amp-ext'), + servicePromise, + ]); + }); - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('custom-template', 'amp-ext1'); - scriptEl.setAttribute('src', ''); - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(win.document.querySelector('script[custom-template="amp-ext1"]')) - .to.not.exist; - }); + it('should register doc-service as ctor and defer install', function*() { + class Service1 {} + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', Service1); + }, + }); + runChunksForTesting(win.document); - it('should import inline script', () => { - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('type', 'application/json'); - scriptEl.setAttribute('data-id', 'test1'); - scriptEl.textContent = '{}'; - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="test1"]')).to.exist; - expect(getShadowRoot(hostElement).querySelector( - 'script[data-id="test1"]').textContent).to.equal('{}'); - }); + // Factory recorded. + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + expect(extHolder.docFactories).to.have.length(1); - it('should ignore inline script if javascript', () => { - const scriptEl1 = win.document.createElement('script'); - scriptEl1.setAttribute('type', 'application/javascript'); - scriptEl1.setAttribute('data-id', 'test1'); - importDoc.head.appendChild(scriptEl1); - const scriptEl2 = win.document.createElement('script'); - scriptEl2.setAttribute('data-id', 'test1'); - importDoc.head.appendChild(scriptEl2); - allowConsoleError(() => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - }); - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="test1"]')).to.not.exist; - }); + const shadowRoot = document.createDocumentFragment(); + const ampdoc = new AmpDocShadow(win, 'https://a.org/', shadowRoot); - it('should start as visible by default', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(viewer.getVisibilityState()).to.equal('visible'); - }); + // Not installed. + expect(getServicePromiseOrNullForDoc(ampdoc, 'service1')).to.be.null; - it('should start as prerender when requested', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl, { - 'visibilityState': 'prerender', + // Install. + extHolder.docFactories[0](ampdoc); + expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); }); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(viewer.getVisibilityState()).to.equal('prerender'); }); + } +); + +describes.realWin( + 'runtime multidoc', + { + amp: {ampdoc: 'multi'}, + }, + env => { + let win; + let extensions; + let extensionsMock; + let ampdocServiceMock; - it('should expose visibility method', () => { - const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(amp.setVisibilityState).to.be.a('function'); - expect(viewer.getVisibilityState()).to.equal('visible'); - - amp.setVisibilityState('inactive'); - expect(viewer.getVisibilityState()).to.equal('inactive'); + beforeEach(() => { + win = env.win; + extensions = env.extensions; + extensionsMock = sandbox.mock(extensions); + ampdocServiceMock = sandbox.mock(env.ampdocService); }); - it('should expose close method and dispose services', () => { - const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(amp.close).to.be.a('function'); - expect(viewer.getVisibilityState()).to.equal('visible'); - - viewer.dispose = sandbox.spy(); - amp.close(); - expect(viewer.getVisibilityState()).to.equal('inactive'); - expect(viewer.dispose).to.be.calledOnce; + afterEach(() => { + extensionsMock.verify(); + ampdocServiceMock.verify(); }); - }); + describe('attachShadowDoc', () => { + const docUrl = 'https://example.org/doc1'; - describe('attachShadowDocAsStream', () => { - const docUrl = 'https://example.org/doc1'; + let clock; + let importDoc; + let hostElement; + let ampdoc; - let hostElement; - let ampdoc; - let shadowDoc; - let writer; + beforeEach(() => { + deactivateChunking(); + clock = sandbox.useFakeTimers(); + hostElement = win.document.createElement('div'); + importDoc = win.document.implementation.createHTMLDocument(''); + importDoc.body.appendChild(win.document.createElement('child')); + const shadowRoot = createShadowRoot(hostElement); + ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - beforeEach(() => { - deactivateChunking(); - hostElement = win.document.createElement('div'); - const shadowRoot = createShadowRoot(hostElement); - ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - - ampdocServiceMock.expects('installShadowDoc') + ampdocServiceMock + .expects('installShadowDoc') .withExactArgs( - docUrl, - sinon.match(arg => arg == getShadowRoot(hostElement))) + docUrl, + sinon.match(arg => arg == getShadowRoot(hostElement)) + ) .returns(ampdoc) .atLeast(0); - ampdocServiceMock.expects('getAmpDoc') + ampdocServiceMock + .expects('getAmpDoc') .withExactArgs(sinon.match(arg => arg == getShadowRoot(hostElement))) .returns(ampdoc) .atLeast(0); - }); + }); - it('should install services and styles', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; + it('should install services and styles', () => { + const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect(ret).to.exist; - const shadowRoot = getShadowRoot(hostElement); + const shadowRoot = getShadowRoot(hostElement); - // URL is set. - expect(shadowRoot.AMP.url).to.equal(docUrl); + // URL is set. + expect(shadowRoot.AMP.url).to.equal(docUrl); - // Stylesheet has been installed. - expect(shadowRoot.querySelector('style[amp-runtime]')).to.exist; + // Stylesheet has been installed. + expect(shadowRoot.querySelector('style[amp-runtime]')).to.exist; - // Doc services have been installed. - expect(ampdoc.services.action).to.exist; - expect(ampdoc.services.action.obj).to.exist; - expect(ampdoc.services.viewer).to.exist; - expect(ampdoc.services.viewer.obj).to.exist; + // Doc services have been installed. + expect(ampdoc.services.action).to.exist; + expect(ampdoc.services.action.obj).to.exist; + expect(ampdoc.services.viewer).to.exist; + expect(ampdoc.services.viewer.obj).to.exist; - // Single-doc bidings have been installed. - expect(shadowDoc.ampdoc).to.equal(ampdoc); - expect(shadowDoc.viewer).to.not.exist; - }); + // Single-doc bidings have been installed. + expect(ret.ampdoc).to.equal(ampdoc); + expect(ret.viewer).to.not.exist; + }); - it('should install doc services', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; + it('should install doc services', () => { + class Service1 {} + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', Service1); + }, + }); - class Service1 {} - win.AMP.push({ - n: 'amp-ext', - f: amp => { - amp.registerServiceForDoc('service1', Service1); - }, - }); + const script = win.document.createElement('script'); + script.setAttribute('custom-element', 'amp-ext'); + script.setAttribute('src', ''); + importDoc.head.appendChild(script); - writer.write(''); - writer.write(''); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - return ampdoc.whenBodyAvailable().then(() => { return extensions.waitForExtension(win, 'amp-ext').then(() => { // Factories have been applied. - expect(getServiceForDoc(ampdoc, 'service1')) - .to.be.instanceOf(Service1); + expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf( + Service1 + ); }); }); - }); - it('should pass init parameters to viewer', () => { - win.AMP.attachShadowDocAsStream(hostElement, docUrl, { - 'test1': '12', - }); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(viewer.getParam('test1')).to.equal('12'); - }); + it('should pass init parameters to viewer', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl, { + 'test1': '12', + }); - it('should update host visibility', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(viewer.getParam('test1')).to.equal('12'); + }); - writer.write('
    '); + it('should update host visibility', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - // Document is invisible at first. - expect(hostElement.style.visibility).to.equal('hidden'); + // Document is invisible at first. + expect(hostElement.style.visibility).to.equal('hidden'); - return ampdoc.whenBodyAvailable().then(() => { // After timeout the doc rendered is started. - expect(hostElement.style.visibility).to.equal('hidden'); - return ampdoc.signals().whenSignal('render-start'); - }).then(() => { + clock.tick(3000); expect(hostElement.style.visibility).to.equal('visible'); + expect(ampdoc.signals().get('render-start')).to.be.ok; + + return ampdoc.whenReady().then(() => { + expect(ampdoc.isReady()).to.be.true; + }); }); - }); - it('should import body', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should import body', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const shadowRoot = getShadowRoot(hostElement); - const body = shadowRoot.querySelector('body') || - shadowRoot.querySelector('amp-body'); + const body = + shadowRoot.querySelector('body') || + shadowRoot.querySelector('amp-body'); expect(body).to.exist; expect(body).to.have.class('amp-shadow'); expect(body.style.position).to.equal('relative'); - env.flushVsync(); expect(body.querySelector('child')).to.exist; expect(ampdoc.getBody()).to.exist; }); - }); - - it('should mark doc as ready', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { - expect(ampdoc.isReady()).to.be.false; - writer.write(''); - writer.write(''); - writer.close(); - return ampdoc.whenReady().then(() => { - expect(ampdoc.isReady()).to.be.true; - }); - }); - }); - it('should read title element', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write('test title'); - return ampdoc.whenBodyAvailable().then(() => { - expect(shadowDoc.title).to.equal('test title'); + it('should read title element', () => { + const titleEl = win.document.createElement('title'); + titleEl.textContent = 'test title'; + importDoc.head.appendChild(titleEl); + const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect(ret.title).to.equal('test title'); expect(getShadowRoot(hostElement).AMP.title).to.equal('test title'); }); - }); - it('should read canonical element', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write( - ''); - return ampdoc.whenBodyAvailable().then(() => { - expect(shadowDoc.canonicalUrl).to.equal('http://example.org/canonical'); + it('should read canonical element', () => { + const canonicalEl = win.document.createElement('link'); + canonicalEl.setAttribute('rel', 'canonical'); + canonicalEl.setAttribute('href', 'http://example.org/canonical'); + importDoc.head.appendChild(canonicalEl); + const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect(ret.canonicalUrl).to.equal('http://example.org/canonical'); }); - }); - it('should import fonts', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - - writer.write( - ''); - writer.write( - ''); - const fontEl2 = win.document.createElement('link'); - fontEl2.setAttribute('rel', 'stylesheet'); - fontEl2.setAttribute('href', 'http://example.org/font2'); - win.document.head.appendChild(fontEl2); - - return ampdoc.whenBodyAvailable().then(() => { - expect(win.document.querySelector( - 'link[href="http://example.org/font1"]')).to.exist; + it('should import fonts', () => { + const fontEl1 = win.document.createElement('link'); + fontEl1.setAttribute('rel', 'stylesheet'); + fontEl1.setAttribute('href', 'http://example.org/font1'); + importDoc.head.appendChild(fontEl1); + const fontEl2 = win.document.createElement('link'); + fontEl2.setAttribute('rel', 'stylesheet'); + fontEl2.setAttribute('href', 'http://example.org/font2'); + importDoc.head.appendChild(fontEl2); + win.document.head.appendChild(fontEl2.cloneNode(true)); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect( + win.document.querySelector('link[href="http://example.org/font1"]') + ).to.exist; // Duplicates are ignored. - expect(win.document.querySelectorAll( - 'link[href="http://example.org/font2"]')).to.have.length(1); + expect( + win.document.querySelectorAll('link[href="http://example.org/font2"]') + ).to.have.length(1); const fontEl = win.document.querySelector( - 'link[href="http://example.org/font1"]'); + 'link[href="http://example.org/font1"]' + ); expect(fontEl.getAttribute('type')).to.equal('text/css'); expect(fontEl.getAttribute('rel')).to.equal('stylesheet'); fontEl.parentElement.removeChild(fontEl); }); - }); - it('should ignore boilerplate style', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should ignore boilerplate style', () => { + const styleEl = win.document.createElement('style'); + styleEl.setAttribute('amp-boilerplate', ''); + importDoc.head.appendChild(styleEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const shadowRoot = getShadowRoot(hostElement); expect(shadowRoot.querySelector('style[amp-boilerplate]')).to.not.exist; }); - }); - it('should import custom style', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should import custom style', () => { + const styleEl = win.document.createElement('style'); + styleEl.setAttribute('amp-custom', ''); + styleEl.textContent = '.custom{}'; + importDoc.head.appendChild(styleEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const shadowRoot = getShadowRoot(hostElement); expect(shadowRoot.querySelector('style[amp-custom]')).to.exist; - expect(shadowRoot.querySelector('style[amp-custom]').textContent) - .to.contain('.custom'); + expect( + shadowRoot.querySelector('style[amp-custom]').textContent + ).to.contain('.custom'); }); - }); - it('should ignore runtime extension', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - extensionsMock.expects('preloadExtension').never(); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable(); - }); + it('should import keyframes style', () => { + const styleEl = win.document.createElement('style'); + styleEl.setAttribute('amp-keyframes', ''); + styleEl.textContent = '.keyframes{}'; + importDoc.head.appendChild(styleEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + const shadowRoot = getShadowRoot(hostElement); + expect(shadowRoot.querySelector('style[amp-custom]')).to.not.exist; + expect(shadowRoot.querySelector('style[amp-keyframes]')).to.exist; + expect( + shadowRoot.querySelector('style[amp-keyframes]').textContent + ).to.contain('.keyframes'); + }); - it('should ignore unknown script', () => { - expectAsyncConsoleError( - '[runtime] - unknown script: [object HTMLScriptElement] ' + - 'https://cdn.ampproject.org/other.js'); + it('should ignore runtime extension', () => { + extensionsMock.expects('preloadExtension').never(); - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - extensionsMock.expects('preloadExtension').never(); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="unknown1"]')).to.not.exist; - expect(win.document.querySelector('script[data-id="unknown1"]')) - .to.not.exist; + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('src', 'https://cdn.ampproject.org/v0.js'); + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + }); + + it('should ignore unknown script', () => { + extensionsMock.expects('preloadExtension').never(); + + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('data-id', 'unknown1'); + scriptEl.setAttribute('src', 'https://cdn.ampproject.org/other.js'); + importDoc.head.appendChild(scriptEl); + allowConsoleError(() => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + }); + expect( + getShadowRoot(hostElement).querySelector('script[data-id="unknown1"]') + ).to.not.exist; + expect(win.document.querySelector('script[data-id="unknown1"]')).to.not + .exist; }); - }); - it('should import extension element', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - extensionsMock.expects('preloadExtension') + it('should import extension element', () => { + extensionsMock + .expects('preloadExtension') .withExactArgs('amp-ext1', '0.1') - .returns(Promise.resolve({ - elements: { - 'amp-ext1': function() {}, - }, - })) + .returns( + Promise.resolve({ + elements: { + 'amp-ext1': function() {}, + }, + }) + ) .once(); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('custom-element', 'amp-ext1'); + scriptEl.setAttribute('src', ''); + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); expect(win.document.querySelector('script[custom-element="amp-ext1"]')) - .to.not.exist; + .to.not.exist; + }); + + it('should import extension element with version ≠ 0.1', () => { + extensionsMock + .expects('preloadExtension') + .withExactArgs('amp-ext1', '1.0') + .returns( + Promise.resolve({ + elements: { + 'amp-ext1': function() {}, + }, + }) + ) + .once(); + + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('custom-element', 'amp-ext1'); + scriptEl.setAttribute( + 'src', + 'https://cdn.ampproject.org/v0/amp-ext1-1.0.js' + ); + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect(win.document.querySelector('script[custom-element="amp-ext1"]')) + .to.not.exist; }); - }); - it('should import extension template', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - extensionsMock.expects('preloadExtension') + it('should import extension template', () => { + extensionsMock + .expects('preloadExtension') .withExactArgs('amp-ext1', '0.1') .returns(Promise.resolve({elements: {}})) .once(); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('custom-template', 'amp-ext1'); + scriptEl.setAttribute('src', ''); + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); expect(win.document.querySelector('script[custom-template="amp-ext1"]')) - .to.not.exist; + .to.not.exist; }); - }); - it('should import inline script', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="test1"]')).to.exist; - expect(getShadowRoot(hostElement).querySelector( - 'script[data-id="test1"]').textContent).to.equal('{}'); + it('should import inline script', () => { + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('type', 'application/json'); + scriptEl.setAttribute('data-id', 'test1'); + scriptEl.textContent = '{}'; + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + ).to.exist; + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + .textContent + ).to.equal('{}'); }); - }); - it('should ignore inline script if javascript', () => { - expectAsyncConsoleError( - '[runtime] - unallowed inline javascript: ' + - '[object HTMLScriptElement]', 2); - - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write( - ''); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable(() => { - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="test1"]')).to.not.exist; + it('should ignore inline script if javascript', () => { + const scriptEl1 = win.document.createElement('script'); + scriptEl1.setAttribute('type', 'application/javascript'); + scriptEl1.setAttribute('data-id', 'test1'); + importDoc.head.appendChild(scriptEl1); + const scriptEl2 = win.document.createElement('script'); + scriptEl2.setAttribute('data-id', 'test1'); + importDoc.head.appendChild(scriptEl2); + allowConsoleError(() => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + }); + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + ).to.not.exist; }); - }); - it('should start as visible by default', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should start as visible by default', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const viewer = getServiceForDoc(ampdoc, 'viewer'); expect(viewer.getVisibilityState()).to.equal('visible'); }); - }); - it('should start as prerender when requested', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl, { - 'visibilityState': 'prerender', - }); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should start as prerender when requested', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl, { + 'visibilityState': 'prerender', + }); const viewer = getServiceForDoc(ampdoc, 'viewer'); expect(viewer.getVisibilityState()).to.equal('prerender'); }); - }); - it('should expose visibility method', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should expose visibility method', () => { + const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(shadowDoc.setVisibilityState).to.be.a('function'); + expect(amp.setVisibilityState).to.be.a('function'); expect(viewer.getVisibilityState()).to.equal('visible'); - shadowDoc.setVisibilityState('inactive'); + amp.setVisibilityState('inactive'); expect(viewer.getVisibilityState()).to.equal('inactive'); }); - }); - it('should expose close method and dispose services', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should expose close method and dispose services', () => { + const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(shadowDoc.close).to.be.a('function'); + expect(amp.close).to.be.a('function'); expect(viewer.getVisibilityState()).to.equal('visible'); viewer.dispose = sandbox.spy(); - shadowDoc.close(); + amp.close(); expect(viewer.getVisibilityState()).to.equal('inactive'); expect(viewer.dispose).to.be.calledOnce; }); }); - }); - - - describes.repeated('messaging', { - 'document.contains is the browser implementation': false, - 'document.contains is a stubbed implementation': true, - }, (name, isStubbedDocumentContains) => { - let doc1, doc2, doc3; - beforeEach(() => { - if (isStubbedDocumentContains) { - // Some browsers implement document.contains wrong, and it returns - // `false` even when this is incorrect. Repeat these tests with the - // faulty implementation. - sandbox.stub(win.document, 'contains').returns(false); - } + describe('attachShadowDocAsStream', () => { + const docUrl = 'https://example.org/doc1'; - doc1 = attach('https://example.org/doc1'); - doc2 = attach('https://example.org/doc2'); - doc3 = attach('https://example.org/doc3'); - }); + let hostElement; + let ampdoc; + let shadowDoc; + let writer; - function attach(docUrl) { - const hostElement = win.document.createElement('div'); - win.document.body.appendChild(hostElement); - const importDoc = win.document.implementation.createHTMLDocument(''); - const shadowRoot = createShadowRoot(hostElement); - const ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); + beforeEach(() => { + deactivateChunking(); + hostElement = win.document.createElement('div'); + const shadowRoot = createShadowRoot(hostElement); + ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - ampdocServiceMock.expects('installShadowDoc') + ampdocServiceMock + .expects('installShadowDoc') .withExactArgs( - docUrl, - sinon.match(arg => arg == getShadowRoot(hostElement))) + docUrl, + sinon.match(arg => arg == getShadowRoot(hostElement)) + ) .returns(ampdoc) .atLeast(0); - ampdocServiceMock.expects('getAmpDoc') + ampdocServiceMock + .expects('getAmpDoc') .withExactArgs(sinon.match(arg => arg == getShadowRoot(hostElement))) .returns(ampdoc) .atLeast(0); + }); - const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - const broadcastReceived = sandbox.spy(); - viewer.onBroadcast(broadcastReceived); - const onMessage = sandbox.stub(); - amp.onMessage(function(eventType, data) { - if (eventType == 'ignore') { - return Promise.resolve(); - } - return onMessage(eventType, data); + it('should install services and styles', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + + const shadowRoot = getShadowRoot(hostElement); + + // URL is set. + expect(shadowRoot.AMP.url).to.equal(docUrl); + + // Stylesheet has been installed. + expect(shadowRoot.querySelector('style[amp-runtime]')).to.exist; + + // Doc services have been installed. + expect(ampdoc.services.action).to.exist; + expect(ampdoc.services.action.obj).to.exist; + expect(ampdoc.services.viewer).to.exist; + expect(ampdoc.services.viewer.obj).to.exist; + + // Single-doc bidings have been installed. + expect(shadowDoc.ampdoc).to.equal(ampdoc); + expect(shadowDoc.viewer).to.not.exist; }); - return {hostElement, amp, ampdoc, viewer, broadcastReceived, onMessage}; - } - it('should broadcast to all but sender', () => { - doc1.viewer.broadcast({test: 1}); - return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { - // Sender is not called. - expect(doc1.broadcastReceived).to.not.be.called; + it('should install doc services', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + + class Service1 {} + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', Service1); + }, + }); - // All others are called. - expect(doc2.broadcastReceived).to.be.calledOnce; - expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); - expect(doc3.broadcastReceived).to.be.calledOnce; - expect(doc3.broadcastReceived.args[0][0]).deep.equal({test: 1}); + writer.write(''); + writer.write(''); - // None of the onMessage are called. - expect(doc1.onMessage).to.not.be.called; - expect(doc2.onMessage).to.not.be.called; - expect(doc3.onMessage).to.not.be.called; + return ampdoc.whenBodyAvailable().then(() => { + return extensions.waitForExtension(win, 'amp-ext').then(() => { + // Factories have been applied. + expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf( + Service1 + ); + }); + }); }); - }); - it('should stop broadcasting after close', () => { - doc3.amp.close(); - doc1.viewer.broadcast({test: 1}); - return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { - // Sender is not called, closed is not called. - expect(doc1.broadcastReceived).to.not.be.called; - expect(doc3.broadcastReceived).to.not.be.called; + it('should pass init parameters to viewer', () => { + win.AMP.attachShadowDocAsStream(hostElement, docUrl, { + 'test1': '12', + }); + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(viewer.getParam('test1')).to.equal('12'); + }); + + it('should update host visibility', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + + writer.write('
    '); - // All others are called. - expect(doc2.broadcastReceived).to.be.calledOnce; - expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + // Document is invisible at first. + expect(hostElement.style.visibility).to.equal('hidden'); + + return ampdoc + .whenBodyAvailable() + .then(() => { + // After timeout the doc rendered is started. + expect(hostElement.style.visibility).to.equal('hidden'); + return ampdoc.signals().whenSignal('render-start'); + }) + .then(() => { + expect(hostElement.style.visibility).to.equal('visible'); + }); + }); + + it('should import body', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const shadowRoot = getShadowRoot(hostElement); + const body = + shadowRoot.querySelector('body') || + shadowRoot.querySelector('amp-body'); + expect(body).to.exist; + expect(body).to.have.class('amp-shadow'); + expect(body.style.position).to.equal('relative'); + env.flushVsync(); + expect(body.querySelector('child')).to.exist; + expect(ampdoc.getBody()).to.exist; + }); + }); + + it('should mark doc as ready', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect(ampdoc.isReady()).to.be.false; + writer.write(''); + writer.write(''); + writer.close(); + return ampdoc.whenReady().then(() => { + expect(ampdoc.isReady()).to.be.true; + }); + }); + }); + + it('should read title element', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write('test title'); + return ampdoc.whenBodyAvailable().then(() => { + expect(shadowDoc.title).to.equal('test title'); + expect(getShadowRoot(hostElement).AMP.title).to.equal('test title'); + }); + }); + + it('should read canonical element', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write( + '' + ); + return ampdoc.whenBodyAvailable().then(() => { + expect(shadowDoc.canonicalUrl).to.equal( + 'http://example.org/canonical' + ); + }); + }); + + it('should import fonts', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + + writer.write( + '' + ); + writer.write( + '' + ); + const fontEl2 = win.document.createElement('link'); + fontEl2.setAttribute('rel', 'stylesheet'); + fontEl2.setAttribute('href', 'http://example.org/font2'); + win.document.head.appendChild(fontEl2); + + return ampdoc.whenBodyAvailable().then(() => { + expect( + win.document.querySelector('link[href="http://example.org/font1"]') + ).to.exist; + // Duplicates are ignored. + expect( + win.document.querySelectorAll( + 'link[href="http://example.org/font2"]' + ) + ).to.have.length(1); + + const fontEl = win.document.querySelector( + 'link[href="http://example.org/font1"]' + ); + expect(fontEl.getAttribute('type')).to.equal('text/css'); + expect(fontEl.getAttribute('rel')).to.equal('stylesheet'); + fontEl.parentElement.removeChild(fontEl); + }); + }); + + it('should ignore boilerplate style', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const shadowRoot = getShadowRoot(hostElement); + expect(shadowRoot.querySelector('style[amp-boilerplate]')).to.not + .exist; + }); + }); + + it('should import custom style', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const shadowRoot = getShadowRoot(hostElement); + expect(shadowRoot.querySelector('style[amp-custom]')).to.exist; + expect( + shadowRoot.querySelector('style[amp-custom]').textContent + ).to.contain('.custom'); + }); + }); + + it('should ignore runtime extension', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + extensionsMock.expects('preloadExtension').never(); + writer.write( + '' + ); + writer.write(''); + return ampdoc.whenBodyAvailable(); + }); + + it('should ignore unknown script', () => { + expectAsyncConsoleError( + '[runtime] - unknown script: [object HTMLScriptElement] ' + + 'https://cdn.ampproject.org/other.js' + ); + + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + extensionsMock.expects('preloadExtension').never(); + writer.write( + '' + ); + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect( + getShadowRoot(hostElement).querySelector( + 'script[data-id="unknown1"]' + ) + ).to.not.exist; + expect(win.document.querySelector('script[data-id="unknown1"]')).to + .not.exist; + }); + }); + + it('should import extension element', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + extensionsMock + .expects('preloadExtension') + .withExactArgs('amp-ext1', '0.1') + .returns( + Promise.resolve({ + elements: { + 'amp-ext1': function() {}, + }, + }) + ) + .once(); + writer.write(''); + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect( + win.document.querySelector('script[custom-element="amp-ext1"]') + ).to.not.exist; + }); + }); + + it('should import extension template', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + extensionsMock + .expects('preloadExtension') + .withExactArgs('amp-ext1', '0.1') + .returns(Promise.resolve({elements: {}})) + .once(); + writer.write(''); + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect( + win.document.querySelector('script[custom-template="amp-ext1"]') + ).to.not.exist; + }); + }); + + it('should import inline script', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write( + '' + ); + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + ).to.exist; + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + .textContent + ).to.equal('{}'); + }); + }); + + it('should ignore inline script if javascript', () => { + expectAsyncConsoleError( + '[runtime] - unallowed inline javascript: ' + + '[object HTMLScriptElement]', + 2 + ); + + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write( + '' + ); + writer.write(''); + writer.write(''); + return ampdoc.whenBodyAvailable(() => { + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + ).to.not.exist; + }); + }); + + it('should start as visible by default', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(viewer.getVisibilityState()).to.equal('visible'); + }); + }); + + it('should start as prerender when requested', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl, { + 'visibilityState': 'prerender', + }); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(viewer.getVisibilityState()).to.equal('prerender'); + }); }); - }); - it('should stop broadcasting after force-close', () => { - doc3.hostElement.parentNode.removeChild(doc3.hostElement); - doc1.viewer.broadcast({test: 1}); - return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { - // Sender is not called, closed is not called. - expect(doc1.broadcastReceived).to.not.be.called; - expect(doc3.broadcastReceived).to.not.be.called; + it('should expose visibility method', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(shadowDoc.setVisibilityState).to.be.a('function'); + expect(viewer.getVisibilityState()).to.equal('visible'); + + shadowDoc.setVisibilityState('inactive'); + expect(viewer.getVisibilityState()).to.equal('inactive'); + }); + }); - // All others are called. - expect(doc2.broadcastReceived).to.be.calledOnce; - expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + it('should expose close method and dispose services', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(shadowDoc.close).to.be.a('function'); + expect(viewer.getVisibilityState()).to.equal('visible'); + + viewer.dispose = sandbox.spy(); + shadowDoc.close(); + expect(viewer.getVisibilityState()).to.equal('inactive'); + expect(viewer.dispose).to.be.calledOnce; + }); }); }); + describes.repeated( + 'messaging', + { + 'document.contains is the browser implementation': false, + 'document.contains is a stubbed implementation': true, + }, + (name, isStubbedDocumentContains) => { + let doc1, doc2, doc3; + + beforeEach(() => { + if (isStubbedDocumentContains) { + // Some browsers implement document.contains wrong, and it returns + // `false` even when this is incorrect. Repeat these tests with the + // faulty implementation. + sandbox.stub(win.document, 'contains').returns(false); + } + + doc1 = attach('https://example.org/doc1'); + doc2 = attach('https://example.org/doc2'); + doc3 = attach('https://example.org/doc3'); + }); + + function attach(docUrl) { + const hostElement = win.document.createElement('div'); + win.document.body.appendChild(hostElement); + const importDoc = win.document.implementation.createHTMLDocument(''); + const shadowRoot = createShadowRoot(hostElement); + const ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - it('should send message', () => { - doc1.onMessage.returns(Promise.resolve()); - return doc1.viewer.sendMessageAwaitResponse('test3', {test: 3}).then( - () => { - expect(doc1.onMessage).to.be.calledOnce; - expect(doc1.onMessage.args[0][0]).to.equal('test3'); - expect(doc1.onMessage.args[0][1]).to.deep.equal({test: 3}); + ampdocServiceMock + .expects('installShadowDoc') + .withExactArgs( + docUrl, + sinon.match(arg => arg == getShadowRoot(hostElement)) + ) + .returns(ampdoc) + .atLeast(0); + ampdocServiceMock + .expects('getAmpDoc') + .withExactArgs( + sinon.match(arg => arg == getShadowRoot(hostElement)) + ) + .returns(ampdoc) + .atLeast(0); + + const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + const viewer = getServiceForDoc(ampdoc, 'viewer'); + const broadcastReceived = sandbox.spy(); + viewer.onBroadcast(broadcastReceived); + const onMessage = sandbox.stub(); + amp.onMessage(function(eventType, data) { + if (eventType == 'ignore') { + return Promise.resolve(); + } + return onMessage(eventType, data); }); - }); + return { + hostElement, + amp, + ampdoc, + viewer, + broadcastReceived, + onMessage, + }; + } - it('should receive message', () => { - doc1.amp.postMessage('broadcast', {test: 4}, true); - expect(doc1.broadcastReceived).to.be.calledOnce; - expect(doc1.broadcastReceived.args[0][0]).to.deep.equal({test: 4}); - }); - }); -}); + it('should broadcast to all but sender', () => { + doc1.viewer.broadcast({test: 1}); + return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { + // Sender is not called. + expect(doc1.broadcastReceived).to.not.be.called; + + // All others are called. + expect(doc2.broadcastReceived).to.be.calledOnce; + expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + expect(doc3.broadcastReceived).to.be.calledOnce; + expect(doc3.broadcastReceived.args[0][0]).deep.equal({test: 1}); + + // None of the onMessage are called. + expect(doc1.onMessage).to.not.be.called; + expect(doc2.onMessage).to.not.be.called; + expect(doc3.onMessage).to.not.be.called; + }); + }); + + it('should stop broadcasting after close', () => { + doc3.amp.close(); + doc1.viewer.broadcast({test: 1}); + return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { + // Sender is not called, closed is not called. + expect(doc1.broadcastReceived).to.not.be.called; + expect(doc3.broadcastReceived).to.not.be.called; + + // All others are called. + expect(doc2.broadcastReceived).to.be.calledOnce; + expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + }); + }); + + it('should stop broadcasting after force-close', () => { + doc3.hostElement.parentNode.removeChild(doc3.hostElement); + doc1.viewer.broadcast({test: 1}); + return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { + // Sender is not called, closed is not called. + expect(doc1.broadcastReceived).to.not.be.called; + expect(doc3.broadcastReceived).to.not.be.called; + + // All others are called. + expect(doc2.broadcastReceived).to.be.calledOnce; + expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + }); + }); + + it('should send message', () => { + doc1.onMessage.returns(Promise.resolve()); + return doc1.viewer + .sendMessageAwaitResponse('test3', {test: 3}) + .then(() => { + expect(doc1.onMessage).to.be.calledOnce; + expect(doc1.onMessage.args[0][0]).to.equal('test3'); + expect(doc1.onMessage.args[0][1]).to.deep.equal({test: 3}); + }); + }); + + it('should receive message', () => { + doc1.amp.postMessage('broadcast', {test: 4}, true); + expect(doc1.broadcastReceived).to.be.calledOnce; + expect(doc1.broadcastReceived.args[0][0]).to.deep.equal({test: 4}); + }); + } + ); + } +); function getShadowRoot(hostElement) { return hostElement.shadowRoot || hostElement.__AMP_SHADOW_ROOT; diff --git a/test/unit/test-sanitizer.js b/test/unit/test-sanitizer.js index e091d6ab55ee..c64ec835e37d 100644 --- a/test/unit/test-sanitizer.js +++ b/test/unit/test-sanitizer.js @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - sanitizeHtml, - sanitizeTagsForTripleMustache, -} from '../../src/sanitizer'; - +import {sanitizeHtml, sanitizeTagsForTripleMustache} from '../../src/sanitizer'; let sanitize; let html; @@ -39,15 +35,17 @@ describe('Caja-based', () => { it('should apply html4/caja restrictions', () => { expect(sanitize('abc')).to.be.equal('ac'); expect(sanitize('abdc')).to.be.equal('ac'); - expect(sanitize('
    b
    ')).to.be - .equal('
    b
    '); + expect(sanitize('
    b
    ')).to.be.equal( + '
    b
    ' + ); }); // DOMPurify doesn't do special whitespace handling in attribute values. it('should catch attribute value whitespace variations', () => { allowConsoleError(() => { - expect(sanitize('ab')) - .to.be.equal('ab'); + expect( + sanitize('ab') + ).to.be.equal('ab'); }); }); @@ -55,31 +53,37 @@ describe('Caja-based', () => { it('should ignore invalid characters in attributes', () => { allowConsoleError(() => { expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); }); }); describe('for ', () => { it('should output [text] and [class] attributes', () => { - expect(sanitize('

    ')).to.be - .equal('

    '); + expect(sanitize('

    ')).to.be.equal( + '

    ' + ); }); it('should add "i-amphtml-binding" for data-amp-bind-*', () => { - expect(sanitize('

    ')).to.be - .equal('

    '); + expect(sanitize('

    ')).to.be.equal( + '

    ' + ); }); it('should NOT rewrite values of binding attributes', () => { // Should not change "foo.bar". Adding `target` attribute is not necessary // (but harmless) since will use rewriteAttributesForElement(). expect(sanitize('link')).to.equal( - 'link'); + 'link' + ); }); }); }); @@ -114,15 +118,20 @@ function runSanitizerTests() { it('should output valid markup', () => { expect(sanitize('

    abc

    ')).to.be.equal('

    abc

    '); expect(sanitize('

    abc

    ')).to.be.equal( - '

    abc

    '); + '

    abc

    ' + ); expect(sanitize('

    ab
    c

    ')).to.be.equal( - '

    ab
    c

    '); - expect(sanitize( + '

    ab
    c

    ' + ); + expect( + sanitize( '

    abc' + - '

    ')) - .to.be.equal( - '

    abc' + - '

    '); + '' + ) + ).to.be.equal( + '

    abc' + + '

    ' + ); }); it('should NOT output security-sensitive markup', () => { @@ -139,12 +148,9 @@ function runSanitizerTests() { }); it('should NOT output security-sensitive markup when nested', () => { - expect(sanitize('ac')) - .to.be.equal('ac'); - expect(sanitize('ac')) - .to.be.equal('ac'); - expect(sanitize('ac')) - .to.be.equal('ac'); + expect(sanitize('ac')).to.be.equal('ac'); + expect(sanitize('ac')).to.be.equal('ac'); + expect(sanitize('ac')).to.be.equal('ac'); }); it('should NOT output security-sensitive markup when broken', () => { @@ -154,36 +160,38 @@ function runSanitizerTests() { it('should output "on" attribute', () => { expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); it('should output "data-, aria-, and role" attributes', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - sanitize('b') + sanitize('b') ); const expected = serialize( - 'b'); + 'b' + ); expectEqualNodeLists(actual, expected); }); it('should output "href" attribute', () => { // Can't use string equality since DOMPurify will reorder attributes. - const actual = serialize( - sanitize('ab') - ); + const actual = serialize(sanitize('ab')); const expected = serialize( - 'ab'); + 'ab' + ); expectEqualNodeLists(actual, expected); }); it('should output "rel" attribute', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - sanitize('ab') + sanitize('ab') ); const expected = serialize( - 'ab'); + 'ab' + ); expectEqualNodeLists(actual, expected); }); @@ -210,145 +218,176 @@ function runSanitizerTests() { it('should default target to _top with href', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - sanitize('ac') + sanitize('ac') ); const expected = serialize( - 'ac'); + 'ac' + ); expectEqualNodeLists(actual, expected); }); it('should NOT default target to _top w/o href', () => { - expect(sanitize( - 'b' - + 'd' - )).to.equal( - 'b' - + 'd'); + expect(sanitize('bd')).to.equal( + 'bd' + ); }); it('should output a valid target', () => { - expect(sanitize('ab')) - .to.equal('ab'); + expect( + sanitize('ab') + ).to.equal('ab'); }); it('should output a valid target in different case', () => { - expect(sanitize('ab')) - .to.equal('ab'); + expect( + sanitize('ab') + ).to.equal('ab'); }); it('should override a unallowed target', () => { - expect(sanitize( - '_self' - + '_parent' - + '_other' - + '_OTHER' - + 'other' - )).to.equal( - '_self' - + '_parent' - + '_other' - + '_OTHER' - + 'other'); + expect( + sanitize( + '_self' + + '_parent' + + '_other' + + '_OTHER' + + 'other' + ) + ).to.equal( + '_self' + + '_parent' + + '_other' + + '_OTHER' + + 'other' + ); }); it('should NOT output security-sensitive attributes', () => { allowConsoleError(() => { - expect(sanitize('ab')).to.be.equal( - 'ab'); + expect(sanitize('ab')).to.be.equal('ab'); expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitize('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); }); it('should NOT output blacklisted values for class attributes', () => { allowConsoleError(() => { - expect(sanitize('

    hello

    ')).to.be - .equal('

    hello

    '); - expect(sanitize('

    hello

    ')).to.be - .equal('

    hello

    '); - expect(sanitize('

    hello

    ')).to.be - .equal('

    hello

    '); + expect(sanitize('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); + expect(sanitize('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); + expect(sanitize('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); }); }); it('should allow amp-subscriptions attributes', () => { - expect(sanitize('
    link
    ')) - .to.equal('
    link
    '); - expect(sanitize('
    link
    ')) - .to.equal('
    link
    '); - expect(sanitize('
    link
    ')) - .to.equal('
    link
    '); - expect(sanitize('
    link
    ')) - .to.equal('
    link
    '); - expect(sanitize('
    link
    ')) - .to.equal('
    link
    '); + expect(sanitize('
    link
    ')).to.equal( + '
    link
    ' + ); + expect( + sanitize('
    link
    ') + ).to.equal('
    link
    '); + expect(sanitize('
    link
    ')).to.equal( + '
    link
    ' + ); + expect(sanitize('
    link
    ')).to.equal( + '
    link
    ' + ); + expect(sanitize('
    link
    ')).to.equal( + '
    link
    ' + ); }); it('should allow source::src with valid protocol', () => { - expect(sanitize('')) - .to.equal(''); + expect(sanitize('')).to.equal( + '' + ); }); // TODO(choumx): HTTPS-only URI attributes are not enforced consistently // in the sanitizer yet. E.g. amp-video requires HTTPS, amp-img does not. // Unskip when this is fixed. it.skip('should not allow source::src with invalid protocol', () => { - expect(sanitize('')) - .to.equal(''); - expect(sanitize('')) - .to.equal(''); + expect(sanitize('')).to.equal( + '' + ); + expect(sanitize('')).to.equal( + '' + ); }); it('should allow div::template', () => { - expect(sanitize('
    ')) - .to.equal('
    '); + expect(sanitize('
    ')).to.equal( + '
    ' + ); }); it('should allow form::action-xhr', () => { - expect(sanitize('
    ')) - .to.equal('
    '); + expect( + sanitize('
    ') + ).to.equal('
    '); }); it('should allow -related attributes', () => { - expect(sanitize('
    ')) - .to.equal('
    '); - expect(sanitize('
    ')) - .to.equal('
    '); - expect(sanitize('
    ')) - .to.equal('
    '); - expect(sanitize('
    ')) - .to.equal('
    '); - expect(sanitize('')) - .to.equal(''); - expect(sanitize('')) - .to.equal(''); + expect(sanitize('
    ')).to.equal( + '
    ' + ); + expect(sanitize('
    ')).to.equal( + '
    ' + ); + expect(sanitize('
    ')).to.equal( + '
    ' + ); + expect(sanitize('
    ')).to.equal( + '
    ' + ); + expect( + sanitize('') + ).to.equal(''); + expect(sanitize('')).to.equal( + '' + ); }); it('should allow attributes', () => { - expect(sanitize('')) - .to.equal(''); + expect(sanitize('')).to.equal( + '' + ); }); it('should output "i-amphtml-key" attribute if diffing is enabled', () => { // Elements with bindings should have i-amphtml-key="". expect(sanitize('

    ', true)).to.match( - /

    <\/p>/); + /

    <\/p>/ + ); // AMP elements should have i-amphtml-key="". expect(sanitize('', true)).to.match( - /<\/amp-img>/); + /<\/amp-img>/ + ); // AMP elements with bindings should have i-amphtml-key="". expect(sanitize('', true)).to.match( - /<\/amp-img>/); + /<\/amp-img>/ + ); // Other elements should NOT have i-amphtml-key-set. expect(sanitize('

    ', true)).to.equal('

    '); }); @@ -369,10 +408,10 @@ function runSanitizerTests() { it('should allow for input type file and password', () => { // Given that the doc is not provided. allowConsoleError(() => { - expect(sanitize('')) - .to.equal(''); - expect(sanitize('')) - .to.equal(''); + expect(sanitize('')).to.equal(''); + expect(sanitize('')).to.equal( + '' + ); }); }); @@ -381,10 +420,12 @@ function runSanitizerTests() { allowConsoleError(() => { expect(sanitize('')).to.equal(''); expect(sanitize('')).to.equal(''); - expect(sanitize('
    ')) - .to.equal('
    '); - expect(sanitize('')) - .to.equal(''); + expect(sanitize('
    ')).to.equal( + '
    ' + ); + expect(sanitize('')).to.equal( + '' + ); }); }); }); @@ -395,60 +436,71 @@ function runSanitizerTests() { }); it('should output valid markup', () => { - expect(sanitizeTagsForTripleMustache('abc')) - .to.be.equal('abc'); + expect(sanitizeTagsForTripleMustache('abc')).to.be.equal( + 'abc' + ); expect(sanitizeTagsForTripleMustache('ab
    c
    ')).to.be.equal( - 'ab
    c
    '); + 'ab
    c
    ' + ); expect(sanitizeTagsForTripleMustache('abc')).to.be.equal( - 'abc'); + 'abc' + ); const markupWithClassAttribute = '

    heading

    '; - expect(sanitizeTagsForTripleMustache(markupWithClassAttribute)) - .to.be.equal(markupWithClassAttribute); + expect( + sanitizeTagsForTripleMustache(markupWithClassAttribute) + ).to.be.equal(markupWithClassAttribute); const markupWithClassesAttribute = - '
    heading
    '; - expect(sanitizeTagsForTripleMustache(markupWithClassesAttribute)) - .to.be.equal(markupWithClassesAttribute); + '
    heading
    '; + expect( + sanitizeTagsForTripleMustache(markupWithClassesAttribute) + ).to.be.equal(markupWithClassesAttribute); const markupParagraph = '

    paragraph

    '; - expect(sanitizeTagsForTripleMustache(markupParagraph)) - .to.be.equal(markupParagraph); + expect(sanitizeTagsForTripleMustache(markupParagraph)).to.be.equal( + markupParagraph + ); }); it('should NOT output non-whitelisted markup', () => { - expect(sanitizeTagsForTripleMustache('ac')) - .to.be.equal('ac'); - expect(sanitizeTagsForTripleMustache('ac')) - .to.be.equal('ac'); + expect(sanitizeTagsForTripleMustache('ac')).to.be.equal( + 'ac' + ); + expect(sanitizeTagsForTripleMustache('ac')).to.be.equal('ac'); }); it('should compensate for broken markup', () => { expect(sanitizeTagsForTripleMustache('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); describe('should sanitize `style` attribute', () => { - it('should allow valid styles',() => { - expect(sanitize('
    Test
    ')) - .to.equal('
    Test
    '); + it('should allow valid styles', () => { + expect(sanitize('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); - it('should ignore styles containing `!important`',() => { + it('should ignore styles containing `!important`', () => { allowConsoleError(() => { - expect(sanitize('
    Test
    ')) - .to.equal('
    Test
    '); + expect( + sanitize('
    Test
    ') + ).to.equal('
    Test
    '); }); }); it('should ignore styles containing `position:fixed`', () => { allowConsoleError(() => { - expect(sanitize('
    Test
    ')) - .to.equal('
    Test
    '); + expect(sanitize('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); }); it('should ignore styles containing `position:sticky`', () => { allowConsoleError(() => { - expect(sanitize('
    Test
    ')) - .to.equal('
    Test
    '); + expect(sanitize('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); }); }); diff --git a/test/unit/test-service.js b/test/unit/test-service.js index 756aae5bdf55..db1ef5f2fdcd 100644 --- a/test/unit/test-service.js +++ b/test/unit/test-service.js @@ -37,9 +37,7 @@ import { } from '../../src/service'; import {loadPromise} from '../../src/event-helper'; - describe('service', () => { - let sandbox; beforeEach(() => { @@ -51,7 +49,6 @@ describe('service', () => { }); describe('disposable interface', () => { - let disposable; let nonDisposable; @@ -69,13 +66,13 @@ describe('service', () => { expect(assertDisposable(disposable)).to.equal(disposable); allowConsoleError(() => { expect(() => assertDisposable(nonDisposable)).to.throw( - /required to implement Disposable/); + /required to implement Disposable/ + ); }); }); }); describe('window singletons', () => { - let Class; let count; let factory; @@ -139,15 +136,19 @@ describe('service', () => { }); it('should throw before creation if factory is not provided', () => { - allowConsoleError(() => { expect(() => { - getService(window, 'c'); - }).to.throw(); }); + allowConsoleError(() => { + expect(() => { + getService(window, 'c'); + }).to.throw(); + }); }); it('should fail without factory on initial setup', () => { - allowConsoleError(() => { expect(() => { - getService(window, 'not-present'); - }).to.throw(/Expected service not-present to be registered/); }); + allowConsoleError(() => { + expect(() => { + getService(window, 'not-present'); + }).to.throw(/Expected service not-present to be registered/); + }); }); it('should provide a promise that resolves when instantiated', () => { @@ -226,7 +227,6 @@ describe('service', () => { }); describe('ampdoc singletons', () => { - let windowApi; let ampdoc; let ampdocMock; @@ -264,7 +264,10 @@ describe('service', () => { }); it('should make per ampdoc singletons and store them in window', () => { - ampdocMock.expects('isSingleDoc').returns(true).atLeast(1); + ampdocMock + .expects('isSingleDoc') + .returns(true) + .atLeast(1); registerServiceBuilderForDoc(node, 'a', factory); const a1 = getServiceForDoc(node, 'a'); registerServiceBuilderForDoc(node, 'a', factory); @@ -288,7 +291,10 @@ describe('service', () => { }); it('should make per ampdoc singletons via ampdoc', () => { - ampdocMock.expects('isSingleDoc').returns(true).atLeast(1); + ampdocMock + .expects('isSingleDoc') + .returns(true) + .atLeast(1); registerServiceBuilderForDoc(ampdoc, 'a', factory); const a1 = getServiceForDoc(ampdoc, 'a'); registerServiceBuilderForDoc(ampdoc, 'a', factory); @@ -302,7 +308,10 @@ describe('service', () => { }); it('should make per ampdoc singletons and store them in ampdoc', () => { - ampdocMock.expects('isSingleDoc').returns(false).atLeast(1); + ampdocMock + .expects('isSingleDoc') + .returns(false) + .atLeast(1); registerServiceBuilderForDoc(node, 'a', factory); const a1 = getServiceForDoc(node, 'a'); registerServiceBuilderForDoc(node, 'a', factory); @@ -345,9 +354,11 @@ describe('service', () => { }); it('should fail without factory on initial setup', () => { - allowConsoleError(() => { expect(() => { - getServiceForDoc(node, 'not-present'); - }).to.throw(/Expected service not-present to be registered/); }); + allowConsoleError(() => { + expect(() => { + getServiceForDoc(node, 'not-present'); + }).to.throw(/Expected service not-present to be registered/); + }); }); it('should provide a promise that resolves when instantiated', () => { @@ -405,21 +416,28 @@ describe('service', () => { }); it('should resolve service for a child window', () => { - ampdocMock.expects('isSingleDoc').returns(true).atLeast(1); + ampdocMock + .expects('isSingleDoc') + .returns(true) + .atLeast(1); registerServiceBuilderForDoc(node, 'c', factory); const c = getServiceForDoc(node, 'c'); // A child. const childWin = {}; - const childWinNode = - {nodeType: 1, ownerDocument: {defaultView: childWin}}; + const childWinNode = { + nodeType: 1, + ownerDocument: {defaultView: childWin}, + }; setParentWindow(childWin, windowApi); expect(getServiceForDoc(childWinNode, 'c')).to.equal(c); // A grandchild. const grandchildWin = {}; - const grandChildWinNode = - {nodeType: 1, ownerDocument: {defaultView: grandchildWin}}; + const grandChildWinNode = { + nodeType: 1, + ownerDocument: {defaultView: grandchildWin}, + }; setParentWindow(grandchildWin, childWin); expect(getServiceForDoc(grandChildWinNode, 'c')).to.equal(c); }); @@ -481,14 +499,15 @@ describe('service', () => { beforeEach(() => { // A child. childWin = {}; - childWinNode = - {nodeType: 1, ownerDocument: {defaultView: childWin}}; + childWinNode = {nodeType: 1, ownerDocument: {defaultView: childWin}}; setParentWindow(childWin, windowApi); // A grandchild. grandchildWin = {}; - grandChildWinNode = - {nodeType: 1, ownerDocument: {defaultView: grandchildWin}}; + grandChildWinNode = { + nodeType: 1, + ownerDocument: {defaultView: grandchildWin}, + }; setParentWindow(grandchildWin, childWin); registerServiceBuilderForDoc(ampdoc, 'c', factory); @@ -501,12 +520,16 @@ describe('service', () => { }); it('should not fallback when opt_fallbackToTopWin is false', () => { - const fromChildNode = - getExistingServiceForDocInEmbedScope(childWinNode, 'c'); + const fromChildNode = getExistingServiceForDocInEmbedScope( + childWinNode, + 'c' + ); expect(fromChildNode).to.be.null; - const fromGrandchildNode = - getExistingServiceForDocInEmbedScope(grandChildWinNode, 'c'); + const fromGrandchildNode = getExistingServiceForDocInEmbedScope( + grandChildWinNode, + 'c' + ); expect(fromGrandchildNode).to.be.null; }); @@ -514,37 +537,55 @@ describe('service', () => { const fallbackToTopWin = true; const fromNode = getExistingServiceForDocInEmbedScope( - node, 'c', fallbackToTopWin); + node, + 'c', + fallbackToTopWin + ); expect(fromNode).to.equal(topService); const fromChildNode = getExistingServiceForDocInEmbedScope( - childWinNode, 'c', fallbackToTopWin); + childWinNode, + 'c', + fallbackToTopWin + ); expect(fromChildNode).to.equal(topService); const fromGrandchildNode = getExistingServiceForDocInEmbedScope( - grandChildWinNode, 'c', fallbackToTopWin); + grandChildWinNode, + 'c', + fallbackToTopWin + ); expect(fromGrandchildNode).to.equal(topService); }); it('should return overriden service', () => { const overridenService = {}; installServiceInEmbedScope(childWin, 'c', overridenService); - expect(getExistingServiceForDocInEmbedScope(childWinNode, 'c')) - .to.equal(overridenService); + expect( + getExistingServiceForDocInEmbedScope(childWinNode, 'c') + ).to.equal(overridenService); // Top-level service doesn't change. - expect(getExistingServiceForDocInEmbedScope( - node, 'c', /* opt_fallbackToTopWin */ true)) - .to.equal(topService); + expect( + getExistingServiceForDocInEmbedScope( + node, + 'c', + /* opt_fallbackToTopWin */ true + ) + ).to.equal(topService); // Notice that only direct overrides are allowed for now. This is // arbitrary can change in the future to allow hierarchical lookup // up the window chain. - expect(getExistingServiceForDocInEmbedScope(grandChildWinNode, 'c')) - .to.be.null; - expect(getExistingServiceForDocInEmbedScope( - grandChildWinNode, 'c', /* opt_fallbackToTopWin */ true)) - .to.equal(topService); + expect(getExistingServiceForDocInEmbedScope(grandChildWinNode, 'c')).to + .be.null; + expect( + getExistingServiceForDocInEmbedScope( + grandChildWinNode, + 'c', + /* opt_fallbackToTopWin */ true + ) + ).to.equal(topService); }); }); @@ -586,7 +627,6 @@ describe('service', () => { }); }); - describe('getParentWindowFrameElement', () => { let iframe; @@ -631,7 +671,9 @@ describe('service', () => { const childWin = {}; Object.defineProperties(childWin, { frameElement: { - get: () => {throw new Error('intentional');}, + get: () => { + throw new Error('intentional'); + }, }, }); setParentWindow(childWin, window); diff --git a/test/unit/test-shadow-embed.js b/test/unit/test-shadow-embed.js index 62419b37116e..f8230976a2d9 100644 --- a/test/unit/test-shadow-embed.js +++ b/test/unit/test-shadow-embed.js @@ -39,207 +39,219 @@ describes.sandboxed('shadow-embed', {}, () => { setShadowDomSupportedVersionForTesting(undefined); }); - [ShadowDomVersion.NONE, ShadowDomVersion.V0, ShadowDomVersion.V1] - .forEach(scenario => { - describe('shadow APIs', () => { - let hostElement; - - beforeEach(function() { - hostElement = document.createElement('div'); - setShadowDomSupportedVersionForTesting(scenario); - setShadowCssSupportedForTesting(undefined); + [ShadowDomVersion.NONE, ShadowDomVersion.V0, ShadowDomVersion.V1].forEach( + scenario => { + describe('shadow APIs', () => { + let hostElement; + + beforeEach(function() { + hostElement = document.createElement('div'); + setShadowDomSupportedVersionForTesting(scenario); + setShadowCssSupportedForTesting(undefined); + }); + + describe(scenario, function() { + before(function() { + if ( + scenario == ShadowDomVersion.V0 && + !Element.prototype.createShadowRoot + ) { + this.skipTest(); + } + + if ( + scenario == ShadowDomVersion.V1 && + !Element.prototype.attachShadow + ) { + this.skipTest(); + } }); - describe(scenario, function() { - before(function() { - if (scenario == ShadowDomVersion.V0 && - !Element.prototype.createShadowRoot) { - this.skipTest(); - } + it('should transform CSS installStylesForDoc for shadow root', () => { + const shadowRoot = createShadowRoot(hostElement); + const ampdoc = new AmpDocShadow( + window, + 'https://a.org/', + shadowRoot + ); + const style = installStylesForDoc(ampdoc, 'body {}', null, true); + expect(shadowRoot.contains(style)).to.be.true; + const css = style.textContent.replace(/\s/g, ''); + if (scenario == ShadowDomVersion.NONE) { + expect(css).to.match(/amp-body/); + } else { + expect(css).to.equal('body{}'); + } + }); - if (scenario == ShadowDomVersion.V1 && - !Element.prototype.attachShadow) { - this.skipTest(); - } + describe('createShadowRoot', () => { + it('should clear duplicate root', () => { + const shadowRoot1 = createShadowRoot(hostElement); + const span = document.createElement('span'); + shadowRoot1.appendChild(span); + expect(shadowRoot1.contains(span)).to.be.true; + + const shadowRoot2 = createShadowRoot(hostElement); + expect(shadowRoot2).to.equal(shadowRoot1); + expect(shadowRoot2.contains(span)).to.be.false; }); - it('should transform CSS installStylesForDoc ' + - 'for shadow root', () => { + it('should have host', () => { const shadowRoot = createShadowRoot(hostElement); - const ampdoc = new AmpDocShadow( - window, 'https://a.org/', shadowRoot); - const style = installStylesForDoc(ampdoc, 'body {}', null, true); - expect(shadowRoot.contains(style)).to.be.true; - const css = style.textContent.replace(/\s/g, ''); - if (scenario == ShadowDomVersion.NONE) { - expect(css).to.match(/amp-body/); - } else { - expect(css).to.equal('body{}'); - } + expect(shadowRoot.host).to.equal(hostElement); }); - describe('createShadowRoot', () => { - it('should clear duplicate root', () => { - const shadowRoot1 = createShadowRoot(hostElement); - const span = document.createElement('span'); - shadowRoot1.appendChild(span); - expect(shadowRoot1.contains(span)).to.be.true; + it('should have getElementById', () => { + const shadowRoot = createShadowRoot(hostElement); + expect(shadowRoot.getElementById).to.be.ok; - const shadowRoot2 = createShadowRoot(hostElement); - expect(shadowRoot2).to.equal(shadowRoot1); - expect(shadowRoot2.contains(span)).to.be.false; - }); + const spanId = 'test' + Math.floor(Math.random() * 10000); + const span = document.createElement('span'); + span.id = spanId; + shadowRoot.appendChild(span); + expect(shadowRoot.getElementById(spanId)).to.equal(span); + }); - it('should have host', () => { + if (scenario == ShadowDomVersion.NONE) { + it('should add id for polyfill', () => { const shadowRoot = createShadowRoot(hostElement); - expect(shadowRoot.host).to.equal(hostElement); + expect(shadowRoot.tagName).to.equal('I-AMPHTML-SHADOW-ROOT'); + expect(shadowRoot.id).to.match(/i-amphtml-sd-\d+/); }); - it('should have getElementById', () => { + it('should add host style for polyfill', () => { + const doc = hostElement.ownerDocument; + doc.body.appendChild(hostElement); + const slot = doc.createElement('div'); + hostElement.appendChild(slot); + expect(slot).to.have.display('block'); const shadowRoot = createShadowRoot(hostElement); - expect(shadowRoot.getElementById).to.be.ok; - - const spanId = 'test' + Math.floor(Math.random() * 10000); - const span = document.createElement('span'); - span.id = spanId; - shadowRoot.appendChild(span); - expect(shadowRoot.getElementById(spanId)).to.equal(span); + expect(hostElement).to.have.class( + 'i-amphtml-shadow-host-polyfill' + ); + expect(slot).to.have.display('none'); + expect(shadowRoot).to.not.have.display('none'); + doc.body.removeChild(hostElement); }); + } - if (scenario == ShadowDomVersion.NONE) { - it('should add id for polyfill', () => { - const shadowRoot = createShadowRoot(hostElement); - expect(shadowRoot.tagName).to.equal('I-AMPHTML-SHADOW-ROOT'); - expect(shadowRoot.id).to.match(/i-amphtml-sd-\d+/); - }); - - it('should add host style for polyfill', () => { - const doc = hostElement.ownerDocument; - doc.body.appendChild(hostElement); - const slot = doc.createElement('div'); - hostElement.appendChild(slot); - expect(slot).to.have.display('block'); - const shadowRoot = createShadowRoot(hostElement); - expect(hostElement).to.have.class( - 'i-amphtml-shadow-host-polyfill'); - expect(slot).to.have.display('none'); - expect(shadowRoot).to.not.have.display('none'); - doc.body.removeChild(hostElement); - }); - } - - // Test scenarios where Shadow Css is not supported - it('Should add an id and class for CSS \ + // Test scenarios where Shadow Css is not supported + it('Should add an id and class for CSS \ encapsulation to the shadow root', () => { - setShadowCssSupportedForTesting(false); - const shadowRoot = createShadowRoot(hostElement); - expect(shadowRoot.id).to.match(/i-amphtml-sd-\d+/); - // Browserify does not support arrow functions with params. - // Using Old School for - const shadowRootClassListArray = - toArray(shadowRoot.host.classList); - let foundShadowCssClass = false; - for (let i = 0; i < shadowRootClassListArray.length; i++) { - if (shadowRootClassListArray[i].match(/i-amphtml-sd-\d+/)) { - foundShadowCssClass = true; - break; - } + setShadowCssSupportedForTesting(false); + const shadowRoot = createShadowRoot(hostElement); + expect(shadowRoot.id).to.match(/i-amphtml-sd-\d+/); + // Browserify does not support arrow functions with params. + // Using Old School for + const shadowRootClassListArray = toArray( + shadowRoot.host.classList + ); + let foundShadowCssClass = false; + for (let i = 0; i < shadowRootClassListArray.length; i++) { + if (shadowRootClassListArray[i].match(/i-amphtml-sd-\d+/)) { + foundShadowCssClass = true; + break; } - expect(foundShadowCssClass).to.be.ok; - }); + } + expect(foundShadowCssClass).to.be.ok; + }); - it('Should transform CSS for the shadow root', () => { - setShadowCssSupportedForTesting(false); - const shadowRoot = createShadowRoot(hostElement); - const ampdoc = new AmpDocShadow( - window, 'https://a.org/', shadowRoot); - const style = - installStylesForDoc(ampdoc, 'body {}', null, true); - expect(shadowRoot.contains(style)).to.be.true; - const css = style.textContent.replace(/\s/g, ''); - expect(css).to.match(/amp-body/); - }); + it('Should transform CSS for the shadow root', () => { + setShadowCssSupportedForTesting(false); + const shadowRoot = createShadowRoot(hostElement); + const ampdoc = new AmpDocShadow( + window, + 'https://a.org/', + shadowRoot + ); + const style = installStylesForDoc(ampdoc, 'body {}', null, true); + expect(shadowRoot.contains(style)).to.be.true; + const css = style.textContent.replace(/\s/g, ''); + expect(css).to.match(/amp-body/); }); + }); - describe('stylesheets', () => { - let parentStylesheet; + describe('stylesheets', () => { + let parentStylesheet; - beforeEach(() => { - parentStylesheet = document.createElement('style'); - parentStylesheet.textContent = '.x {background: red}'; - document.body.appendChild(parentStylesheet); - document.body.appendChild(hostElement); - }); + beforeEach(() => { + parentStylesheet = document.createElement('style'); + parentStylesheet.textContent = '.x {background: red}'; + document.body.appendChild(parentStylesheet); + document.body.appendChild(hostElement); + }); - afterEach(() => { - document.body.removeChild(parentStylesheet); - document.body.removeChild(hostElement); - }); + afterEach(() => { + document.body.removeChild(parentStylesheet); + document.body.removeChild(hostElement); + }); - it('should have shadow stylesheets and not global', () => { - const shadowRoot = createShadowRoot(hostElement); - const shadowStyle = document.createElement('style'); - shadowStyle.textContent = '.x {background: green}'; - shadowRoot.appendChild(shadowStyle); - - const {styleSheets} = shadowRoot; - expect(styleSheets).to.exist; - expect(styleSheets).to.have.length(1); - expect(styleSheets[0].ownerNode).to.equal(shadowStyle); - }); + it('should have shadow stylesheets and not global', () => { + const shadowRoot = createShadowRoot(hostElement); + const shadowStyle = document.createElement('style'); + shadowStyle.textContent = '.x {background: green}'; + shadowRoot.appendChild(shadowStyle); + + const {styleSheets} = shadowRoot; + expect(styleSheets).to.exist; + expect(styleSheets).to.have.length(1); + expect(styleSheets[0].ownerNode).to.equal(shadowStyle); }); + }); - // TODO(aghassemi, #12499): Make this work with latest mocha / karma - describe.skip('importShadowBody', () => { - let shadowRoot, source, child1, child2; - - beforeEach(() => { - shadowRoot = createShadowRoot(hostElement); - source = document.createElement('body'); - child1 = document.createElement('div'); - child1.id = 'child1'; - child2 = document.createElement('div'); - child2.id = 'child2'; - source.appendChild(child1); - source.appendChild(child2); - }); + // TODO(aghassemi, #12499): Make this work with latest mocha / karma + describe.skip('importShadowBody', () => { + let shadowRoot, source, child1, child2; + + beforeEach(() => { + shadowRoot = createShadowRoot(hostElement); + source = document.createElement('body'); + child1 = document.createElement('div'); + child1.id = 'child1'; + child2 = document.createElement('div'); + child2.id = 'child2'; + source.appendChild(child1); + source.appendChild(child2); + }); - it('should import body with all children', () => { - expect(shadowRoot.body).to.be.undefined; - const body = importShadowBody(shadowRoot, source, true); - expect(shadowRoot.body).to.equal(body); - expect(body.tagName).to.equal( - scenario == ShadowDomVersion.NONE ? 'AMP-BODY' : 'BODY'); - expect(body.style.position).to.equal('relative'); - if (scenario == ShadowDomVersion.NONE) { - expect(body.style.display).to.equal('block'); - } - expect(shadowRoot.contains(body)).to.be.true; - expect(body.children).to.have.length(2); - expect(body.children[0].id).to.equal('child1'); - expect(body.children[1].id).to.equal('child2'); - }); + it('should import body with all children', () => { + expect(shadowRoot.body).to.be.undefined; + const body = importShadowBody(shadowRoot, source, true); + expect(shadowRoot.body).to.equal(body); + expect(body.tagName).to.equal( + scenario == ShadowDomVersion.NONE ? 'AMP-BODY' : 'BODY' + ); + expect(body.style.position).to.equal('relative'); + if (scenario == ShadowDomVersion.NONE) { + expect(body.style.display).to.equal('block'); + } + expect(shadowRoot.contains(body)).to.be.true; + expect(body.children).to.have.length(2); + expect(body.children[0].id).to.equal('child1'); + expect(body.children[1].id).to.equal('child2'); + }); - it('should import shallow body', () => { - expect(shadowRoot.body).to.be.undefined; - const body = importShadowBody(shadowRoot, source, false); - expect(shadowRoot.body).to.equal(body); - expect(body.tagName).to.equal( - scenario == ShadowDomVersion.NONE ? 'AMP-BODY' : 'BODY'); - expect(body.style.position).to.equal('relative'); - if (scenario == ShadowDomVersion.NONE) { - expect(body.style.display).to.equal('block'); - } - expect(shadowRoot.contains(body)).to.be.true; - expect(body.children).to.have.length(0); - }); + it('should import shallow body', () => { + expect(shadowRoot.body).to.be.undefined; + const body = importShadowBody(shadowRoot, source, false); + expect(shadowRoot.body).to.equal(body); + expect(body.tagName).to.equal( + scenario == ShadowDomVersion.NONE ? 'AMP-BODY' : 'BODY' + ); + expect(body.style.position).to.equal('relative'); + if (scenario == ShadowDomVersion.NONE) { + expect(body.style.display).to.equal('block'); + } + expect(shadowRoot.contains(body)).to.be.true; + expect(body.children).to.have.length(0); }); }); }); }); + } + ); describe('isShadowRoot', () => { - it('should yield false for non-nodes', () => { expect(isShadowRoot(null)).to.be.false; expect(isShadowRoot(undefined)).to.be.false; @@ -274,8 +286,8 @@ describes.sandboxed('shadow-embed', {}, () => { }); it('should yield true for polyfill', () => { - expect(isShadowRoot(document.createElement( - 'i-amphtml-shadow-root'))).to.be.true; + expect(isShadowRoot(document.createElement('i-amphtml-shadow-root'))).to + .be.true; }); }); @@ -318,8 +330,7 @@ describes.sandboxed('shadow-embed', {}, () => { it('should replace root selectors', () => { expect(scope('html {}')).to.equal('.h amp-html {}'); expect(scope('body {}')).to.equal('.h amp-body {}'); - expect(scope('html {} body {}')).to.equal( - '.h amp-html {}.h amp-body {}'); + expect(scope('html {} body {}')).to.equal('.h amp-html {}.h amp-body {}'); expect(scope('html, body {}')).to.equal('.h amp-html, .h amp-body {}'); expect(scope('body.x {}')).to.equal('.h amp-body.x {}'); expect(scope('body::after {}')).to.equal('.h amp-body::after {}'); @@ -369,26 +380,24 @@ describes.sandboxed('shadow-embed', {}, () => { }); it('should resolve to streamer', () => { - expect(createShadowDomWriter(win)) - .to.be.instanceOf(ShadowDomWriterStreamer); + expect(createShadowDomWriter(win)).to.be.instanceOf( + ShadowDomWriterStreamer + ); expect(createHTMLDocumentSpy).to.be.calledOnce; expect(createHTMLDocumentSpy).to.be.calledWith(''); }); it('should resolve to bulk without API', () => { delete win.document.implementation.createHTMLDocument; - expect(createShadowDomWriter(win)) - .to.be.instanceOf(ShadowDomWriterBulk); + expect(createShadowDomWriter(win)).to.be.instanceOf(ShadowDomWriterBulk); delete win.document.implementation; - expect(createShadowDomWriter(win)) - .to.be.instanceOf(ShadowDomWriterBulk); + expect(createShadowDomWriter(win)).to.be.instanceOf(ShadowDomWriterBulk); expect(createHTMLDocumentSpy).to.not.be.called; }); it('should resolve to bulk on firefox', () => { isFirefox = true; - expect(createShadowDomWriter(win)) - .to.be.instanceOf(ShadowDomWriterBulk); + expect(createShadowDomWriter(win)).to.be.instanceOf(ShadowDomWriterBulk); expect(createHTMLDocumentSpy).to.not.be.called; }); }); @@ -485,8 +494,9 @@ describes.sandboxed('shadow-embed', {}, () => { }); it('should not parse noscript as markup', () => { - writer.write(''); + writer.write( + '' + ); return waitForNextBodyChunk().then(() => { expect(win.document.body.querySelector('child1')).to.exist; expect(win.document.body.querySelector('child2')).not.to.exist; diff --git a/test/unit/test-size-list.js b/test/unit/test-size-list.js index 7c6ea36eecf5..cd0dbce6f839 100644 --- a/test/unit/test-size-list.js +++ b/test/unit/test-size-list.js @@ -16,9 +16,7 @@ import {SizeList, parseSizeList} from '../../src/size-list'; - describe('SizeList parseSizeList', () => { - it('should accept single option', () => { const res = parseSizeList(' \n 111px \n '); expect(res.sizes_.length).to.equal(1); @@ -48,12 +46,14 @@ describe('SizeList parseSizeList', () => { it('should accept complicated media conditions', () => { const res = parseSizeList( - ' \n screen and (min-width: 1000px) \t ' + + ' \n screen and (min-width: 1000px) \t ' + ' and (max-width: 2000px) 222px \n,' + - ' 111px \n'); + ' 111px \n' + ); expect(res.sizes_.length).to.equal(2); expect(res.sizes_[0].mediaQuery).to.equal( - 'screen and (min-width: 1000px) and (max-width: 2000px)'); + 'screen and (min-width: 1000px) and (max-width: 2000px)' + ); expect(res.sizes_[0].size).to.equal('222px'); expect(res.sizes_[1].mediaQuery).to.equal(undefined); expect(res.sizes_[1].size).to.equal('111px'); @@ -74,8 +74,9 @@ describe('SizeList parseSizeList', () => { }); it('should accept CSS functions', () => { - const res = parseSizeList('screen calc(111vw + 10px) \n' + - ', ca_1-C((50vw+20px) / 2) '); + const res = parseSizeList( + 'screen calc(111vw + 10px) \n, ca_1-C((50vw+20px) / 2) ' + ); expect(res.sizes_.length).to.equal(2); expect(res.sizes_[0].mediaQuery).to.equal('screen'); expect(res.sizes_[0].size).to.equal('calc(111vw + 10px)'); @@ -84,8 +85,9 @@ describe('SizeList parseSizeList', () => { }); it('should tolerate right paren', () => { - const res = parseSizeList('(min-width:2000px)calc(11px)' + - ',(min-width:1000px)11px,12px'); + const res = parseSizeList( + '(min-width:2000px)calc(11px),(min-width:1000px)11px,12px' + ); expect(res.sizes_.length).to.equal(3); expect(res.sizes_[0].mediaQuery).to.equal('(min-width:2000px)'); expect(res.sizes_[0].size).to.equal('calc(11px)'); @@ -97,47 +99,59 @@ describe('SizeList parseSizeList', () => { it('should fail on invalid CSS functions', () => { // Spaces are not allowed between function name and `(`. - allowConsoleError(() => { expect(() => { - parseSizeList('screen calc (111vw + 10px) \n, 10px '); - }).to.throw(/Invalid CSS function/); }); + allowConsoleError(() => { + expect(() => { + parseSizeList('screen calc (111vw + 10px) \n, 10px '); + }).to.throw(/Invalid CSS function/); + }); // Parens don't match. - allowConsoleError(() => { expect(() => { - parseSizeList('screen calc(111vw + 10px)) \n, 10px '); - }).to.throw(/Invalid CSS function/); }); - allowConsoleError(() => { expect(() => { - parseSizeList('screen calc((111vw + 10px) \n, 10px '); - }).to.throw(/Invalid CSS function/); }); + allowConsoleError(() => { + expect(() => { + parseSizeList('screen calc(111vw + 10px)) \n, 10px '); + }).to.throw(/Invalid CSS function/); + }); + allowConsoleError(() => { + expect(() => { + parseSizeList('screen calc((111vw + 10px) \n, 10px '); + }).to.throw(/Invalid CSS function/); + }); }); it('should accept percent when allowed', () => { - const res = parseSizeList(' \n 111% \n ', - /* opt_allowPercentAsLength */ true); + const res = parseSizeList( + ' \n 111% \n ', + /* opt_allowPercentAsLength */ true + ); expect(res.sizes_.length).to.equal(1); expect(res.sizes_[0].mediaQuery).to.equal(undefined); expect(res.sizes_[0].size).to.equal('111%'); }); it('should not accept percent', () => { - allowConsoleError(() => { expect(() => { - parseSizeList(' \n 111% \n ', /* opt_allowPercentAsLength */ false); - }).to.throw(/Invalid length value/); }); + allowConsoleError(() => { + expect(() => { + parseSizeList(' \n 111% \n ', /* opt_allowPercentAsLength */ false); + }).to.throw(/Invalid length value/); + }); }); it('should fail bad length', () => { - allowConsoleError(() => { expect(() => { - parseSizeList(' \n 111 \n '); - }).to.throw(/Invalid length value/); }); + allowConsoleError(() => { + expect(() => { + parseSizeList(' \n 111 \n '); + }).to.throw(/Invalid length value/); + }); }); }); - describe('SizeList construct', () => { - it('should have at least one option', () => { - allowConsoleError(() => { expect(() => { - new SizeList([]); - }).to.throw(/SizeList must have at least one option/); }); + allowConsoleError(() => { + expect(() => { + new SizeList([]); + }).to.throw(/SizeList must have at least one option/); + }); }); it('the last option must not have a query', () => { @@ -146,22 +160,25 @@ describe('SizeList construct', () => { new SizeList([{mediaQuery: 'screen', size: '111px'}]); }).to.throw(/The last option must not have a media condition/); expect(() => { - new SizeList([{mediaQuery: 'print', size: '222px'}, - {mediaQuery: 'screen', size: '111px'}]); + new SizeList([ + {mediaQuery: 'print', size: '222px'}, + {mediaQuery: 'screen', size: '111px'}, + ]); }).to.throw(/The last option must not have a media condition/); }); }); it('non-last options must have media query', () => { - allowConsoleError(() => { expect(() => { - new SizeList([{size: '222px'}, {size: '111px'}]); - }).to.throw( - /All options except for the last must have a media condition/); + allowConsoleError(() => { + expect(() => { + new SizeList([{size: '222px'}, {size: '111px'}]); + }).to.throw( + /All options except for the last must have a media condition/ + ); }); }); }); - describe('SizeList select', () => { it('should select default last option', () => { const sizeList = new SizeList([ @@ -170,11 +187,13 @@ describe('SizeList select', () => { {mediaQuery: 'media3', size: '222px'}, {size: '111px'}, ]); - expect(sizeList.select({ - matchMedia: () => { - return {}; - }, - })).to.equal('111px'); + expect( + sizeList.select({ + matchMedia: () => { + return {}; + }, + }) + ).to.equal('111px'); }); it('should select a matching option', () => { @@ -184,14 +203,16 @@ describe('SizeList select', () => { {mediaQuery: 'media3', size: '222px'}, {size: '111px'}, ]); - expect(sizeList.select({ - matchMedia: mq => { - if (mq == 'media2') { - return {matches: true}; - } - return {}; - }, - })).to.equal('333px'); + expect( + sizeList.select({ + matchMedia: mq => { + if (mq == 'media2') { + return {matches: true}; + } + return {}; + }, + }) + ).to.equal('333px'); }); it('should select first matching option', () => { @@ -201,13 +222,15 @@ describe('SizeList select', () => { {mediaQuery: 'media3', size: '222px'}, {size: '111px'}, ]); - expect(sizeList.select({ - matchMedia: mq => { - if (mq == 'media1' || mq == 'media2') { - return {matches: true}; - } - return {}; - }, - })).to.equal('444px'); + expect( + sizeList.select({ + matchMedia: mq => { + if (mq == 'media1' || mq == 'media2') { + return {matches: true}; + } + return {}; + }, + }) + ).to.equal('444px'); }); }); diff --git a/test/unit/test-srcset.js b/test/unit/test-srcset.js index 7194f4b96d2c..51fac6db4f8d 100644 --- a/test/unit/test-srcset.js +++ b/test/unit/test-srcset.js @@ -21,10 +21,8 @@ import { srcsetFromSrc, } from '../../src/srcset'; - describe('Srcset', () => { describe('parseSrcset', () => { - function test(s, expected) { const res = parseSrcset(s); expect(res.sources_.length).to.equal(expected.length); @@ -38,18 +36,12 @@ describe('Srcset', () => { } it('should accept single source, default to 1px', () => { - test(' \n image \n ', [ - {url: 'image', dpr: 1}, - ]); + test(' \n image \n ', [{url: 'image', dpr: 1}]); }); it('should ignore empty source', () => { - test(' \n image \n, ', [ - {url: 'image', dpr: 1}, - ]); - test(' , \n image \n, ', [ - {url: 'image', dpr: 1}, - ]); + test(' \n image \n, ', [{url: 'image', dpr: 1}]); + test(' , \n image \n, ', [{url: 'image', dpr: 1}]); }); it('should accept multiple sources, default to 1x', () => { @@ -124,12 +116,8 @@ describe('Srcset', () => { {url: 'image,1x', dpr: 1}, {url: 'image,2x', dpr: 2}, ]); - test(' \n image,1 \n ', [ - {url: 'image,1', dpr: 1}, - ]); - test(' \n image,1x \n ', [ - {url: 'image,1x', dpr: 1}, - ]); + test(' \n image,1 \n ', [{url: 'image,1', dpr: 1}]); + test(' \n image,1x \n ', [{url: 'image,1x', dpr: 1}]); }); it('should accept no-whitestpace', () => { @@ -145,12 +133,8 @@ describe('Srcset', () => { {url: 'image,2', dpr: 1}, {url: 'image,1', dpr: 2}, ]); - test('image,2 2x', [ - {url: 'image,2', dpr: 2}, - ]); - test('image,1', [ - {url: 'image,1', dpr: 1}, - ]); + test('image,2 2x', [{url: 'image,2', dpr: 2}]); + test('image,1', [{url: 'image,1', dpr: 1}]); }); it('should accept other special chars in URLs', () => { @@ -169,35 +153,32 @@ describe('Srcset', () => { {url: 'image,2x', dpr: 1}, {url: 'image,1x', dpr: 2}, ]); - test(' \n image,1x \n ', [ - {url: 'image,1x', dpr: 1}, - ]); - test(' \n image,1w \n ', [ - {url: 'image,1w', dpr: 1}, - ]); + test(' \n image,1x \n ', [{url: 'image,1x', dpr: 1}]); + test(' \n image,1w \n ', [{url: 'image,1w', dpr: 1}]); }); it('should not accept mixed sources', () => { - allowConsoleError(() => { expect(() => { - parseSrcset(' \n image1 100w\n , \n image2 1.5x\n , image3 '); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + parseSrcset(' \n image1 100w\n , \n image2 1.5x\n , image3 '); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should parse misc examples', () => { - test('image-1x.png 1x, image-2x.png 2x, image-3x.png 3x, image-4x.png 4x', - [ - {url: 'image-1x.png', dpr: 1}, - {url: 'image-2x.png', dpr: 2}, - {url: 'image-3x.png', dpr: 3}, - {url: 'image-4x.png', dpr: 4}, - ]); - test('image,one.png', [ - {url: 'image,one.png', dpr: 1}, - ]); + test( + 'image-1x.png 1x, image-2x.png 2x, image-3x.png 3x, image-4x.png 4x', + [ + {url: 'image-1x.png', dpr: 1}, + {url: 'image-2x.png', dpr: 2}, + {url: 'image-3x.png', dpr: 3}, + {url: 'image-4x.png', dpr: 4}, + ] + ); + test('image,one.png', [{url: 'image,one.png', dpr: 1}]); }); }); - describe('srcsetFromElement', () => { function test(srcset, src, expected) { const element = document.createElement('div'); @@ -233,15 +214,11 @@ describe('Srcset', () => { }); it('should select src when only src available', () => { - test(undefined, 'image-0.png', [ - {url: 'image-0.png', dpr: 1}, - ]); + test(undefined, 'image-0.png', [{url: 'image-0.png', dpr: 1}]); }); it('should select src when only srcset is empty', () => { - test('', 'image-0.png', [ - {url: 'image-0.png', dpr: 1}, - ]); + test('', 'image-0.png', [{url: 'image-0.png', dpr: 1}]); }); it('should prefer srcset to src', () => { @@ -252,21 +229,20 @@ describe('Srcset', () => { }); it('should allow non-compliant src with space', () => { - test(undefined, 'image 0.png', [ - {url: 'image 0.png', dpr: 1}, - ]); + test(undefined, 'image 0.png', [{url: 'image 0.png', dpr: 1}]); }); it('should require srcset or src to be available', () => { - allowConsoleError(() => { expect(() => { - srcsetFromElement(document.createElement('div')); - }).to.throw( - /Either non-empty "srcset" or "src" attribute must be specified/); + allowConsoleError(() => { + expect(() => { + srcsetFromElement(document.createElement('div')); + }).to.throw( + /Either non-empty "srcset" or "src" attribute must be specified/ + ); }); }); }); - describe('srcsetFromSrc', () => { it('should construct with undefined width and 1 dpr', () => { const srcset = srcsetFromSrc('image-0.png'); @@ -279,49 +255,61 @@ describe('Srcset', () => { }); }); - describe('construct', () => { it('should enforce only one type of descriptor per source', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image-1000', width: 100, dpr: 2}]); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image-1000', width: 100, dpr: 2}]); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should not allow 0-width descriptor', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image-1000', width: 0}]); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image-1000', width: 0}]); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should not allow 0-dpr descriptor', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image-1000', dpr: 0}]); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image-1000', dpr: 0}]); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should enforce only one type of descriptor total', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image-1000', width: 100}, - {url: 'image-2x', dpr: 2}]); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([ + {url: 'image-1000', width: 100}, + {url: 'image-2x', dpr: 2}, + ]); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should not allow duplicate sources', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image', width: 100}, - {url: 'image', width: 100}]); - }).to.throw(/Duplicate width/); }); - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image', dpr: 2}, {url: 'image', dpr: 2}]); - }).to.throw(/Duplicate dpr/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image', width: 100}, {url: 'image', width: 100}]); + }).to.throw(/Duplicate width/); + }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image', dpr: 2}, {url: 'image', dpr: 2}]); + }).to.throw(/Duplicate dpr/); + }); }); }); - describe('select', () => { it('select by width', () => { const srcset = parseSrcset( - 'image-1000 1000w, image-500 500w, image-250 250w, image 50w'); + 'image-1000 1000w, image-500 500w, image-250 250w, image 50w' + ); // DPR = 1 expect(srcset.select(2000, 1)).to.equal('image-1000'); @@ -359,7 +347,8 @@ describe('Srcset', () => { it('select by width with preference toward higher width', () => { const srcset = parseSrcset( - 'image-1000 1000w, image-500 500w, image-250 250w, image 50w'); + 'image-1000 1000w, image-500 500w, image-250 250w, image 50w' + ); // For DPR=1 and 2. // Bull's eye. diff --git a/test/unit/test-ssr-template-helper.js b/test/unit/test-ssr-template-helper.js index 77a472fa1e58..ae3246bec0ac 100644 --- a/test/unit/test-ssr-template-helper.js +++ b/test/unit/test-ssr-template-helper.js @@ -17,153 +17,189 @@ import {Services} from '../../src/services'; import {SsrTemplateHelper} from '../../src/ssr-template-helper'; -describes.fakeWin('ssr-template-helper', { - amp: true, -}, env => { - let ampdoc; - let hasCapabilityStub; - let sandbox; - let ssrTemplateHelper; - const sourceComponent = 'amp-list'; - let maybeFindTemplateStub; - let templates; - let viewer; - let win; +describes.fakeWin( + 'ssr-template-helper', + { + amp: true, + }, + env => { + let ampdoc; + let hasCapabilityStub; + let sandbox; + let ssrTemplateHelper; + const sourceComponent = 'amp-list'; + let maybeFindTemplateStub; + let templates; + let viewer; + let win; - beforeEach(() => { - ampdoc = env.ampdoc; - sandbox = sinon.sandbox; - win = env.win; - templates = Services.templatesFor(win); - viewer = Services.viewerForDoc(ampdoc); - hasCapabilityStub = sandbox.stub(viewer, 'hasCapability'); - maybeFindTemplateStub = sandbox.stub(templates, 'maybeFindTemplate'); - ssrTemplateHelper = - new SsrTemplateHelper(sourceComponent, viewer, templates); - }); - - afterEach(() => { - win.document.documentElement.removeAttribute( - 'allow-viewer-render-template'); - sandbox.restore(); - }); - - describe('isSupported', () => { - it('should return true if doc level opt-in', () => { - win.document.documentElement.setAttribute( - 'allow-viewer-render-template', true); - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); - expect(ssrTemplateHelper.isSupported()).to.be.true; + beforeEach(() => { + ampdoc = env.ampdoc; + sandbox = sinon.sandbox; + win = env.win; + templates = Services.templatesFor(win); + viewer = Services.viewerForDoc(ampdoc); + hasCapabilityStub = sandbox.stub(viewer, 'hasCapability'); + maybeFindTemplateStub = sandbox.stub(templates, 'maybeFindTemplate'); + ssrTemplateHelper = new SsrTemplateHelper( + sourceComponent, + viewer, + templates + ); }); - it('should return false if not doc level opt-in', () => { - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); - expect(ssrTemplateHelper.isSupported()).to.be.false; + afterEach(() => { + win.document.documentElement.removeAttribute( + 'allow-viewer-render-template' + ); + sandbox.restore(); }); - it('should return false if doc level opt-in but viewer does not have ' - + 'capability', () => { - win.document.documentElement.setAttribute( - 'allow-viewer-render-template', true); - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); - expect(ssrTemplateHelper.isSupported()).to.be.false; + describe('isSupported', () => { + it('should return true if doc level opt-in', () => { + win.document.documentElement.setAttribute( + 'allow-viewer-render-template', + true + ); + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); + expect(ssrTemplateHelper.isSupported()).to.be.true; + }); + + it('should return false if not doc level opt-in', () => { + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); + expect(ssrTemplateHelper.isSupported()).to.be.false; + }); + + it( + 'should return false if doc level opt-in but viewer does not have ' + + 'capability', + () => { + win.document.documentElement.setAttribute( + 'allow-viewer-render-template', + true + ); + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); + expect(ssrTemplateHelper.isSupported()).to.be.false; + } + ); }); - }); - describe('fetchAndRenderTemplate', () => { - it('should build payload', () => { - const request = { - 'xhrUrl': 'https://www.abracadabra.org/some-json', - 'fetchOpt': { - 'body': {}, - 'credentials': undefined, - 'headers': undefined, - 'method': 'GET', - 'requireAmpResponseSourceOrigin': false, - 'ampCors': true, - }, - }; - const sendMessage = sandbox.spy(viewer, 'sendMessageAwaitResponse'); - maybeFindTemplateStub.returns(null); - const templates = { - successTemplate: {'innerHTML': '
    much success
    '}, - errorTemplate: {'innerHTML': '
    try again
    '}, - }; - ssrTemplateHelper.fetchAndRenderTemplate( - {}, request, templates, {attr: 'test'}); - expect(sendMessage).calledWith('viewerRenderTemplate', { - 'ampComponent': { - 'type': 'amp-list', - 'successTemplate': { - 'type': 'amp-mustache', - 'payload': '
    much success
    ', - }, - 'errorTemplate': { - 'type': 'amp-mustache', - 'payload': '
    try again
    ', - }, - 'attr': 'test', - }, - 'originalRequest': { - 'init': { - 'ampCors': true, + describe('fetchAndRenderTemplate', () => { + it('should build payload', () => { + const request = { + 'xhrUrl': 'https://www.abracadabra.org/some-json', + 'fetchOpt': { 'body': {}, 'credentials': undefined, 'headers': undefined, 'method': 'GET', 'requireAmpResponseSourceOrigin': false, + 'ampCors': true, + }, + }; + const sendMessage = sandbox.spy(viewer, 'sendMessageAwaitResponse'); + maybeFindTemplateStub.returns(null); + const templates = { + successTemplate: {'innerHTML': '
    much success
    '}, + errorTemplate: {'innerHTML': '
    try again
    '}, + }; + ssrTemplateHelper.fetchAndRenderTemplate({}, request, templates, { + attr: 'test', + }); + expect(sendMessage).calledWith('viewerRenderTemplate', { + 'ampComponent': { + 'type': 'amp-list', + 'successTemplate': { + 'type': 'amp-mustache', + 'payload': '
    much success
    ', + }, + 'errorTemplate': { + 'type': 'amp-mustache', + 'payload': '
    try again
    ', + }, + 'attr': 'test', + }, + 'originalRequest': { + 'init': { + 'ampCors': true, + 'body': {}, + 'credentials': undefined, + 'headers': undefined, + 'method': 'GET', + 'requireAmpResponseSourceOrigin': false, + }, + 'input': 'https://www.abracadabra.org/some-json', }, - 'input': 'https://www.abracadabra.org/some-json', - }, + }); }); }); - }); - describe('rendering templates', () => { - let findAndSetHtmlForTemplate; - let findAndRenderTemplate; - let findAndRenderTemplateArray; - beforeEach(() => { - win.document.documentElement.setAttribute( - 'allow-viewer-render-template', true); - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); - findAndSetHtmlForTemplate = - sandbox.stub(templates, 'findAndSetHtmlForTemplate'); - findAndRenderTemplate = - sandbox.stub(templates, 'findAndRenderTemplate'); - findAndRenderTemplateArray = - sandbox.stub(templates, 'findAndRenderTemplateArray'); - }); - - describe('renderTemplate', () => { - it('should set html template', () => { - ssrTemplateHelper.renderTemplate( - {}, {html: '
    some template
    '}); - expect(findAndSetHtmlForTemplate) - .to.have.been.calledWith({}, '
    some template
    '); + describe('rendering templates', () => { + let findAndSetHtmlForTemplate; + let findAndRenderTemplate; + let findAndRenderTemplateArray; + beforeEach(() => { + win.document.documentElement.setAttribute( + 'allow-viewer-render-template', + true + ); + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); + findAndSetHtmlForTemplate = sandbox.stub( + templates, + 'findAndSetHtmlForTemplate' + ); + findAndRenderTemplate = sandbox.stub( + templates, + 'findAndRenderTemplate' + ); + findAndRenderTemplateArray = sandbox.stub( + templates, + 'findAndRenderTemplateArray' + ); }); - it('should throw error if html template is not defined', () => { - allowConsoleError(() => { expect(() => { - ssrTemplateHelper.renderTemplate({}, {html: null}); - }).to.throw(/Server side html response must be defined/); }); - }); + describe('renderTemplate', () => { + it('should set html template', () => { + ssrTemplateHelper.renderTemplate( + {}, + {html: '
    some template
    '} + ); + expect(findAndSetHtmlForTemplate).to.have.been.calledWith( + {}, + '
    some template
    ' + ); + }); - it('should render template ', () => { - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); - ssrTemplateHelper.renderTemplate( - {}, {data: '
    some template
    '}); - expect(findAndRenderTemplate) - .to.have.been.calledWith({}, {data: '
    some template
    '}); - }); + it('should throw error if html template is not defined', () => { + allowConsoleError(() => { + expect(() => { + ssrTemplateHelper.renderTemplate({}, {html: null}); + }).to.throw(/Server side html response must be defined/); + }); + }); + + it('should render template ', () => { + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); + ssrTemplateHelper.renderTemplate( + {}, + {data: '
    some template
    '} + ); + expect(findAndRenderTemplate).to.have.been.calledWith( + {}, + {data: '
    some template
    '} + ); + }); - it('should set template array ', () => { - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); - ssrTemplateHelper.renderTemplate( - {}, [{data: '
    some template
    '}]); - expect(findAndRenderTemplateArray) - .to.have.been.calledWith({}, [{data: '
    some template
    '}]); + it('should set template array ', () => { + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); + ssrTemplateHelper.renderTemplate({}, [ + {data: '
    some template
    '}, + ]); + expect(findAndRenderTemplateArray).to.have.been.calledWith({}, [ + {data: '
    some template
    '}, + ]); + }); }); }); - }); -}); + } +); diff --git a/test/unit/test-standard-actions.js b/test/unit/test-standard-actions.js index a51af83a6dd5..48daba4e9123 100644 --- a/test/unit/test-standard-actions.js +++ b/test/unit/test-standard-actions.js @@ -48,13 +48,14 @@ describes.sandboxed('StandardActions', {}, () => { } function stubMutate(methodName) { - return sandbox.stub(standardActions.resources_, methodName).callsFake( - (unusedElement, mutator) => mutator()); + return sandbox + .stub(standardActions.resources_, methodName) + .callsFake((unusedElement, mutator) => mutator()); } function expectElementMutatedAsync(element) { - expect(mutateElementStub.withArgs(element, sinon.match.any)) - .to.be.calledOnce; + expect(mutateElementStub.withArgs(element, sinon.match.any)).to.be + .calledOnce; } function expectElementToHaveBeenHidden(element) { @@ -114,35 +115,43 @@ describes.sandboxed('StandardActions', {}, () => { standardActions = new StandardActions(ampdoc); mutateElementStub = stubMutate('mutateElement'); scrollStub = sandbox.stub( - standardActions.viewport_, - 'animateScrollIntoView'); - + standardActions.viewport_, + 'animateScrollIntoView' + ); }); describe('getAutofocusElementForShowAction', () => { - let html; beforeEach(() => { html = htmlFor(document); }); it('returns element (direct)', () => { - const el = html``; + const el = html` + + `; expect(getAutofocusElementForShowAction(el)).to.equal(el); }); it('returns element (wrapped)', () => { - const el = html``; - const wrapper = html`
    `; + const el = html` + + `; + const wrapper = html` +
    + `; wrapper.firstElementChild.appendChild(el); expect(getAutofocusElementForShowAction(wrapper)).to.equal(el); }); it('returns null', () => { - const el = html`
    `; + const el = html` +
    +
    +
    + `; expect(getAutofocusElementForShowAction(el)).to.be.null; }); - }); describe('"hide" action', () => { @@ -195,7 +204,6 @@ describes.sandboxed('StandardActions', {}, () => { }); describe('iOS force sync', () => { - let html; beforeEach(() => { html = htmlFor(document); @@ -203,46 +211,57 @@ describes.sandboxed('StandardActions', {}, () => { }); it('executes asynchronously when no autofocus (wrapped)', () => { - const node = html`
    `; + const node = html` +
    +
    +
    + `; standardActions.handleShow_(trustedInvocation({node})); expectElementToHaveBeenShown(node, /* sync */ false); }); it('executes asynchronously when no autofocus (direct)', () => { - const node = html``; + const node = html` + + `; standardActions.handleShow_(trustedInvocation({node})); expectElementToHaveBeenShown(node, /* sync */ false); }); it('executes synchronously when autofocus (wrapped)', () => { - const node = html`
    `; + const node = html` +
    +
    +
    + `; standardActions.handleShow_(trustedInvocation({node})); expectElementToHaveBeenShown(node, /* sync */ true); }); it('executes synchronously when autofocus (direct)', () => { - const node = html``; + const node = html` + + `; standardActions.handleShow_(trustedInvocation({node})); expectElementToHaveBeenShown(node, /* sync */ true); }); - }); describe('autofocus', () => { - let html; beforeEach(() => { html = htmlFor(document); }); describe('iOS force sync', () => { - beforeEach(() => { stubPlatformIsIos(); }); it('focuses [autofocus] element synchronously (direct)', () => { - const node = html``; + const node = html` + + `; node.focus = sandbox.spy(); standardActions.handleShow_(trustedInvocation({node})); @@ -252,8 +271,12 @@ describes.sandboxed('StandardActions', {}, () => { }); it('focuses [autofocus] element synchronously (wrapped)', () => { - const wrapper = html`
    `; - const node = html``; + const wrapper = html` +
    + `; + const node = html` + + `; node.focus = sandbox.spy(); wrapper.firstElementChild.appendChild(node); @@ -264,7 +287,9 @@ describes.sandboxed('StandardActions', {}, () => { }); it('does not focus element', () => { - const node = html``; + const node = html` + + `; node.focus = sandbox.spy(); standardActions.handleShow_(trustedInvocation({node})); @@ -272,13 +297,14 @@ describes.sandboxed('StandardActions', {}, () => { expectElementMutatedAsync(node); expect(node.focus).to.not.have.been.called; }); - }); it('focuses [autofocus] element asynchronously (direct)', () => { stubPlatformIsIos(false); - const node = html``; + const node = html` + + `; node.focus = sandbox.spy(); standardActions.handleShow_(trustedInvocation({node})); @@ -290,8 +316,12 @@ describes.sandboxed('StandardActions', {}, () => { it('focuses [autofocus] element asynchronously (wrapped)', () => { stubPlatformIsIos(false); - const wrapper = html`
    `; - const node = html``; + const wrapper = html` +
    + `; + const node = html` + + `; node.focus = sandbox.spy(); wrapper.firstElementChild.appendChild(node); @@ -302,7 +332,9 @@ describes.sandboxed('StandardActions', {}, () => { }); it('does not focus element', () => { - const node = html``; + const node = html` + + `; node.focus = sandbox.spy(); standardActions.handleShow_(trustedInvocation({node})); @@ -310,9 +342,7 @@ describes.sandboxed('StandardActions', {}, () => { expectElementMutatedAsync(node); expect(node.focus).to.not.have.been.called; }); - }); - }); describe('"toggle" action', () => { @@ -373,7 +403,8 @@ describes.sandboxed('StandardActions', {}, () => { satisfiesTrust: () => true, args: { 'class': dummyClass, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToHaveClass(element, dummyClass); }); @@ -386,7 +417,8 @@ describes.sandboxed('StandardActions', {}, () => { satisfiesTrust: () => true, args: { 'class': dummyClass, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToDropClass(element, dummyClass); }); @@ -399,7 +431,8 @@ describes.sandboxed('StandardActions', {}, () => { args: { 'class': dummyClass, 'force': true, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToHaveClass(element, dummyClass); }); @@ -413,7 +446,8 @@ describes.sandboxed('StandardActions', {}, () => { args: { 'class': dummyClass, 'force': true, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToHaveClass(element, dummyClass); }); @@ -426,7 +460,8 @@ describes.sandboxed('StandardActions', {}, () => { args: { 'class': dummyClass, 'force': false, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToDropClass(element, dummyClass); }); @@ -440,7 +475,8 @@ describes.sandboxed('StandardActions', {}, () => { args: { 'class': dummyClass, 'force': false, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToDropClass(element, dummyClass); }); @@ -524,7 +560,11 @@ describes.sandboxed('StandardActions', {}, () => { return standardActions.handleAmpTarget_(invocation).then(() => { expect(navigator.navigateTo).to.be.calledOnce; expect(navigator.navigateTo).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: undefined, opener: undefined}); + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: undefined, opener: undefined} + ); }); }); @@ -535,23 +575,34 @@ describes.sandboxed('StandardActions', {}, () => { return standardActions.handleAmpTarget_(invocation).then(() => { expect(navigator.navigateTo).to.be.calledOnce; expect(navigator.navigateTo).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: undefined, opener: undefined}); + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: undefined, opener: undefined} + ); }); }); - it('should pass if node does not have throwIfCannotNavigate(), ' + - 'given target', () => { - invocation.caller.tagName = 'AMP-FOO'; - invocation.caller.getImpl = () => Promise.resolve({}); - invocation.args['target'] = '_blank'; - invocation.args['opener'] = true; - - return standardActions.handleAmpTarget_(invocation).then(() => { - expect(navigator.navigateTo).to.be.calledOnce; - expect(navigator.navigateTo).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: '_blank', opener: true}); - }); - }); + it( + 'should pass if node does not have throwIfCannotNavigate(), ' + + 'given target', + () => { + invocation.caller.tagName = 'AMP-FOO'; + invocation.caller.getImpl = () => Promise.resolve({}); + invocation.args['target'] = '_blank'; + invocation.args['opener'] = true; + + return standardActions.handleAmpTarget_(invocation).then(() => { + expect(navigator.navigateTo).to.be.calledOnce; + expect(navigator.navigateTo).to.be.calledWithExactly( + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: '_blank', opener: true} + ); + }); + } + ); it('should check throwIfCannotNavigate() for AMP elements', function*() { const userError = sandbox.stub(user(), 'error'); @@ -563,25 +614,36 @@ describes.sandboxed('StandardActions', {}, () => { yield standardActions.handleAmpTarget_(invocation); expect(navigator.navigateTo).to.be.calledOnce; expect(navigator.navigateTo).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: undefined, opener: undefined}); + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: undefined, opener: undefined} + ); // Should succeed if throwIfCannotNavigate() returns null. - invocation.caller.getImpl = () => Promise.resolve({ - throwIfCannotNavigate: () => null, - }); + invocation.caller.getImpl = () => + Promise.resolve({ + throwIfCannotNavigate: () => null, + }); yield standardActions.handleAmpTarget_(invocation); expect(navigator.navigateTo).to.be.calledTwice; expect(navigator.navigateTo.getCall(1)).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: undefined, opener: undefined}); + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: undefined, opener: undefined} + ); // Should fail if throwIfCannotNavigate() throws an error. - invocation.caller.getImpl = () => Promise.resolve({ - throwIfCannotNavigate: () => { throw new Error('Fake error.'); }, - }); + invocation.caller.getImpl = () => + Promise.resolve({ + throwIfCannotNavigate: () => { + throw new Error('Fake error.'); + }, + }); yield standardActions.handleAmpTarget_(invocation); expect(navigator.navigateTo).to.be.calledTwice; - expect(userError).to.be.calledWith('STANDARD-ACTIONS', - 'Fake error.'); + expect(userError).to.be.calledWith('STANDARD-ACTIONS', 'Fake error.'); }); }); @@ -591,11 +653,14 @@ describes.sandboxed('StandardActions', {}, () => { let winCloseStub; beforeEach(() => { - navigateToStub = sandbox.stub(standardActions, 'handleNavigateTo_') - .returns(Promise.resolve()); + navigateToStub = sandbox + .stub(standardActions, 'handleNavigateTo_') + .returns(Promise.resolve()); - closeOrNavigateToSpy = sandbox.spy(standardActions, - 'handleCloseOrNavigateTo_'); + closeOrNavigateToSpy = sandbox.spy( + standardActions, + 'handleCloseOrNavigateTo_' + ); winCloseStub = sandbox.stub(win, 'close'); winCloseStub.callsFake(() => { @@ -609,45 +674,43 @@ describes.sandboxed('StandardActions', {}, () => { invocation.caller = {tagName: 'DIV'}; }); - it('should be implemented', async() => { + it('should be implemented', async () => { await standardActions.handleAmpTarget_(invocation); expect(closeOrNavigateToSpy).to.be.calledOnce; expect(closeOrNavigateToSpy).to.be.calledWithExactly(invocation); }); - it('should close window if allowed', async() => { + it('should close window if allowed', async () => { win.opener = {}; win.parent = win; await standardActions.handleAmpTarget_(invocation); expect(winCloseStub).to.be.calledOnce; expect(navigateToStub).to.be.not.called; - }); - it('should NOT close if no opener', async() => { + it('should NOT close if no opener', async () => { win.opener = null; win.parent = win; await standardActions.handleAmpTarget_(invocation); expect(winCloseStub).to.be.not.called; }); - it('should NOT close if has a parent', async() => { + it('should NOT close if has a parent', async () => { win.opener = {}; win.parent = {}; await standardActions.handleAmpTarget_(invocation); expect(winCloseStub).to.be.not.called; }); - it('should NOT close if in multi-doc', async() => { + it('should NOT close if in multi-doc', async () => { win.opener = {}; win.parent = win; sandbox.stub(ampdoc, 'isSingleDoc').returns(false); await standardActions.handleAmpTarget_(invocation); expect(winCloseStub).to.be.not.called; - }); - it('should navigate if not allowed to close', async() => { + it('should navigate if not allowed to close', async () => { win.opener = null; win.parent = win; sandbox.stub(ampdoc, 'isSingleDoc').returns(false); @@ -656,7 +719,7 @@ describes.sandboxed('StandardActions', {}, () => { expect(navigateToStub).to.be.called; }); - it('should navigate if win.close rejects', async() => { + it('should navigate if win.close rejects', async () => { win.opener = {}; win.parent = win; winCloseStub.callsFake(() => { @@ -676,7 +739,6 @@ describes.sandboxed('StandardActions', {}, () => { expect(goBackStub).to.be.calledOnce; }); - it('should implement optoutOfCid', function*() { const cid = cidServiceForDocForTesting(ampdoc); const optoutStub = sandbox.stub(cid, 'optOut'); @@ -693,8 +755,10 @@ describes.sandboxed('StandardActions', {}, () => { // Bind.invoke() doesn't actually resolve with a value, // but add one here to check that the promise is chained. bind.invoke.returns(Promise.resolve('set-state-complete')); - sandbox.stub(Services, 'bindForDocOrNull') - .withArgs(element).returns(Promise.resolve(bind)); + sandbox + .stub(Services, 'bindForDocOrNull') + .withArgs(element) + .returns(Promise.resolve(bind)); invocation.method = 'setState'; invocation.args = { @@ -716,8 +780,10 @@ describes.sandboxed('StandardActions', {}, () => { // Bind.invoke() doesn't actually resolve with a value, // but add one here to check that the promise is chained. bind.invoke.returns(Promise.resolve('push-state-complete')); - sandbox.stub(Services, 'bindForDocOrNull') - .withArgs(element).returns(Promise.resolve(bind)); + sandbox + .stub(Services, 'bindForDocOrNull') + .withArgs(element) + .returns(Promise.resolve(bind)); invocation.method = 'pushState'; invocation.args = { @@ -755,10 +821,10 @@ describes.sandboxed('StandardActions', {}, () => { }; invocation.node = ampdoc; const element = createElement(); - const elStub = sandbox.stub(ampdoc, 'getElementById') - .returns(element); - const scrollStub = sandbox.stub(standardActions, 'handleScrollTo_') - .returns('scrollToResponsePromise'); + const elStub = sandbox.stub(ampdoc, 'getElementById').returns(element); + const scrollStub = sandbox + .stub(standardActions, 'handleScrollTo_') + .returns('scrollToResponsePromise'); const result = standardActions.handleAmpTarget_(invocation); expect(elStub).to.be.calledWith('testIdElement'); invocation.node = element; @@ -780,8 +846,10 @@ describes.sandboxed('StandardActions', {}, () => { addGlobalMethodHandler: sandbox.spy(), }; const embedElement = embedWin.document.documentElement; - sandbox.stub(Services, 'actionServiceForDoc') - .withArgs(embedElement).returns(embedActions); + sandbox + .stub(Services, 'actionServiceForDoc') + .withArgs(embedElement) + .returns(embedActions); }); it('should configured the embedded actions service', () => { diff --git a/test/unit/test-static-template.js b/test/unit/test-static-template.js index 3d742c49adf8..ae89ca5da098 100644 --- a/test/unit/test-static-template.js +++ b/test/unit/test-static-template.js @@ -34,7 +34,9 @@ describe('Static Template', () => { it('works as a variable', () => { const html = htmlFor(document); - const div = html`

    `; + const div = html` +

    + `; expect(div.tagName).to.equal('DIV'); expect(div.getAttribute('attr')).to.equal('test'); @@ -54,13 +56,17 @@ describe('Static Template', () => { const iDoc = iframe.contentDocument; const html = htmlFor(document); - let div = html`
    `; + let div = html` +
    + `; expect(div.ownerDocument).to.equal(document); div = htmlFor(iDoc)`
    `; expect(div.ownerDocument).to.equal(iDoc); - div = html`
    `; + div = html` +
    + `; expect(div.ownerDocument).to.equal(iDoc); // Cleanup @@ -106,8 +112,8 @@ describe('Static Template', () => { it('finds all elements with ref attribute', () => { // Prove it doesn't need html helper const el = document.createElement('div'); - el./*TEST*/innerHTML = '
    ' + - '
    '; + el./*TEST*/ innerHTML = + '
    '; const refs = htmlRefs(el); expect(refs).to.deep.equal({ diff --git a/test/unit/test-storage.js b/test/unit/test-storage.js index 8826af7decf9..ba1ef7e71063 100644 --- a/test/unit/test-storage.js +++ b/test/unit/test-storage.js @@ -23,7 +23,6 @@ import { } from '../../src/service/storage-impl'; import {dev} from '../../src/log'; - describe('Storage', () => { let sandbox; let storage; @@ -70,10 +69,12 @@ describe('Storage', () => { function expectStorage(keyValues) { const list = []; for (const k in keyValues) { - list.push(storage.get(k).then(value => { - const expectedValue = keyValues[k]; - expect(value).to.equal(expectedValue, `For "${k}"`); - })); + list.push( + storage.get(k).then(value => { + const expectedValue = keyValues[k]; + expect(value).to.equal(expectedValue, `For "${k}"`); + }) + ); } return Promise.all(list); } @@ -82,54 +83,67 @@ describe('Storage', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); - return storage.get('key1').then(() => { - return storage.storePromise_; - }).then(store => { - expect(store.maxValues_).to.equal(8); - }); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); + return storage + .get('key1') + .then(() => { + return storage.storePromise_; + }) + .then(store => { + expect(store.maxValues_).to.equal(8); + }); }); it('should initialize empty store with prototype-less objects', () => { - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(null)) - .once(); - return storage.get('key1').then(() => { - return storage.storePromise_; - }).then(store => { - expect(store.obj.__proto__).to.be.undefined; - expect(store.values_.__proto__).to.be.undefined; - }); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(null)) + .once(); + return storage + .get('key1') + .then(() => { + return storage.storePromise_; + }) + .then(store => { + expect(store.obj.__proto__).to.be.undefined; + expect(store.values_.__proto__).to.be.undefined; + }); }); it('should restore store with prototype-less objects', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); - return storage.get('key1').then(() => { - return storage.storePromise_; - }).then(store => { - expect(store.obj.__proto__).to.be.undefined; - expect(store.values_.__proto__).to.be.undefined; - }); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); + return storage + .get('key1') + .then(() => { + return storage.storePromise_; + }) + .then(store => { + expect(store.obj.__proto__).to.be.undefined; + expect(store.values_.__proto__).to.be.undefined; + }); }); it('should get the value first time and reuse store', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); expect(storage.storePromise_).to.not.exist; const promise = storage.get('key1'); return promise.then(value => { @@ -146,10 +160,11 @@ describe('Storage', () => { }); it('should get the value from first ever request and reuse store', () => { - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(null)) - .once(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(null)) + .once(); expect(storage.storePromise_).to.not.exist; const promise = storage.get('key1'); return promise.then(value => { @@ -166,10 +181,11 @@ describe('Storage', () => { }); it('should recover from binding failure', () => { - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.reject('intentional')) - .once(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.reject('intentional')) + .once(); expect(storage.storePromise_).to.not.exist; const promise = storage.get('key1'); return promise.then(value => { @@ -179,10 +195,11 @@ describe('Storage', () => { }); it('should recover from binding error', () => { - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve('UNKNOWN FORMAT')) - .once(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve('UNKNOWN FORMAT')) + .once(); expect(storage.storePromise_).to.not.exist; const promise = storage.get('key1'); return promise.then(value => { @@ -195,88 +212,114 @@ describe('Storage', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); - bindingMock.expects('saveBlob') - .withExactArgs('https://acme.com', sinon.match(arg => { + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); + bindingMock + .expects('saveBlob') + .withExactArgs( + 'https://acme.com', + sinon.match(arg => { const store2 = new Store(JSON.parse(atob(arg))); - return (store2.get('key1') !== undefined && - store2.get('key2') !== undefined); - })) - .returns(Promise.resolve()) - .twice(); - viewerMock.expects('broadcast') - .withExactArgs(sinon.match(arg => { - return (arg['type'] == 'amp-storage-reset' && - arg['origin'] == 'https://acme.com'); - })) - .twice(); + return ( + store2.get('key1') !== undefined && store2.get('key2') !== undefined + ); + }) + ) + .returns(Promise.resolve()) + .twice(); + viewerMock + .expects('broadcast') + .withExactArgs( + sinon.match(arg => { + return ( + arg['type'] == 'amp-storage-reset' && + arg['origin'] == 'https://acme.com' + ); + }) + ) + .twice(); expect(storage.storePromise_).to.not.exist; const promise = storage.set('key1', true); - return promise.then(() => { - const store1Promise = storage.storePromise_; - expect(store1Promise).to.exist; - - // Repeat. - return storage.set('key2', true).then(() => { - expect(storage.storePromise_).to.equal(store1Promise); - }); - }).then(() => { - return expectStorage({ - 'key1': true, - 'key2': true, + return promise + .then(() => { + const store1Promise = storage.storePromise_; + expect(store1Promise).to.exist; + + // Repeat. + return storage.set('key2', true).then(() => { + expect(storage.storePromise_).to.equal(store1Promise); + }); + }) + .then(() => { + return expectStorage({ + 'key1': true, + 'key2': true, + }); }); - }); }); it('should remove the key first time and reuse store', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); - bindingMock.expects('saveBlob') - .withExactArgs('https://acme.com', sinon.match(arg => { + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); + bindingMock + .expects('saveBlob') + .withExactArgs( + 'https://acme.com', + sinon.match(arg => { const store2 = new Store(JSON.parse(atob(arg))); - return (store2.get('key1') === undefined); - })) - .returns(Promise.resolve()) - .twice(); - viewerMock.expects('broadcast') - .withExactArgs(sinon.match(arg => { - return (arg['type'] == 'amp-storage-reset' && - arg['origin'] == 'https://acme.com'); - })) - .twice(); + return store2.get('key1') === undefined; + }) + ) + .returns(Promise.resolve()) + .twice(); + viewerMock + .expects('broadcast') + .withExactArgs( + sinon.match(arg => { + return ( + arg['type'] == 'amp-storage-reset' && + arg['origin'] == 'https://acme.com' + ); + }) + ) + .twice(); expect(storage.storePromise_).to.not.exist; const promise = storage.remove('key1'); - return promise.then(() => { - const store1Promise = storage.storePromise_; - expect(store1Promise).to.exist; - - // Repeat. - return storage.remove('key2').then(() => { - expect(storage.storePromise_).to.equal(store1Promise); - }); - }).then(() => { - return expectStorage({ - 'key1': undefined, - 'key2': undefined, + return promise + .then(() => { + const store1Promise = storage.storePromise_; + expect(store1Promise).to.exist; + + // Repeat. + return storage.remove('key2').then(() => { + expect(storage.storePromise_).to.equal(store1Promise); + }); + }) + .then(() => { + return expectStorage({ + 'key1': undefined, + 'key2': undefined, + }); }); - }); }); it('should react to reset messages', () => { const store1 = new Store({}); store1.set('key1', 'value1'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .twice(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .twice(); return storage.get('key1').then(value => { expect(value).to.equal('value1'); const store1Promise = storage.storePromise_; @@ -298,10 +341,11 @@ describe('Storage', () => { it('should ignore unrelated reset messages', () => { const store1 = new Store({}); store1.set('key1', 'value1'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .twice(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .twice(); return storage.get('key1').then(value => { expect(value).to.equal('value1'); const store1Promise = storage.storePromise_; @@ -317,7 +361,6 @@ describe('Storage', () => { }); }); - describe('Store', () => { let sandbox; let clock; @@ -438,7 +481,6 @@ describe('Store', () => { }); }); - describe('LocalStorageBinding', () => { let sandbox; let windowApi; @@ -475,20 +517,22 @@ describe('LocalStorageBinding', () => { }); it('should load store when available', () => { - localStorageMock.expects('getItem') - .withExactArgs('amp-store:https://acme.com') - .returns('BLOB1') - .once(); + localStorageMock + .expects('getItem') + .withExactArgs('amp-store:https://acme.com') + .returns('BLOB1') + .once(); return binding.loadBlob('https://acme.com').then(blob => { expect(blob).to.equal('BLOB1'); }); }); it('should load default store when not yet available', () => { - localStorageMock.expects('getItem') - .withExactArgs('amp-store:https://acme.com') - .returns(undefined) - .once(); + localStorageMock + .expects('getItem') + .withExactArgs('amp-store:https://acme.com') + .returns(undefined) + .once(); return binding.loadBlob('https://acme.com').then(blob => { expect(blob).to.not.exist; }); @@ -496,89 +540,107 @@ describe('LocalStorageBinding', () => { it('should reject on local storage failure w/ localStorage support', () => { binding.isLocalStorageSupported_ = true; - localStorageMock.expects('getItem') - .withExactArgs('amp-store:https://acme.com') - .throws(new Error('unknown')) - .once(); - return binding.loadBlob('https://acme.com') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('ERROR'); - }); + localStorageMock + .expects('getItem') + .withExactArgs('amp-store:https://acme.com') + .throws(new Error('unknown')) + .once(); + return binding + .loadBlob('https://acme.com') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('ERROR'); + }); }); it('should succeed loadBlob w/o localStorage support', () => { binding.isLocalStorageSupported_ = false; - localStorageMock.expects('getItem') - .withExactArgs('amp-store:https://acme.com') - .throws(new Error('unknown')) - .once(); - return binding.loadBlob('https://acme.com') - .then(res => `SUCCESS ${res}`, () => 'ERROR').then(res => { - // Resolves with null - expect(res).to.equal('SUCCESS null'); - }); + localStorageMock + .expects('getItem') + .withExactArgs('amp-store:https://acme.com') + .throws(new Error('unknown')) + .once(); + return binding + .loadBlob('https://acme.com') + .then(res => `SUCCESS ${res}`, () => 'ERROR') + .then(res => { + // Resolves with null + expect(res).to.equal('SUCCESS null'); + }); }); it('should bypass loading from localStorage if getItem throws', () => { - localStorageMock.expects('getItem') - .throws(new Error('unknown')) - .once(); + localStorageMock + .expects('getItem') + .throws(new Error('unknown')) + .once(); binding = new LocalStorageBinding(windowApi); localStorageMock.expects('getItem').never(); - return binding.loadBlob('https://acme.com') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('SUCCESS'); - }); + return binding + .loadBlob('https://acme.com') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('SUCCESS'); + }); }); it('should save store', () => { - localStorageMock.expects('setItem') - .withExactArgs('amp-store:https://acme.com', 'BLOB1') - .once(); + localStorageMock + .expects('setItem') + .withExactArgs('amp-store:https://acme.com', 'BLOB1') + .once(); return binding.saveBlob('https://acme.com', 'BLOB1'); }); it('should reject on save store failure', () => { - localStorageMock.expects('setItem') - .withExactArgs('amp-store:https://acme.com', 'BLOB1') - .throws(new Error('unknown')) - .once(); - return binding.saveBlob('https://acme.com', 'BLOB1') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('ERROR'); - }); + localStorageMock + .expects('setItem') + .withExactArgs('amp-store:https://acme.com', 'BLOB1') + .throws(new Error('unknown')) + .once(); + return binding + .saveBlob('https://acme.com', 'BLOB1') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('ERROR'); + }); }); it('should succeed saveBlob w/o localStorage support', () => { binding.isLocalStorageSupported_ = false; - localStorageMock.expects('setItem') - .withExactArgs('amp-store:https://acme.com', 'BLOB1') - .throws(new Error('unknown')) - .once(); + localStorageMock + .expects('setItem') + .withExactArgs('amp-store:https://acme.com', 'BLOB1') + .throws(new Error('unknown')) + .once(); // Never reaches setItem - return binding.saveBlob('https://acme.com', 'BLOB1') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('SUCCESS'); - }); + return binding + .saveBlob('https://acme.com', 'BLOB1') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('SUCCESS'); + }); }); it('should bypass saving to localStorage if getItem throws', () => { const setItemSpy = sandbox.spy(windowApi.localStorage, 'setItem'); - localStorageMock.expects('getItem') - .throws(new Error('unknown')) - .once(); + localStorageMock + .expects('getItem') + .throws(new Error('unknown')) + .once(); binding = new LocalStorageBinding(windowApi); // Never reaches setItem - return binding.saveBlob('https://acme.com', 'BLOB1') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(setItemSpy).to.have.not.been.called; - expect(res).to.equal('SUCCESS'); - }); + return binding + .saveBlob('https://acme.com', 'BLOB1') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(setItemSpy).to.have.not.been.called; + expect(res).to.equal('SUCCESS'); + }); }); }); - describe('ViewerStorageBinding', () => { let sandbox; let viewer; @@ -599,61 +661,81 @@ describe('ViewerStorageBinding', () => { }); it('should load store from viewer', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('loadStore', sinon.match(arg => { - return (arg['origin'] == 'https://acme.com'); - })) - .returns(Promise.resolve({'blob': 'BLOB1'})) - .once(); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs( + 'loadStore', + sinon.match(arg => { + return arg['origin'] == 'https://acme.com'; + }) + ) + .returns(Promise.resolve({'blob': 'BLOB1'})) + .once(); return binding.loadBlob('https://acme.com').then(blob => { expect(blob).to.equal('BLOB1'); }); }); it('should load default store when not yet available', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('loadStore', sinon.match(arg => { - return (arg['origin'] == 'https://acme.com'); - })) - .returns(Promise.resolve({})) - .once(); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs( + 'loadStore', + sinon.match(arg => { + return arg['origin'] == 'https://acme.com'; + }) + ) + .returns(Promise.resolve({})) + .once(); return binding.loadBlob('https://acme.com').then(blob => { expect(blob).to.not.exist; }); }); it('should reject on viewer failure', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('loadStore', sinon.match(arg => { - return (arg['origin'] == 'https://acme.com'); - })) - .returns(Promise.reject('unknown')) - .once(); - return binding.loadBlob('https://acme.com') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('ERROR'); - }); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs( + 'loadStore', + sinon.match(arg => { + return arg['origin'] == 'https://acme.com'; + }) + ) + .returns(Promise.reject('unknown')) + .once(); + return binding + .loadBlob('https://acme.com') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('ERROR'); + }); }); it('should save store', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('saveStore', sinon.match(arg => { - return (arg['origin'] == 'https://acme.com' && - arg['blob'] == 'BLOB1'); - })) - .returns(Promise.resolve()) - .once(); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs( + 'saveStore', + sinon.match(arg => { + return arg['origin'] == 'https://acme.com' && arg['blob'] == 'BLOB1'; + }) + ) + .returns(Promise.resolve()) + .once(); return binding.saveBlob('https://acme.com', 'BLOB1'); }); it('should reject on save store failure', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('saveStore', sinon.match(() => true)) - .returns(Promise.reject('unknown')) - .once(); - return binding.saveBlob('https://acme.com', 'BLOB1') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('ERROR'); - }); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs('saveStore', sinon.match(() => true)) + .returns(Promise.reject('unknown')) + .once(); + return binding + .saveBlob('https://acme.com', 'BLOB1') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('ERROR'); + }); }); }); diff --git a/test/unit/test-string.js b/test/unit/test-string.js index ab4ca3186d03..3a60b25714df 100644 --- a/test/unit/test-string.js +++ b/test/unit/test-string.js @@ -66,7 +66,6 @@ describe('includes', () => { }); describe('expandTemplate', () => { - const data = { 'x': 'Test 1', 'y': 'Test 2', @@ -112,12 +111,11 @@ describe('expandTemplate', () => { it('should handle multiple iterations when asked to.', () => { expect(expandTemplate('${tox}', testGetter, 2)).to.equal('Test 1'); expect(expandTemplate('${toxy}', testGetter, 2)).to.equal('Test 1Test 2'); - expect(expandTemplate('${totoxy}', testGetter, 2)).to.equal( - '${x}${y}'); - expect(expandTemplate('${totoxy}', testGetter, 3)).to.equal( - 'Test 1Test 2'); + expect(expandTemplate('${totoxy}', testGetter, 2)).to.equal('${x}${y}'); + expect(expandTemplate('${totoxy}', testGetter, 3)).to.equal('Test 1Test 2'); expect(expandTemplate('${totoxy}', testGetter, 10)).to.equal( - 'Test 1Test 2'); + 'Test 1Test 2' + ); }); it('should handle circular expansions without hanging', () => { diff --git a/test/unit/test-style-installer.js b/test/unit/test-style-installer.js index 7a5a0ea4b027..8baa75b8b252 100644 --- a/test/unit/test-style-installer.js +++ b/test/unit/test-style-installer.js @@ -24,9 +24,7 @@ import {installPerformanceService} from '../../src/service/performance-impl'; import {isAnimationNone} from '../../testing/test-helper'; import {setShadowDomSupportedVersionForTesting} from '../../src/web-components'; - describe('Styles', () => { - describes.realWin('makeBodyVisible', {amp: true}, env => { let win, doc, ampdoc; let resources; @@ -75,8 +73,9 @@ describe('Styles', () => { expect(getStyle(doc.body, 'animation')).to.equal(''); expect(ampdoc.signals().get('render-start')).to.be.null; - waitForServicesStub.withArgs(win) - .returns(Promise.resolve(['service1', 'service2'])); + waitForServicesStub + .withArgs(win) + .returns(Promise.resolve(['service1', 'service2'])); styles.makeBodyVisible(doc); return new Promise(resolve => { setTimeout(resolve, 0); @@ -103,239 +102,276 @@ describe('Styles', () => { }); }); - describes.repeated('installStylesForDoc', { - 'single': {}, - 'shadow native': {}, - 'shadow polyfill': {}, - }, variantName => { - const url = 'https://acme.org/doc1'; - - describes.realWin(' ', {}, env => { - let win, doc, ampdoc; - let head; - - beforeEach(() => { - win = env.win; - doc = win.document; - - // Don't install AMP runtime itself, because test fixtures automatically - // setup stylesheets as well. - if (variantName == 'single') { - ampdoc = new AmpDocSingle(win); - } else { - const hostElement = doc.createElement('div'); - doc.body.appendChild(hostElement); - setShadowDomSupportedVersionForTesting(undefined); - if (variantName == 'shadow polyfill') { - setShadowDomSupportedVersionForTesting('none'); + describes.repeated( + 'installStylesForDoc', + { + 'single': {}, + 'shadow native': {}, + 'shadow polyfill': {}, + }, + variantName => { + const url = 'https://acme.org/doc1'; + + describes.realWin(' ', {}, env => { + let win, doc, ampdoc; + let head; + + beforeEach(() => { + win = env.win; + doc = win.document; + + // Don't install AMP runtime itself, because test fixtures automatically + // setup stylesheets as well. + if (variantName == 'single') { + ampdoc = new AmpDocSingle(win); + } else { + const hostElement = doc.createElement('div'); + doc.body.appendChild(hostElement); + setShadowDomSupportedVersionForTesting(undefined); + if (variantName == 'shadow polyfill') { + setShadowDomSupportedVersionForTesting('none'); + } + const shadowRoot = createShadowRoot(hostElement); + ampdoc = new AmpDocShadow(win, url, shadowRoot); } - const shadowRoot = createShadowRoot(hostElement); - ampdoc = new AmpDocShadow(win, url, shadowRoot); - } - head = ampdoc.getHeadNode(); - }); - - afterEach(() => { - setShadowDomSupportedVersionForTesting(undefined); - }); - - /** - * @param {!Document} doc - * @param {string} cssText - * @param {boolean} isRuntimeCss - * @param {string=} opt_ext - * @return {!Promise} - */ - function installStylesAsPromise(cssText, isRuntimeCss, opt_ext) { - return new Promise(resolve => { - styles.installStylesForDoc(ampdoc, cssText, resolve, - isRuntimeCss, opt_ext); - }); - } - - it('should install runtime styles', () => { - const cssText = 'amp-element{}'; - return installStylesAsPromise(cssText, true).then(styleEl => { - expect(styleEl.parentNode).to.equal(head); - expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(styleEl); - expect(styleEl.hasAttribute('amp-runtime')).to.be.true; - expect(styleEl.textContent).to.match(/amp-element\s*\{/); + head = ampdoc.getHeadNode(); }); - }); - it('should install extension styles after runtime', () => { - const runtimeCssText = 'amp-runtime{}'; - const extCssText = 'amp-ext1{}'; - return installStylesAsPromise(runtimeCssText, true).then(() => { - const otherEl = doc.createElement('link'); - head.appendChild(otherEl); - // Install extension styles. - return installStylesAsPromise(extCssText, false, 'amp-ext1'); - }).then(styleEl => { - expect(styleEl.parentNode).to.equal(head); - expect(styleEl.previousElementSibling) - .to.equal(head.__AMP_CSS_SM['amp-runtime']); - expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); - expect(styleEl.textContent).to.match(/amp-ext1\s*\{/); + afterEach(() => { + setShadowDomSupportedVersionForTesting(undefined); }); - }); + /** + * @param {!Document} doc + * @param {string} cssText + * @param {boolean} isRuntimeCss + * @param {string=} opt_ext + * @return {!Promise} + */ + function installStylesAsPromise(cssText, isRuntimeCss, opt_ext) { + return new Promise(resolve => { + styles.installStylesForDoc( + ampdoc, + cssText, + resolve, + isRuntimeCss, + opt_ext + ); + }); + } + it('should install runtime styles', () => { + const cssText = 'amp-element{}'; + return installStylesAsPromise(cssText, true).then(styleEl => { + expect(styleEl.parentNode).to.equal(head); + expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(styleEl); + expect(styleEl.hasAttribute('amp-runtime')).to.be.true; + expect(styleEl.textContent).to.match(/amp-element\s*\{/); + }); + }); - it('should install user styles after everything else', () => { - const runtimeCssText = 'amp-runtime{}'; - const userCssText = 'user{}'; - const otherEl = doc.createElement('link'); - return installStylesAsPromise(runtimeCssText, true).then(() => { - head.appendChild(otherEl); - return installStylesAsPromise(userCssText, false, 'amp-custom'); - }).then(styleEl => { - expect(styleEl.parentNode).to.equal(head); - expect(styleEl.previousElementSibling).to.equal(otherEl); - expect(styleEl.hasAttribute('amp-custom')).to.be.true; - expect(styleEl.hasAttribute('amp-extension')).to.be.false; - expect(styleEl.textContent).to.match(/user\s*\{/); + it('should install extension styles after runtime', () => { + const runtimeCssText = 'amp-runtime{}'; + const extCssText = 'amp-ext1{}'; + return installStylesAsPromise(runtimeCssText, true) + .then(() => { + const otherEl = doc.createElement('link'); + head.appendChild(otherEl); + // Install extension styles. + return installStylesAsPromise(extCssText, false, 'amp-ext1'); + }) + .then(styleEl => { + expect(styleEl.parentNode).to.equal(head); + expect(styleEl.previousElementSibling).to.equal( + head.__AMP_CSS_SM['amp-runtime'] + ); + expect(styleEl.getAttribute('amp-extension')).to.equal( + 'amp-ext1' + ); + expect(styleEl.textContent).to.match(/amp-ext1\s*\{/); + }); }); - }); - it('should not create duplicate runtime style', () => { - let firstStyleEl; - return installStylesAsPromise('', true).then(styleEl => { - firstStyleEl = styleEl; - // Duplicate call. - return installStylesAsPromise('other{}', true); - }).then(styleEl => { - expect(styleEl).to.equal(firstStyleEl); - expect(styleEl.textContent).to.equal('other{}'); - expect(head.querySelectorAll('style[amp-runtime]')) - .to.have.length(1); + it('should install user styles after everything else', () => { + const runtimeCssText = 'amp-runtime{}'; + const userCssText = 'user{}'; + const otherEl = doc.createElement('link'); + return installStylesAsPromise(runtimeCssText, true) + .then(() => { + head.appendChild(otherEl); + return installStylesAsPromise(userCssText, false, 'amp-custom'); + }) + .then(styleEl => { + expect(styleEl.parentNode).to.equal(head); + expect(styleEl.previousElementSibling).to.equal(otherEl); + expect(styleEl.hasAttribute('amp-custom')).to.be.true; + expect(styleEl.hasAttribute('amp-extension')).to.be.false; + expect(styleEl.textContent).to.match(/user\s*\{/); + }); }); - }); - it('should discover existing runtime style', () => { - const serverEl = doc.createElement('style'); - serverEl.setAttribute('amp-runtime', ''); - head.appendChild(serverEl); - return installStylesAsPromise('other{}', true).then(styleEl => { - expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(serverEl); - expect(styleEl).to.equal(serverEl); - expect(styleEl.textContent).to.equal('other{}'); - expect(head.querySelectorAll('style[amp-runtime]')) - .to.have.length(1); + it('should not create duplicate runtime style', () => { + let firstStyleEl; + return installStylesAsPromise('', true) + .then(styleEl => { + firstStyleEl = styleEl; + // Duplicate call. + return installStylesAsPromise('other{}', true); + }) + .then(styleEl => { + expect(styleEl).to.equal(firstStyleEl); + expect(styleEl.textContent).to.equal('other{}'); + expect( + head.querySelectorAll('style[amp-runtime]') + ).to.have.length(1); + }); }); - }); - it('should re-create runtime style if absent', () => { - return installStylesAsPromise('other{}', true).then(styleEl => { - expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(styleEl); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-runtime]')) - .to.have.length(1); + it('should discover existing runtime style', () => { + const serverEl = doc.createElement('style'); + serverEl.setAttribute('amp-runtime', ''); + head.appendChild(serverEl); + return installStylesAsPromise('other{}', true).then(styleEl => { + expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(serverEl); + expect(styleEl).to.equal(serverEl); + expect(styleEl.textContent).to.equal('other{}'); + expect(head.querySelectorAll('style[amp-runtime]')).to.have.length( + 1 + ); + }); }); - }); - it('should discover existing extension style', () => { - const serverEl = doc.createElement('style'); - serverEl.setAttribute('amp-extension', 'amp-ext1'); - head.appendChild(serverEl); - const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); - return promise.then(styleEl => { - expect(head.__AMP_CSS_SM['amp-runtime']).to.not.exist; - expect(styleEl).to.equal(serverEl); - expect(styleEl.textContent).to.equal('other{}'); - expect(head.querySelectorAll('style[amp-extension=amp-ext1]')) - .to.have.length(1); + it('should re-create runtime style if absent', () => { + return installStylesAsPromise('other{}', true).then(styleEl => { + expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(styleEl); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect(head.querySelectorAll('style[amp-runtime]')).to.have.length( + 1 + ); + }); }); - }); - it('should re-create extension style', () => { - installStylesAsPromise('runtime{}', true); - const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); - return promise.then(styleEl => { - expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-extension=amp-ext1]')) - .to.have.length(1); + it('should discover existing extension style', () => { + const serverEl = doc.createElement('style'); + serverEl.setAttribute('amp-extension', 'amp-ext1'); + head.appendChild(serverEl); + const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); + return promise.then(styleEl => { + expect(head.__AMP_CSS_SM['amp-runtime']).to.not.exist; + expect(styleEl).to.equal(serverEl); + expect(styleEl.textContent).to.equal('other{}'); + expect( + head.querySelectorAll('style[amp-extension=amp-ext1]') + ).to.have.length(1); + }); }); - }); - it('should re-create extension style w/o cache', () => { - const runtimeStyle = doc.createElement('style'); - runtimeStyle.setAttribute('amp-runtime', ''); - head.appendChild(runtimeStyle); - // Additional element to test the correct insertion order. - head.appendChild(doc.createElement('link')); - const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); - return promise.then(styleEl => { - expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-extension=amp-ext1]')) - .to.have.length(1); - expect(styleEl.previousElementSibling).to.equal(runtimeStyle); + it('should re-create extension style', () => { + installStylesAsPromise('runtime{}', true); + const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); + return promise.then(styleEl => { + expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect( + head.querySelectorAll('style[amp-extension=amp-ext1]') + ).to.have.length(1); + }); }); - }); - it('should use the cached extension style', () => { - const cachedExtStyle = doc.createElement('style'); - cachedExtStyle.textContent = 'ext1{}'; - head.appendChild(cachedExtStyle); - head.__AMP_CSS_SM = { - 'amp-extension=amp-ext1': cachedExtStyle, - }; - const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); - return promise.then(styleEl => { - expect(styleEl).to.equal(cachedExtStyle); - expect(head.__AMP_CSS_SM['amp-extension=amp-ext1']) - .to.equal(cachedExtStyle); - // Ensure the style is not re-inserted. - expect(head.querySelectorAll('style[amp-extension=amp-ext1]')) - .to.have.length(0); + it('should re-create extension style w/o cache', () => { + const runtimeStyle = doc.createElement('style'); + runtimeStyle.setAttribute('amp-runtime', ''); + head.appendChild(runtimeStyle); + // Additional element to test the correct insertion order. + head.appendChild(doc.createElement('link')); + const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); + return promise.then(styleEl => { + expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect( + head.querySelectorAll('style[amp-extension=amp-ext1]') + ).to.have.length(1); + expect(styleEl.previousElementSibling).to.equal(runtimeStyle); + }); }); - }); - it('should create a amp-custom style', () => { - const promise = installStylesAsPromise( - 'other{}', false, 'amp-custom'); - return promise.then(styleEl => { - expect(styleEl.getAttribute('amp-custom')).to.equal(''); - expect(head.lastElementChild).to.equal(styleEl); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-custom]')) - .to.have.length(1); + it('should use the cached extension style', () => { + const cachedExtStyle = doc.createElement('style'); + cachedExtStyle.textContent = 'ext1{}'; + head.appendChild(cachedExtStyle); + head.__AMP_CSS_SM = { + 'amp-extension=amp-ext1': cachedExtStyle, + }; + const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); + return promise.then(styleEl => { + expect(styleEl).to.equal(cachedExtStyle); + expect(head.__AMP_CSS_SM['amp-extension=amp-ext1']).to.equal( + cachedExtStyle + ); + // Ensure the style is not re-inserted. + expect( + head.querySelectorAll('style[amp-extension=amp-ext1]') + ).to.have.length(0); + }); }); - }); - it('should create a amp-keyframes style', () => { - const promise = installStylesAsPromise( - 'other{}', false, 'amp-keyframes'); - return promise.then(styleEl => { - expect(styleEl.getAttribute('amp-keyframes')).to.equal(''); - expect(head.lastElementChild).to.equal(styleEl); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-keyframes]')) - .to.have.length(1); + it('should create a amp-custom style', () => { + const promise = installStylesAsPromise( + 'other{}', + false, + 'amp-custom' + ); + return promise.then(styleEl => { + expect(styleEl.getAttribute('amp-custom')).to.equal(''); + expect(head.lastElementChild).to.equal(styleEl); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect(head.querySelectorAll('style[amp-custom]')).to.have.length( + 1 + ); + }); }); - }); - it('should use a transform', () => { - styles.installCssTransformer(head, function(css) { - return css.toUpperCase(); + it('should create a amp-keyframes style', () => { + const promise = installStylesAsPromise( + 'other{}', + false, + 'amp-keyframes' + ); + return promise.then(styleEl => { + expect(styleEl.getAttribute('amp-keyframes')).to.equal(''); + expect(head.lastElementChild).to.equal(styleEl); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect( + head.querySelectorAll('style[amp-keyframes]') + ).to.have.length(1); + }); }); - const promise1 = installStylesAsPromise('style1{}', true); - const promise2 = installStylesAsPromise( - 'style2{}', false, 'amp-ext1'); - const promise3 = installStylesAsPromise( - 'style3{}', false, 'amp-custom'); - return Promise.all([promise1, promise2, promise3]).then(styleEls => { - expect(styleEls).to.have.length(3); - expect(styleEls[0].textContent).to.contain('STYLE1'); - expect(styleEls[1].textContent).to.contain('STYLE2'); - expect(styleEls[2].textContent).to.contain('STYLE3'); + + it('should use a transform', () => { + styles.installCssTransformer(head, function(css) { + return css.toUpperCase(); + }); + const promise1 = installStylesAsPromise('style1{}', true); + const promise2 = installStylesAsPromise( + 'style2{}', + false, + 'amp-ext1' + ); + const promise3 = installStylesAsPromise( + 'style3{}', + false, + 'amp-custom' + ); + return Promise.all([promise1, promise2, promise3]).then(styleEls => { + expect(styleEls).to.have.length(3); + expect(styleEls[0].textContent).to.contain('STYLE1'); + expect(styleEls[1].textContent).to.contain('STYLE2'); + expect(styleEls[2].textContent).to.contain('STYLE3'); + }); }); }); - }); - }); - + } + ); describes.realWin('installStylesLegacy', {}, env => { let win, doc; @@ -354,8 +390,13 @@ describe('Styles', () => { */ function installStylesAsPromise(cssText, isRuntimeCss, opt_ext) { return new Promise(resolve => { - styles.installStylesLegacy(doc, cssText, resolve, - isRuntimeCss, opt_ext); + styles.installStylesLegacy( + doc, + cssText, + resolve, + isRuntimeCss, + opt_ext + ); }); } @@ -372,18 +413,21 @@ describe('Styles', () => { it('should install extension styles after runtime', () => { const runtimeCssText = '/*amp-runtime*/'; const extCssText = '/*amp-ext1*/'; - return installStylesAsPromise(runtimeCssText, true).then(() => { - const otherEl = doc.createElement('link'); - doc.head.appendChild(otherEl); - // Install extension styles. - return installStylesAsPromise(extCssText, false, 'amp-ext1'); - }).then(styleEl => { - expect(styleEl.parentElement).to.equal(doc.head); - expect(styleEl.previousElementSibling) - .to.equal(doc.head.__AMP_CSS_SM['amp-runtime']); - expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); - expect(styleEl.textContent).to.equal(extCssText); - }); + return installStylesAsPromise(runtimeCssText, true) + .then(() => { + const otherEl = doc.createElement('link'); + doc.head.appendChild(otherEl); + // Install extension styles. + return installStylesAsPromise(extCssText, false, 'amp-ext1'); + }) + .then(styleEl => { + expect(styleEl.parentElement).to.equal(doc.head); + expect(styleEl.previousElementSibling).to.equal( + doc.head.__AMP_CSS_SM['amp-runtime'] + ); + expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); + expect(styleEl.textContent).to.equal(extCssText); + }); }); it('should create a amp-custom style', () => { @@ -392,8 +436,9 @@ describe('Styles', () => { expect(styleEl.getAttribute('amp-custom')).to.equal(''); expect(doc.head.lastElementChild).to.equal(styleEl); expect(styleEl.textContent).to.equal('/*other*/'); - expect(doc.head.querySelectorAll('style[amp-custom]')) - .to.have.length(1); + expect(doc.head.querySelectorAll('style[amp-custom]')).to.have.length( + 1 + ); }); }); }); diff --git a/test/unit/test-style.js b/test/unit/test-style.js index 5e2c716754da..59c18acf6883 100644 --- a/test/unit/test-style.js +++ b/test/unit/test-style.js @@ -17,7 +17,6 @@ import * as st from '../../src/style'; describe('Style', () => { - let sandbox; beforeEach(() => { @@ -72,21 +71,25 @@ describe('Style', () => { width: st.px(101), }); expect(element.style.width).to.equal('101px'); - expect(element.style.getPropertyPriority('width')) - .to.equal('important'); + expect(element.style.getPropertyPriority('width')).to.equal('important'); }); it('setImportantStyles with vendor prefix', () => { const spy = sandbox.spy(); - const element = {style: { - WebkitTransitionDurationImportant: '', - setProperty: spy, - }}; + const element = { + style: { + WebkitTransitionDurationImportant: '', + setProperty: spy, + }, + }; st.setImportantStyles(element, { transitionDurationImportant: '1s', }); - expect(spy).to.have.been.calledWith('WebkitTransitionDurationImportant', - '1s', 'important'); + expect(spy).to.have.been.calledWith( + 'WebkitTransitionDurationImportant', + '1s', + 'important' + ); }); it('px', () => { @@ -113,59 +116,75 @@ describe('Style', () => { it('removeAlphaFromColor', () => { expect(st.removeAlphaFromColor('rgba(1, 1, 1, 0)')).to.equal( - 'rgba(1, 1, 1, 1)'); - expect(st.removeAlphaFromColor('rgb(1, 1, 1)')).to.equal( - 'rgb(1, 1, 1)'); + 'rgba(1, 1, 1, 1)' + ); + expect(st.removeAlphaFromColor('rgb(1, 1, 1)')).to.equal('rgb(1, 1, 1)'); expect(st.removeAlphaFromColor('rgba(0, 0, 0,-0.5)')).to.equal( - 'rgba(0, 0, 0, 1)'); + 'rgba(0, 0, 0, 1)' + ); }); describe('getVendorJsPropertyName', () => { - it('no prefix', () => { const element = {style: {transitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('transitionDuration'); }); it('should use cached previous result', () => { let element = {style: {transitionDuration: ''}}; - let prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration'); + let prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration' + ); expect(prop).to.equal('transitionDuration'); element = {style: {WebkitTransitionDuration: ''}}; - prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration'); + prop = st.getVendorJsPropertyName(element.style, 'transitionDuration'); expect(prop).to.equal('transitionDuration'); }); it('Webkit', () => { const element = {style: {WebkitTransitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('WebkitTransitionDuration'); }); it('Moz', () => { const element = {style: {MozTransitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('MozTransitionDuration'); }); it('ms', () => { const element = {style: {msTransitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('msTransitionDuration'); }); it('O opera', () => { const element = {style: {OTransitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('OTransitionDuration'); }); }); diff --git a/test/unit/test-task-queue.js b/test/unit/test-task-queue.js index f12877c6867b..c89de301c155 100644 --- a/test/unit/test-task-queue.js +++ b/test/unit/test-task-queue.js @@ -16,9 +16,7 @@ import {TaskQueue} from '../../src/service/task-queue'; - describe('TaskQueue', () => { - let sandbox; let clock; let queue; @@ -45,9 +43,11 @@ describe('TaskQueue', () => { expect(queue.getLastEnqueueTime()).to.equal(1000); expect(queue.getLastDequeueTime()).to.equal(0); - allowConsoleError(() => { expect(() => { - queue.enqueue({id: '1'}); - }).to.throw(/Task already enqueued/); }); + allowConsoleError(() => { + expect(() => { + queue.enqueue({id: '1'}); + }).to.throw(/Task already enqueued/); + }); queue.dequeue({id: '1'}); expect(queue.getTaskById('1')).to.equal(null); diff --git a/test/unit/test-template.js b/test/unit/test-template.js index 432d6f06ce44..e51c3c76695a 100644 --- a/test/unit/test-template.js +++ b/test/unit/test-template.js @@ -73,8 +73,11 @@ describes.fakeWin('Template', {amp: true}, env => { it('should render immediately', () => { const templateElement = createTemplateElement(); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); return templates.renderTemplate(templateElement, {value: 1}).then(res => { expect(res.textContent).to.equal('abc1'); }); @@ -84,45 +87,65 @@ describes.fakeWin('Template', {amp: true}, env => { const templateElement = createTemplateElement(); // Use TemplateImplCheckingViewer to make sure viewerCanRenderTemplates // works correctly when the template is later detached. - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImplCheckingViewer); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImplCheckingViewer + ); const viewerService = getServiceForDoc(templateElement, 'viewer'); env.sandbox.stub(viewerService, 'hasCapability').returns(true); - return templates.renderTemplate(templateElement, {value: 1}).then(() => { - templateElement.parentElement.removeChild(templateElement); - return templates.renderTemplate(templateElement, {value: 2}); - }).then(res => { - expect(res.textContent).to.equal('abc2'); - }); + return templates + .renderTemplate(templateElement, {value: 1}) + .then(() => { + templateElement.parentElement.removeChild(templateElement); + return templates.renderTemplate(templateElement, {value: 2}); + }) + .then(res => { + expect(res.textContent).to.equal('abc2'); + }); }); it('should render array', () => { const templateElement = createTemplateElement(); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); - return templates.renderTemplateArray(templateElement, - [{value: 1}, {value: 2}]).then(res => { - expect(res).to.have.length.of(2); - expect(res[0].textContent).to.equal('abc1'); - expect(res[1].textContent).to.equal('abc2'); - }); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); + return templates + .renderTemplateArray(templateElement, [{value: 1}, {value: 2}]) + .then(res => { + expect(res).to.have.length.of(2); + expect(res[0].textContent).to.equal('abc1'); + expect(res[1].textContent).to.equal('abc2'); + }); }); it('should NOT allow registering template class twice', () => { const templateElement = createTemplateElement(); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); - allowConsoleError(() => { expect(() => { - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); - }).to.throw(/Duplicate template type/); }); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); + allowConsoleError(() => { + expect(() => { + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); + }).to.throw(/Duplicate template type/); + }); }); it('should block render until template registered', () => { const templateElement = createTemplateElement(); const scriptElement = doc.createElement('script'); - scriptElement.setAttribute('custom-template', - templateElement.getAttribute('type')); + scriptElement.setAttribute( + 'custom-template', + templateElement.getAttribute('type') + ); doc.body.appendChild(scriptElement); let result = undefined; templates.renderTemplate(templateElement, {value: 0}).then(res => { @@ -138,12 +161,17 @@ describes.fakeWin('Template', {amp: true}, env => { it('should unblock render when template registered', () => { const templateElement = createTemplateElement(); const scriptElement = doc.createElement('script'); - scriptElement.setAttribute('custom-template', - templateElement.getAttribute('type')); + scriptElement.setAttribute( + 'custom-template', + templateElement.getAttribute('type') + ); doc.body.appendChild(scriptElement); const p = templates.renderTemplate(templateElement, {value: 1}); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); return p.then(res => { expect(res.textContent).to.equal('abc1'); }); @@ -152,22 +180,29 @@ describes.fakeWin('Template', {amp: true}, env => { it('should unblock render for parallel templates', () => { const templateElement = createTemplateElement(); const scriptElement = doc.createElement('script'); - scriptElement.setAttribute('custom-template', - templateElement.getAttribute('type')); + scriptElement.setAttribute( + 'custom-template', + templateElement.getAttribute('type') + ); doc.body.appendChild(scriptElement); const p1 = templates.renderTemplate(templateElement, {value: 1}); const p2 = templates.renderTemplate(templateElement, {value: 2}); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); // This is just a complicated way to say Promise -> all. - return p1.then(res1 => { - return p2.then(res2 => { - return [res1, res2]; + return p1 + .then(res1 => { + return p2.then(res2 => { + return [res1, res2]; + }); + }) + .then(res => { + expect(res[0].textContent).to.equal('abc1'); + expect(res[1].textContent).to.equal('abc2'); }); - }).then(res => { - expect(res[0].textContent).to.equal('abc1'); - expect(res[1].textContent).to.equal('abc2'); - }); }); it('should discover template via ID', () => { @@ -181,10 +216,11 @@ describes.fakeWin('Template', {amp: true}, env => { const parentElement = doc.createElement('div'); parentElement.setAttribute('template', id); doc.body.appendChild(parentElement); - return templates.findAndRenderTemplate(parentElement, {value: 1}).then( - res => { - expect(res.textContent).to.equal('abc1'); - }); + return templates + .findAndRenderTemplate(parentElement, {value: 1}) + .then(res => { + expect(res.textContent).to.equal('abc1'); + }); }); it('should require discovered template via ID to be "template"', () => { @@ -197,11 +233,14 @@ describes.fakeWin('Template', {amp: true}, env => { parentElement.setAttribute('template', id); doc.body.appendChild(parentElement); const regexError = new RegExp( - 'Template must be defined in a