diff --git a/.eslintrc b/.eslintrc index 25b51cccc6a1..5611ee487d33 100644 --- a/.eslintrc +++ b/.eslintrc @@ -80,6 +80,7 @@ "local/is-experiment-on": 2, "local/json-configuration": 2, "local/no-array-destructuring": 2, + "local/no-arrow-on-register-functions": 2, "local/no-deep-destructuring": 2, "local/no-destructure-assignment-params": 2, "local/no-duplicate-import": 2, diff --git a/.github/ISSUE_TEMPLATE/cherry_pick_template.md b/.github/ISSUE_TEMPLATE/cherry_pick_template.md index b37c6689f6bb..f191fd124825 100644 --- a/.github/ISSUE_TEMPLATE/cherry_pick_template.md +++ b/.github/ISSUE_TEMPLATE/cherry_pick_template.md @@ -1,27 +1,31 @@ --- -name: Cherry-pick template +name: Cherry-pick request about: Used to request a cherry-pick. See go.amp.dev/cherry-picks title: "\U0001F338 Cherry-pick request for # into # (Pending)" -labels: 'Type: Release' +labels: + 'Cherry-pick: Beta, Cherry-pick: Experimental, Cherry-pick: LTS, Cherry-pick: Stable, Type: + Release' +assignees: '' --- # Cherry-pick request -| Issue | PR | Production? | RC? | [Release issue](https://github.com/ampproject/amphtml/labels/Type%3A%20Release) | -| :---------------: | :------------: | :----------: | :----------: | :-----------------------------------------------------------------------------: | -| #<_ISSUE_NUMBER_> | #<_PR_NUMBER_> | **** | **** | #<_RELEASE_ISSUE_> | +| Issue | PR | Beta / Experimental? | Stable? | LTS? | [Release issue](https://github.com/ampproject/amphtml/labels/Type%3A%20Release) | +| :---------------: | :------------: | :------------------: | :----------: | :----------: | ------------------------------------------------------------------------------- | +| #<_ISSUE_NUMBER_> | #<_PR_NUMBER_> | **** | **** | **** | #<_RELEASE_ISSUE_> | -## Why does this issue meet the [cherry-pick criteria](https://github.com/ampproject/amphtml/blob/master/contributing/release-schedule.md#cherry-pick-criteria)? +## Why does this issue meet the [cherry-pick criteria](https://github.com/ampproject/amphtml/blob/master/contributing/contributing-code.md#Cherry-picks)? -## Why is a RC cherry-pick not needed? +## Why is a Beta / Experimental cherry-pick not needed? <_YOUR_REASONS_> + +## Why is an LTS cherry-pick needed? + +<_YOUR_REASONS_> + + diff --git a/3p/integration.js b/3p/integration.js index 2a3d4cb1e72f..ccf2ac35fdb2 100644 --- a/3p/integration.js +++ b/3p/integration.js @@ -254,6 +254,7 @@ import {swoop} from '../ads/swoop'; import {taboola} from '../ads/taboola'; import {tcsemotion} from '../ads/tcsemotion'; import {teads} from '../ads/teads'; +import {temedya} from '../ads/temedya'; import {torimochi} from '../ads/torimochi'; import {tracdelight} from '../ads/tracdelight'; import {triplelift} from '../ads/triplelift'; @@ -270,6 +271,7 @@ import {viralize} from '../ads/viralize'; import {vmfive} from '../ads/vmfive'; import {webediads} from '../ads/webediads'; import {weboramaDisplay} from '../ads/weborama'; +import {whopainfeed} from '../ads/whopainfeed'; import {widespace} from '../ads/widespace'; import {wisteria} from '../ads/wisteria'; import {wpmedia} from '../ads/wpmedia'; @@ -327,6 +329,8 @@ const AMP_EMBED_ALLOWED = { strossle: true, svknative: true, taboola: true, + temedya: true, + whopainfeed: true, yahoonativeads: true, zen: true, zergnet: true, @@ -543,6 +547,7 @@ register('swoop', swoop); register('taboola', taboola); register('tcsemotion', tcsemotion); register('teads', teads); +register('temedya', temedya); register('torimochi', torimochi); register('tracdelight', tracdelight); register('triplelift', triplelift); @@ -561,6 +566,7 @@ register('viralize', viralize); register('vmfive', vmfive); register('webediads', webediads); register('weborama-display', weboramaDisplay); +register('whopainfeed', whopainfeed); register('widespace', widespace); register('wisteria', wisteria); register('wpmedia', wpmedia); diff --git a/ads/_config.js b/ads/_config.js index d8714b864aee..42199838b082 100755 --- a/ads/_config.js +++ b/ads/_config.js @@ -476,7 +476,6 @@ const adConfig = jsonConfiguration({ }, 'forkmedia': { - prefetch: 'https://delivery.forkcdn.com/rappio/inread/v1.1/amp/inread.js', renderStartImplemented: true, }, @@ -1049,6 +1048,14 @@ const adConfig = jsonConfiguration({ consentHandlingOverride: true, }, + 'temedya': { + prefetch: [ + 'https://vidyome-com.cdn.vidyome.com/vidyome/builds/temedya-amp.js', + 'https://vidyome-com.cdn.vidyome.com/vidyome/builds/widgets.js', + ], + renderStartImplemented: true, + }, + 'torimochi': { renderStartImplemented: true, }, @@ -1126,6 +1133,10 @@ const adConfig = jsonConfiguration({ ], }, + 'whopainfeed': { + prefetch: 'https://widget.infeed.com.ar/widget/widget-amp.js', + }, + 'widespace': {}, 'wisteria': { @@ -1144,7 +1155,7 @@ const adConfig = jsonConfiguration({ }, 'yahoo': { - prefetch: 'https://s.yimg.com/os/ampad/display.js', + prefetch: 'https://s.yimg.com/aaq/ampad/display.js', preconnect: 'https://us.adserver.yahoo.com', }, diff --git a/ads/forkmedia.js b/ads/forkmedia.js index a23a4d5e0693..fa2ec2de793e 100644 --- a/ads/forkmedia.js +++ b/ads/forkmedia.js @@ -14,32 +14,30 @@ * limitations under the License. */ -import {loadScript, validateData} from '../3p/3p'; +import {loadScript} from '../3p/3p'; /** * @param {!Window} global * @param {!Object} data */ export function forkmedia(global, data) { - validateData(data, ['product']); - let src = null; if (data.product === 'inread') { src = 'https://delivery.forkcdn.com/rappio/inread/v1.1/amp/inread.js'; - } - - if (src) { - loadScript( - global, - src, - () => { - global.context.renderStart(); - }, - () => { - global.context.noContentAvailable(); - } - ); + } else if (data.product === 'vibe') { + src = 'https://vibecdn.forkcdn.com/Inarticle/amp/iav.js'; } else { - global.context.noContentAvailable(); + src = 'https://delivery.forkcdn.com/amp/default.js'; } + + loadScript( + global, + src, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/google/a4a/shared/test/test-url-builder.js b/ads/google/a4a/shared/test/test-url-builder.js index 2c99a7300791..6cfd67fd6806 100644 --- a/ads/google/a4a/shared/test/test-url-builder.js +++ b/ads/google/a4a/shared/test/test-url-builder.js @@ -19,7 +19,7 @@ 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'); + buildUrl('https://example.test', {'key': 'value'}, Infinity) + ).to.equal('https://example.test?key=value'); }); }); diff --git a/ads/google/a4a/test/test-utils.js b/ads/google/a4a/test/test-utils.js index 091d4c5ea00e..76bde3c5535f 100644 --- a/ads/google/a4a/test/test-utils.js +++ b/ads/google/a4a/test/test-utils.js @@ -300,7 +300,7 @@ describe('Google A4A utils', () => { expect( getAmpRuntimeTypeParameter({ AMP_CONFIG: {type: 'experimental'}, - location: {origin: 'https://www.example.com'}, + location: {origin: 'https://www.example.test'}, }) ).to.be.null; }); diff --git a/ads/google/imaVideo.js b/ads/google/imaVideo.js index 8c8615f4e222..41ea09637176 100644 --- a/ads/google/imaVideo.js +++ b/ads/google/imaVideo.js @@ -283,6 +283,17 @@ export function imaVideo(global, data) { }); controlsVisible = false; + // Play button + playPauseDiv = createIcon(global, 'play'); + playPauseDiv.id = 'ima-play-pause'; + setStyles(playPauseDiv, { + 'width': '30px', + 'height': '30px', + 'margin-right': '20px', + 'font-size': '1.25em', + 'cursor': 'pointer', + }); + controlsDiv.appendChild(playPauseDiv); // Ad progress countdownWrapperDiv = global.document.createElement('div'); countdownWrapperDiv.id = 'ima-countdown'; @@ -301,17 +312,6 @@ export function imaVideo(global, data) { countdownDiv = global.document.createElement('div'); countdownWrapperDiv.appendChild(countdownDiv); controlsDiv.appendChild(countdownWrapperDiv); - // Play button - playPauseDiv = createIcon(global, 'play'); - playPauseDiv.id = 'ima-play-pause'; - setStyles(playPauseDiv, { - 'width': '30px', - 'height': '30px', - 'margin-right': '20px', - 'font-size': '1.25em', - 'cursor': 'pointer', - }); - controlsDiv.appendChild(playPauseDiv); // Current time and duration. timeDiv = global.document.createElement('div'); timeDiv.id = 'ima-time'; @@ -1138,13 +1138,17 @@ export function onPlayPauseClick() { * @visibleForTesting */ export function playVideo() { - setStyle(adContainerDiv, 'display', 'none'); + if (adsActive) { + adsManager.resume(); + } else { + setStyle(adContainerDiv, 'display', 'none'); + // Kick off the hide controls timer. + showControls(); + videoPlayer.play(); + } playerState = PlayerStates.PLAYING; - // Kick off the hide controls timer. - showControls(); - changeIcon(playPauseDiv, 'pause'); postMessage({event: VideoEvents.PLAYING}); - videoPlayer.play(); + changeIcon(playPauseDiv, 'pause'); } /** @@ -1153,20 +1157,22 @@ export function playVideo() { * @visibleForTesting */ export function pauseVideo(event = null) { - videoPlayer.pause(); - playerState = PlayerStates.PAUSED; - // Show controls and keep them there because we're paused. - clearInterval(hideControlsTimeout); - if (!adsActive) { + if (adsActive) { + adsManager.pause(); + } else { + videoPlayer.pause(); + // Show controls and keep them there because we're paused. + clearInterval(hideControlsTimeout); showControls(); + if (event && event.type == 'webkitendfullscreen') { + // Video was paused because we exited fullscreen. + videoPlayer.removeEventListener('webkitendfullscreen', pauseVideo); + fullscreen = false; + } } - changeIcon(playPauseDiv, 'play'); + playerState = PlayerStates.PAUSED; postMessage({event: VideoEvents.PAUSE}); - if (event && event.type == 'webkitendfullscreen') { - // Video was paused because we exited fullscreen. - videoPlayer.removeEventListener('webkitendfullscreen', pauseVideo); - fullscreen = false; - } + changeIcon(playPauseDiv, 'play'); } /** @@ -1313,7 +1319,7 @@ function onFullscreenChange(global) { /** * Show a subset of controls when ads are playing. - * Visible controls are countdownDiv, muteUnmuteDiv, and fullscreenDiv + * Visible controls are countdownDiv, playPauseDiv, muteUnmuteDiv, and fullscreenDiv * * @visibleForTesting */ @@ -1322,24 +1328,21 @@ export function showAdControls() { const isSkippable = currentAd ? currentAd.getSkipTimeOffset() !== -1 : false; const miniControls = hasMobileStyles && isSkippable; // hide non-ad controls - const hideElement = button => setStyle(button, 'display', 'none'); - [playPauseDiv, timeDiv, progressBarWrapperDiv].forEach(hideElement); + [timeDiv, progressBarWrapperDiv].forEach(button => { + setStyle(button, 'display', 'none'); + }); // set ad control styles setStyles(controlsDiv, { 'height': miniControls ? '20px' : '30px', 'justify-content': 'flex-end', 'padding': '10px', }); - const buttonDefaults = { - 'height': miniControls ? '18px' : '22px', - }; - setStyles(fullscreenDiv, buttonDefaults); - setStyles( - muteUnmuteDiv, - Object.assign(buttonDefaults, { - 'margin-right': '10px', - }) - ); + [fullscreenDiv, playPauseDiv, muteUnmuteDiv].forEach(button => { + setStyles(button, {'height': miniControls ? '18px' : '22px'}); + }); + setStyles(muteUnmuteDiv, {'margin-right': '10px'}); + // show pause button while ad begins playing + changeIcon(playPauseDiv, 'pause'); // show ad controls setStyle(countdownWrapperDiv, 'display', 'flex'); showControls(); @@ -1359,17 +1362,14 @@ export function resetControlsAfterAd() { 'height': '100px', 'padding': '60px 10px 10px', }); - const buttonDefaults = {'height': '30px'}; - setStyles(fullscreenDiv, buttonDefaults); - setStyles( - muteUnmuteDiv, - Object.assign(buttonDefaults, { - 'margin-right': '20px', - }) - ); + [fullscreenDiv, playPauseDiv, muteUnmuteDiv].forEach(button => { + setStyles(button, {'height': '30px'}); + }); + setStyles(muteUnmuteDiv, {'margin-right': '20px'}); // show non-ad controls - const showElement = button => setStyle(button, 'display', 'block'); - [playPauseDiv, timeDiv, progressBarWrapperDiv].forEach(showElement); + [timeDiv, progressBarWrapperDiv].forEach(button => { + setStyle(button, 'display', 'block'); + }); } /** @@ -1423,10 +1423,7 @@ function onMessage(global, event) { } switch (msg['func']) { case 'playVideo': - if (adsActive) { - adsManager.resume(); - postMessage({event: VideoEvents.PLAYING}); - } else if (playbackStarted) { + if (adsActive || playbackStarted) { playVideo(); } else { // Auto-play support @@ -1434,12 +1431,7 @@ function onMessage(global, event) { } break; case 'pauseVideo': - if (adsActive) { - adsManager.pause(); - postMessage({event: VideoEvents.PAUSE}); - } else if (playbackStarted) { - pauseVideo(); - } + pauseVideo(); break; case 'mute': muteVideo(); diff --git a/ads/temedya.js b/ads/temedya.js new file mode 100644 index 000000000000..0388bb7a632a --- /dev/null +++ b/ads/temedya.js @@ -0,0 +1,46 @@ +/** + * Copyright 2020 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 {loadScript, validateData} from '../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function temedya(global, data) { + validateData(data, ['title']); + global._temedya = global._temedya || { + title: data.title, + keyId: data.keyId, + siteId: data.siteId, + siteUrl: data.siteUrl, + typeId: data.typeId, + paidItem: data.paidItem, + organicItem: data.organicItem, + theme: data.theme, + }; + global._temedya.AMPCallbacks = { + renderStart: global.context.renderStart, + noContentAvailable: global.context.noContentAvailable, + }; + // load the temedya AMP JS file script asynchronously + loadScript( + global, + 'https://vidyome-com.cdn.vidyome.com/vidyome/builds/temedya-amp.js', + () => {}, + global.context.noContentAvailable + ); +} diff --git a/ads/temedya.md b/ads/temedya.md new file mode 100644 index 000000000000..65b941c0da22 --- /dev/null +++ b/ads/temedya.md @@ -0,0 +1,53 @@ + + +# TE Medya + +## Example of TE Medya's widget implementation + +### Basic + +```html + + +``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +### Required parameters + +- `data-title`: Widget Title +- `data-siteId`: Vidyome Website Id +- `data-keyId`: Vidyome Widget Key Id +- `data-siteUrl`: Web Site URL +- `data-typeId`: Widget Type ID (7) +- `data-paidItem`: Paid Item Count +- `data-organicItem`: Organic Item Count +- `data-theme`: Theme Type (light or dark) diff --git a/ads/whopainfeed.js b/ads/whopainfeed.js new file mode 100644 index 000000000000..a202e786c322 --- /dev/null +++ b/ads/whopainfeed.js @@ -0,0 +1,34 @@ +/** + * Copyright 2020 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 {loadScript, validateData} from '../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function whopainfeed(global, data) { + validateData(data, ['siteid']); + + global._whopainfeed = global._whopainfeed || { + viewId: global.context.pageViewId, + siteId: data['siteid'], + testMode: data['testmode'] || 'false', + template: data['template'] || 'default', + }; + + loadScript(global, 'https://widget.infeed.com.ar/widget/widget-amp.js'); +} diff --git a/ads/whopainfeed.md b/ads/whopainfeed.md new file mode 100644 index 000000000000..4c644cfd10fb --- /dev/null +++ b/ads/whopainfeed.md @@ -0,0 +1,51 @@ + + +# Whopa InFeed + +## Example installation of the InFeed widget + +### Basic + +```html + + +``` + +## Configuration + +For details on the configuration, please contact Whopa Team support@whopa.net \ +These configurations are relevant for both `` and ``. + +### Required parameters + +- `data-siteId`: Site ID provided by Whopa InFeed Team. + +### Optional parameters + +- `data-template`: The Template of Widget. + +**Resolution** + +You can set an initial height of what the widget height is supposed to be. That is, instead of `height="100"`, if the widget's final height is 600px, then set `height="600"`. Setting the initial height **_will not_** finalize the widget height if it's different from the actual. The widget will resize to it's true dimensions after the widget leaves the viewport. diff --git a/ads/yahoo.js b/ads/yahoo.js index 09ca195ff85e..c427884f28aa 100644 --- a/ads/yahoo.js +++ b/ads/yahoo.js @@ -23,5 +23,5 @@ import {validateData, writeScript} from '../3p/3p'; export function yahoo(global, data) { validateData(data, ['sid', 'site', 'sa']); global.yadData = data; - writeScript(global, 'https://s.yimg.com/os/ampad/display.js'); + writeScript(global, 'https://s.yimg.com/aaq/ampad/display.js'); } diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/index.js b/build-system/babel-plugins/babel-plugin-const-transformer/index.js new file mode 100644 index 000000000000..39f1d5cd80ee --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/index.js @@ -0,0 +1,35 @@ +/** + * Copyright 2020 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. + */ + +/** + * Changes the + * The above said variables are in src/mode.js file. + * @param {Object} babelTypes + * @return {!Object} + */ +module.exports = function({types: t}) { + return { + visitor: { + VariableDeclaration(path) { + if (path.node.kind !== 'const') { + return; + } + + path.replaceWith(t.variableDeclaration('let', path.node.declarations)); + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-let/input.js b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-let/input.js new file mode 100644 index 000000000000..58ec6a4619f2 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-let/input.js @@ -0,0 +1,40 @@ +/** + * Copyright 2020 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. + */ + +let foo = true; +let bar = 1; +let baz = NaN; +let yep = 'hello'; +let another = {}; +let array = []; + +function thing() { + let foo = true; + let bar = 1; + let baz = NaN; + let yep = 'hello'; + let another = {}; + let array = []; +} + +thing: { + let foo = true; + let bar = 1; + let baz = NaN; + let yep = 'hello'; + let another = {}; + let array = []; +} diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-let/options.json b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-let/options.json new file mode 100644 index 000000000000..3af353780ef0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-let/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../../../babel-plugin-const-transformer"] +} diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-let/output.js b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-let/output.js new file mode 100644 index 000000000000..3b4e5eec2a88 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-let/output.js @@ -0,0 +1,39 @@ +/** + * Copyright 2020 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. + */ +let foo = true; +let bar = 1; +let baz = NaN; +let yep = 'hello'; +let another = {}; +let array = []; + +function thing() { + let foo = true; + let bar = 1; + let baz = NaN; + let yep = 'hello'; + let another = {}; + let array = []; +} + +thing: { + let foo = true; + let bar = 1; + let baz = NaN; + let yep = 'hello'; + let another = {}; + let array = []; +} \ No newline at end of file diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-var/input.js b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-var/input.js new file mode 100644 index 000000000000..1be6fa370b5d --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-var/input.js @@ -0,0 +1,40 @@ +/** + * Copyright 2020 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. + */ + +var foo = true; +var bar = 1; +var baz = NaN; +var yep = 'hello'; +var another = {}; +var array = []; + +function thing() { + var foo = true; + var bar = 1; + var baz = NaN; + var yep = 'hello'; + var another = {}; + var array = []; +} + +thing: { + var foo = true; + var bar = 1; + var baz = NaN; + var yep = 'hello'; + var another = {}; + var array = []; +} diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-var/options.json b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-var/options.json new file mode 100644 index 000000000000..3af353780ef0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-var/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../../../babel-plugin-const-transformer"] +} diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-var/output.js b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-var/output.js new file mode 100644 index 000000000000..3d40d849889c --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/ignored-var/output.js @@ -0,0 +1,39 @@ +/** + * Copyright 2020 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. + */ +var foo = true; +var bar = 1; +var baz = NaN; +var yep = 'hello'; +var another = {}; +var array = []; + +function thing() { + var foo = true; + var bar = 1; + var baz = NaN; + var yep = 'hello'; + var another = {}; + var array = []; +} + +thing: { + var foo = true; + var bar = 1; + var baz = NaN; + var yep = 'hello'; + var another = {}; + var array = []; +} diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/should-transform/input.js b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/should-transform/input.js new file mode 100644 index 000000000000..601818da4b0b --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/should-transform/input.js @@ -0,0 +1,40 @@ +/** + * Copyright 2020 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. + */ + +const foo = true; +const bar = 1; +const baz = NaN; +const yep = 'hello'; +const another = {}; +const array = []; + +function thing() { + const foo = true; + const bar = 1; + const baz = NaN; + const yep = 'hello'; + const another = {}; + const array = []; +} + +thing: { + const foo = true; + const bar = 1; + const baz = NaN; + const yep = 'hello'; + const another = {}; + const array = []; +} \ No newline at end of file diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/should-transform/options.json b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/should-transform/options.json new file mode 100644 index 000000000000..3af353780ef0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/should-transform/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../../../babel-plugin-const-transformer"] +} diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/should-transform/output.js b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/should-transform/output.js new file mode 100644 index 000000000000..3b4e5eec2a88 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/fixtures/transform-assertions/should-transform/output.js @@ -0,0 +1,39 @@ +/** + * Copyright 2020 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. + */ +let foo = true; +let bar = 1; +let baz = NaN; +let yep = 'hello'; +let another = {}; +let array = []; + +function thing() { + let foo = true; + let bar = 1; + let baz = NaN; + let yep = 'hello'; + let another = {}; + let array = []; +} + +thing: { + let foo = true; + let bar = 1; + let baz = NaN; + let yep = 'hello'; + let another = {}; + let array = []; +} \ No newline at end of file diff --git a/build-system/babel-plugins/babel-plugin-const-transformer/test/index.js b/build-system/babel-plugins/babel-plugin-const-transformer/test/index.js new file mode 100644 index 000000000000..bb2e50c56b6a --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-const-transformer/test/index.js @@ -0,0 +1,19 @@ +/** + * Copyright 2020 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. + */ + +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); diff --git a/build-system/compile/bundles.config.js b/build-system/compile/bundles.config.js index 1eb94f427590..84274d59bcec 100644 --- a/build-system/compile/bundles.config.js +++ b/build-system/compile/bundles.config.js @@ -144,13 +144,13 @@ exports.jsBundles = { includePolyfills: false, }, }, - 'amp-story-embed.js': { + 'amp-story-player.js': { srcDir: './src/', - srcFilename: 'amp-story-embed.js', + srcFilename: 'amp-story-player.js', destDir: './dist', minifiedDestDir: './dist', options: { - minifiedName: 'amp-story-embed-v0.js', + minifiedName: 'amp-story-player-v0.js', includePolyfills: false, }, }, @@ -927,6 +927,13 @@ exports.extensionBundles = [ }, type: TYPES.MISC, }, + { + name: 'amp-stream-gallery', + version: '0.1', + latestVersion: '0.1', + options: {hasCss: true}, + type: TYPES.MISC, + }, { name: 'amp-selector', version: '0.1', diff --git a/build-system/compile/compile.js b/build-system/compile/compile.js index 8beafb5cb516..f8a7dacbea0a 100644 --- a/build-system/compile/compile.js +++ b/build-system/compile/compile.js @@ -18,6 +18,7 @@ const argv = require('minimist')(process.argv.slice(2)); const del = require('del'); const fs = require('fs-extra'); +const gap = require('gulp-append-prepend'); const gulp = require('gulp'); const gulpIf = require('gulp-if'); const nop = require('gulp-nop'); @@ -398,6 +399,12 @@ function compile( ) .on('error', reject) .pipe(sourcemaps.write('.')) + .pipe( + gulpIf( + !!argv.esm, + gap.appendText(`\n//# sourceMappingURL=${outputFilename}.map`) + ) + ) .pipe(gulp.dest(outputDir)) .on('end', resolve); } diff --git a/build-system/compile/sources.js b/build-system/compile/sources.js index cc9f758f1891..20e29b25ff9a 100644 --- a/build-system/compile/sources.js +++ b/build-system/compile/sources.js @@ -49,6 +49,8 @@ const COMMON_GLOBS = [ 'node_modules/web-activities/activity-ports.js', 'node_modules/@ampproject/animations/package.json', 'node_modules/@ampproject/animations/dist/animations.mjs', + 'node_modules/@ampproject/viewer-messaging/package.json', + 'node_modules/@ampproject/viewer-messaging/messaging.js', 'node_modules/@ampproject/worker-dom/package.json', 'node_modules/@ampproject/worker-dom/dist/amp/main.mjs', 'node_modules/preact/package.json', diff --git a/build-system/eslint-rules/no-arrow-on-register-functions.js b/build-system/eslint-rules/no-arrow-on-register-functions.js new file mode 100644 index 000000000000..985cb988c23c --- /dev/null +++ b/build-system/eslint-rules/no-arrow-on-register-functions.js @@ -0,0 +1,38 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +const expression = [ + 'CallExpression[callee.property.name=/registerService.*/]', + 'CallExpression[callee.name=/registerService.*/]', +].join(','); + +module.exports = function(context) { + return { + [expression]: function(node) { + node.arguments.forEach(arg => { + if (arg.type === 'ArrowFunctionExpression') { + // TODO(erwinm): add fixer method. + context.report({ + node, + message: + 'registerService* methods should not use arrow functions as a constructor.', + }); + } + }); + }, + }; +}; diff --git a/build-system/externs/amp.extern.js b/build-system/externs/amp.extern.js index 7bb93d80c53d..df222d573f67 100644 --- a/build-system/externs/amp.extern.js +++ b/build-system/externs/amp.extern.js @@ -220,8 +220,12 @@ window.AMP.viewport = {}; window.AMP.viewport.getScrollLeft; window.AMP.viewport.getScrollWidth; window.AMP.viewport.getWidth; -window.AMP.attachShadowDoc; -window.AMP.attachShadowDocAsStream; + +/** @type {function(!HTMLElement, !Document, !string, Object)} */ +window.AMP.attachShadowDoc = function(element, document, url, options) {}; + +/** @type {function(!HTMLElement, !string, Object)} */ +window.AMP.attachShadowDocAsStream = function(element, url, options) {}; /** @constructor */ function AmpConfigType() {} diff --git a/build-system/global-configs/canary-config.json b/build-system/global-configs/canary-config.json index eb745ad345e5..a2322ad9f52e 100644 --- a/build-system/global-configs/canary-config.json +++ b/build-system/global-configs/canary-config.json @@ -14,8 +14,7 @@ "amp-ad-ff-adx-ady": 0.01, "amp-auto-ads-adsense-holdout": 0.1, "amp-auto-ads-no-op-experiment": 0.05, - "amp-consent-geo-override": 1, - "amp-consent-v2": 1, + "amp-consent-restrict-fullscreen": 1, "amp-mega-menu": 1, "amp-nested-menu": 1, "amp-playbuzz": 1, @@ -28,7 +27,6 @@ "chunked-amp": 1, "doubleclickSraExp": 0.01, "doubleclickSraReportExcludedBlock": 0.1, - "fie-css-cleanup": 1, "fix-inconsistent-responsive-height-selection": 0, "fixed-elements-in-lightbox": 1, "flexAdSlots": 0.05, @@ -38,6 +36,5 @@ "pump-early-frame": 1, "swg-gpay-api": 1, "swg-gpay-native": 1, - "use-responsive-ads-for-responsive-sizing-in-auto-ads": 0.05, "version-locking": 1 } diff --git a/build-system/global-configs/prod-config.json b/build-system/global-configs/prod-config.json index 0ad5912a687d..7bd00a5e51e3 100644 --- a/build-system/global-configs/prod-config.json +++ b/build-system/global-configs/prod-config.json @@ -14,8 +14,6 @@ "amp-ad-ff-adx-ady": 0.01, "amp-auto-ads-adsense-holdout": 0.1, "amp-auto-ads-no-op-experiment": 0.05, - "amp-consent-geo-override": 1, - "amp-consent-v2": 1, "amp-mega-menu": 1, "amp-nested-menu": 1, "amp-playbuzz": 1, @@ -28,7 +26,6 @@ "chunked-amp": 1, "doubleclickSraExp": 0.01, "doubleclickSraReportExcludedBlock": 0.1, - "fie-css-cleanup": 1, "fix-inconsistent-responsive-height-selection": 0, "fixed-elements-in-lightbox": 1, "flexAdSlots": 0.05, @@ -38,6 +35,5 @@ "pump-early-frame": 1, "swg-gpay-api": 1, "swg-gpay-native": 1, - "use-responsive-ads-for-responsive-sizing-in-auto-ads": 0.05, "version-locking": 1 } diff --git a/build-system/server/amp4test.js b/build-system/server/amp4test.js index 26b880043ce8..227932359e03 100644 --- a/build-system/server/amp4test.js +++ b/build-system/server/amp4test.js @@ -189,7 +189,7 @@ app.get('/a4a/:bid', (req, res) => { cors.enableCors(req, res); const {bid} = req.params; const body = ` - + diff --git a/build-system/server/app.js b/build-system/server/app.js index 99f228f0cc5f..0b9acb9e8780 100644 --- a/build-system/server/app.js +++ b/build-system/server/app.js @@ -34,6 +34,12 @@ const upload = require('multer')(); const pc = process; const autocompleteEmailData = require('./autocomplete-test-data'); const runVideoTestBench = require('./app-video-testbench'); +const { + getVariableRequest, + runVariableSubstitution, + saveVariableRequest, + saveVariables, +} = require('./variable-substitution'); const { recaptchaFrameRequestHandler, recaptchaRouter, @@ -407,7 +413,7 @@ app.use('/form/autocomplete/query', (req, res) => { }); app.use('/form/autocomplete/error', (req, res) => { - res(500); + res.status(500).end(); }); app.use('/form/mention/query', (req, res) => { @@ -684,7 +690,7 @@ function getLiveBlogItem() { @@ -755,6 +761,26 @@ app.use('/impression-proxy/', (req, res) => { // Or fake response with status 204 if viewer replaceUrl is provided }); +/** + * Acts in a similar fashion to /serve_mode_change. Saves + * analytics requests via /run-variable-substitution, and + * then returns the encoded/substituted/replaced request + * via /get-variable-request. + */ + +// Saves the variables input to be used in run-variable-substitution +app.get('/save-variables', saveVariables); + +// Creates an iframe with amp-analytics. Analytics request +// uses save-variable-request as its endpoint. +app.get('/run-variable-substitution', runVariableSubstitution); + +// Saves the analytics request to the dev server. +app.get('/save-variable-request', saveVariableRequest); + +// Returns the saved analytics request. +app.get('/get-variable-request', getVariableRequest); + let forcePromptOnNext = false; app.post('/get-consent-v1/', (req, res) => { cors.assertCors(req, res, ['POST']); diff --git a/build-system/server/variable-substitution.js b/build-system/server/variable-substitution.js new file mode 100644 index 000000000000..f85db9800837 --- /dev/null +++ b/build-system/server/variable-substitution.js @@ -0,0 +1,127 @@ +/** + * Copyright 2020 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. + */ + +let url; +let variableSubstitution; +let variables; + +function saveVariables(req, res) { + const requestVariables = {}; + // For when a JSON is entered + if (req.query && Object.keys(req.query).length === 2) { + const entries = Object.entries(req.query); + try { + Object.assign(requestVariables, JSON.parse(entries[1][0])); + } catch (e) { + res.send(` + + + AMP Analytics + + +

Error:

+ ${e} + + `); + return; + } + } else { + // Remove variables that don't have values + const keys = Object.keys(req.query); + for (let i = 0; i < keys.length; i++) { + if (req.query[keys[i]]) { + requestVariables[keys[i]] = req.query[keys[i]]; + } + } + } + variables = requestVariables; + res.json({'vars': variables}); + return; +} + +function runVariableSubstitution(req, res) { + variables = variables || {}; + // Don't include the incremented number sent in to make a new request + const testParameters = Object.keys(req.query) + .map(value => { + return `${value}=${req.query[value]}`; + }) + .slice(1) + .join('&'); + res.send(` + + + AMP Analytics + + + + + + + + +

'' request:

+ ${ + testParameters + ? 'http://ads.localhost:8000/save-variable-request?' + testParameters + : 'N/A' + } + + + + + + `); +} + +function saveVariableRequest(req, res) { + res.setHeader('Access-Control-Allow-Credentials', true); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET'); + res.setHeader('Access-Control-Allow-Headers', 'text/plain'); + variableSubstitution = req.query; + url = req.originalUrl; +} + +function getVariableRequest(req, res) { + res.json({'Results': variableSubstitution, 'URL': url}); + return; +} + +module.exports = { + getVariableRequest, + runVariableSubstitution, + saveVariableRequest, + saveVariables, +}; diff --git a/build-system/tasks/build.js b/build-system/tasks/build.js index c6ed6b7ff17f..19c8abd4179b 100644 --- a/build-system/tasks/build.js +++ b/build-system/tasks/build.js @@ -126,6 +126,7 @@ module.exports = { build.description = 'Builds the AMP library'; build.flags = { config: ' Sets the runtime\'s AMP_CONFIG to one of "prod" or "canary"', + fortesting: ' Builds the AMP library for local testing', extensions: ' Builds only the listed extensions.', extensions_from: ' Builds only the extensions from the listed AMP(s).', noextensions: ' Builds with no extensions.', diff --git a/build-system/tasks/changelog.js b/build-system/tasks/changelog.js deleted file mode 100644 index c751476e156d..000000000000 --- a/build-system/tasks/changelog.js +++ /dev/null @@ -1,661 +0,0 @@ -/** - * 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. - */ -'use strict'; - -/** - * @fileoverview Creates a gulp task that fetches the titles and files - * of pull requests using the github API, from `branch` up to the last git tag. - * Only includes pull requests that have code changes and not only markdown, - * json, and yaml changes. - */ - -const argv = require('minimist')(process.argv.slice(2)); -const assert = require('assert'); -const childProcess = require('child_process'); -const colors = require('ansi-colors'); -const config = require('../test-configs/config'); -const extend = require('util')._extend; -const git = require('gulp-git'); -const log = require('fancy-log'); -const util = require('util'); - -const {GITHUB_ACCESS_TOKEN} = process.env; - -const exec = util.promisify(childProcess.exec); -const gitExec = util.promisify(git.exec); -const request = util.promisify(require('request')); - -const {branch: overrideBranch, dryrun: isDryrun} = argv; - -const pullOptions = { - url: 'https://api.github.com/repos/ampproject/amphtml/pulls', - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, -}; -const latestReleaseOptions = { - url: 'https://api.github.com/repos/ampproject/amphtml/releases/latest', - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, -}; - -if (GITHUB_ACCESS_TOKEN) { - pullOptions.qs = { - 'access_token': GITHUB_ACCESS_TOKEN, - }; -} - -if (GITHUB_ACCESS_TOKEN) { - latestReleaseOptions.qs = { - 'access_token': GITHUB_ACCESS_TOKEN, - }; -} - -/** - * @typedef {{ - * logs: !Array, - * tag: (string|undefined), - * changelog: (string|undefined), - * baseTag: (string|undefined), - * branch: (string|undefined) - * }} - */ -let GitMetadataDef; - -/** - * @typedef {{ - * title: string, - * sha: string, - * pr: (PrMetadata|undefined) - * }} - */ -let LogMetadataDef; - -/** - * @typedef {{ - * id: number, - * title: string, - * body: string, - * merge_commit_sha: string, - * url: string, - * filenames: !Array - * }} - */ -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.' - ) - ); - return; - } - - return getGitMetadata(); -} - -/** - * @return {!Promise} - */ -function getGitMetadata() { - if (!argv.tag) { - throw new Error('no tag value passed in. See --tag flag option.'); - } - - const gitMetadata = { - logs: [], - tag: undefined, - baseTag: undefined, - 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); -} - -/** - * Get the last git tag this current HEAD bases off of from. - * - * @param {!GitMetadataDef} gitMetadata - * @return {!GitMetadataDef} - */ -function getLastGitTag(gitMetadata) { - const command = 'git describe --tags --abbrev=0'; - - return exec(command).then(lastTag => { - gitMetadata.baseTag = lastTag.stdout.trim(); - return gitMetadata; - }); -} - -/** - * Get the current working branch name. - * - * @param {!GitMetadataDef} gitMetadata - * @return {!GitMetadataDef} - */ -function getCurrentBranchName(gitMetadata) { - if (overrideBranch) { - gitMetadata.branch = overrideBranch; - return gitMetadata; - } - const command = 'git rev-parse --abbrev-ref HEAD'; - - return exec(command).then(branchName => { - gitMetadata.branch = branchName.stdout.trim(); - return gitMetadata; - }); -} - -/** - * @param {string} version - * @param {string} changelog - * @param {string} sha - * @return {!Promise} - */ -function submitReleaseNotes(version, changelog, sha) { - const name = String(version); - const options = { - url: 'https://api.github.com/repos/ampproject/amphtml/releases', - method: 'POST', - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, - json: true, - body: { - 'tag_name': name, - 'target_commitish': sha, - 'name': name, - 'body': changelog, - 'draft': true, - 'prerelease': true, - }, - }; - - if (GITHUB_ACCESS_TOKEN) { - options.qs = { - 'access_token': GITHUB_ACCESS_TOKEN, - }; - } - - return request(options).then(function() { - log(colors.green('Release Notes submitted')); - }); -} - -/** - * @return {!Promise} - */ -function getCurrentSha() { - return gitExec({args: 'rev-parse HEAD'}).then(function(sha) { - return sha.trim(); - }); -} - -/** - * @param {!GitMetadataDef} gitMetadata - * @return {!GitMetadataDef} - */ -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`; - } - - // 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 += '\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; - }); - changelog += uniqueItems.join(''); - changelog += '
\n'; - }); - - gitMetadata.changelog = changelog; - return gitMetadata; -} - -/** - * @param {!GitMetadata} gitMetadata - * @return {!Object} - */ -function buildSections(gitMetadata) { - const sections = {}; - gitMetadata.logs.forEach(function(log) { - const {pr} = log; - if (!pr) { - return; - } - const hasNonDocChange = !pr.filenames.every(function(filename) { - return config.changelogIgnoreFileTypes.test(filename); - }); - const listItem = `${pr.title.trim()} (#${pr.id})\n`; - if (hasNonDocChange) { - changelog += listItem; - } - - pr.filenames.forEach(function(filename) { - let section; - let body = ''; - const path = filename.split('/'); - const isExtensionChange = path[0] == 'extensions'; - const isBuiltinChange = path[0] == 'builtins'; - const isAdsChange = path[0] == 'ads'; - // TODO: figure out how to break down validator changes since - // it is usually a big PR with a number of commits, and the commit - // message is what is useful for a changelog. - const isValidatorChange = path[0] == 'validator'; - - if (isExtensionChange) { - section = path[1]; - } else if (isBuiltinChange && isJs(path[1])) { - // builtins files dont have a nested per component folder. - section = path[1].replace(/\.js$/, ''); - } else if (isAdsChange && isJs(path[1])) { - section = 'ads'; - } else if (isValidatorChange) { - section = 'validator'; - } - - if (section) { - if (!sections[section]) { - sections[section] = []; - } - // if its the validator section, read the body of the PR - // and format it correctly under the bullet list. - if (section == 'validator') { - body = `${pr.body}\n`; - } - sections[section].push(listItem + body); - } - }); - }); - return sections; -} - -/** - * Get the latest git tag from either a normal release or from a canary release. - * @param {!GitMetadataDef} gitMetadata - * @return {!Promise} - */ -function getLastProdReleasedGitTag(gitMetadata) { - return request(latestReleaseOptions).then(res => { - const body = JSON.parse(res.body); - if (!body.tag_name) { - throw new Error('getLastProdReleasedGitTag: ' + body.message); - } - gitMetadata.tag = body.tag_name; - return gitMetadata; - }); -} - -/** - * Runs `git log ${branch}...{tag} --pretty=oneline --first-parent` - * @param {!GitMetadataDef} gitMetadata - * @return {!Promise} - */ -function getGitLog(gitMetadata) { - 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' + - 'Make sure to fetch and rebase (or reset --hard) the latest ' + - 'from remote upstream.' - ); - } - const commits = logs.split('\n').filter(log => !!log.length); - gitMetadata.logs = commits.map(log => { - const words = log.split(' '); - return {sha: words.shift(), title: words.join(' ')}; - }); - return gitMetadata; - }); -} - -/** - * @param {!GitMetadataDef} gitMetadata - * @return {!Promise} - */ -function getGithubPullRequestsMetadata(gitMetadata) { - // (erwinm): Github seems to only return data for the first 3 pages - // from my manual testing. - return Promise.all([ - getClosedPullRequests(1), - 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 Promise.resolve(); - }); - return Promise.all(githubPrRequest).then(() => { - return gitMetadata; - }); - }); -} - -/** - * We either fetch the pulls/${id}/files but if we have no PrMetadata yet, - * we will try and also fetch pulls/${id} first before fetching - * pulls/${id}/files. - * - * @param {!GitMetadataDef} gitMetadata - * @return {!Promise} - */ -function getGithubFilesMetadata(gitMetadata) { - const githubFileRequests = gitMetadata.logs.map(log => { - if (log.pr) { - const fileOptions = extend({}, pullOptions); - fileOptions.url = `${log.pr.url}/files`; - return getPullRequestFiles(fileOptions, log.pr); - } - return Promise.resolve(); - }); - return Promise.all(githubFileRequests).then(() => { - return gitMetadata; - }); -} - -/** - * Fetches pulls?page=${opt_page} - * - * @param {number=} opt_page - * @return {!Promise} - */ -function getClosedPullRequests(opt_page) { - opt_page = opt_page || 1; - const options = extend({}, pullOptions); - options.qs = { - state: 'closed', - page: opt_page, - 'access_token': GITHUB_ACCESS_TOKEN, - }; - return request(options).then(res => { - const prs = JSON.parse(res.body); - assert(Array.isArray(prs), 'prs must be an array.'); - return prs; - }); -} - -/** - * @param {Object} prOption - * @param {!LogMetadataDef} log - * @return {!Promise} - */ -function getPullRequest(prOption, log) { - return request(prOption).then(function(res) { - const pr = JSON.parse(res.body); - assert(typeof pr === 'object', 'Pull Requests Metadata must be an object'); - log.pr = buildPrMetadata(pr); - return log.pr; - }); -} - -/** - * @param {!Object} filesOption - * @param {!PrMetadataDef} pr - * @return {!Promise} - */ -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 - ); - const filenames = body.map(function(file) { - return file.filename; - }); - - pr.filenames = filenames; - return pr; - }); -} - -function errHandler(err) { - let msg = err; - if (err.message) { - msg = err.message; - } - log(colors.red(msg)); -} - -/** - * Check if the string starts with "Merge pull request #" - * @param {string} str - * @return {boolean} - */ -function isPrIdInTitle(str) { - return str./* OK*/ indexOf('Merge pull request #') == 0; -} - -/** - * @param {string} commit - * @return {number} - */ -function getPrIdFromCommit(commit) { - // We only need the PR id - const id = commit.split(' ')[3].slice(1); - const value = parseInt(id, 10); - assert(value > 0, 'Should be an integer greater than 0. ' + value); - return id; -} - -/** - * Checks if string ends with ".js" - * @param {string} str - * @return {boolean} - */ -function isJs(str) { - return str./* OK*/ endsWith('.js'); -} - -/** - * @param {!JSONValue} pr - * @return {!PrMetadata} - */ -function buildPrMetadata(pr) { - return { - 'id': pr.number, - 'title': pr.title, - 'body': pr.body, - 'merge_commit_sha': pr.merge_commit_sha, - 'url': pr._links.self.href, - }; -} - -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.' - ) - ); - return; - } - if (!argv.message) { - log(colors.red('--message flag must be set.')); - } - return update(); -} - -function update() { - const url = - 'https://api.github.com/repos/ampproject/amphtml/releases/tags/' + - `${argv.tag}`; - const tagsOptions = { - url, - method: 'GET', - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, - }; - - const releasesOptions = { - url: 'https://api.github.com/repos/ampproject/amphtml/releases/', - method: 'PATCH', - body: {}, - json: true, - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, - }; - - if (GITHUB_ACCESS_TOKEN) { - tagsOptions.qs = { - 'access_token': GITHUB_ACCESS_TOKEN, - }; - releasesOptions.qs = { - 'access_token': GITHUB_ACCESS_TOKEN, - }; - } - - return request(tagsOptions).then(res => { - const release = JSON.parse(res.body); - if (!release.body) { - return; - } - const {id} = release; - releasesOptions.url += id; - if (argv.suffix) { - releasesOptions.body.body = release.body + argv.message; - } 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)); - }); - }); -} - -module.exports = { - changelog, - changelogUpdate, -}; - -changelog.description = 'Create github release draft'; -changelog.flags = { - dryrun: ' Generate changelog but dont push it out', - type: ' Pass in "canary" to generate a canary changelog', - tag: ' The git tag and github release label', -}; - -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/css.js b/build-system/tasks/css.js index e3e5a9bdd504..49e397b762e2 100644 --- a/build-system/tasks/css.js +++ b/build-system/tasks/css.js @@ -54,6 +54,18 @@ const cssEntryPoints = [ // than the JS file to avoid loading CSS as JS outCss: 'video-autoplay-out.css', }, + { + // Publisher imported CSS for `src/amp-story-player.js`. + path: 'amp-story-player.css', + outJs: 'amp-story-player.css.js', + outCss: 'amp-story-player-v0.css', + }, + { + // Internal CSS used for the iframes inside `src/amp-story-player.js`. + path: 'amp-story-player-iframe.css', + outJs: 'amp-story-player-iframe.css.js', + outCss: 'amp-story-player-iframe-v0.css', + }, ]; /** diff --git a/build-system/tasks/dist.js b/build-system/tasks/dist.js index aadb9e6e0ce2..a382ef179882 100644 --- a/build-system/tasks/dist.js +++ b/build-system/tasks/dist.js @@ -26,6 +26,7 @@ const { compileJs, endBuildStep, hostname, + maybeToEsmName, mkdirSync, printConfigHelp, printNobuildHelp, @@ -104,7 +105,10 @@ async function dist() { // own processing). Executed after `compileCss` and `compileJison` so their // results can be copied too. if (!argv.single_pass) { - transferSrcsToTempDir({isForTesting: argv.fortesting}); + transferSrcsToTempDir({ + isForTesting: argv.fortesting, + isEsmBuild: argv.esm, + }); } await copyCss(); @@ -125,11 +129,11 @@ async function dist() { await stopNailgunServer(distNailgunPort); if (argv.esm) { - await Promise.all([ - createModuleCompatibleES5Bundle('v0.js'), - createModuleCompatibleES5Bundle('amp4ads-v0.js'), - createModuleCompatibleES5Bundle('shadow-v0.js'), - ]); + await createModuleCompatibleES5Bundle('v0.mjs'); + if (!argv.core_runtime_only) { + await createModuleCompatibleES5Bundle('amp4ads-v0.mjs'); + await createModuleCompatibleES5Bundle('shadow-v0.mjs'); + } } if (!argv.core_runtime_only) { @@ -154,7 +158,7 @@ function buildExperiments(options) { watch: false, minify: options.minify || argv.minify, includePolyfills: true, - minifiedName: 'experiments.js', + minifiedName: maybeToEsmName('experiments.js'), } ); } @@ -196,7 +200,7 @@ async function buildWebPushPublisherFiles(options) { WEB_PUSH_PUBLISHER_FILES.forEach(fileName => { const tempBuildDir = `build/all/amp-web-push-${version}/`; const builtName = fileName + '.js'; - const minifiedName = fileName + '.js'; + const minifiedName = maybeToEsmName(fileName + '.js'); const p = compileJs('./' + tempBuildDir, builtName, './' + distDir, { watch: options.watch, includePolyfills: true, @@ -277,7 +281,7 @@ async function generateFileListing() { const filesOut = `${distDir}/files.txt`; fs.writeFileSync(filesOut, ''); const files = (await walk(distDir)).map(f => f.replace(`${distDir}/`, '')); - fs.writeFileSync(filesOut, files.join('\n')); + fs.writeFileSync(filesOut, files.join('\n') + '\n'); endBuildStep('Generated', filesOut, startTime); } @@ -319,7 +323,7 @@ function postBuildWebPushPublisherFilesVersion() { WEB_PUSH_PUBLISHER_VERSIONS.forEach(version => { const basePath = `extensions/amp-web-push/${version}/`; WEB_PUSH_PUBLISHER_FILES.forEach(fileName => { - const minifiedName = fileName + '.js'; + const minifiedName = maybeToEsmName(fileName + '.js'); if (!fs.existsSync(distDir + '/' + minifiedName)) { throw new Error(`Cannot find ${distDir}/${minifiedName}`); } @@ -426,7 +430,8 @@ module.exports = { /* eslint "google-camelcase/google-camelcase": 0 */ -dist.description = 'Build production binaries'; +dist.description = + 'Compiles AMP production binaries and applies AMP_CONFIG to runtime files'; dist.flags = { pseudo_names: ' Compiles with readable names. ' + @@ -435,6 +440,7 @@ dist.flags = { ' Outputs compiled code with whitespace. ' + 'Great for debugging production code.', fortesting: ' Compiles production binaries for local testing', + noconfig: ' Compiles production binaries without applying AMP_CONFIG', 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", extensions: ' Builds only the listed extensions.', diff --git a/build-system/tasks/e2e/expect.js b/build-system/tasks/e2e/expect.js index 324ed20d1978..102b50b482c5 100644 --- a/build-system/tasks/e2e/expect.js +++ b/build-system/tasks/e2e/expect.js @@ -245,33 +245,29 @@ function installBrowserAssertions(_networkLogger) { function installBrowserWrappers(chai, utils) { const {Assertion} = chai; - const should = chai.should(); // eslint-disable-line no-unused-vars // Assert that a request with a testUrl was sent // Example usage: await expect(testUrl).to.have.been.sent; - utils.addProperty(Assertion.prototype, 'sent', function() { - const assertAnySentRequests = function(url) { - const promise = networkLogger.getSentRequests(url); - return promise.should.eventually.not.have.length(0); - }; - - this.assert( - assertAnySentRequests(this._obj), - 'expected #{this} to have been sent', - 'expected #{this} to have been sent' - ); + utils.addProperty(Assertion.prototype, 'sent', async function() { + const url = this._obj; + const requests = await networkLogger.getSentRequests(url); + this.assert(0 < requests.length, 'expected #{this} to have been sent'); }); + Assertion.overwriteProperty('sent', overwriteAlwaysUseSuper(utils)); // Assert that a request was sent n number of times // Example usage: await expect(testUrl).to.have.sentCount(n); - utils.addMethod(Assertion.prototype, 'sentCount', function(count) { - const assertSentCount = function(url, count) { - const promise = networkLogger.getSentRequests(url); - return promise.should.eventually.have.length(count); - }; - - this.assert(assertSentCount(this._obj, count)); + utils.addMethod(Assertion.prototype, 'sentCount', async function(count) { + const url = this._obj; + const requests = await networkLogger.getSentRequests(url); + this.assert( + count === requests.length, + `expected #{this} to have been sent ${ + count == 1 ? 'once' : count + ' times' + }` + ); }); + Assertion.overwriteMethod('sentCount', overwriteAlwaysUseSuper(utils)); } module.exports = { diff --git a/build-system/tasks/e2e/package.json b/build-system/tasks/e2e/package.json index 1db5acdbfb9d..a0064aca47d6 100644 --- a/build-system/tasks/e2e/package.json +++ b/build-system/tasks/e2e/package.json @@ -6,8 +6,8 @@ "devDependencies": { "@babel/register": "7.8.3", "babel-regenerator-runtime": "6.5.0", - "chromedriver": "79.0.0", - "puppeteer": "2.0.0", + "chromedriver": "80.0.1", + "puppeteer": "2.1.1", "geckodriver": "1.19.1", "selenium-webdriver": "4.0.0-alpha.5" } diff --git a/build-system/tasks/e2e/yarn.lock b/build-system/tasks/e2e/yarn.lock index 64360f5bb805..5e9e79515d55 100644 --- a/build-system/tasks/e2e/yarn.lock +++ b/build-system/tasks/e2e/yarn.lock @@ -13,6 +13,32 @@ pirates "^4.0.0" source-map-support "^0.5.16" +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + +"@testim/chrome-version@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.0.7.tgz#0cd915785ec4190f08a3a6acc9b61fc38fb5f1a9" + integrity sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw== + "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -27,6 +53,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/mime-types@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73" + integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM= + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -42,6 +73,11 @@ adm-zip@0.4.11: resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.11.tgz#2aa54c84c4b01a9d0fb89bb11982a51f13e3d62a" integrity sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA== +agent-base@5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" + integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== + agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -49,59 +85,30 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -ajv@^6.5.5: - version "6.7.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96" - integrity sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== +aggregate-error@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" + integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== dependencies: - safer-buffer "~2.1.0" + clean-stack "^2.0.0" + indent-string "^4.0.0" -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== +axios@^0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" babel-regenerator-runtime@6.5.0: version "6.5.0" @@ -113,13 +120,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - bluebird@3.4.6: version "3.4.6" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.6.tgz#01da8d821d87813d158967e743d5fe6c62cf8c0f" @@ -133,6 +133,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -143,33 +150,27 @@ capture-stack-trace@^1.0.0: resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - chownr@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== -chromedriver@79.0.0: - version "79.0.0" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-79.0.0.tgz#1660ac29924dfcd847911025593d6b6746aeea35" - integrity sha512-DO29C7ntJfzu6q1vuoWwCON8E9x5xzopt7Q41A7Dr7hBKcdNpGw1l9DTt9b+l1qviOWiJLGsD+jHw21ptEHubA== +chromedriver@80.0.1: + version "80.0.1" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-80.0.1.tgz#35c1642e2d864b9e8262f291003e455b0e422917" + integrity sha512-VfRtZUpBUIjeypS+xM40+VD9g4Drv7L2VibG/4+0zX3mMx4KayN6gfKETycPfO6JwQXTLSxEr58fRcrsa8r5xQ== dependencies: - del "^4.1.1" + "@testim/chrome-version" "^1.0.7" + axios "^0.19.2" + del "^5.1.0" extract-zip "^1.6.7" - mkdirp "^0.5.1" - request "^2.88.0" + mkdirp "^1.0.3" tcp-port-used "^1.0.1" -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" - integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== - dependencies: - delayed-stream "~1.0.0" +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== commondir@^1.0.1: version "1.0.1" @@ -191,7 +192,7 @@ concat-stream@1.6.2: readable-stream "^2.2.2" typedarray "^0.0.6" -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -203,13 +204,6 @@ create-error-class@^3.0.1: dependencies: capture-stack-trace "^1.0.0" -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -217,6 +211,13 @@ debug@2.6.9: dependencies: ms "2.0.0" +debug@4, debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" @@ -224,6 +225,13 @@ debug@4.1.0: dependencies: ms "^2.1.1" +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -231,35 +239,31 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -del@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" - integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== +del@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" + integrity sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA== + dependencies: + globby "^10.0.1" + graceful-fs "^4.2.2" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.1" + p-map "^3.0.0" + rimraf "^3.0.0" + slash "^3.0.0" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + path-type "^4.0.0" duplexer2@^0.1.4: version "0.1.4" @@ -268,14 +272,6 @@ duplexer2@^0.1.4: dependencies: readable-stream "^2.0.2" -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - error-ex@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -295,11 +291,6 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - extract-zip@^1.6.6, extract-zip@^1.6.7: version "1.6.7" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" @@ -310,25 +301,23 @@ extract-zip@^1.6.6, extract-zip@^1.6.7: mkdirp "0.5.1" yauzl "2.4.1" -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-glob@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.1.tgz#87ee30e9e9f3eb40d6f254a7997655da753d7c82" + integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= +fastq@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" + integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA== + dependencies: + reusify "^1.0.0" fd-slicer@~1.0.1: version "1.0.1" @@ -337,6 +326,13 @@ fd-slicer@~1.0.1: dependencies: pend "~1.2.0" +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + find-cache-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -353,19 +349,12 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" + debug "=3.1.0" fs-minipass@^1.2.5: version "1.2.5" @@ -390,14 +379,14 @@ geckodriver@1.19.1: https-proxy-agent "3.0.0" tar "4.4.2" -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= +glob-parent@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== dependencies: - assert-plus "^1.0.0" + is-glob "^4.0.1" -glob@^7.0.3, glob@^7.1.3: +glob@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -409,16 +398,19 @@ glob@^7.0.3, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= +globby@^10.0.1: + version "10.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" + integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" + "@types/glob" "^7.1.1" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.0.3" + glob "^7.1.3" + ignore "^5.1.1" + merge2 "^1.2.3" + slash "^3.0.0" got@5.6.0: version "5.6.0" @@ -442,27 +434,10 @@ got@5.6.0: unzip-response "^1.0.0" url-parse-lax "^1.0.0" -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" +graceful-fs@^4.2.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== https-proxy-agent@3.0.0: version "3.0.0" @@ -472,19 +447,29 @@ https-proxy-agent@3.0.0: agent-base "^4.3.0" debug "^3.1.0" -https-proxy-agent@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81" - integrity sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg== +https-proxy-agent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" + integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== dependencies: - agent-base "^4.3.0" - debug "^3.1.0" + agent-base "5" + debug "4" + +ignore@^5.1.1: + version "5.1.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" + integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -508,24 +493,32 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-path-cwd@^2.0.0: +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-cwd@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" - integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== - dependencies: - is-path-inside "^2.1.0" - -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" - integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== - dependencies: - path-is-inside "^1.0.2" +is-path-inside@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" + integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== is-plain-obj@^1.0.0: version "1.1.0" @@ -547,11 +540,6 @@ is-stream@^1.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - is-url@^1.2.2: version "1.2.4" resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" @@ -571,41 +559,6 @@ isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - jszip@^3.1.5: version "3.2.2" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d" @@ -649,17 +602,30 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -mime-db@~1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" - integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== +merge2@^1.2.3, merge2@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.21" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" - integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== dependencies: - mime-db "~1.37.0" + braces "^3.0.1" + picomatch "^2.0.5" + +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@^2.1.25: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" mime@^2.0.3: version "2.4.0" @@ -693,13 +659,18 @@ minizlib@^1.1.0: dependencies: minipass "^2.2.1" -mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@0.5.1, mkdirp@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" +mkdirp@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" + integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -720,11 +691,6 @@ node-status-codes@^1.0.0: resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" integrity sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8= -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - object-assign@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -756,10 +722,12 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" p-try@^2.0.0: version "2.2.0" @@ -788,25 +756,20 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -pify@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= +picomatch@^2.0.5: + version "2.2.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" + integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== pify@^4.0.1: version "4.0.1" @@ -859,40 +822,22 @@ proxy-from-env@^1.0.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= -psl@^1.1.24: - version "1.1.31" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" - integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0: +puppeteer@2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -puppeteer@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.0.0.tgz#0612992e29ec418e0a62c8bebe61af1a64d7ec01" - integrity sha512-t3MmTWzQxPRP71teU6l0jX47PHXlc4Z52sQv4LJQSZLq1ttkKS2yGM3gaI57uQwZkNaoGd0+HPPMELZkcyhlqA== + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.1.1.tgz#ccde47c2a688f131883b50f2d697bd25189da27e" + integrity sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg== dependencies: + "@types/mime-types" "^2.1.0" debug "^4.1.0" extract-zip "^1.6.6" - https-proxy-agent "^3.0.0" + https-proxy-agent "^4.0.0" mime "^2.0.3" + mime-types "^2.1.25" progress "^2.0.1" proxy-from-env "^1.0.0" rimraf "^2.6.1" ws "^6.1.0" -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - read-all-stream@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" @@ -914,31 +859,10 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" +reusify@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rimraf@^2.6.1, rimraf@^2.6.3: version "2.6.3" @@ -947,16 +871,23 @@ rimraf@^2.6.1, rimraf@^2.6.3: dependencies: glob "^7.1.3" -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + +safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -982,6 +913,11 @@ set-immediate-shim@~1.0.1: resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + source-map-support@^0.5.16: version "0.5.16" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" @@ -995,21 +931,6 @@ source-map@^0.6.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -sshpk@^1.7.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de" - integrity sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -1050,25 +971,12 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + is-number "^7.0.0" typedarray@^0.0.6: version "0.0.6" @@ -1080,13 +988,6 @@ unzip-response@^1.0.0: resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" integrity sha1-uYTwh3/AqJwsdzzB73tbIytbBv4= -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - url-parse-lax@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" @@ -1099,20 +1000,6 @@ util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" diff --git a/build-system/tasks/extension-helpers.js b/build-system/tasks/extension-helpers.js index 9bc024a3a765..97f85aa8b430 100644 --- a/build-system/tasks/extension-helpers.js +++ b/build-system/tasks/extension-helpers.js @@ -24,10 +24,10 @@ const { extensionBundles, verifyExtensionBundles, } = require('../compile/bundles.config'); -const {compileJs, mkdirSync} = require('./helpers'); const {endBuildStep} = require('./helpers'); const {isTravisBuild} = require('../common/travis'); const {jsifyCssAsync} = require('./jsify-css'); +const {maybeToEsmName, compileJs, mkdirSync} = require('./helpers'); const {vendorConfigs} = require('./vendor-configs'); const {green, red, cyan} = colors; @@ -368,6 +368,32 @@ async function doBuildExtension(extensions, extension, options) { ); } +/** + * Watches the contents of an extension directory. When a file in the given path + * changes, the extension is rebuilt. + * + * @param {string} path + * @param {string} name + * @param {string} version + * @param {string} latestVersion + * @param {boolean} hasCss + * @param {?Object} options + */ +function watchExtension(path, name, version, latestVersion, hasCss, options) { + watch(path + '/**/*', function() { + const bundleComplete = buildExtension( + name, + version, + latestVersion, + hasCss, + {...options, continueOnError: true} + ); + if (options.onWatchBuild) { + options.onWatchBuild(bundleComplete); + } + }); +} + /** * Copies extensions from * extensions/$name/$version/$name.js @@ -408,18 +434,12 @@ async function buildExtension( // recompiles JS. if (options.watch) { options.watch = false; - watch(path + '/**/*', function() { - const bundleComplete = buildExtension( - name, - version, - latestVersion, - hasCss, - {...options, continueOnError: true} - ); - if (options.onWatchBuild) { - options.onWatchBuild(bundleComplete); - } - }); + watchExtension(path, name, version, latestVersion, hasCss, options); + // When an ad network extension is being watched, also watch amp-a4a. + if (name.match(/amp-ad-network-.*-impl/)) { + const a4aPath = `extensions/amp-a4a/${version}`; + watchExtension(a4aPath, name, version, latestVersion, hasCss, options); + } } if (hasCss) { mkdirSync('build'); @@ -524,10 +544,12 @@ async function buildExtensionJs(path, name, version, latestVersion, options) { const aliasBundle = extensionAliasBundles[name]; const isAliased = aliasBundle && aliasBundle.version == version; if (isAliased) { - const src = `${name}-${version}${options.minify ? '' : '.max'}.js`; - const dest = `${name}-${aliasBundle.aliasedVersion}${ - options.minify ? '' : '.max' - }.js`; + const src = maybeToEsmName( + `${name}-${version}${options.minify ? '' : '.max'}.js` + ); + const dest = maybeToEsmName( + `${name}-${aliasBundle.aliasedVersion}${options.minify ? '' : '.max'}.js` + ); fs.copySync(`dist/v0/${src}`, `dist/v0/${dest}`); fs.copySync(`dist/v0/${src}.map`, `dist/v0/${dest}.map`); } diff --git a/build-system/tasks/firebase.js b/build-system/tasks/firebase.js index b17a4c97b047..82db707e2329 100644 --- a/build-system/tasks/firebase.js +++ b/build-system/tasks/firebase.js @@ -18,10 +18,10 @@ const colors = require('ansi-colors'); const fs = require('fs-extra'); const log = require('fancy-log'); const path = require('path'); +const {applyAmpConfig} = require('./helpers'); const {build} = require('./build'); const {clean} = require('./clean'); const {dist} = require('./dist'); -const {enableLocalTesting} = require('./helpers'); async function walk(dest) { const filelist = []; @@ -80,8 +80,11 @@ async function firebase() { if (argv.fortesting) { await Promise.all([ - enableLocalTesting('firebase/dist.3p/current/integration.js'), - enableLocalTesting('firebase/dist/amp.js'), + applyAmpConfig( + 'firebase/dist.3p/current/integration.js', + /* localDev */ true + ), + applyAmpConfig('firebase/dist/amp.js', /* localDev */ true), ]); } diff --git a/build-system/tasks/helpers.js b/build-system/tasks/helpers.js index df3cea12e09c..50ed903c7cf9 100644 --- a/build-system/tasks/helpers.js +++ b/build-system/tasks/helpers.js @@ -88,7 +88,12 @@ const UNMINIFIED_TARGETS = [ * Note: keep this list in sync with release script. Contact @ampproject/wg-infra * for details. */ -const MINIFIED_TARGETS = ['alp.js', 'amp4ads-v0.js', 'shadow-v0.js', 'v0.js']; +const MINIFIED_TARGETS = [ + 'alp.js', + 'amp4ads-v0.js', + 'shadow-v0.js', + 'v0.js', +].map(maybeToEsmName); /** * Settings for the global Babelify transform while compiling unminified code @@ -213,7 +218,7 @@ async function compileAllJs(watch, minify) { doBuildJs(jsBundles, 'recaptcha.js', {watch, minify}), doBuildJs(jsBundles, 'amp-viewer-host.max.js', {watch, minify}), doBuildJs(jsBundles, 'video-iframe-integration.js', {watch, minify}), - doBuildJs(jsBundles, 'amp-story-embed.js', {watch, minify}), + doBuildJs(jsBundles, 'amp-story-player.js', {watch, minify}), doBuildJs(jsBundles, 'amp-inabox-host.js', {watch, minify}), doBuildJs(jsBundles, 'amp-shadow.js', {watch, minify}), doBuildJs(jsBundles, 'amp-inabox.js', {watch, minify}), @@ -257,6 +262,14 @@ function appendToCompiledFile(srcFilename, destFilePath) { } } +function toEsmName(name) { + return name.replace(/\.js$/, '.mjs'); +} + +function maybeToEsmName(name) { + return argv.esm ? toEsmName(name) : name; +} + /** * Minifies a given JavaScript file entry point. * @param {string} srcDir @@ -268,7 +281,7 @@ function appendToCompiledFile(srcFilename, destFilePath) { function compileMinifiedJs(srcDir, srcFilename, destDir, options) { const timeInfo = {}; const entryPoint = path.join(srcDir, srcFilename); - const {minifiedName} = options; + const minifiedName = maybeToEsmName(options.minifiedName); return closureCompile(entryPoint, destDir, minifiedName, options, timeInfo) .then(function() { const destPath = path.join(destDir, minifiedName); @@ -278,33 +291,44 @@ function compileMinifiedJs(srcDir, srcFilename, destDir, options) { internalRuntimeVersion ); if (options.latestName) { - fs.copySync(destPath, path.join(destDir, options.latestName)); + fs.copySync( + destPath, + path.join(destDir, maybeToEsmName(options.latestName)) + ); } }) .then(() => { let name = minifiedName; if (options.latestName) { - name += ` → ${options.latestName}`; + name += ` → ${maybeToEsmName(options.latestName)}`; } if (options.singlePassCompilation) { altMainBundles.forEach(bundle => { - name += `, ${bundle.name}.js`; + name += `, ${maybeToEsmName(`${bundle.name}.js`)}`; }); name += ', and all extensions'; } endBuildStep('Minified', name, timeInfo.startTime); }) .then(() => { - if (argv.fortesting && MINIFIED_TARGETS.includes(minifiedName)) { - return enableLocalTesting(`${destDir}/${minifiedName}`); + if (!argv.noconfig && MINIFIED_TARGETS.includes(minifiedName)) { + return applyAmpConfig( + maybeToEsmName(`${destDir}/${minifiedName}`), + /* localDev */ !!argv.fortesting + ); } }) .then(() => { - if (!argv.fortesting || !options.singlePassCompilation) { + if (argv.noconfig || !options.singlePassCompilation) { return; } return Promise.all( - altMainBundles.map(({name}) => enableLocalTesting(`dist/${name}.js`)) + altMainBundles.map(({name}) => + applyAmpConfig( + maybeToEsmName(`dist/${name}.js`), + /* localDev */ !!argv.fortesting + ) + ) ); }); } @@ -443,7 +467,10 @@ function compileUnminifiedJs(srcDir, srcFilename, destDir, options) { }) .then(() => { if (UNMINIFIED_TARGETS.includes(destFilename)) { - return enableLocalTesting(`${destDir}/${destFilename}`); + return applyAmpConfig( + `${destDir}/${destFilename}`, + /* localDev */ true + ); } }); } @@ -555,12 +582,14 @@ function printNobuildHelp() { } /** - * Enables runtime to be used for local testing by writing AMP_CONFIG to file. - * Called at the end of "gulp build" and "gulp dist --fortesting". + * Writes AMP_CONFIG to a runtime file. Optionally enables localDev mode and + * fortesting mode. Called by "gulp build" and "gulp dist" while building + * various runtime files. * @param {string} targetFile File to which the config is to be written. + * @param {boolean} localDev Whether or not to enable local development. * @return {!Promise} */ -async function enableLocalTesting(targetFile) { +async function applyAmpConfig(targetFile, localDev) { const config = argv.config === 'canary' ? 'canary' : 'prod'; const baseConfigFile = 'build-system/global-configs/' + config + '-config.json'; @@ -570,7 +599,7 @@ async function enableLocalTesting(targetFile) { config, targetFile, baseConfigFile, - /* opt_localDev */ true, + /* opt_localDev */ localDev, /* opt_localBranch */ true, /* opt_branch */ false, /* opt_fortesting */ !!argv.fortesting @@ -696,6 +725,7 @@ function transferSrcsToTempDir(options = {}) { } module.exports = { + applyAmpConfig, BABELIFY_GLOBAL_TRANSFORM, BABELIFY_PLUGINS, bootstrapThirdPartyFrames, @@ -706,9 +736,9 @@ module.exports = { compileTs, devDependencies, doBuildJs, - enableLocalTesting, endBuildStep, hostname, + maybeToEsmName, mkdirSync, printConfigHelp, printNobuildHelp, diff --git a/build-system/tasks/integration.js b/build-system/tasks/integration.js index 887d7e9f0a6b..60976152d1ed 100644 --- a/build-system/tasks/integration.js +++ b/build-system/tasks/integration.js @@ -70,6 +70,7 @@ integration.flags = { 'chrome_flags': ' Uses the given flags to launch Chrome', 'compiled': ' Changes integration tests to use production JS binaries for execution', + 'single_pass': ' Run tests in Single Pass mode', 'config': ' Sets the runtime\'s AMP_CONFIG to one of "prod" (default) or "canary"', 'coverage': ' Run tests in code coverage mode', @@ -81,7 +82,9 @@ integration.flags = { 'nobuild': ' Skips build step', 'nohelp': ' Silence help messages that are printed prior to test run', 'safari': ' Runs tests on Safari', - 'saucelabs': ' Runs tests on saucelabs (requires setup)', + 'saucelabs': ' Runs tests on Sauce Labs (requires setup)', + 'stable': ' Runs Sauce Labs tests on stable browsers', + 'beta': ' Runs Sauce Labs tests on beta browsers', 'testnames': ' Lists the name of each test being run', 'verbose': ' With logging enabled', 'watch': ' Watches for changes in files, runs corresponding test(s)', diff --git a/build-system/tasks/karma.conf.js b/build-system/tasks/karma.conf.js index e05028d437d6..9f7008f894c9 100644 --- a/build-system/tasks/karma.conf.js +++ b/build-system/tasks/karma.conf.js @@ -31,9 +31,9 @@ const COMMON_CHROME_FLAGS = [ // Reduces the odds of Sauce labs timing out during tests. See #16135 and #24286. // Reference: https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-Timeouts const SAUCE_TIMEOUT_CONFIG = { - maxDuration: 10 * 60, + maxDuration: 30 * 60, commandTimeout: 10 * 60, - idleTimeout: 10 * 60, + idleTimeout: 30 * 60, }; const BABELIFY_CONFIG = { diff --git a/build-system/tasks/presubmit-checks.js b/build-system/tasks/presubmit-checks.js index 23a88a69b645..22c9edfe10e2 100644 --- a/build-system/tasks/presubmit-checks.js +++ b/build-system/tasks/presubmit-checks.js @@ -54,6 +54,7 @@ const forbiddenTerms = { whitelist: [ 'build-system/server/amp4test.js', 'build-system/server/app-index/boilerplate.js', + 'build-system/server/variable-substitution.js', 'build-system/tasks/extension-generator/index.js', 'css/ampdoc.css', 'css/ampshared.css', @@ -127,6 +128,7 @@ const forbiddenTerms = { 'build-system/tasks/check-owners.js', 'build-system/tasks/check-types.js', 'build-system/tasks/dist.js', + 'build-system/tasks/dns-monitor.js', 'build-system/tasks/generate-runner.js', 'build-system/tasks/helpers.js', 'build-system/tasks/prettify.js', @@ -224,6 +226,14 @@ const forbiddenTerms = { 'testing/iframe.js', ], }, + 'installMutatorServiceForDoc': { + message: privateServiceFactory, + whitelist: [ + 'src/inabox/inabox-services.js', + 'src/service/core-services.js', + 'src/service/mutator-impl.js', + ], + }, 'installPerformanceService': { message: privateServiceFactory, whitelist: [ @@ -233,6 +243,14 @@ const forbiddenTerms = { 'src/service/performance-impl.js', ], }, + 'installResourcesServiceForDoc': { + message: privateServiceFactory, + whitelist: [ + 'src/inabox/inabox-services.js', + 'src/service/core-services.js', + 'src/service/resources-impl.js', + ], + }, 'installStorageServiceForDoc': { message: privateServiceFactory, whitelist: [ @@ -284,15 +302,6 @@ const forbiddenTerms = { 'src/service/vsync-impl.js', ], }, - 'installResourcesServiceForDoc': { - message: privateServiceFactory, - whitelist: [ - 'src/inabox/inabox-services.js', - 'src/service/core-services.js', - 'src/service/resources-impl.js', - 'src/service/standard-actions-impl.js', - ], - }, 'installXhrService': { message: privateServiceFactory, whitelist: [ @@ -359,6 +368,7 @@ const forbiddenTerms = { 'src/service/navigation.js', 'src/service/url-impl.js', 'dist.3p/current/integration.js', + 'src/amp-story-player.js', ], }, '\\.sendMessage\\(': { @@ -572,7 +582,7 @@ const forbiddenTerms = { }, '\\.schedulePass\\(': { message: 'schedulePass is heavy, think twice before using it', - whitelist: ['src/service/resources-impl.js'], + whitelist: ['src/service/mutator-impl.js', 'src/service/resources-impl.js'], }, '\\.requireLayout\\(': { message: @@ -849,6 +859,7 @@ const forbiddenTerms = { 'test/unit/test-mode.js', 'test/unit/test-motion.js', 'test/unit/test-mustache.js', + 'test/unit/test-mutator.js', 'test/unit/test-object.js', 'test/unit/test-observable.js', 'test/unit/test-pass.js', @@ -1092,6 +1103,7 @@ const forbiddenTermsSrcInclusive = { 'extensions/amp-analytics/0.1/config.js', 'extensions/amp-analytics/0.1/cookie-writer.js', 'extensions/amp-analytics/0.1/requests.js', + 'extensions/amp-analytics/0.1/variables.js', ], }, '\\.expandInputValueSync\\(': { @@ -1124,6 +1136,7 @@ const forbiddenTermsSrcInclusive = { 'build-system/server/amp4test.js', 'build-system/server/app-index/amphtml-helpers.js', 'build-system/server/app-video-testbench.js', + 'build-system/server/variable-substitution.js', 'build-system/server/app.js', 'build-system/server/app-utils.js', 'build-system/server/shadow-viewer.js', diff --git a/build-system/tasks/process-3p-github-pr.js b/build-system/tasks/process-3p-github-pr.js index 1c3623585ca8..5adc810a15e0 100644 --- a/build-system/tasks/process-3p-github-pr.js +++ b/build-system/tasks/process-3p-github-pr.js @@ -341,6 +341,10 @@ function assignIssue(issue, assignees) { return request(options); } +module.exports = { + process3pGithubPr, +}; + process3pGithubPr.description = 'Automatically triage 3P integration PRs'; process3pGithubPr.flags = { 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 deleted file mode 100644 index c5899a30cab8..000000000000 --- a/build-system/tasks/release-tagging.js +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Copyright 2017 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. - */ -'use strict'; - -const argv = require('minimist')(process.argv.slice(2)); -const colors = require('ansi-colors'); -const fs = require('fs-extra'); -const git = require('gulp-git'); -const log = require('fancy-log'); -const util = require('util'); - -const {GITHUB_ACCESS_TOKEN} = process.env; -const gitExec = util.promisify(git.exec); -const request = util.promisify(require('request')); - -const isDryrun = argv.dryrun; -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. - * @return {!Promise} - */ -function releaseTagFor(type, dir) { - log('Tag release for: ', type); - let promise = Promise.resolve(); - const ampDir = dir + '/amphtml'; - - // 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; - } - } - }); - - // Checkout tag. - promise = promise.then(function() { - log('Git tag: ', tag); - return gitExec({ - cwd: ampDir, - args: 'checkout ' + tag, - }); - }); - - // 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]); - } - } - }); - - // Update. - const label = LABELS[type]; - promise = promise.then(function() { - log('Update ' + pullRequests.length + ' pull requests'); - const updates = []; - pullRequests.forEach(function(pullRequest) { - updates.push(applyLabel(pullRequest, label)); - }); - return Promise.all(updates); - }); - - return promise.then(function() { - log(colors.green('Tag release for ' + type + ' done.')); - }); -} - -/** - * @param {string} pullRequest - * @param {string} label - * @return {!Promise} - */ -function applyLabel(pullRequest, label) { - if (verbose && isDryrun) { - log('Apply label ' + label + ' for #' + pullRequest); - } - if (isDryrun) { - return Promise.resolve(); - } - return githubRequest('/issues/' + pullRequest + '/labels', 'POST', [ - label, - ]).then(function() { - if (verbose) { - log(colors.green('Label applied ' + label + ' for #' + pullRequest)); - } - }); -} - -/** - * @param {string} dir Working dir. - * @return {!Promise} - */ -function gitFetch(dir) { - const ampDir = dir + '/amphtml'; - let clonePromise; - if (fs.existsSync(ampDir)) { - clonePromise = Promise.resolve(); - } else { - clonePromise = gitExec({ - cwd: dir, - args: 'clone https://github.com/ampproject/amphtml.git', - }); - } - return clonePromise.then(function() { - return gitExec({ - cwd: ampDir, - args: 'fetch --tags', - }); - }); -} - -/** - * @param {string} path - * @param {string=} opt_method - * @param {*} opt_data - * @return {!Promise<*>} - */ -function githubRequest(path, opt_method, opt_data) { - const options = { - url: 'https://api.github.com/repos/ampproject/amphtml' + path, - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, - qs: { - 'access_token': GITHUB_ACCESS_TOKEN, - }, - }; - if (opt_method) { - options.method = opt_method; - } - if (opt_data) { - options.json = true; - options.body = opt_data; - } - return request(options); -} - -/** - * @return {!Promise} - */ -function releaseTag() { - let promise = Promise.resolve(); - - const dir = 'build/tagging'; - log('Work dir: ', dir); - fs.mkdirpSync(dir); - promise = promise.then(function() { - return gitFetch(dir); - }); - - const type = argv.type || 'all'; - if (type == 'all' || type == 'canary') { - promise = promise.then(function() { - return releaseTagFor('canary', dir); - }); - } - if (type == 'all' || type == 'prod') { - promise = promise.then(function() { - return releaseTagFor('prod', dir); - }); - } - return promise; -} - -module.exports = { - releaseTag, -}; - -releaseTag.description = 'Tag the releases in pull requests'; -releaseTag.flags = { - dryrun: ' Generate update log but dont push it out', - type: ' Either of "canary", "prod" or "all". Default is "all".', -}; diff --git a/build-system/tasks/visual-diff/index.js b/build-system/tasks/visual-diff/index.js index 82e95f66e699..cf16247b4b3f 100644 --- a/build-system/tasks/visual-diff/index.js +++ b/build-system/tasks/visual-diff/index.js @@ -41,6 +41,7 @@ const { const {execOrDie, execScriptAsync} = require('../../common/exec'); const {isTravisBuild} = require('../../common/travis'); const {startServer, stopServer} = require('../serve'); +const {waitUntilUsed} = require('tcp-port-used'); // optional dependencies for local development (outside of visual diff tests) let puppeteer; @@ -53,6 +54,9 @@ const VIEWPORT_WIDTH = 1400; const VIEWPORT_HEIGHT = 100000; const HOST = 'localhost'; const PORT = 8000; +const PERCY_AGENT_PORT = 5338; +const PERCY_AGENT_RETRY_MS = 100; +const PERCY_AGENT_TIMEOUT_MS = 5000; const NAVIGATE_TIMEOUT_MS = 30000; const MAX_PARALLEL_TABS = 5; const WAIT_FOR_TABS_MS = 1000; @@ -131,11 +135,20 @@ async function launchPercyAgent() { } const env = argv.percy_agent_debug ? {LOG_LEVEL: 'debug'} : {}; - percyAgentProcess_ = execScriptAsync('npx percy start', { - cwd: __dirname, - env: Object.assign(env, process.env), - stdio: ['ignore', process.stdout, process.stderr], - }); + percyAgentProcess_ = execScriptAsync( + `npx percy start --port ${PERCY_AGENT_PORT}`, + { + cwd: __dirname, + env: Object.assign(env, process.env), + stdio: ['ignore', process.stdout, process.stderr], + } + ); + await waitUntilUsed( + PERCY_AGENT_PORT, + PERCY_AGENT_RETRY_MS, + PERCY_AGENT_TIMEOUT_MS + ); + log('info', 'Percy agent is reachable on port', PERCY_AGENT_PORT); } /** diff --git a/build-system/tasks/visual-diff/package.json b/build-system/tasks/visual-diff/package.json index 75954d9429d9..d7b23ed9a741 100644 --- a/build-system/tasks/visual-diff/package.json +++ b/build-system/tasks/visual-diff/package.json @@ -4,8 +4,8 @@ "version": "0.1.0", "description": "Gulp visual diff", "devDependencies": { - "@percy/agent": "0.20.12", - "@percy/puppeteer": "1.0.8", - "puppeteer": "2.0.0" + "@percy/agent": "0.21.0", + "@percy/puppeteer": "1.1.0", + "puppeteer": "2.1.1" } } diff --git a/build-system/tasks/visual-diff/yarn.lock b/build-system/tasks/visual-diff/yarn.lock index 785a137adc3b..5b74c96001e5 100644 --- a/build-system/tasks/visual-diff/yarn.lock +++ b/build-system/tasks/visual-diff/yarn.lock @@ -118,10 +118,10 @@ resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493" integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw== -"@percy/agent@0.20.12": - version "0.20.12" - resolved "https://registry.yarnpkg.com/@percy/agent/-/agent-0.20.12.tgz#3d470e6883e54643a7caeefbcf86f9c7d589fc0c" - integrity sha512-dTIsTXIjUFhEAxeMPrnIrt19X5jjoZoyXc7Sw4lmBII9SJRaQJeXUTPKvuXRB242mbN/d4mgSp+tx/HowVAXfA== +"@percy/agent@0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@percy/agent/-/agent-0.21.0.tgz#319b0fcbfe5a9ff86a39c6fdf692f3cc5a4faac0" + integrity sha512-gcWqIJCF+1HKN3jSY0zqf9EOc2Vwxb0VUL6ratwbMce7msGPSge60sOaWs43/gfpT5JSAec9FUKRx0wO/rEY2g== dependencies: "@oclif/command" "1.5.19" "@oclif/config" "^1" @@ -173,10 +173,10 @@ retry-axios "^1.0.1" winston "^3.0.0" -"@percy/puppeteer@1.0.8": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@percy/puppeteer/-/puppeteer-1.0.8.tgz#9ce4359f885e446bea12bfaa99e746ff38755b03" - integrity sha512-YTR5scNNANchsP4iUurXYHVaBg3XPJBMmnfgHwJD9/SnSMjPvSEr7uN4cPGKH78rWibl/js8FFggedkK5+7fQA== +"@percy/puppeteer@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@percy/puppeteer/-/puppeteer-1.1.0.tgz#3b055cd4931865f131b3b525be5971ec4a7c8012" + integrity sha512-ndQDSC4UYE0WrK+6e14xpuz4FktSG2FwiQJ7n68QVm4TcgetdeXkgcAn1xtwvDbLpEcMyzcQEPBJMOMNf7Nfdg== dependencies: "@percy/agent" "~0" @@ -194,6 +194,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/mime-types@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73" + integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM= + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -212,6 +217,11 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +agent-base@5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" + integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== + agent-base@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -624,6 +634,13 @@ debug@2.6.9: dependencies: ms "2.0.0" +debug@4, debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -638,13 +655,6 @@ debug@^3.0.0, debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - deepmerge@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.0.tgz#77a97af6746882cd1ed85d4b925be8cd4120b630" @@ -1090,6 +1100,14 @@ https-proxy-agent@^3.0.0: agent-base "^4.3.0" debug "^3.1.0" +https-proxy-agent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" + integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== + dependencies: + agent-base "5" + debug "4" + hyperlinker@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" @@ -1353,6 +1371,11 @@ mime-db@1.40.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + mime-db@~1.37.0: version "1.37.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" @@ -1365,6 +1388,13 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "~1.37.0" +mime-types@^2.1.25: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + mime-types@~2.1.24: version "2.1.24" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" @@ -1576,15 +1606,17 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -puppeteer@2.0.0, puppeteer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.0.0.tgz#0612992e29ec418e0a62c8bebe61af1a64d7ec01" - integrity sha512-t3MmTWzQxPRP71teU6l0jX47PHXlc4Z52sQv4LJQSZLq1ttkKS2yGM3gaI57uQwZkNaoGd0+HPPMELZkcyhlqA== +puppeteer@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.1.1.tgz#ccde47c2a688f131883b50f2d697bd25189da27e" + integrity sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg== dependencies: + "@types/mime-types" "^2.1.0" debug "^4.1.0" extract-zip "^1.6.6" - https-proxy-agent "^3.0.0" + https-proxy-agent "^4.0.0" mime "^2.0.3" + mime-types "^2.1.25" progress "^2.0.1" proxy-from-env "^1.0.0" rimraf "^2.6.1" @@ -1604,6 +1636,20 @@ puppeteer@^1.13.0: rimraf "^2.6.1" ws "^6.1.0" +puppeteer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.0.0.tgz#0612992e29ec418e0a62c8bebe61af1a64d7ec01" + integrity sha512-t3MmTWzQxPRP71teU6l0jX47PHXlc4Z52sQv4LJQSZLq1ttkKS2yGM3gaI57uQwZkNaoGd0+HPPMELZkcyhlqA== + dependencies: + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^3.0.0" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" diff --git a/build-system/test-configs/dep-check-config.js b/build-system/test-configs/dep-check-config.js index 3bff9692d6bd..afcab4604c9e 100644 --- a/build-system/test-configs/dep-check-config.js +++ b/build-system/test-configs/dep-check-config.js @@ -250,6 +250,11 @@ exports.rules = [ 'extensions/amp-carousel/0.2/amp-carousel.js->extensions/amp-base-carousel/0.1/child-layout-manager.js', 'extensions/amp-inline-gallery/0.1/amp-inline-gallery.js->extensions/amp-base-carousel/0.1/carousel-events.js', 'extensions/amp-inline-gallery/0.1/amp-inline-gallery-thumbnails.js->extensions/amp-base-carousel/0.1/carousel-events.js', + 'extensions/amp-stream-gallery/0.1/amp-stream-gallery.js->extensions/amp-base-carousel/0.1/action-source.js', + 'extensions/amp-stream-gallery/0.1/amp-stream-gallery.js->extensions/amp-base-carousel/0.1/carousel.js', + 'extensions/amp-stream-gallery/0.1/amp-stream-gallery.js->extensions/amp-base-carousel/0.1/carousel-events.js', + 'extensions/amp-stream-gallery/0.1/amp-stream-gallery.js->extensions/amp-base-carousel/0.1/child-layout-manager.js', + 'extensions/amp-stream-gallery/0.1/amp-stream-gallery.js->extensions/amp-base-carousel/0.1/responsive-attributes.js', // Facebook components 'extensions/amp-facebook-page/0.1/amp-facebook-page.js->extensions/amp-facebook/0.1/facebook-loader.js', diff --git a/builtins/amp-pixel.js b/builtins/amp-pixel.js index 629d3924fc8b..b31dec8bc9d2 100644 --- a/builtins/amp-pixel.js +++ b/builtins/amp-pixel.js @@ -93,6 +93,9 @@ export class AmpPixel extends BaseElement { return Services.urlReplacementsForDoc(this.element) .expandUrlAsync(this.assertSource_(src)) .then(src => { + if (!this.win) { + return; + } const pixel = createPixel(this.win, src, this.referrerPolicy_); dev().info(TAG, 'pixel triggered: ', src); return pixel; diff --git a/contributing/TESTING.md b/contributing/TESTING.md index cc64d1046f82..fb34b5334d63 100644 --- a/contributing/TESTING.md +++ b/contributing/TESTING.md @@ -48,7 +48,8 @@ Before running these commands, make sure you have Node.js, yarn, and Gulp instal | `gulp --extensions=minimal_set` | Runs "watch" and "serve", after building the extensions needed to load `article.amp.html`. | | `gulp --extensions_from=examples/foo.amp.html` | Runs "watch" and "serve", after building only extensions from the listed examples. | | `gulp --noextensions` | Runs "watch" and "serve" without building any extensions. | -| `gulp dist` | Builds production binaries. | +| `gulp dist` | Builds production binaries and applies AMP_CONFIG to runtime files. | +| `gulp dist --noconfig` | Builds production binaries without applying AMP_CONFIG to runtime files. | | `gulp dist --extensions=amp-foo,amp-bar` | Builds production binaries, with only the listed extensions. | | `gulp dist --extensions=minimal_set` | Builds production binaries, with only the extensions needed to load `article.amp.html`. | | `gulp dist --extensions_from=examples/foo.amp.html` | Builds production binaries, with only extensions from the listed examples. | diff --git a/contributing/getting-started-e2e.md b/contributing/getting-started-e2e.md index a484fb9d882b..5c8a935c3998 100644 --- a/contributing/getting-started-e2e.md +++ b/contributing/getting-started-e2e.md @@ -175,7 +175,7 @@ Now that you have all of the files copied locally you can actually build the cod An alternative to installing `yarn` is to invoke each Yarn command in this guide with `npx yarn` during local development. This will automatically use the current stable version of `yarn`. -- Closure Compiler is automatically installed by Yarn, but it requires Java 8 which you need to install separately. AMP's version of Closure Compiler won't run with newer versions of Java. Download an installer for Mac, Linux or Windows [here](http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html). +- Closure Compiler is automatically installed by Yarn, but it requires Java 8 which you need to install separately. AMP's version of Closure Compiler won't run with newer versions of Java. Download an installer for Mac, Linux or Windows [here](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html). - Note: If you are using Mac OS and have multiple versions of Java installed, make sure you are using Java 8 by adding this to `~/.bashrc`: diff --git a/css/OWNERS b/css/OWNERS index 68ea9f7bbf65..086d6b752a6d 100644 --- a/css/OWNERS +++ b/css/OWNERS @@ -10,5 +10,9 @@ {name: 'camelburrito', requestReviews: false}, ], }, + { + pattern: '{amp-story-*}.css', + owners: [{name: 'newmuis'}, {name: 'gmajoulet'}, {name: 'enriqe'}], + }, ], } diff --git a/css/amp-story-player-iframe.css b/css/amp-story-player-iframe.css new file mode 100644 index 000000000000..1a52f07f7ba2 --- /dev/null +++ b/css/amp-story-player-iframe.css @@ -0,0 +1,43 @@ +/** + * Copyright 2020 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. + */ + +:host { + all: initial; + display: block; + border-radius: 0 !important; + width: 360px; + height: 600px; + overflow: auto; +} + +.story-player-iframe { + height: 100%; + width: 100%; + flex: 0 0 100%; + border: 0; + opacity: 0; + transition: opacity 500ms ease; +} + +main { + display: flex; + flex-direction: row; + height: 100%; +} + +.i-amphtml-story-player-loaded iframe { + opacity: 1; +} diff --git a/css/amp-story-player.css b/css/amp-story-player.css new file mode 100644 index 000000000000..92b293701a45 --- /dev/null +++ b/css/amp-story-player.css @@ -0,0 +1,67 @@ +/** + * Copyright 2020 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. + */ + +amp-story-player { + width: 360px; + height: 600px; + position: relative; + display: block; +} + +amp-story-player a:first-of-type { + width: 100%; + height: 100%; + background: var(--story-player-poster, lightgrey); + background-size: 100% 100%; + display: block; +} + +amp-story-player a:not(:first-of-type) { + visibility: hidden; +} + +amp-story-player::after { + content: " "; + position: absolute; + box-sizing: border-box; + top: calc(50% - 32px); + left: calc(50% - 32px); + width: 64px; + height: 64px; + border-radius: 50%; + border: 6px solid #fff; + border-color: #fff transparent #fff transparent; + filter: drop-shadow(0px 1px 3px rgba(0, 0, 0, 0.25)); + animation-name: i-amphtml-story-player-spinner; + animation-duration: 4400ms; + animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + animation-iteration-count: infinite; +} + +@keyframes i-amphtml-story-player-spinner { + 12.5% { transform: rotate(135deg) } + 25% { transform: rotate(270deg) } + 37.5% { transform: rotate(405deg) } + 50% { transform: rotate(540deg) } + 62.5% { transform: rotate(675deg) } + 75% { transform: rotate(810deg) } + 87.5% { transform: rotate(945deg) } + to { transform: rotate(1080deg) } +} + +.i-amphtml-story-player-loaded::after { + visibility: hidden; +} diff --git a/examples/ads.amp.esm.html b/examples/ads.amp.esm.html new file mode 100644 index 000000000000..eb0e1cfdb146 --- /dev/null +++ b/examples/ads.amp.esm.html @@ -0,0 +1,2456 @@ + + + + + Ad examples + + + + + + + + + + + +
+
+ + + +
+
+ +

1WO

+ + + +

24smi

+ + + +

A8

+ + + +

A9 Search Ads

+ + + +

A9 Recom Unsaved

+ + + +

A9 Custom Ads

+ + + +

A9 Recom Ads Sync

+ + + +

A9 Recom Ads Async

+ + + + +

AccessTrade

+ + + +

Ad.Agio

+ + + +

Adblade

+ + + +

AdButler

+ + + +

adincube

+ + + +

ADITION

+ + + +

Ad Generation

+ + + +

AdGlare

+ + + +

Adhese

+ +
+
+
+ + +
+
+
+ +

AdFox

+ + + +

Adman

+ + + +

AdmanMedia

+ + + +

Admixer

+ + + +
asd
+
+ +

AdOcean

+ + + + + + +

AdOcean master-slave

+ + + + + + +

Adop

+ + +

AdPicker

+ + + +

AdPlugg

+ + + +

Adpon

+ + + +

AdReactor

+ + + +

AdSense

+ + + +

AdSensor

+ + + + + +

AdServSolutions

+ + + +

AdsLoom

+ + + +

AdsNative

+ + + +

AdSpeed

+ +
+
+
+ +

AdSpirit

+ + + +

AdStir 320x50 banner

+ + + +

AdStyle responsive widget

+ + + +

AdTech (1x1 fake image ad)

+ + + +

AdThrive 320x50 banner

+ + + +

AdUnity (300x250 demo banner)

+ + + +

AdThrive 300x250 banner

+ + + +

Ad Up Technology

+ + + +

Adventive

+ + + +

Adverline

+ + + +

Adverticum

+ + + +

AdvertServe

+ + + +

Adyoulike

+ + +

Affiliate-B

+ + + +

AJA

+ + + +

AMoAd banner

+ + + +

AMoAd native

+ + + +

Aniview

+ + + +

AppNexus with JSON based configuration multi ad

+ + + + + + + + + +

AppVador

+ + + +

Atomx

+ + + +

Baidu

+ + + +

BeaverAds

+ + + + + + +

Bidtellect

+ + + +

Blade

+ + + +

brainy

+ + + +

Broadstreet Ads

+ +
+
+
+ +

Bringhub Mini-Storefront

+ + + +

ByPlay

+ + + +

CA A.J.A. Infeed

+ + + +

CA ProFit-X

+ + + + + + + +

Cedato

+ + + +

Conative

+ + + + +

Connatix

+ + + +

Content.ad Banner 320x50

+ + + +

Content.ad Banner 300x250

+ + + +

Content.ad Banner 300x250 2x2

+ + + +

Content.ad Banner 300x600

+ + + +

Content.ad Banner 300x600 5x2

+ + + +

Criteo Passback

+

Due to ad targeting, the slot might not load ad.

+ + + + +

CSA

+ + + +

Custom leaderboard

+ + + + +

Custom square

+ + + + +

Custom leaderboard with no slot specified

+ + + + +

Cxense Display

+ + + +

Dable

+ + + +

Directadvert

+ + + +

DistroScale

+ + + +

DotAndAds masthead

+ + + +

DotAndAds 300x250 box

+ + + +

Doubleclick

+ + + +

Doubleclick with JSON based parameters

+ + + +

Doubleclick no ad

+ + + +

Doubleclick with overriden size

+ + + +

Doubleclick challenging ad

+ + + +

DynAd

+ + + +

eADV

+ + + +

Empower

+ + + +

Engageya widget

+ + + +

Epeex

+ + + +

E-Planning 320x50

+ + + +

Ezoic

+ + + +

FlexOneELEPHANT

+ + + +

FlexOneHARRIER

+ + + +

Felmat

+ + + +

Flite

+ + + +

fluct

+ + + +

Fork Media

+ + + +

Fusion

+ + + +

FreeWheel

+ + + +

Geniee SSP

+ + + +

Giraff

+ + + +

GMOSSP 320x50 banner

+ + + +

GumGum 300x100 banner

+ + + +

Holder 300x250 banner

+ + + +

iBillboard 300x250 banner

+ + + +

Idealmedia

+ + + +

I-Mobile 320x50 banner

+ + + +

Imonomy 728x90 banner

+ + + +

Imedia

+ + + + + + +

Index Exchange Header Tag

+ + + +

Industrybrains

+ + + +

InMobi

+ + + +

Innity

+ + + + + + +

Insticator

+ + + +

Invibes

+ + + +

Jubna widget

+ + + +

Kargo

+ + + +

Kiosked

+ + + +

Kixer

+ + + +

Kuadio

+ + + +

Lentainform

+ + + +

Ligatus

+ + + +

LockerDome

+ + + +

logly

+ + + +

LOKA

+ + + +

MADS

+ + + +

MANTIS

+ + + + + + +

Media.Net Header Bidder Tag

+ + + +

Media.Net Contextual Monetization Tag

+ + + +

Mediavine

+ +
+
+
+ +

Medyanet

+ + + +

Meg

+ + + +

Mgid

+ + + +

MicroAd 320x50 banner

+ + + +

MixiMedia

+ + + +

Mixpo

+ + + +

Monetizer101

+ + + +

mox

+ + + +

myTarget

+ + + +

myWidget

+ + + +

Nativeroll

+ + + +

Nativery

+ + + +

Nativo

+ + + +

Navegg

+
It is common to see a no-fill ad in this example.
+ + + +

Nend

+ + + +

NETLETIX

+ + + +

Noddus

+ + + +

Nokta

+ + + +

Newsroom AI

+ + + +

OneAD

+ + + +

OnNetwork

+ + + +

Open AdStream single ad

+ + + +

OpenX

+ + + + +

Opinary

+ + + +

Outbrain widget

+ + + +

Pixels Examples

+ + + +

Plista responsive widget

+ + + +

polymorphicAds

+ + + +

popIn native ad

+ + + +

Postquare widget

+ + + +

Pressboard

+ + + +

PromoteIQ

+ + + +

PubExchange

+ + + +

PubGuru

+ + + +

Pubmine 300x250

+ + + + + + +

PulsePoint Header Bidding 300x250

+ + + +

PulsePoint 300x250

+ + + +

Puffnetwork 300x250

+ + + +

Purch 300x250

+ + + +

Quora

+ + + +

Rambler&Co

+ + + +

Rambler&Co RNetPlus

+ + + +

RbInfox

+ + + +

ReadMo

+ + + +

Realclick

+ + + +

recomAD

+ + + +

Relap

+ + + +

Revcontent Responsive Tag

+ + + +

RevJet Tag

+ + + +

Red for Publishers

+ + + +

Rubicon Project Smart Tag

+ + + +

RUNative

+ + + +

SAS CI 360 Match

+ + + +

Seeding Alliance

+ + + +

Sekindo

+ + + +

Sharethrough

+ + + +

SHE Media

+ +
+
+
+ +

Sklik

+ + + +

SlimCut Media

+ + + +

SmartAdServer ad

+ + + +

smartclip

+ + + +

SMI2

+ + + +

SmileWanted ad

+ + + +

sogou ad

+ + + + + + +

Sortable ad

+ + + +

SOVRN

+ + + +

Speakol

+ + + +

SpotX

+ + + +

Spring Ads

+ + + + + + + +

SSP

+ + + +

Strossle

+ + + +

Sulvo

+ + +

SunMedia

+ + + +

SVK Native widget

+ + + +

Swoop

+ + + +

Taboola responsive widget

+ + + +

TcsEmotion AMP tag

+ + + +

Teads

+ + + +

TE Medya

+ + + +

Torimochi

+ + + +

Tracdelight

+ + + +

TripleLift

+ + + +

Trugaze

+ + + +

UAS

+ + + +

Unruly

+ + + +

ucfunnel

+ + +

UZOU

+ + + +

Weborama

+ + + +

Widespace Panorama Ad

+ + + +

Widespace Takeover Ad

+ + + +

Wisteria Ad

+ + + +

Xlift native ad

+ + + +

Yahoo Display

+ + + +

Yahoo Native Ads

+ + + +

YahooJP YDN

+ + + +

Yandex

+ + + +

Yengo

+ + + +

Yieldbot

+ + + +

YIELD ONE

+ + + +

Yieldmo

+ + + +

Yieldpro

+ + + +

ValueCommerce

+ + + +

VDO.AI

+ + + +

Video intelligence

+ + + +

Videonow

+ + + +

Viralize

+ + + +

VMFive

+ + + +

Webediads

+
It's a private ad network with strict ad targeting, hence very common to see a no-fill.
+ + + +

Whopa InFeed

+ + +

WP Media

+ + +

ZEDO

+ + + +

Zen

+ + + +

ZergNet

+ + + +

Zucks

+ + + + + + + diff --git a/examples/ads.amp.html b/examples/ads.amp.html index 4c4ba67869b0..057396cb327d 100644 --- a/examples/ads.amp.html +++ b/examples/ads.amp.html @@ -258,6 +258,7 @@ + @@ -276,6 +277,7 @@ + @@ -2152,6 +2154,21 @@

Teads

data-pid="42266" layout="responsive">
+ +

TE Medya

+ +

Torimochi

Webediads data-query="amptest=1"> +

Whopa InFeed

+ +

WP Media

+ @@ -57,6 +70,12 @@ } }, "target2": { + "finalUrl": "http://localhost:8000/?product2&x=CLICK_X&y=CLICK_Y&e=_elem&shouldNotBeReplaced=AMP_VERSION", + "vars": { + "_elem": {"defaultValue": "headline"} + } + }, + "target2_filtered": { "finalUrl": "http://localhost:8000/?product2&r=RANDOM", "trackingUrls": [ "http://localhost:8000/?tracking-url&clicked=_elem" @@ -92,7 +111,7 @@

amp-ad-e

product 1

-
+

product 2

<A> tags don't work well (try middle-clicking) @@ -108,5 +127,23 @@

amp-ad-e

+
+ +
product 1
+
product 2
+
+ +
diff --git a/examples/amp-consent/cmp-vendors.amp.html b/examples/amp-consent/cmp-vendors.amp.html index 0bc3a5005ff4..5d950b4d5d29 100644 --- a/examples/amp-consent/cmp-vendors.amp.html +++ b/examples/amp-consent/cmp-vendors.amp.html @@ -33,7 +33,7 @@ .filterBar form * { margin-left: 10px; - margin-right 10px; + margin-right: 10px; } .filterBar .clearLocalStorage { @@ -95,8 +95,11 @@ + + + @@ -105,7 +108,6 @@

AMP Consent CMP

-

Please open your devtools an enable the consent v2 experiment: `AMP.toggleExperiment('amp-consent-v2', true)`

Image that is blocked by both consent

@@ -137,6 +139,23 @@

Image that is NOT blocked by consent

+ + + +
+ Post Prompt UI + +
+
+ + +
+ Post Prompt UI + +
+
+ + +
+ Post Prompt UI + +
+
+ + - - - - -

This is a NON-AMP page that embeds a story below:

- - - A local’s guide to what to eat and do in New York City - - - A local’s guide to what to eat and do in Miami - - - A local’s guide to what to eat and do in Mexico City - - - A local’s guide to what to eat and do in Rome - - - - diff --git a/examples/amp-story/player.html b/examples/amp-story/player.html new file mode 100644 index 000000000000..d9a1091dcd25 --- /dev/null +++ b/examples/amp-story/player.html @@ -0,0 +1,63 @@ + + + Story Player (Non-AMP) + + + + + + +

This is a NON-AMP page that embeds a story below:

+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras viverra neque ex, sit amet varius sem maximus sed. Suspendisse potenti. Donec erat purus, sagittis sit amet tincidunt ut, maximus sit amet massa. Maecenas venenatis fringilla dui vitae vestibulum. Fusce imperdiet euismod lobortis. Nullam sagittis nunc at tristique mattis. In sodales consectetur mollis. Maecenas sollicitudin, ex vel tempor rutrum, turpis eros interdum enim, sit amet volutpat tortor dolor at ipsum. Nam posuere velit vel urna vulputate interdum. Aenean eu vulputate lorem. Praesent nec nunc sodales, egestas orci sed, hendrerit mauris. Ut blandit turpis non erat sagittis, quis fermentum odio feugiat.
+ + + A local’s guide to what to eat and do in New York City + + + A local’s guide to what to eat and do in Miami + + + A local’s guide to what to eat and do in Mexico City + + + A local’s guide to what to eat and do in Rome + + +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras viverra neque ex, sit amet varius sem maximus sed. Suspendisse potenti. Donec erat purus, sagittis sit amet tincidunt ut, maximus sit amet massa. Maecenas venenatis fringilla dui vitae vestibulum. Fusce imperdiet euismod lobortis. Nullam sagittis nunc at tristique mattis. In sodales consectetur mollis. Maecenas sollicitudin, ex vel tempor rutrum, turpis eros interdum enim, sit amet volutpat tortor dolor at ipsum. Nam posuere velit vel urna vulputate interdum. Aenean eu vulputate lorem. Praesent nec nunc sodales, egestas orci sed, hendrerit mauris. Ut blandit turpis non erat sagittis, quis fermentum odio feugiat.
+ + diff --git a/examples/amphtml-ads/visibility-ad.a4a.html b/examples/amphtml-ads/visibility-ad.a4a.html index 6f861d5b3b1f..091f626f26a4 100644 --- a/examples/amphtml-ads/visibility-ad.a4a.html +++ b/examples/amphtml-ads/visibility-ad.a4a.html @@ -34,7 +34,7 @@ diff --git a/examples/autocomplete.amp.html b/examples/autocomplete.amp.html index 37f5c44f45cd..1ce20b23600b 100644 --- a/examples/autocomplete.amp.html +++ b/examples/autocomplete.amp.html @@ -69,7 +69,6 @@

Autocomplete

Inline

-
Inline } - -
- -
-

Hello World

Select Event

min-characters = 0, partial red underline

-
Select Event > - -
- -
-

Hello World

Disable browser autofill

diff --git a/examples/bind/basic.amp.html b/examples/bind/basic.amp.html index 51bb8c98eaf7..6e435e206909 100644 --- a/examples/bind/basic.amp.html +++ b/examples/bind/basic.amp.html @@ -9,7 +9,8 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +

AMP #0

+Go to iframe +

+ Quisque ultricies id augue a convallis. Vivamus euismod est quis tellus laoreet lacinia. In quam tellus, mollis nec porta eget, volutpat sit amet nibh. Duis ac odio sem. Sed consequat, ante gravida fringilla suscipit, libero libero ullamcorper metus, nec porta est elit at est. Curabitur vel diam ligula. Nulla bibendum malesuada odio. +

+

+ + Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt. + +

+

+

+ Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt. +
+

+ +

+ Top + Image0 + Footer +

+ +

+

+ + + + + + +
+

+

+ + + + + + + + +

+

+ + + + + + + + +

+

+ + + +

+ +

Video

+ + +

Youtube

+ + + + + +

Audio

+ + +

Lightbox

+ +

+ +

+

+ +

+ +
+ + + +

Scrollable Lightbox

+ + +

+ +

+ +

+ +

+ +

+ +

+ + + + +
+ + + +

Images

+

+ Responsive (w/srcset, w/image-lightbox) +

+ + +

+ +

+ Fixed +

+ +

+ +

+ None +

+ +

+

+ Lorem ipsum dolor sit amet. +

+ + +

+ Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt. Propriae tincidunt id nec, elit nusquam te mea, ius noster platonem in. Mea an idque minim, sit sale deleniti apeirian et. Omnium legendos tractatos cu mea. Vix in stet dolorem accusamus. Iisque rationibus consetetur in cum, quo unum nulla legere ut. Simul numquam saperet no sit. +

+ +

+ Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt. Propriae tincidunt id nec, elit nusquam te mea, ius noster platonem in. Mea an idque minim, sit sale deleniti apeirian et. Omnium legendos tractatos cu mea. Vix in stet dolorem accusamus. Iisque rationibus consetetur in cum, quo unum nulla legere ut. Simul numquam saperet no sit. +

+

+ +

+

+ +

+

+ +

+

+ Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt. Propriae tincidunt id nec, elit nusquam te mea, ius noster platonem in. Mea an idque minim, sit sale deleniti apeirian et. Omnium legendos tractatos cu mea. Vix in stet dolorem accusamus. Iisque rationibus consetetur in cum, quo unum nulla legere ut. Simul numquam saperet no sit. +

+
+

Media query selection

+ + +

+

+ +

+

+ +

+

+ +

+

+ Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt. Propriae tincidunt id nec, elit nusquam te mea, ius noster platonem in. Mea an idque minim, sit sale deleniti apeirian et. Omnium legendos tractatos cu mea. Vix in stet dolorem accusamus. Iisque rationibus consetetur in cum, quo unum nulla legere ut. Simul numquam saperet no sit. +

+

+ +

+

+ +

+

+ +

+

Twitter

+ + + + + + + +

+ +

+

+ Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt. Propriae tincidunt id nec, elit nusquam te mea, ius noster platonem in. Mea an idque minim, sit sale deleniti apeirian et. Omnium legendos tractatos cu mea. Vix in stet dolorem accusamus. Iisque rationibus consetetur in cum, quo unum nulla legere ut. Simul numquam saperet no sit. +

+

Instagram

+ + +

+ Lorem ipsum dolor sit amet, has nisl nihil convenire et, vim at aeque inermis reprehendunt. Propriae tincidunt id nec, elit nusquam te mea, ius noster platonem in. Mea an idque minim, sit sale deleniti apeirian et. Omnium legendos tractatos cu mea. Vix in stet dolorem accusamus. Iisque rationibus consetetur in cum, quo unum nulla legere ut. Simul numquam saperet no sit. +

+

AddThis

+ + +

IFrame

+ + +
+ Go back +
+

+ + + +

SVG

+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + diff --git a/examples/pwa-multidoc-loader.html b/examples/pwa-multidoc-loader.html new file mode 100644 index 000000000000..ddf701cf0f37 --- /dev/null +++ b/examples/pwa-multidoc-loader.html @@ -0,0 +1,156 @@ + + + + PWA multidoc loader + + + + + + +

PWA multidoc loader

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/examples/visual-tests/amp-consent/amp-consent.amp.html b/examples/visual-tests/amp-consent/amp-consent.amp.html deleted file mode 100644 index fc3f607c25e8..000000000000 --- a/examples/visual-tests/amp-consent/amp-consent.amp.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - Hello, AMPs - - - - - - - - -

amp-consent

- -

Image that is blocked by '_till_responded' consent

- - - -

Image that is blocked by '_till_accepted' consent

- - - -

Image that is blocked by '_auto_reject' consent

- - - -

Image that is blocked by 'default' consent

- - - -

Image that is blocked by default (not specified) consent

- - - -

Image that is NOT blocked by consent

- - - - - - - - - diff --git a/examples/visual-tests/amp-consent/amp-consent.js b/examples/visual-tests/amp-consent/amp-consent.js deleted file mode 100644 index 0bae3f12255d..000000000000 --- a/examples/visual-tests/amp-consent/amp-consent.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -const {verifySelectorsVisible} = require('../../../build-system/tasks/visual-diff/helpers'); - -module.exports = { - 'accept consent': async (page, name) => { - await page.tap('#consent-ui #accept'); - - await verifySelectorsVisible(page, name, [ - 'amp-img[data-block-on-consent="_till_responded"] img', - 'amp-img[data-block-on-consent="_till_accepted"] img', - 'amp-img[data-block-on-consent="default"] img', - '#not-specified img' - ]); - }, - - 'reject consent': async (page, name) => { - await page.tap('#consent-ui #reject'); - - await verifySelectorsVisible(page, name, [ - 'amp-img[data-block-on-consent="_till_responded"] img' - ]); - }, - - 'should preserve accepted state': async (page, name) => { - // Accept the consent - await page.tap('#consent-ui #accept'); - - // Refresh the page - await page.reload(); - - await verifySelectorsVisible(page, name, [ - 'amp-img[data-block-on-consent="_till_responded"] img', - 'amp-img[data-block-on-consent="_till_accepted"] img', - 'amp-img[data-block-on-consent="default"] img', - '#not-specified img' - ]); - } -}; diff --git a/extensions/amp-a4a/0.1/a4a-variable-source.js b/extensions/amp-a4a/0.1/a4a-variable-source.js index a60819452cb4..c086e738b0de 100644 --- a/extensions/amp-a4a/0.1/a4a-variable-source.js +++ b/extensions/amp-a4a/0.1/a4a-variable-source.js @@ -39,9 +39,6 @@ const WHITELISTED_VARIABLES = [ 'COUNTER', 'DOCUMENT_CHARSET', 'DOCUMENT_REFERRER', - 'FIRST_CONTENTFUL_PAINT', - 'FIRST_VIEWPORT_READY', - 'MAKE_BODY_VISIBLE', 'PAGE_VIEW_ID', 'RANDOM', 'SCREEN_COLOR_DEPTH', diff --git a/extensions/amp-a4a/0.1/amp-a4a.js b/extensions/amp-a4a/0.1/amp-a4a.js index 034f32009fdc..710d3bfe9d87 100644 --- a/extensions/amp-a4a/0.1/amp-a4a.js +++ b/extensions/amp-a4a/0.1/amp-a4a.js @@ -561,9 +561,14 @@ export class AmpA4A extends AMP.BaseElement { */ shouldInitializePromiseChain_() { const slotRect = this.getIntersectionElementLayoutBox(); - if ( + const fixedSizeZeroHeightOrWidth = this.getLayout() != Layout.FLUID && - (slotRect.height == 0 || slotRect.width == 0) + (slotRect.height == 0 || slotRect.width == 0); + if ( + fixedSizeZeroHeightOrWidth || + this.element.hasAttribute('hidden') || + // TODO(levitzky): May need additional checks for other display:hidden cases. + this.element.classList.contains('i-amphtml-hidden-by-media-query') ) { dev().fine( TAG, 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 a7d2f86a8ee0..465e73c190f7 100644 --- a/extensions/amp-a4a/0.1/real-time-config-manager.js +++ b/extensions/amp-a4a/0.1/real-time-config-manager.js @@ -148,8 +148,8 @@ export class RealTimeConfigManager { * Converts a URL into its corresponding shortened callout string. * We also truncate to a maximum length of 50 characters. * For instance, if we are passed - * "https://example.com/example.php?foo=a&bar=b, then we return - * example.com/example.php + * "https://example.test/example.php?foo=a&bar=b, then we return + * example.test/example.php * @param {string} url * @return {string} */ 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 120165234c9f..57f2d2e93e95 100644 --- a/extensions/amp-a4a/0.1/test/test-amp-a4a.js +++ b/extensions/amp-a4a/0.1/test/test-amp-a4a.js @@ -1400,6 +1400,48 @@ describe('amp-a4a', () => { a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.ok; }); + it('does not initialize promise chain when hidden by media query', async () => { + const fixture = await createIframePromise(); + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + const {doc} = fixture; + const rect = layoutRectLtwh(0, 0, 200, 200); + const a4aElement = createA4aElement(doc, rect); + a4aElement.classList.add('i-amphtml-hidden-by-media-query'); + const a4a = new MockA4AImpl(a4aElement); + a4a.buildCallback(); + a4a.onLayoutMeasure(); + expect(a4a.adPromise_).to.not.be.ok; + // test without media query + a4aElement.classList.remove('i-amphtml-hidden-by-media-query'); + a4a.onLayoutMeasure(); + expect(a4a.adPromise_).to.be.ok; + }); + it('does not initialize promise chain when has attribute "hidden"', async () => { + const fixture = await createIframePromise(); + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + const {doc} = fixture; + const rect = layoutRectLtwh(0, 0, 200, 200); + const a4aElement = createA4aElement(doc, rect); + a4aElement.setAttribute('hidden', ''); + const a4a = new MockA4AImpl(a4aElement); + a4a.buildCallback(); + a4a.onLayoutMeasure(); + expect(a4a.adPromise_).to.not.be.ok; + // test without media query + a4aElement.removeAttribute('hidden'); + a4a.onLayoutMeasure(); + expect(a4a.adPromise_).to.be.ok; + }); /** * @param {boolean} isValidCreative 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 a55619d5716b..492c17ceb651 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 @@ -108,18 +108,18 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { describe('#getCalloutParam_', () => { it('should convert url to callout param when parseable', () => { - const url = 'https://www.example.com/endpoint.php?unincluded'; + const url = 'https://www.example.test/endpoint.php?unincluded'; const ard = getCalloutParam_(url); - expect(ard).to.equal('www.example.com/endpoint.php'); + expect(ard).to.equal('www.example.test/endpoint.php'); }); it('should convert & trunc url when parseable', () => { const url = - 'https://www.example.com/thisIsTooMany' + + 'https://www.example.test/thisIsTooMany' + 'Characters1234567891011121314.php'; const ard = getCalloutParam_(url); expect(ard).to.equal( - 'www.example.com/thisIsTooManyCharacters12345678910' + 'www.example.test/thisIsTooManyCharacters1234567891' ); }); }); @@ -976,7 +976,7 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { imageStub = env.sandbox.stub(env.win, 'Image').returns(imageMock); errorType = RTC_ERROR_ENUM.TIMEOUT; - errorReportingUrl = 'https://www.example.com?e=ERROR_TYPE&h=HREF'; + errorReportingUrl = 'https://www.example.test?e=ERROR_TYPE&h=HREF'; const whitelist = {ERROR_TYPE: true, HREF: true}; const macros = { ERROR_TYPE: errorType, diff --git a/extensions/amp-a4a/amp-a4a-format.md b/extensions/amp-a4a/amp-a4a-format.md index 0f5857ea8ee2..db28197ebcc7 100644 --- a/extensions/amp-a4a/amp-a4a-format.md +++ b/extensions/amp-a4a/amp-a4a-format.md @@ -262,6 +262,7 @@ AMPHTML ad creative. Extensions or builtin tags not explicitly listed are prohib - [amp-mustache](https://amp.dev/documentation/components/amp-mustache) - [amp-pixel](https://amp.dev/documentation/components/amp-pixel) - [amp-position-observer](https://amp.dev/documentation/components/amp-position-observer) +- [amp-selector](https://amp.dev/documentation/components/amp-selector) - [amp-social-share](https://amp.dev/documentation/components/amp-social-share) - [amp-video](https://amp.dev/documentation/components/amp-video) diff --git a/extensions/amp-access-poool/0.1/poool-impl.js b/extensions/amp-access-poool/0.1/poool-impl.js index b38a954a8ff8..518c5bb5ba6e 100644 --- a/extensions/amp-access-poool/0.1/poool-impl.js +++ b/extensions/amp-access-poool/0.1/poool-impl.js @@ -64,8 +64,8 @@ export class PooolVendor { /** @private {!../../amp-access/0.1/amp-access-source.AccessSource} */ this.accessSource_ = accessSource; - /** @const @private {!../../../src/service/resources-interface.ResourcesInterface} */ - this.resources_ = Services.resourcesForDoc(this.ampdoc); + /** @const @private {!../../../src/service/mutator-interface.MutatorInterface} */ + this.mutator_ = Services.mutatorForDoc(this.ampdoc); /** @private {string} */ this.accessUrl_ = ACCESS_CONFIG['authorization']; @@ -216,7 +216,7 @@ export class PooolVendor { .querySelector('[poool-access-preview]'); if (articlePreview) { - this.resources_.mutateElement(articlePreview, () => { + this.mutator_.mutateElement(articlePreview, () => { articlePreview.setAttribute('amp-access-hide', ''); }); } @@ -226,7 +226,7 @@ export class PooolVendor { .querySelector('[poool-access-content]'); if (articleContent) { - this.resources_.mutateElement(articleContent, () => { + this.mutator_.mutateElement(articleContent, () => { articleContent.removeAttribute('amp-access-hide'); }); } diff --git a/extensions/amp-access-scroll/0.1/amp-access-scroll.css b/extensions/amp-access-scroll/0.1/amp-access-scroll.css index 4ac9a9d236e4..eaf92926e443 100644 --- a/extensions/amp-access-scroll/0.1/amp-access-scroll.css +++ b/extensions/amp-access-scroll/0.1/amp-access-scroll.css @@ -24,18 +24,9 @@ bottom: 0; } -.amp-access-scroll-placeholder { - padding-top: 5px; - padding-left: 16px; - background-color: #fff; - border-top: 1px solid #eee; - border-bottom: 1px solid #eee; - box-sizing: border-box; -} - .amp-access-scroll-audio { position: fixed; - background-color: #fff; + background-color: transparent; position: fixed; z-index: 2147483647; display: block; @@ -45,8 +36,6 @@ height: 100px; left: 0; width: 100%; - border-top: 1px solid #e1e6e5; /* gray2 */ - border-bottom: 1px solid transparent; } @media (min-width: 600px) { .amp-access-scroll-audio { @@ -55,6 +44,5 @@ left: auto; right: 16px; width: 475px; - border: 1px solid #e1e6e5; /* gray2 */ } } diff --git a/extensions/amp-access-scroll/0.1/scroll-bar.js b/extensions/amp-access-scroll/0.1/scroll-bar.js index b9eb24588555..d2ecf7acf83e 100644 --- a/extensions/amp-access-scroll/0.1/scroll-bar.js +++ b/extensions/amp-access-scroll/0.1/scroll-bar.js @@ -88,34 +88,12 @@ class Bar extends ScrollComponent { export class ScrollUserBar extends Bar { /** - * Add a scrollbar placeholder and then load the scrollbar URL in the iframe. + * Load the scrollbar URL in the iframe. * * @override * */ makeIframe_() { const frame = Bar.prototype.makeIframe_.call(this); - // Add a placeholder element to display while scrollbar iframe loads. - const placeholder = this.el( - 'div', - dict({ - 'class': 'amp-access-scroll-bar amp-access-scroll-placeholder', - }), - [ - this.el( - 'img', - dict({ - 'src': - 'https://static.scroll.com/assets/icn-scroll-logo32-9f4ceef399905139bbd26b87bfe94542.svg', - 'layout': 'fixed', - 'width': 32, - 'height': 32, - }) - ), - ] - ); - - this.doc_.getBody().appendChild(placeholder); - // Set iframe to scrollbar URL. this.accessSource_ .buildUrl( @@ -127,10 +105,6 @@ export class ScrollUserBar extends Bar { false ) .then(scrollbarUrl => { - frame.onload = () => { - // On iframe load, remove placeholder element. - this.doc_.getBody().removeChild(placeholder); - }; frame.setAttribute('src', scrollbarUrl); }); return frame; 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 f940511c45bf..1a54c5bf4669 100644 --- a/extensions/amp-access/0.1/amp-access-server-jwt.js +++ b/extensions/amp-access/0.1/amp-access-server-jwt.js @@ -98,14 +98,8 @@ 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"]'); - /** @private @const {?string} */ - this.serverState_ = stateElement - ? stateElement.getAttribute('content') - : null; + this.serverState_ = ampdoc.getMetaByName('i-amphtml-access-state'); const isInExperiment = isExperimentOn(ampdoc.win, 'amp-access-server-jwt'); diff --git a/extensions/amp-access/0.1/amp-access-server.js b/extensions/amp-access/0.1/amp-access-server.js index 805ce20bb8d0..a89499989ec3 100644 --- a/extensions/amp-access/0.1/amp-access-server.js +++ b/extensions/amp-access/0.1/amp-access-server.js @@ -81,14 +81,8 @@ 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"]'); - /** @private @const {?string} */ - this.serverState_ = stateElement - ? stateElement.getAttribute('content') - : null; + this.serverState_ = ampdoc.getMetaByName('i-amphtml-access-state'); const isInExperiment = isExperimentOn(ampdoc.win, 'amp-access-server'); diff --git a/extensions/amp-accordion/amp-accordion.md b/extensions/amp-accordion/amp-accordion.md index fa489664dd04..be092f52589e 100644 --- a/extensions/amp-accordion/amp-accordion.md +++ b/extensions/amp-accordion/amp-accordion.md @@ -5,7 +5,7 @@ formats: - ads - email teaser: - text: Provides a way for viewers to have a glance at the outline of the content and jump to a section of their choice at will. + text: A stacked list of headers that collapse or expand content sections with user interaction. --- [filter formats="email"] -- An `amp-accordion` can contain one or more `
` elements as its direct children. +- An `amp-accordion` accepts one or more `
` elements as its direct children. - Each `
` must contain exactly two direct children. -- The first child (of the section) represents the heading for the section and must be a heading element (one of `h1`, `h2`, ..., `h6`, `header`). -- The second child (of the section) can be any tag allowed in AMP HTML and represents the content of the section. -- Clicking/tapping on the heading of a section expands or collapses the section. + - The first child in a `
` is the heading for that section of the `amp-accordion`. It must be a heading element: [`

-

`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/heading_elements) or [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header). + - The second child in a `
` is the expandable/collapsable content. It can be any tag allowed in [AMP for Email](../../spec/email/amp-email-html.md). +- Clicking/tapping on a `
` heading expands or collapses the section. [/filter] -#### Example: Displaying an accordion +### Example -In this example, we display three sections, where the third section is expanded on page load. Also, we opted out of preserving the collapsed/expanded state by setting `disable-session-states`. +The example below contains an `amp-accordion` with three sections. The `expanded` attribute on the third section expands it on page load. [filter formats="websites, ads"]Including the `disable-session-state` attribute preserves the collapsed/expanded state.[/filter] [example preview="inline" playground="true" imports="amp-accordion"] ```html - +

Section 1

Content in section 1.

@@ -79,82 +79,222 @@ In this example, we display three sections, where the third section is expanded [/example] -{% call callout('Tip', type='success') %} -To see more demos of the `amp-accordion`, visit [AMP By Example](https://amp.dev/documentation/examples/components/amp-accordion/). -{% endcall %} +## Attributes -### Events +### `animate` -The events below will be triggered on `section`s of `accordion`. +Include the `animate` attribute on `` to add a slight "roll down" animation on expansion, and "roll up" animation on collapse. - - - - - - - - - -
expandThis event is triggered on the target section that changes from collapsed state to expanded state. Notice that calling expand on an already expanded section would not trigger this event.
collapseThis event is triggered on the target section that changes from expanded state to collapsed state. Notice that calling collapse on an already collapsed section would not trigger this event.
+[example preview="inline" playground="true" imports="amp-accordion"] + +```html + +
+

Section 1

+

Content in section 1.

+
+
+

Section 2

+
Content in section 2.
+
+
+

Section 3

+ +
+
+``` + +[/example] [filter formats="websites, stories"] -### Actions - - - - - - - - - - - - - - -
toggleThis action toggles between the expanded and collapsed states of the amp-accordion. When called with no arguments, it will toggle all sections of the accordion. A single section may be specified with the section argument and the corresponding id as the value.
expandThis action expands an amp-accordion. If it is already expanded, it will stay so. When called with no arguments, it will expand all sections of the accordion. A single section may be specified with the section argument and the corresponding id as the value.
collapseThis action collapses an amp-accordion. If it is already collapsed, it will stay so. When called with no arguments, it will collapse all sections of the accordion. A single section may be specified with the section argument and the corresponding id as the value.
+### `disable-session-states` + +Include the `disable-session-states` attribute on `` to disable collapsed/expanded state preservation. [/filter] -#### Attributes +### `expanded` + +Include the `expanded` attribute on one or more nested `
` to display those section as expanded on page load. -##### `animate` +### `expand-single-section` + +Include the `expand-single-section` attribute to enforce one `
` expansion at a time. If the user clicks/taps on an unopened `
`, any currently expanded `
` will collapse. + +[example preview="inline" playground="true" imports="amp-accordion"] + +```html + +
+

Section 1

+

Content in section 1.

+
+
+

Section 2

+
Content in section 2.
+
+
+

Section 3

+ +
+
+``` + +[/example] + +### `[data-expand]` + +Bind the `[data-expand]` attribute on a `
` to expand or collapse the section. An expanded section will collapse if the expression evaluates to false. A collapsed section will expand if the expression evaluates to anything that is not false. + +[example preview="inline" playground="true" imports="amp-accordion, amp-bind"] + +```html + +
+

Section 1

+

Bunch of awesome content

+
+
+

Section 2

+
Bunch of awesome content
+
+
+

Section 3

+
Bunch of awesome content
+
+
+ + +``` -Set this attribute on the `` to animate the expansion / collapse of all accordion sections. +[/example] [filter formats="websites, stories"] -##### `disable-session-states` +## Actions + +### `toggle` + +The `toggle` action switches the `expanded` and `collapsed` states of the `amp-accordion` sections. When called with no arguments, it will toggle all sections of the accordion. Specify a specific section by adding the `section` argument and the corresponding `id` as the value. -Set this attribute on the `` to opt out of preserving the collapsed/expanded state of the accordion. +[example preview="inline" playground="true" imports="amp-accordion"] + +```html + +
+

Section 1

+

Bunch of awesome content

+
+
+

Section 2

+
Bunch of awesome content
+
+
+

Section 3

+
Bunch of awesome content
+
+
+ + +``` + +[/example] + +### `expand` + +The `expand` action expands the sections of the `amp-accordion`. If a section is already expanded, it will stay expanded. When called with no arguments, it will expand all sections of the accordion. Specify a section by adding the `section` argument and the corresponding `id` as the value. + +```html + + +``` + +### `collapse` + +The `collapse` action collapses the sections of the `amp-accordion`. If a section is already collapsed, it will stay collapsed. When called with no argument, it will collapse all section of the accordion. Specify a section by adding the `section` argument and the corresponding `id` as the value. + +```html + + +``` [/filter] -##### `expanded` +## Events + +The `amp-accordion` events below trigger on accordion sections when clicked. + +[example preview="inline" playground="true" imports="amp-accordion"] + +```html + +
+

Section 1

+

Opening me will open Section 2

+
+
+

Section 2

+
Closing me will close Section 1
+
+
+

Section 3

+
Bunch of awesome content
+
+
+``` -Set this attribute on a `
` to display the section as expanded on page load. +[/example] -##### `expand-single-section` +### `expand` -Set this attribute on the `` to only allow one `
` to be expanded at a time. If the user focuses on one `
` any other previously expanded `
` will be collapsed. +The `expand` event triggers on the target `amp-accordion` section that changes from the collapsed state to the expanded state. Calling `expand` on an already expanded section will not trigger the `expand` event. -##### `[data-expand]` +### `collapse` -Bind this attribute on a `
` to expand or collapse the section. An expression that evaluates to `false` will collapse the section if it is expanded, and anything else will expand the section if it is collapsed. +The `collapse` event triggers on the target `amp-accordion` section that changes from the expanded state to the collapsed state. Calling `collapse` on an already collapsed section will not trigger the event. ## Styling -- You may use the `amp-accordion` element selector to style it freely. +Use the `amp-accordion` element selector to style an `amp-accordion`. + +```css +amp-accordion { + background-color: green; +} +``` + - `amp-accordion` elements are always `display: block`. -- The `
`, heading, and content elements cannot be float-able. -- When the section is expanded, the `
` element has an `expanded` attribute. +- [`float`](https://www.w3schools.com/cssref/pr_class_float.asp) cannot style the `
`, heading, and content elements. +- An expanded section applies the `expanded` attribute to the `
` element. - The content element is clear-fixed with `overflow: hidden` and hence cannot have scrollbars. - Margins of the ``, `
`, heading, and content elements are set to 0 and can be overridden in custom styles. - Both the header and content elements are `position: relative`. +## Accessibility + +`amp-accordion` automatically adds the following [ARIA attributes](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA): + +- [`aria-controls`](https://www.w3.org/TR/wai-aria-1.1/#aria-controls): applied to the header element of each `amp-accordion` section. +- [`aria-expanded` (state)](https://www.w3.org/TR/wai-aria-1.1/#aria-expanded): applied to the header element of each `amp-accordion` section. + ## Validation See [amp-accordion rules](https://github.com/ampproject/amphtml/blob/master/extensions/amp-accordion/validator-amp-accordion.protoascii) in the AMP validator specification. diff --git a/extensions/amp-ad-exit/0.1/amp-ad-exit.js b/extensions/amp-ad-exit/0.1/amp-ad-exit.js index 933c73065511..688e7e008b30 100644 --- a/extensions/amp-ad-exit/0.1/amp-ad-exit.js +++ b/extensions/amp-ad-exit/0.1/amp-ad-exit.js @@ -14,6 +14,7 @@ * limitations under the License. */ +import {ActionTrust} from '../../../src/action-constants'; import {FilterType} from './filters/filter'; import {HostServices} from '../../../src/inabox/host-services'; import { @@ -58,6 +59,12 @@ export class AmpAdExit extends AMP.BaseElement { */ this.targets_ = {}; + /** + * Maps variable target name to an actual target name. + * @private @const {!Object} + */ + this.variableTargets_ = {}; + /** * Filters to apply to every target. * @private @const {!Array} @@ -73,6 +80,11 @@ export class AmpAdExit extends AMP.BaseElement { this.userFilters_ = {}; this.registerAction('exit', this.exit.bind(this)); + this.registerAction( + 'setVariable', + this.setVariable.bind(this), + ActionTrust.LOW + ); /** @private @const {!Object>} */ this.vendorResponses_ = {}; @@ -93,8 +105,26 @@ export class AmpAdExit extends AMP.BaseElement { exit(invocation) { const {args} = invocation; let {event} = invocation; - const target = this.targets_[args['target']]; - userAssert(target, `Exit target not found: '${args['target']}'`); + userAssert( + 'variable' in args != 'target' in args, + `One and only one of 'target' and 'variable' must be specified` + ); + let targetName; + if ('variable' in args) { + targetName = this.variableTargets_[args['variable']]; + if (!targetName) { + targetName = args['default']; + } + userAssert( + targetName, + `Variable target not found, variable:'${args['variable']}', default:'${args['default']}'` + ); + delete args['default']; + } else { + targetName = args['target']; + } + const target = this.targets_[targetName]; + userAssert(target, `Exit target not found: '${targetName}'`); userAssert(event, 'Unexpected null event'); event = /** @type {!../../../src/service/action-impl.ActionEventDef} */ (event); @@ -137,6 +167,16 @@ export class AmpAdExit extends AMP.BaseElement { } } + /** + * @param {!../../../src/service/action-impl.ActionInvocation} invocation + */ + setVariable(invocation) { + const {args} = invocation; + const pointToTarget = this.targets_[args['target']]; + userAssert(pointToTarget, `Exit target not found: '${args['target']}'`); + this.variableTargets_[args['name']] = args['target']; + } + /** * @param {!Object} args * @param {!../../../src/service/action-impl.ActionEventDef} event diff --git a/extensions/amp-ad-exit/0.1/test-e2e/test-amp-ad-exit.js b/extensions/amp-ad-exit/0.1/test-e2e/test-amp-ad-exit.js index 5c9800df544e..df5bdd32c17d 100644 --- a/extensions/amp-ad-exit/0.1/test-e2e/test-amp-ad-exit.js +++ b/extensions/amp-ad-exit/0.1/test-e2e/test-amp-ad-exit.js @@ -96,5 +96,38 @@ describes.endtoend( 'http://localhost:8000/amp4test/request-bank/e2e/deposit/tracking' ).to.have.sentCount(1); }); + + it('variable target "current" should point to product1 by default', async () => { + const headline = await controller.findElement('h1'); + await setTime(Number.MAX_VALUE); + await controller.click(headline); + + const windows = await controller.getAllWindows(); + await expect(windows.length).to.equal(2); + + await controller.switchToWindow(windows[1]); + await expect(await controller.getCurrentUrl()).to.match( + /^http:\/\/localhost:8000\/\?product1&x=\d+&y=\d+&e=headline&shouldNotBeReplaced=AMP_VERSION$/ + ); + }); + + it('should open product2 after setting varible target', async () => { + const headline = await controller.findElement('h1'); + const nextButton = await controller.findElement('#next-btn'); + await setTime(Number.MAX_VALUE); + await controller.click(nextButton); + await controller.click(headline); + + const windows = await controller.getAllWindows(); + await expect(windows.length).to.equal(2); + + await controller.switchToWindow(windows[1]); + await expect(await controller.getCurrentUrl()).to.match( + /^http:\/\/localhost:8000\/\?product2&r=0\.\d+$/ + ); + await expect( + 'http://localhost:8000/amp4test/request-bank/e2e/deposit/tracking' + ).to.have.sentCount(1); + }); } ); diff --git a/extensions/amp-ad-exit/0.1/test/test-amp-ad-exit.js b/extensions/amp-ad-exit/0.1/test/test-amp-ad-exit.js index a6ef52807467..9cd8ca63546f 100644 --- a/extensions/amp-ad-exit/0.1/test/test-amp-ad-exit.js +++ b/extensions/amp-ad-exit/0.1/test/test-amp-ad-exit.js @@ -175,6 +175,23 @@ describes.realWin( win.document.body.appendChild(adDiv); } + function pointTo(target) { + element.implementation_.executeAction({ + method: 'setVariable', + args: {name: 'indirect', target}, + satisfiesTrust: () => true, + }); + } + + function exitIndirect() { + element.implementation_.executeAction({ + method: 'exit', + args: {variable: 'indirect', default: 'simple'}, + event: makeClickEvent(1001), + satisfiesTrust: () => true, + }); + } + beforeEach(() => { clock = env.sandbox.useFakeTimers(); win = env.win; @@ -757,5 +774,145 @@ describes.realWin( '12345' ); }); + + it('should exit to the default target if varible target is never set', () => { + const open = env.sandbox.stub(win, 'open').callsFake(() => { + return {name: 'fakeWin'}; + }); + exitIndirect(); + expect(open).to.have.been.calledOnce; + expect(open).to.have.been.calledWith( + EXIT_CONFIG.targets.simple.finalUrl, + '_blank' + ); + }); + + it('should cause error when variable target is never set and default value is not provided', () => { + try { + allowConsoleError(() => { + element.implementation_.executeAction({ + method: 'exit', + args: { + variable: 'indirect', + }, + event: makeClickEvent(1001), + satisfiesTrust: () => true, + }); + }); + } catch (expected) { + return; + } + expect.fail(); + }); + + it('should cause error when variable target was pointed to an invalid target', () => { + try { + allowConsoleError(() => { + pointTo('not-a-real-target'); + }); + } catch (expected) { + return; + } + expect.fail(); + }); + + it('should cause error when exiting to an invalid variable target', () => { + try { + allowConsoleError(() => { + element.implementation_.executeAction({ + method: 'exit', + args: {variable: 'not-a-real-target', default: 'not-a-real-target'}, + event: makeClickEvent(1001), + satisfiesTrust: () => true, + }); + }); + } catch (expected) { + return; + } + expect.fail(); + }); + + it('should cause error when neither "target" nor "variable" is provided in arguments', () => { + try { + allowConsoleError(() => { + element.implementation_.executeAction({ + method: 'exit', + args: {}, + event: makeClickEvent(1001), + satisfiesTrust: () => true, + }); + }); + } catch (expected) { + return; + } + expect.fail(); + }); + + it('should cause error when both "target" and "variable" are provided in arguments', () => { + try { + allowConsoleError(() => { + element.implementation_.executeAction({ + method: 'exit', + args: { + target: 'customVars', + variable: 'indirect', + default: 'simple', + }, + event: makeClickEvent(1001), + satisfiesTrust: () => true, + }); + }); + } catch (expected) { + return; + } + expect.fail(); + }); + + it('should exit to the pointed-to target and work with custom URL variables', () => { + const open = env.sandbox.stub(win, 'open').callsFake(() => { + return {name: 'fakeWin'}; + }); + if (!win.navigator) { + win.navigator = {sendBeacon: () => false}; + } + const sendBeacon = env.sandbox + .stub(win.navigator, 'sendBeacon') + .callsFake(() => true); + + pointTo('clickTargetTest'); + exitIndirect(); + expect(open).to.have.been.calledOnce; + expect(open).to.have.been.calledWith( + EXIT_CONFIG.targets.clickTargetTest.finalUrl, + '_top' + ); + + pointTo('customVars'); + element.implementation_.executeAction({ + method: 'exit', + args: { + variable: 'indirect', + _foo: 'foo', + _bar: 'bar', + _numVar: 0, + _boolVar: false, + }, + event: makeClickEvent(1001, 101, 102), + satisfiesTrust: () => true, + }); + expect(open).to.have.been.calledTwice; + expect(open).to.have.been.calledWith( + 'http://localhost:8000/vars?foo=foo', + '_blank' + ); + expect(sendBeacon).to.have.been.calledWith( + 'http://localhost:8000/tracking?bar=bar', + '' + ); + expect(sendBeacon).to.have.been.calledWith( + 'http://localhost:8000/tracking?numVar=0&boolVar=false', + '' + ); + }); } ); diff --git a/extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js b/extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js index eeec543efc62..1bcb9376f9d5 100644 --- a/extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js +++ b/extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js @@ -24,7 +24,6 @@ import {EXPERIMENT_INFO_MAP as AMPDOC_FIE_EXPERIMENT_INFO_MAP} from '../../../sr import {AdsenseSharedState} from './adsense-shared-state'; import {AmpA4A} from '../../amp-a4a/0.1/amp-a4a'; import {CONSENT_POLICY_STATE} from '../../../src/consent-state'; -import {FIE_CSS_CLEANUP_EXP} from '../../../src/friendly-iframe-embed'; import {Navigation} from '../../../src/service/navigation'; import { QQID_HEADER, @@ -222,13 +221,6 @@ export class AmpAdNetworkAdsenseImpl extends AmpA4A { Number(this.element.getAttribute('height')) > 0, branches: ['21062003', '21062004'], }, - [[FIE_CSS_CLEANUP_EXP.branch]]: { - isTrafficEligible: () => true, - branches: [ - [FIE_CSS_CLEANUP_EXP.control], - [FIE_CSS_CLEANUP_EXP.experiment], - ], - }, ...AMPDOC_FIE_EXPERIMENT_INFO_MAP, }); const setExps = randomlySelectUnsetExperiments(this.win, experimentInfoMap); 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 1240d8770476..82f5bf9ce961 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 @@ -50,7 +50,6 @@ import { } from '../../../ads/google/a4a/utils'; import {CONSENT_POLICY_STATE} from '../../../src/consent-state'; import {Deferred} from '../../../src/utils/promise'; -import {FIE_CSS_CLEANUP_EXP} from '../../../src/friendly-iframe-embed'; import { FlexibleAdSlotDataTypeDef, getFlexibleAdSlotData, @@ -400,13 +399,6 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { key => DOUBLECLICK_SRA_EXP_BRANCHES[key] ), }, - [[FIE_CSS_CLEANUP_EXP.branch]]: { - isTrafficEligible: () => true, - branches: [ - [FIE_CSS_CLEANUP_EXP.control], - [FIE_CSS_CLEANUP_EXP.experiment], - ], - }, [ZINDEX_EXP]: { isTrafficEligible: () => true, branches: Object.values(ZINDEX_EXP_BRANCHES), @@ -755,6 +747,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { }; return { PAGEVIEWID: () => Services.documentInfoForDoc(this.element).pageViewId, + PAGEVIEWID_64: () => + Services.documentInfoForDoc(this.element).pageViewId64, HREF: () => this.win.location.href, REFERRER: opt_timeout => this.getReferrer_(opt_timeout), TGT: () => @@ -775,6 +769,12 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { return this.element.getAttribute(name); } }, + ELEMENT_POS: () => this.element.getPageLayoutBox().top, + SCROLL_TOP: () => + Services.viewportForDoc(this.getAmpDoc()).getScrollTop(), + PAGE_HEIGHT: () => + Services.viewportForDoc(this.getAmpDoc()).getScrollHeight(), + BKG_STATE: () => (this.getAmpDoc().isVisible() ? 'visible' : 'hidden'), CANONICAL_URL: () => Services.documentInfoForDoc(this.element).canonicalUrl, }; 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 45dfbb7a503c..a40d9d0d7299 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 @@ -412,11 +412,23 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { env.win.document, env.win ); + const docViewport = Services.viewportForDoc(this.getAmpDoc()); impl.populateAdUrlState(); const customMacros = impl.getCustomRealTimeConfigMacros_(); expect(customMacros.PAGEVIEWID()).to.equal(docInfo.pageViewId); + expect(customMacros.PAGEVIEWID_64()).to.equal(docInfo.pageViewId64); expect(customMacros.HREF()).to.equal(env.win.location.href); expect(customMacros.TGT()).to.equal(JSON.stringify(json['targeting'])); + expect(customMacros.ELEMENT_POS()).to.equal( + element.getBoundingClientRect().top + scrollY + ); + expect(customMacros.SCROLL_TOP()).to.equal(docViewport.getScrollTop()); + expect(customMacros.PAGE_HEIGHT()).to.equal( + docViewport.getScrollHeight() + ); + expect(customMacros.BKG_STATE()).to.equal( + this.getAmpDoc().isVisible() ? 'visible' : 'hidden' + ); Object.keys(macros).forEach(macro => { expect(customMacros.ATTR(macro)).to.equal(macros[macro]); }); diff --git a/extensions/amp-ad-network-doubleclick-impl/doubleclick-rtc.md b/extensions/amp-ad-network-doubleclick-impl/doubleclick-rtc.md index 13a26808afaf..ae5183ebab9e 100644 --- a/extensions/amp-ad-network-doubleclick-impl/doubleclick-rtc.md +++ b/extensions/amp-ad-network-doubleclick-impl/doubleclick-rtc.md @@ -19,6 +19,7 @@ For instructions on how to set the rtc-config attribute on the amp-ad, refer to Google Ad Manager's RTC implementation has made many macros available for RTC url expansion. Please note that the time to expand the URL is counted against the RTC timeout. Additionally, note that all RTC URLs are truncated at 16384 characters, so keep possible truncation in mind when determining which macros to include, and which order to include them in your URL. Currently available macros are as follows: - **PAGEVIEWID** - pageViewId +- **PAGEVIEWID_64** - pageViewId64 - **HREF** - equivalent to window.context.location.href - **ATTR(height)** - Height attribute of the amp-ad element - **ATTR(width)** - Width attribute of the amp-ad element @@ -28,9 +29,14 @@ Google Ad Manager's RTC implementation has made many macros available for RTC ur - **ATTR(data-override-width)** - data-override-width attribute of the amp-ad element - **ATTR(data-override-height)** - data-override-height attribute of the amp-ad element - **ATTR(data-json)** - data-json attribute of the amp-ad element +- **ELEMENT_POS** - Offset of the element from document's top +- **SCROLL_TOP** - Number of pixels that the user scrolled from the document's top +- **PAGE_HEIGHT** - Height of the amp-doc +- **BKG_STATE** - Current visibility state of the amp-doc - **ADCID** - adClientId - **TGT** - Just the targeting piece of data-json -- **CANONICAL_URL** - The canonical URL of the page. +- **CANONICAL_URL** - The canonical URL of the page + - **TIMEOUT** - The publisher-specified timeout for the RTC callout. ## Response and Endpoint Specification diff --git a/extensions/amp-ad-network-fake-impl/0.1/test/test-amp-ad-network-fake-impl.js b/extensions/amp-ad-network-fake-impl/0.1/test/test-amp-ad-network-fake-impl.js index 5147114c30dc..07f26d6a4de2 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/test/test-amp-ad-network-fake-impl.js +++ b/extensions/amp-ad-network-fake-impl/0.1/test/test-amp-ad-network-fake-impl.js @@ -37,7 +37,7 @@ describes.realWin( const metaCharset = ``; const metaViewport = ``; const ampCustomStyle = ``; - const linkIcon = ``; + const linkIcon = ``; let doc; let win; let fakeImplElem; diff --git a/extensions/amp-ad-network-fake-impl/0.1/test/test-external-reorder-head-transformer.js b/extensions/amp-ad-network-fake-impl/0.1/test/test-external-reorder-head-transformer.js index 77671f37d3f7..be37b8cb21d4 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/test/test-external-reorder-head-transformer.js +++ b/extensions/amp-ad-network-fake-impl/0.1/test/test-external-reorder-head-transformer.js @@ -30,7 +30,7 @@ const crossorigin = ``; const ampCustomStyle = ``; -const linkIcon = ``; +const linkIcon = ``; const ampViewerIntegration = ``; const ampGmail = ``; diff --git a/extensions/amp-ad/0.1/amp-ad-3p-impl.js b/extensions/amp-ad/0.1/amp-ad-3p-impl.js index be1168817df9..352556211866 100644 --- a/extensions/amp-ad/0.1/amp-ad-3p-impl.js +++ b/extensions/amp-ad/0.1/amp-ad-3p-impl.js @@ -47,7 +47,6 @@ import { getConsentPolicyState, } from '../../../src/consent'; import {getIframe, preloadBootstrap} from '../../../src/3p-frame'; -import {isExperimentOn} from '../../../src/experiments'; import {moveLayoutRect} from '../../../src/layout-rect'; import {toWin} from '../../../src/types'; @@ -355,11 +354,9 @@ export class AmpAd3PImpl extends AMP.BaseElement { const consentPromise = this.getConsentState(); const consentPolicyId = super.getConsentPolicy(); - const isConsentV2Experiment = isExperimentOn(this.win, 'amp-consent-v2'); - const consentStringPromise = - consentPolicyId && isConsentV2Experiment - ? getConsentPolicyInfo(this.element, consentPolicyId) - : Promise.resolve(null); + const consentStringPromise = consentPolicyId + ? getConsentPolicyInfo(this.element, consentPolicyId) + : Promise.resolve(null); const sharedDataPromise = consentPolicyId ? getConsentPolicySharedData(this.element, consentPolicyId) : Promise.resolve(null); @@ -384,9 +381,7 @@ export class AmpAd3PImpl extends AMP.BaseElement { 'initialConsentState': consents[1], 'consentSharedData': consents[2], }); - if (isConsentV2Experiment) { - opt_context['initialConsentValue'] = consents[3]; - } + opt_context['initialConsentValue'] = consents[3]; // In this path, the request and render start events are entangled, // because both happen inside a cross-domain iframe. Separating them diff --git a/extensions/amp-ad/0.1/test/test-amp-ad-3p-impl.js b/extensions/amp-ad/0.1/test/test-amp-ad-3p-impl.js index 889dc65d1682..c3626d5771b5 100644 --- a/extensions/amp-ad/0.1/test/test-amp-ad-3p-impl.js +++ b/extensions/amp-ad/0.1/test/test-amp-ad-3p-impl.js @@ -33,7 +33,7 @@ function createAmpAd(win, attachToAmpdoc = false, ampdoc) { type: '_ping_', width: 300, height: 250, - src: 'https://testsrc', + src: 'https://src.test', 'data-valid': 'true', 'data-width': '6666', }); @@ -54,7 +54,7 @@ describes.realWin( { amp: { runtimeOn: false, - canonicalUrl: 'https://canonical.url', + canonicalUrl: 'https://canonical.test', }, allowExternalResources: true, }, @@ -101,10 +101,12 @@ describes.realWin( expect(url).to.match(/frame(.max)?.html/); const data = JSON.parse(iframe.name).attributes; expect(data).to.have.property('type', '_ping_'); - expect(data).to.have.property('src', 'https://testsrc'); + expect(data).to.have.property('src', 'https://src.test'); expect(data).to.have.property('width', 300); expect(data).to.have.property('height', 250); - expect(data._context.canonicalUrl).to.equal('https://canonical.url/'); + expect(data._context.canonicalUrl).to.equal( + 'https://canonical.test/' + ); }); }); @@ -227,7 +229,7 @@ describes.realWin( }); it('should use custom path', () => { - const remoteUrl = 'https://example.com/boot/remote.html'; + const remoteUrl = 'https://src.test/boot/remote.html'; const meta = win.document.createElement('meta'); meta.setAttribute('name', 'amp-3p-iframe-src'); meta.setAttribute('content', remoteUrl); @@ -245,7 +247,7 @@ describes.realWin( it('should use default path if custom disabled', () => { const meta = win.document.createElement('meta'); meta.setAttribute('name', 'amp-3p-iframe-src'); - meta.setAttribute('content', 'https://example.com/boot/remote.html'); + meta.setAttribute('content', 'https://src.test/boot/remote.html'); win.document.head.appendChild(meta); ad3p.config.remoteHTMLDisabled = true; ad3p.onLayoutMeasure(); @@ -340,13 +342,13 @@ describes.realWin( ); expect(preconnects[preconnects.length - 1]).to.have.property( 'href', - 'https://testsrc/' + 'https://src.test/' ); }); }); it('should use remote html path for preload', () => { - const remoteUrl = 'https://example.com/boot/remote.html'; + const remoteUrl = 'https://src.test/boot/remote.html'; const meta = win.document.createElement('meta'); meta.setAttribute('name', 'amp-3p-iframe-src'); meta.setAttribute('content', remoteUrl); @@ -365,7 +367,7 @@ describes.realWin( it('should not use remote html path for preload if disabled', () => { const meta = win.document.createElement('meta'); meta.setAttribute('name', 'amp-3p-iframe-src'); - meta.setAttribute('content', 'https://example.com/boot/remote.html'); + meta.setAttribute('content', 'https://src.test/boot/remote.html'); win.document.head.appendChild(meta); ad3p.config.remoteHTMLDisabled = true; ad3p.buildCallback(); diff --git a/extensions/amp-ad/0.1/test/test-amp-ad-custom.js b/extensions/amp-ad/0.1/test/test-amp-ad-custom.js index 0cf9b4780006..e4c64423c1ec 100644 --- a/extensions/amp-ad/0.1/test/test-amp-ad-custom.js +++ b/extensions/amp-ad/0.1/test/test-amp-ad-custom.js @@ -85,14 +85,14 @@ describes.realWin('Amp custom ad', {amp: true}, env => { })); // Single ad with no slot - const url1 = 'example.com/ad'; + const url1 = 'example.test/ad'; const element1 = getCustomAd(doc, url1); const ad1 = new AmpAdCustom(element1); ad1.buildCallback(); ad1.layoutCallback(); // Single ad with no slot - const url2 = 'example.com/ad'; + const url2 = 'example.test/ad'; const element2 = getCustomAd(doc, url2); const ad2 = new AmpAdCustom(element2); ad2.buildCallback(); diff --git a/extensions/amp-ad/0.1/test/test-amp-ad.js b/extensions/amp-ad/0.1/test/test-amp-ad.js index 44563f29c145..ba0ca905f258 100644 --- a/extensions/amp-ad/0.1/test/test-amp-ad.js +++ b/extensions/amp-ad/0.1/test/test-amp-ad.js @@ -143,7 +143,7 @@ describes.realWin('Ad loader', {amp: true}, env => { it('falls back to Delayed Fetch if remote.html is used', () => { const meta = doc.createElement('meta'); meta.setAttribute('name', 'amp-3p-iframe-src'); - meta.setAttribute('content', 'https://example.com/remote.html'); + meta.setAttribute('content', 'https://example.test/remote.html'); doc.head.appendChild(meta); a4aRegistry['zort'] = (win, element, useRemoteHtml) => { return !useRemoteHtml; @@ -182,7 +182,7 @@ describes.realWin('Ad loader', {amp: true}, env => { it('uses Fast Fetch if remote.html and RTC are used', () => { const meta = doc.createElement('meta'); meta.setAttribute('name', 'amp-3p-iframe-src'); - meta.setAttribute('content', 'https://example.com/remote.html'); + meta.setAttribute('content', 'https://example.test/remote.html'); doc.head.appendChild(meta); a4aRegistry['zort'] = function() { return true; @@ -211,7 +211,7 @@ describes.realWin('Ad loader', {amp: true}, env => { it('uses Fast Fetch if remote.html is used but disabled', () => { const meta = doc.createElement('meta'); meta.setAttribute('name', 'amp-3p-iframe-src'); - meta.setAttribute('content', 'https://example.com/remote.html'); + meta.setAttribute('content', 'https://example.test/remote.html'); doc.head.appendChild(meta); adConfig['zort'] = {remoteHTMLDisabled: true}; a4aRegistry['zort'] = function() { diff --git a/extensions/amp-ad/amp-ad.md b/extensions/amp-ad/amp-ad.md index 3ba3940b3839..2eb12e5fde36 100644 --- a/extensions/amp-ad/amp-ad.md +++ b/extensions/amp-ad/amp-ad.md @@ -450,6 +450,8 @@ See [amp-ad rules](https://github.com/ampproject/amphtml/blob/master/extensions/ - [SVK-Native](../../ads/svknative.md) - [Strossle](../../ads/strossle.md) - [Taboola](../../ads/taboola.md) +- [TE Medya](../../ads/temedya.md) +- [Whopa InFeed](../../ads/whopainfeed.md) - [Yahoo Native Ads](../../ads/yahoonativeads.md) - [Zen](../../ads/zen.md) - [ZergNet](../../ads/zergnet.md) diff --git a/extensions/amp-addthis/0.1/amp-addthis.js b/extensions/amp-addthis/0.1/amp-addthis.js index 36a5f947840f..e97abf0c1cd1 100644 --- a/extensions/amp-addthis/0.1/amp-addthis.js +++ b/extensions/amp-addthis/0.1/amp-addthis.js @@ -52,7 +52,6 @@ import {DwellMonitor} from './addthis-utils/monitors/dwell-monitor'; import {PostMessageDispatcher} from './post-message-dispatcher'; import {ScrollMonitor} from './addthis-utils/monitors/scroll-monitor'; import {Services} from '../../../src/services'; - import {callEng} from './addthis-utils/eng'; import {callLojson} from './addthis-utils/lojson'; import {callPjson} from './addthis-utils/pjson'; @@ -66,6 +65,7 @@ import { } from './addthis-utils/mode'; import {getOgImage} from './addthis-utils/meta'; import {getWidgetOverload} from './addthis-utils/get-widget-id-overloaded-with-json-for-anonymous-mode'; +import {internalRuntimeVersion} from '../../../src/internal-version'; import {isLayoutSizeDefined} from '../../../src/layout'; import {listen} from '../../../src/event-helper'; import {parseUrlDeprecated} from '../../../src/url'; @@ -299,7 +299,10 @@ class AmpAddThis extends AMP.BaseElement { dict({ 'frameborder': 0, 'title': ALT_TEXT, - 'src': `${ORIGIN}/dc/amp-addthis.html`, + // Document has overly long cache age: go.amp.dev/issue/24848 + // Adding AMP runtime version as a meaningless query param to force bust + // cached versions. + 'src': `${ORIGIN}/dc/amp-addthis.html?_amp_=${internalRuntimeVersion()}`, 'id': this.widgetId_, 'pco': this.productCode_, 'containerClassName': this.containerClassName_, diff --git a/extensions/amp-addthis/0.1/test/test-amp-addthis.js b/extensions/amp-addthis/0.1/test/test-amp-addthis.js index 9a4e0b63b81e..0750fcb5db44 100644 --- a/extensions/amp-addthis/0.1/test/test-amp-addthis.js +++ b/extensions/amp-addthis/0.1/test/test-amp-addthis.js @@ -31,6 +31,7 @@ import {getDetailsForMeta, getMetaElements} from './../addthis-utils/meta'; import {getKeywordsString} from './../addthis-utils/classify'; import {getSessionId} from '../addthis-utils/session'; import {getWidgetOverload} from '../addthis-utils/get-widget-id-overloaded-with-json-for-anonymous-mode'; +import {startsWith} from '../../../../src/string'; import {toArray} from '../../../../src/types'; describes.realWin( @@ -101,9 +102,11 @@ describes.realWin( function testIframe(iframe) { expect(iframe).to.not.equal(null); - expect(iframe.getAttribute('src')).to.equal( - `${ORIGIN}/dc/amp-addthis.html` - ); + const srcPrefix = `${ORIGIN}/dc/amp-addthis.html?`; + expect( + startsWith(iframe.getAttribute('src'), srcPrefix), + `iframe src starts with ${srcPrefix}` + ).to.be.true; expect(iframe.getAttribute('title')).to.equal(ALT_TEXT); } diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index 2f286d51f0ac..38b26eb4a744 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -301,8 +301,8 @@ export class AmpAnalytics extends AMP.BaseElement { const expansionOptions = this.expansionOptions_( dict({}), trigger, - undefined, - true + undefined /* opt_iterations */, + true /* opt_noEncode */ ); const TAG = this.getName_(); if (!trigger) { @@ -361,7 +361,11 @@ export class AmpAnalytics extends AMP.BaseElement { } else if (trigger['selector']) { // Expand the selector using variable expansion. return this.variableService_ - .expandTemplate(trigger['selector'], expansionOptions) + .expandTemplate( + trigger['selector'], + expansionOptions, + this.element + ) .then(selector => { trigger['selector'] = selector; this.addTrigger_(trigger); @@ -753,7 +757,7 @@ export class AmpAnalytics extends AMP.BaseElement { */ expandTemplateWithUrlParams_(spec, expansionOptions) { return this.variableService_ - .expandTemplate(spec, expansionOptions) + .expandTemplate(spec, expansionOptions, this.element) .then(key => Services.urlReplacementsForDoc(this.element).expandUrlAsync( key, diff --git a/extensions/amp-analytics/0.1/linker-manager.js b/extensions/amp-analytics/0.1/linker-manager.js index 62b5f7829570..b6ba787f9fd9 100644 --- a/extensions/amp-analytics/0.1/linker-manager.js +++ b/extensions/amp-analytics/0.1/linker-manager.js @@ -208,7 +208,7 @@ export class LinkerManager { expandTemplateWithUrlParams_(template, expansionOptions) { const bindings = this.variableService_.getMacros(this.element_); return this.variableService_ - .expandTemplate(template, expansionOptions) + .expandTemplate(template, expansionOptions, this.element_) .then(expanded => { const urlReplacements = Services.urlReplacementsForDoc(this.element_); return urlReplacements.expandUrlAsync(expanded, bindings); @@ -216,24 +216,36 @@ export class LinkerManager { } /** - * If the document has existing cid meta tag they do not need to explicity + * If the document has existing cid meta tag they do not need to explicitly * opt-in to use linker. * @return {boolean} * @private */ isLegacyOptIn_() { - const optInMeta = this.ampdoc_.win.document.head./*OK*/ querySelector( - 'meta[name="amp-google-client-id-api"][content="googleanalytics"]' - ); + if (this.type_ !== 'googleanalytics') { + return false; + } + if ( - !optInMeta || - optInMeta.hasAttribute(LINKER_CREATED) || - this.type_ !== 'googleanalytics' + this.ampdoc_.getMetaByName('amp-google-client-id-api') !== + 'googleanalytics' ) { return false; } - optInMeta.setAttribute(LINKER_CREATED, ''); + const headNode = this.ampdoc_.getHeadNode(); + const linkerCreatedEl = + headNode instanceof ShadowRoot + ? this.ampdoc_.getBody() + : headNode.querySelector( + 'meta[name="amp-google-client-id-api"][content="googleanalytics"]' + ); + + if (linkerCreatedEl.hasAttribute(LINKER_CREATED)) { + return false; + } + + linkerCreatedEl.setAttribute(LINKER_CREATED, ''); return true; } diff --git a/extensions/amp-analytics/0.1/requests.js b/extensions/amp-analytics/0.1/requests.js index 9d49833840ac..57eefb415c53 100644 --- a/extensions/amp-analytics/0.1/requests.js +++ b/extensions/amp-analytics/0.1/requests.js @@ -129,7 +129,7 @@ export class RequestHandler { this.lastTrigger_ = trigger; const bindings = this.variableService_.getMacros(this.element_); bindings['RESOURCE_TIMING'] = getResourceTiming( - this.ampdoc_, + this.element_, trigger['resourceTimingSpec'], this.startTime_ ); @@ -139,7 +139,10 @@ export class RequestHandler { this.baseUrlTemplatePromise_ = this.variableService_.expandTemplate( this.baseUrl, - expansionOption + expansionOption, + this.element_, + bindings, + this.whiteList_ ); this.baseUrlPromise_ = this.baseUrlTemplatePromise_.then(baseUrl => { @@ -162,7 +165,13 @@ export class RequestHandler { this.requestOriginPromise_ = this.variableService_ // expand variables in request origin - .expandTemplate(this.requestOrigin_, requestOriginExpansionOpt) + .expandTemplate( + this.requestOrigin_, + requestOriginExpansionOpt, + this.element_, + bindings, + this.whiteList_ + ) // substitute in URL values e.g. DOCUMENT_REFERRER -> https://example.com .then(expandedRequestOrigin => { return this.urlReplacementService_.expandUrlAsync( @@ -182,6 +191,7 @@ export class RequestHandler { params, expansionOption, bindings, + this.element_, this.whiteList_ ).then(params => { return dict({ @@ -410,7 +420,7 @@ export function expandPostMessage( expansionOption.freezeVar('extraUrlParams'); const basePromise = variableService - .expandTemplate(msg, expansionOption) + .expandTemplate(msg, expansionOption, element) .then(base => { return urlReplacementService.expandStringAsync(base, bindings); }); @@ -427,7 +437,8 @@ export function expandPostMessage( urlReplacementService, params, expansionOption, - bindings + bindings, + element ).then(extraUrlParams => { return defaultSerializer(expandedMsg, [ dict({'extraUrlParams': extraUrlParams}), @@ -443,6 +454,7 @@ export function expandPostMessage( * @param {!Object} params * @param {!./variables.ExpansionOptions} expansionOption * @param {!Object} bindings + * @param {!Element} element * @param {!Object=} opt_whitelist * @return {!Promise} * @private @@ -453,6 +465,7 @@ function expandExtraUrlParams( params, expansionOption, bindings, + element, opt_whitelist ) { const requestPromises = []; @@ -469,7 +482,7 @@ function expandExtraUrlParams( if (typeof value === 'string') { const request = variableService - .expandTemplate(value, option) + .expandTemplate(value, option, element) .then(value => urlReplacements.expandStringAsync(value, bindings, opt_whitelist) ) diff --git a/extensions/amp-analytics/0.1/resource-timing.js b/extensions/amp-analytics/0.1/resource-timing.js index d0357798a3bb..e720fc23374e 100644 --- a/extensions/amp-analytics/0.1/resource-timing.js +++ b/extensions/amp-analytics/0.1/resource-timing.js @@ -244,14 +244,14 @@ function filterEntries(entries, resourceDefs) { * single string. * @param {!Array} entries * @param {!JsonObject} resourceTimingSpec - * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc + * @param {!Element} element amp-analytics element. * @return {!Promise} */ -function serialize(entries, resourceTimingSpec, ampdoc) { +function serialize(entries, resourceTimingSpec, element) { const resources = resourceTimingSpec['resources']; const encoding = resourceTimingSpec['encoding']; - const variableService = variableServiceForDoc(ampdoc); + const variableService = variableServiceForDoc(element); const format = (val, relativeTo = 0) => Math.round(val - relativeTo).toString(encoding['base'] || 10); @@ -261,19 +261,19 @@ function serialize(entries, resourceTimingSpec, ampdoc) { return entryToExpansionOptions(entry, name, format); }) .map(expansion => - variableService.expandTemplate(encoding['entry'], expansion) + variableService.expandTemplate(encoding['entry'], expansion, element) ); return Promise.all(promises).then(vars => vars.join(encoding['delim'])); } /** * Serializes resource timing entries according to the resource timing spec. - * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc + * @param {!Element} element amp-analytics element. * @param {!JsonObject} resourceTimingSpec * @return {!Promise} */ -function serializeResourceTiming(ampdoc, resourceTimingSpec) { - const {win} = ampdoc; +function serializeResourceTiming(element, resourceTimingSpec) { + const {win} = element.getAmpDoc(); // Check that the performance timing API exists before and that the spec is // valid before proceeding. If not, we simply return an empty string. if ( @@ -307,19 +307,19 @@ function serializeResourceTiming(ampdoc, resourceTimingSpec) { return Promise.resolve(''); } // Yield the thread in case iterating over all resources takes a long time. - return yieldThread(() => serialize(entries, resourceTimingSpec, ampdoc)); + return yieldThread(() => serialize(entries, resourceTimingSpec, element)); } /** - * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc + * @param {!Element} element amp-analytics element. * @param {!JsonObject|undefined} spec resource timing spec. * @param {number} startTime start timestamp. * @return {!Promise} */ -export function getResourceTiming(ampdoc, spec, startTime) { +export function getResourceTiming(element, spec, startTime) { // Only allow collecting timing within 1s if (spec && Date.now() < startTime + 60 * 1000) { - return serializeResourceTiming(ampdoc, spec); + return serializeResourceTiming(element, spec); } else { return Promise.resolve(''); } diff --git a/extensions/amp-analytics/0.1/test/test-amp-analytics.js b/extensions/amp-analytics/0.1/test/test-amp-analytics.js index 374dd135a72f..03589c519ed5 100644 --- a/extensions/amp-analytics/0.1/test/test-amp-analytics.js +++ b/extensions/amp-analytics/0.1/test/test-amp-analytics.js @@ -56,12 +56,12 @@ describes.realWin( const jsonMockResponses = { '//invalidConfig': '{"transport": {"iframe": "fake.com"}}', '//config1': '{"vars": {"title": "remote"}}', - 'https://foo/Test%20Title': '{"vars": {"title": "magic"}}', - '//config-rv2': '{"requests": {"foo": "https://example.com/remote"}}', + 'https://foo/My%20Test%20Title': '{"vars": {"title": "magic"}}', + '//config-rv2': '{"requests": {"foo": "https://example.test/remote"}}', 'https://rewriter.com': '{"vars": {"title": "rewritten"}}', }; const trivialConfig = { - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': {'pageview': {'on': 'visible', 'request': 'foo'}}, }; @@ -98,7 +98,7 @@ describes.realWin( doc = win.document; ampdoc = env.ampdoc; configWithCredentials = false; - doc.title = 'Test Title'; + doc.title = 'My Test Title'; resetServiceForTesting(win, 'xhr'); jsonRequestConfigs = {}; registerServiceBuilder(win, 'xhr', function() { @@ -194,7 +194,7 @@ describes.realWin( it('sends a basic hit', function() { const analytics = getAnalyticsTag(trivialConfig); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/bar'); + requestVerifier.verifyRequest('https://example.test/bar'); }); }); @@ -287,7 +287,7 @@ describes.realWin( it('does not send a hit when request is not provided', function() { expectAsyncConsoleError(onAndRequestAttributesError); const analytics = getAnalyticsTag({ - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': [{'on': 'visible'}], }); @@ -307,33 +307,33 @@ describes.realWin( it('expands nested requests', function() { const analytics = getAnalyticsTag({ 'requests': { - 'foo': 'https://example.com/bar&${foobar}&baz', + 'foo': 'https://example.test/bar&${foobar}&baz', 'foobar': 'f1', }, 'triggers': [{'on': 'visible', 'request': 'foo'}], }); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/bar&f1&baz'); + requestVerifier.verifyRequest('https://example.test/bar&f1&baz'); }); }); it('expand nested requests with vendor provided value', () => { const analytics = getAnalyticsTag({ 'requests': { - 'foo': 'https://example.com/bar&${clientId}&baz', + 'foo': 'https://example.test/bar&${clientId}&baz', 'clientId': 'c1', }, 'triggers': [{'on': 'visible', 'request': 'foo'}], }); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/bar&c1&baz'); + requestVerifier.verifyRequest('https://example.test/bar&c1&baz'); }); }); it('expands nested requests (3 levels)', function() { const analytics = getAnalyticsTag({ 'requests': { - 'foo': 'https://example.com/bar&${foobar}', + 'foo': 'https://example.test/bar&${foobar}', 'foobar': '${baz}', 'baz': 'b1', }, @@ -341,14 +341,14 @@ describes.realWin( }); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/bar&b1'); + requestVerifier.verifyRequest('https://example.test/bar&b1'); }); }); it('should tolerate invalid triggers', function() { expectAsyncConsoleError(/No request strings defined/); const analytics = getAnalyticsTag({ - 'request': {'foo': 'https://example.com'}, + 'request': {'foo': 'https://example.test'}, 'triggers': [], }); return waitForNoSendRequest(analytics); @@ -369,7 +369,7 @@ describes.realWin( expectAsyncConsoleError(/Request string not found/); const analytics = getAnalyticsTag({ 'requests': { - 'foo': 'https://example.com/bar&${foobar}', + 'foo': 'https://example.test/bar&${foobar}', 'foobar': '${baz}', 'baz': 'b1', }, @@ -377,7 +377,7 @@ describes.realWin( }); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/bar&b1'); + requestVerifier.verifyRequest('https://example.test/bar&b1'); requestVerifier.verifyRequest('b1'); }); }); @@ -386,14 +386,14 @@ describes.realWin( const analytics = getAnalyticsTag({ 'requests': { 'htmlAttrRequest': - 'https://example.com/bar&ids=${htmlAttr(div,id)}', + 'https://example.test/bar&ids=${htmlAttr(div,id)}', }, 'triggers': [{'on': 'visible', 'request': 'htmlAttrRequest'}], }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/bar&ids=HTML_ATTR(div,id)' + 'https://example.test/bar&ids=HTML_ATTR(div,id)' ); }); }); @@ -401,14 +401,14 @@ describes.realWin( it('fills cid', function() { const analytics = getAnalyticsTag({ 'requests': { - 'foo': 'https://example.com/cid=${clientId(analytics-abc)}', + 'foo': 'https://example.test/cid=${clientId(analytics-abc)}', }, 'triggers': [{'on': 'visible', 'request': 'foo'}], }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequestMatch( - /^https:\/\/example.com\/cid=[a-zA-Z\-]+/ + /^https:\/\/example.test\/cid=[a-zA-Z\-]+/ ); }); }); @@ -440,7 +440,7 @@ describes.realWin( it('should create and destroy analytics group', () => { const analytics = getAnalyticsTag({ - requests: {foo: 'https://example.com/bar'}, + requests: {foo: 'https://example.test/bar'}, triggers: [{on: 'click', selector: '${foo}', request: 'foo'}], vars: {foo: 'bar'}, }); @@ -459,7 +459,7 @@ describes.realWin( it('expands urls in config request', () => { const analytics = getAnalyticsTag( { - 'requests': {'foo': 'https://example.com/${title}'}, + 'requests': {'foo': 'https://example.test/${title}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -467,13 +467,13 @@ describes.realWin( } ); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/magic'); + requestVerifier.verifyRequest('https://example.test/magic'); }); }); it('updates requestCount on each request', () => { const analytics = getAnalyticsTag({ - 'host': 'example.com', + 'host': 'example.test', 'requests': { 'pageview1': '/test1=${requestCount}', 'pageview2': '/test2=${requestCount}', @@ -494,7 +494,7 @@ describes.realWin( it('expands trigger vars', () => { const analytics = getAnalyticsTag({ 'requests': { - 'pageview': 'https://example.com/test1=${var1}&test2=${var2}', + 'pageview': 'https://example.test/test1=${var1}&test2=${var2}', }, 'triggers': [ { @@ -509,7 +509,7 @@ describes.realWin( }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/test1=x&test2=test2' + 'https://example.test/test1=x&test2=test2' ); }); }); @@ -521,13 +521,13 @@ describes.realWin( 'var2': 'test2', }, 'requests': { - 'pageview': 'https://example.com/test1=${var1}&test2=${var2}', + 'pageview': 'https://example.test/test1=${var1}&test2=${var2}', }, 'triggers': [{'on': 'visible', 'request': 'pageview'}], }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/test1=x&test2=test2' + 'https://example.test/test1=x&test2=test2' ); }); }); @@ -539,24 +539,26 @@ describes.realWin( const analytics = getAnalyticsTag({ 'requests': { 'pageview': - 'https://example.com/title=${title}&ref=${documentReferrer}', + 'https://example.test/title=${title}&ref=${documentReferrer}', }, 'triggers': [{'on': 'visible', 'request': 'pageview'}], }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequestMatch( - /https:\/\/example.com\/title=Test%20Title&ref=http%3A%2F%2Ffake.example%2F%3Ffoo%3Dbar/ + /https:\/\/example.test\/title=My%20Test%20Title&ref=http%3A%2F%2Ffake.example%2F%3Ffoo%3Dbar/ ); }); }); it('expands url-replacements vars', function() { const analytics = getAnalyticsTag({ - 'requests': {'foo': 'https://example.com/TITLE'}, + 'requests': {'foo': 'https://example.test/TITLE'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/Test%20Title'); + requestVerifier.verifyRequest( + 'https://example.test/My%20Test%20Title' + ); }); }); @@ -567,7 +569,7 @@ describes.realWin( 'var2': 'config2', }, 'requests': { - 'pageview': 'https://example.com/test1=${var1}&test2=${var2}', + 'pageview': 'https://example.test/test1=${var1}&test2=${var2}', }, 'triggers': [ { @@ -581,7 +583,7 @@ describes.realWin( }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/test1=trigger1&test2=config2' + 'https://example.test/test1=trigger1&test2=config2' ); }); }); @@ -617,13 +619,13 @@ describes.realWin( const analytics = getAnalyticsTag({ 'vars': {'random': 428}, 'requests': { - 'pageview': 'https://example.com/test1=${title}&test2=${random}', + 'pageview': 'https://example.test/test1=${title}&test2=${random}', }, 'triggers': [{'on': 'visible', 'request': 'pageview'}], }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/test1=Test%20Title&test2=428' + 'https://example.test/test1=My%20Test%20Title&test2=428' ); }); }); @@ -635,7 +637,7 @@ describes.realWin( 'c2': 'config&2', }, 'requests': { - 'base': 'https://example.com/test?c1=${c1}&t1=${t1}', + 'base': 'https://example.test/test?c1=${c1}&t1=${t1}', 'pageview': '${base}&c2=${c2}&t2=${t2}', }, 'triggers': [ @@ -651,7 +653,7 @@ describes.realWin( }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/test?c1=config%201&t1=trigger%3D1&c2=config%262&t2=trigger%3F2' + 'https://example.test/test?c1=config%201&t1=trigger%3D1&c2=config%262&t2=trigger%3F2' ); }); }); @@ -663,7 +665,7 @@ describes.realWin( 'c2': 'config&2', }, 'requests': { - 'base': 'https://example.com/test?', + 'base': 'https://example.test/test?', 'pageview': '${base}c1=${c1}&c2=${c2}', }, 'triggers': [ @@ -675,7 +677,7 @@ describes.realWin( }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/test?c1=Config%2C%20The%20Barbarian,config%201&c2=config%262' + 'https://example.test/test?c1=Config%2C%20The%20Barbarian,config%201&c2=config%262' ); }); }); @@ -687,7 +689,7 @@ describes.realWin( const analytics = getAnalyticsTag({ 'requests': { 'pageview': - 'https://example.com/test1=${var1}&test2=${var2}&title=TITLE', + 'https://example.test/test1=${var1}&test2=${var2}&title=TITLE', }, 'triggers': [ { @@ -702,7 +704,7 @@ describes.realWin( }); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequestMatch( - /https:\/\/example.com\/test1=x&test2=http%3A%2F%2Ffake.example%2F%3Ffoo%3Dbar&title=Test%20Title/ + /https:\/\/example.test\/test1=x&test2=http%3A%2F%2Ffake.example%2F%3Ffoo%3Dbar&title=My%20Test%20Title/ ); }); }); @@ -710,7 +712,7 @@ describes.realWin( it('expands complex vars', () => { const analytics = getAnalyticsTag({ 'requests': { - 'pageview': 'https://example.com/test1=${qp_foo}', + 'pageview': 'https://example.test/test1=${qp_foo}', }, 'triggers': [ { @@ -737,7 +739,7 @@ describes.realWin( return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/test1=_query_param_foo_' + 'https://example.test/test1=_query_param_foo_' ); }); }); @@ -748,7 +750,7 @@ describes.realWin( const tracker = ins.root_.getTracker('click', ClickEventTracker); const addStub = env.sandbox.stub(tracker, 'add'); const analytics = getAnalyticsTag({ - requests: {foo: 'https://example.com/bar'}, + requests: {foo: 'https://example.test/bar'}, triggers: [{on: 'click', selector: '${foo}', request: 'foo'}], vars: {foo: 'bar'}, }); @@ -764,7 +766,7 @@ describes.realWin( const tracker = ins.root_.getTracker('click', ClickEventTracker); const addStub = env.sandbox.stub(tracker, 'add'); const analytics = getAnalyticsTag({ - requests: {foo: 'https://example.com/bar'}, + requests: {foo: 'https://example.test/bar'}, triggers: [ {on: 'click', selector: '${foo}, ${bar}', request: 'foo'}, ], @@ -800,17 +802,17 @@ describes.realWin( 'p:nth-child(2)', ].map(selectorExpansionTest); - it('does not expands selector with platform variable', () => { + it('expands selector with platform variable', () => { const tracker = ins.root_.getTracker('click', ClickEventTracker); const addStub = env.sandbox.stub(tracker, 'add'); const analytics = getAnalyticsTag({ - requests: {foo: 'https://example.com/bar'}, + requests: {foo: 'https://example.test/bar'}, triggers: [{on: 'click', selector: '${title}', request: 'foo'}], }); return waitForNoSendRequest(analytics).then(() => { expect(addStub).to.be.calledOnce; const config = addStub.args[0][2]; - expect(config['selector']).to.equal('TITLE'); + expect(config['selector']).to.equal('My Test Title'); }); }); }); @@ -821,7 +823,7 @@ describes.realWin( Promise.resolve({ 'requests': { 'foo': { - baseUrl: 'https://example.com/bar', + baseUrl: 'https://example.test/bar', }, }, 'triggers': { @@ -842,7 +844,7 @@ describes.realWin( win['foo'] = {'bar': () => false}; const analytics = getAnalyticsTag(trivialConfig); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/bar'); + requestVerifier.verifyRequest('https://example.test/bar'); }); }); @@ -863,7 +865,7 @@ describes.realWin( 'type': 'testVendor', }); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/bar'); + requestVerifier.verifyRequest('https://example.test/bar'); }); }); }); @@ -874,7 +876,7 @@ describes.realWin( Promise.resolve({ 'requests': { 'foo': { - baseUrl: 'https://example.com/bar', + baseUrl: 'https://example.test/bar', }, }, 'triggers': { @@ -911,7 +913,7 @@ describes.realWin( 'type': 'testVendor', }); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/bar'); + requestVerifier.verifyRequest('https://example.test/bar'); }); }); }); @@ -920,7 +922,7 @@ describes.realWin( let config; beforeEach(() => { config = { - vars: {host: 'example.com', path: 'helloworld'}, + vars: {host: 'example.test', path: 'helloworld'}, extraUrlParams: { 's.evar0': '0', 's.evar1': '${path}', @@ -935,7 +937,7 @@ describes.realWin( const analytics = getAnalyticsTag(config); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/helloworld?a=b&s.evar0=0&s.evar1=helloworld&foofoo=baz' + 'https://example.test/helloworld?a=b&s.evar0=0&s.evar1=helloworld&foofoo=baz' ); }); }); @@ -945,7 +947,7 @@ describes.realWin( const analytics = getAnalyticsTag(config); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/helloworld?a=b&foofoo=baz&v0=0&v1=helloworld' + 'https://example.test/helloworld?a=b&foofoo=baz&v0=0&v1=helloworld' ); }); }); @@ -956,7 +958,7 @@ describes.realWin( const analytics = getAnalyticsTag(config); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/helloworld?a=b&foofoo=baz&v0=0&v1=helloworld&c=d&v=e' + 'https://example.test/helloworld?a=b&foofoo=baz&v0=0&v1=helloworld&c=d&v=e' ); }); }); @@ -967,7 +969,7 @@ describes.realWin( const analytics = getAnalyticsTag(config); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/helloworld?s.evar0=0&s.evar1=helloworld&foofoo=baz&a=b' + 'https://example.test/helloworld?s.evar0=0&s.evar1=helloworld&foofoo=baz&a=b' ); }); }); @@ -977,7 +979,7 @@ describes.realWin( const analytics = getAnalyticsTag(config); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/helloworld?a=b&foo=0' + 'https://example.test/helloworld?a=b&foo=0' ); }); }); @@ -1407,7 +1409,7 @@ describes.realWin( it('should resume fetch when consent is given', () => { const analytics = getAnalyticsTag( { - 'requests': {'foo': 'https://example.com/local'}, + 'requests': {'foo': 'https://example.test/local'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -1421,14 +1423,14 @@ describes.realWin( }); return waitForSendRequest(analytics).then(() => { - requestVerifier.verifyRequest('https://example.com/local'); + requestVerifier.verifyRequest('https://example.test/local'); }); }); it('should not fetch when consent is not given', () => { const analytics = getAnalyticsTag( { - 'requests': {'foo': 'https://example.com/local'}, + 'requests': {'foo': 'https://example.test/local'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -1456,7 +1458,7 @@ describes.realWin( () => { const analytics = getAnalyticsTag( { - 'requests': {'foo': 'https://example.com/local'}, + 'requests': {'foo': 'https://example.test/local'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -1495,7 +1497,7 @@ describes.realWin( const addStub = env.sandbox.stub(tracker, 'add'); const analytics = getAnalyticsTag( { - requests: {foo: 'https://example.com/bar'}, + requests: {foo: 'https://example.test/bar'}, triggers: [{on: 'click', request: 'foo'}], }, { @@ -1513,7 +1515,7 @@ describes.realWin( const addStub = env.sandbox.stub(tracker, 'add'); const analytics = getAnalyticsTag( { - requests: {foo: 'https://example.com/bar'}, + requests: {foo: 'https://example.test/bar'}, triggers: [{on: 'visible', selector: 'amp-iframe', request: 'foo'}], }, { @@ -1536,7 +1538,7 @@ describes.realWin( { 'requests': { 'pageview': - 'https://example.com/test1=${var1}&CLIENT_ID(analytics-abc)=${var2}', + 'https://example.test/test1=${var1}&CLIENT_ID(analytics-abc)=${var2}', }, 'triggers': [ { @@ -1555,7 +1557,7 @@ describes.realWin( ); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/test1=CLIENT_ID(analytics-abc)&' + + 'https://example.test/test1=CLIENT_ID(analytics-abc)&' + 'CLIENT_ID(analytics-abc)=test2' ); }); @@ -1565,7 +1567,7 @@ describes.realWin( const analytics = getAnalyticsTag( { 'requests': { - 'foo': 'https://example.com/cid=${clientId(analytics-abc)}', + 'foo': 'https://example.test/cid=${clientId(analytics-abc)}', }, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, @@ -1576,7 +1578,7 @@ describes.realWin( return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/cid=CLIENT_ID(analytics-abc)' + 'https://example.test/cid=CLIENT_ID(analytics-abc)' ); }); }); @@ -1584,7 +1586,7 @@ describes.realWin( it('should replace whitelist variable', () => { const analytics = getAnalyticsTag( { - 'requests': {'foo': 'https://example.com/random=${random}'}, + 'requests': {'foo': 'https://example.test/random=${random}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -1595,7 +1597,7 @@ describes.realWin( return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequestMatch( - /^https:\/\/example.com\/random=0.[0-9]/ + /^https:\/\/example.test\/random=0.[0-9]/ ); }); }); @@ -1605,7 +1607,7 @@ describes.realWin( { 'requests': { 'foo': - 'https://example.com/cid=${clientId(analytics-abc)}random=RANDOM', + 'https://example.test/cid=${clientId(analytics-abc)}random=RANDOM', }, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, @@ -1617,7 +1619,7 @@ describes.realWin( return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequestMatch( - /https:\/\/example\.com\/cid=CLIENT_ID\(analytics-abc\)random=0\.[0-9]+/ + /https:\/\/example\.test\/cid=CLIENT_ID\(analytics-abc\)random=0\.[0-9]+/ ); }); }); @@ -1627,7 +1629,7 @@ describes.realWin( { 'requests': { 'pageview': - 'https://example.com/VIEWER&_VERSION&' + + 'https://example.test/VIEWER&_VERSION&' + 'test1=${var1}&test2=${var2}&test3=${var3}&url=AMPDOC_URL', }, 'triggers': [ @@ -1649,7 +1651,7 @@ describes.realWin( ); return waitForSendRequest(analytics).then(() => { requestVerifier.verifyRequest( - 'https://example.com/VIEWER&%24internalRuntimeVersion%24' + + 'https://example.test/VIEWER&%24internalRuntimeVersion%24' + '&test1=x&test2=about%3Asrcdoc&test3=CLIENT_ID' + '&url=about%3Asrcdoc' ); @@ -1794,7 +1796,7 @@ describes.realWin( it('does send a hit when parentPostMessage is provided inabox', function() { env.win.__AMP_MODE.runtime = 'inabox'; const analytics = getAnalyticsTag({ - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': [{'on': 'visible', 'parentPostMessage': 'foo'}], }); postMessageSpy = env.sandbox.spy(analytics.win.parent, 'postMessage'); @@ -1816,7 +1818,7 @@ describes.realWin( it('does not send with parentPostMessage not inabox', function() { const analytics = getAnalyticsTag({ - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': [ { 'on': 'visible', @@ -1834,7 +1836,7 @@ describes.realWin( env.win.__AMP_MODE.runtime = 'inabox'; expectAsyncConsoleError(onAndRequestAttributesInaboxError); const analytics = getAnalyticsTag({ - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': [{'on': 'visible'}], }); postMessageSpy = env.sandbox.spy(analytics.win.parent, 'postMessage'); @@ -1846,7 +1848,7 @@ describes.realWin( it('send when request and parentPostMessage are provided', function() { env.win.__AMP_MODE.runtime = 'inabox'; const analytics = getAnalyticsTag({ - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': [ { 'on': 'visible', diff --git a/extensions/amp-analytics/0.1/test/test-config-new.js b/extensions/amp-analytics/0.1/test/test-config-new.js index 327ebc95b8cd..ff6b24f14d7a 100644 --- a/extensions/amp-analytics/0.1/test/test-config-new.js +++ b/extensions/amp-analytics/0.1/test/test-config-new.js @@ -56,7 +56,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { describe('handles top level fields correctly', () => { it('propogates requestOrigin into each request object', () => { fakeVendorJson = { - 'requestOrigin': 'https://example.com', + 'requestOrigin': 'https://example.test', 'requests': {'test1': '/test1', 'test2': '/test1/test2'}, }; @@ -65,11 +65,11 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'test1': { - origin: 'https://example.com', + origin: 'https://example.test', baseUrl: '/test1', }, 'test2': { - origin: 'https://example.com', + origin: 'https://example.test', baseUrl: '/test1/test2', }, }); @@ -152,7 +152,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { const element = getAnalyticsTag( { - 'requests': {'foo': 'https://example.com/${bar}'}, + 'requests': {'foo': 'https://example.test/${bar}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, {'type': vendorName} @@ -161,7 +161,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'foo': { - baseUrl: 'https://example.com/${bar}', + baseUrl: 'https://example.test/${bar}', }, 'bar': { baseUrl: 'foobar', @@ -190,7 +190,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { { 'requests': { 'foo': { - 'baseUrl': 'https://example.com/${bar}', + 'baseUrl': 'https://example.test/${bar}', 'batchInterval': 0, }, 'bar': 'bar-i', @@ -203,7 +203,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'foo': { - 'baseUrl': 'https://example.com/${bar}', + 'baseUrl': 'https://example.test/${bar}', 'batchInterval': 0, }, 'bar': { @@ -236,7 +236,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { { 'requests': { 'foo': { - 'baseUrl': 'https://example.com/${bar}', + 'baseUrl': 'https://example.test/${bar}', 'batchInterval': 0, }, 'bar': { @@ -251,7 +251,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'foo': { - 'baseUrl': 'https://example.com/${bar}', + 'baseUrl': 'https://example.test/${bar}', 'batchInterval': 0, }, 'bar': { @@ -271,14 +271,14 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { it('inline and remote both string', () => { fakeRemoteJson = { requests: { - foo: 'https://example.com/remote', + foo: 'https://example.test/remote', }, }; const element = getAnalyticsTag( { 'vars': {'title': 'local'}, - 'requests': {'foo': 'https://example.com/${title}'}, + 'requests': {'foo': 'https://example.test/${title}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -289,7 +289,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'foo': { - 'baseUrl': 'https://example.com/remote', + 'baseUrl': 'https://example.test/remote', }, }); expect(config['triggers']).to.deep.equal([ @@ -363,7 +363,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { it('fails for inline optout config', () => { const element = getAnalyticsTag({ - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], 'optout': 'foo.bar', }); @@ -401,7 +401,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { it('fails for inlined iframePing config', () => { const element = getAnalyticsTag({ - 'element': {'foo': 'https://example.com/bar'}, + 'element': {'foo': 'https://example.test/bar'}, 'triggers': [{'on': 'visible', 'iframePing': true}], }); return expect( @@ -467,7 +467,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { }; const element = getAnalyticsTag( { - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], transport: { image: false, @@ -498,7 +498,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { const element = getAnalyticsTag( { 'vars': {'title': 'local'}, - 'requests': {'foo': 'https://example.com/${title}'}, + 'requests': {'foo': 'https://example.test/${title}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -520,7 +520,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { const element = getAnalyticsTag( { 'vars': {'title': 'local'}, - 'requests': {'foo': 'https://example.com/${title}'}, + 'requests': {'foo': 'https://example.test/${title}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -542,7 +542,7 @@ describes.realWin('AnalyticsConfig', {amp: false}, env => { const element = getAnalyticsTag( { 'vars': {'title': 'local'}, - 'requests': {'foo': 'https://example.com/${title}'}, + 'requests': {'foo': 'https://example.test/${title}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { diff --git a/extensions/amp-analytics/0.1/test/test-config.js b/extensions/amp-analytics/0.1/test/test-config.js index ed2da198873d..4ce7bd642389 100644 --- a/extensions/amp-analytics/0.1/test/test-config.js +++ b/extensions/amp-analytics/0.1/test/test-config.js @@ -39,7 +39,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { describe('handles top level fields correctly', () => { it('propogates requestOrigin into each request object', () => { ANALYTICS_CONFIG['-test-venfor'] = { - 'requestOrigin': 'https://example.com', + 'requestOrigin': 'https://example.test', 'requests': {'test1': '/test1', 'test2': '/test1/test2'}, }; @@ -48,11 +48,11 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'test1': { - origin: 'https://example.com', + origin: 'https://example.test', baseUrl: '/test1', }, 'test2': { - origin: 'https://example.com', + origin: 'https://example.test', baseUrl: '/test1/test2', }, }); @@ -135,7 +135,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { const element = getAnalyticsTag( { - 'requests': {'foo': 'https://example.com/${bar}'}, + 'requests': {'foo': 'https://example.test/${bar}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, {'type': '-test-venfor'} @@ -144,7 +144,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'foo': { - baseUrl: 'https://example.com/${bar}', + baseUrl: 'https://example.test/${bar}', }, 'bar': { baseUrl: 'foobar', @@ -173,7 +173,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { { 'requests': { 'foo': { - 'baseUrl': 'https://example.com/${bar}', + 'baseUrl': 'https://example.test/${bar}', 'batchInterval': 0, }, 'bar': 'bar-i', @@ -186,7 +186,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'foo': { - 'baseUrl': 'https://example.com/${bar}', + 'baseUrl': 'https://example.test/${bar}', 'batchInterval': 0, }, 'bar': { @@ -219,7 +219,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { { 'requests': { 'foo': { - 'baseUrl': 'https://example.com/${bar}', + 'baseUrl': 'https://example.test/${bar}', 'batchInterval': 0, }, 'bar': { @@ -234,7 +234,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'foo': { - 'baseUrl': 'https://example.com/${bar}', + 'baseUrl': 'https://example.test/${bar}', 'batchInterval': 0, }, 'bar': { @@ -255,7 +255,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { const element = getAnalyticsTag( { 'vars': {'title': 'local'}, - 'requests': {'foo': 'https://example.com/${title}'}, + 'requests': {'foo': 'https://example.test/${title}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -268,7 +268,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { json() { return Promise.resolve({ requests: { - foo: 'https://example.com/remote', + foo: 'https://example.test/remote', }, }); }, @@ -278,7 +278,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { return new AnalyticsConfig(element).loadConfig().then(config => { expect(config['requests']).to.deep.equal({ 'foo': { - 'baseUrl': 'https://example.com/remote', + 'baseUrl': 'https://example.test/remote', }, }); expect(config['triggers']).to.deep.equal([ @@ -352,7 +352,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { it('fails for inline optout config', () => { const element = getAnalyticsTag({ - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], 'optout': 'foo.bar', }); @@ -390,7 +390,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { it('fails for inlined iframePing config', () => { const element = getAnalyticsTag({ - 'element': {'foo': 'https://example.com/bar'}, + 'element': {'foo': 'https://example.test/bar'}, 'triggers': [{'on': 'visible', 'iframePing': true}], }); return expect( @@ -456,7 +456,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { }; const element = getAnalyticsTag( { - 'requests': {'foo': 'https://example.com/bar'}, + 'requests': {'foo': 'https://example.test/bar'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], transport: { image: false, @@ -480,7 +480,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { const element = getAnalyticsTag( { 'vars': {'title': 'local'}, - 'requests': {'foo': 'https://example.com/${title}'}, + 'requests': {'foo': 'https://example.test/${title}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -514,7 +514,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { const element = getAnalyticsTag( { 'vars': {'title': 'local'}, - 'requests': {'foo': 'https://example.com/${title}'}, + 'requests': {'foo': 'https://example.test/${title}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { @@ -532,7 +532,7 @@ describes.realWin.skip('AnalyticsConfig', {amp: false}, env => { const element = getAnalyticsTag( { 'vars': {'title': 'local'}, - 'requests': {'foo': 'https://example.com/${title}'}, + 'requests': {'foo': 'https://example.test/${title}'}, 'triggers': [{'on': 'visible', 'request': 'foo'}], }, { diff --git a/extensions/amp-analytics/0.1/test/test-cookie-writer.js b/extensions/amp-analytics/0.1/test/test-cookie-writer.js index 975269abf7b7..e5fbcd6eb40f 100644 --- a/extensions/amp-analytics/0.1/test/test-cookie-writer.js +++ b/extensions/amp-analytics/0.1/test/test-cookie-writer.js @@ -235,7 +235,7 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { ).callsFake((name, id) => { return `${name}-${id}`; }); - win.location = 'https://example.com/?a=123&b=567'; + win.location = 'https://example.test/?a=123&b=567'; const cookieWriter = new CookieWriter( win, win.document.body, @@ -270,7 +270,7 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { }); it('should write cookie under eTLD+1 domain with right exp.', () => { - win.location = 'https://www.example.com/?a=123&b=567'; + win.location = 'https://www.example.test/?a=123&b=567'; const cookieWriter = new CookieWriter( win, win.document.body, @@ -284,14 +284,14 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { ); return cookieWriter.write().then(() => { expect(win.document.lastSetCookieRaw).to.equal( - 'aCookie=123; path=/; domain=example.com; ' + + 'aCookie=123; path=/; domain=example.test; ' + 'expires=Tue, 01 Jan 2019 08:00:00 GMT' ); }); }); it('should write cookie with custom expiration (number value)', () => { - win.location = 'https://www.example.com/'; + win.location = 'https://www.example.test/'; const cookieWriter = new CookieWriter( win, win.document.body, @@ -306,14 +306,14 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { ); return cookieWriter.write().then(() => { expect(win.document.lastSetCookieRaw).to.equal( - 'aCookie=testValue; path=/; domain=example.com; ' + + 'aCookie=testValue; path=/; domain=example.test; ' + 'expires=Mon, 08 Jan 2018 08:00:00 GMT' ); }); }); it('should write cookie with custom expiration (string value)', () => { - win.location = 'https://www.example.com/'; + win.location = 'https://www.example.test/'; const cookieWriter = new CookieWriter( win, win.document.body, @@ -328,14 +328,14 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { ); return cookieWriter.write().then(() => { expect(win.document.lastSetCookieRaw).to.equal( - 'aCookie=testValue; path=/; domain=example.com; ' + + 'aCookie=testValue; path=/; domain=example.test; ' + 'expires=Mon, 08 Jan 2018 08:00:00 GMT' ); }); }); it('should write cookie with custom expiration (decimal value)', () => { - win.location = 'https://www.example.com/'; + win.location = 'https://www.example.test/'; const cookieWriter = new CookieWriter( win, win.document.body, @@ -351,14 +351,14 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { return cookieWriter.write().then(() => { expect(win.document.lastSetCookieRaw).to.equal( - 'aCookie=testValue; path=/; domain=example.com; ' + + 'aCookie=testValue; path=/; domain=example.test; ' + 'expires=Mon, 08 Jan 2018 08:00:00 GMT' ); }); }); it('should write cookie with custom expiration (zero value)', () => { - win.location = 'https://www.example.com/'; + win.location = 'https://www.example.test/'; const cookieWriter = new CookieWriter( win, win.document.body, @@ -373,14 +373,14 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { ); return cookieWriter.write().then(() => { expect(win.document.lastSetCookieRaw).to.equal( - 'aCookie=testValue; path=/; domain=example.com; ' + + 'aCookie=testValue; path=/; domain=example.test; ' + 'expires=Mon, 01 Jan 2018 08:00:00 GMT' ); }); }); it('should write cookie with custom expiration (negative value)', () => { - win.location = 'https://www.example.com/'; + win.location = 'https://www.example.test/'; const cookieWriter = new CookieWriter( win, win.document.body, @@ -395,14 +395,14 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { ); return cookieWriter.write().then(() => { expect(win.document.lastSetCookieRaw).to.equal( - 'aCookie=testValue; path=/; domain=example.com; ' + + 'aCookie=testValue; path=/; domain=example.test; ' + 'expires=Mon, 25 Dec 2017 08:00:00 GMT' ); }); }); it('should write cookie with default expiration (invalid string value)', () => { - win.location = 'https://www.example.com/'; + win.location = 'https://www.example.test/'; const cookieWriter = new CookieWriter( win, win.document.body, @@ -418,7 +418,7 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { return allowConsoleError(() => { return cookieWriter.write().then(() => { expect(win.document.lastSetCookieRaw).to.equal( - 'aCookie=testValue; path=/; domain=example.com; ' + + 'aCookie=testValue; path=/; domain=example.test; ' + 'expires=Tue, 01 Jan 2019 08:00:00 GMT' ); }); @@ -426,7 +426,7 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { }); it('should not write empty cookie', () => { - win.location = 'https://www.example.com/?a=123&b=567'; + win.location = 'https://www.example.test/?a=123&b=567'; const cookieWriter = new CookieWriter( win, win.document.body, @@ -445,7 +445,7 @@ describes.fakeWin('amp-analytics.cookie-writer value', {amp: true}, env => { }); it('should not write cookie if macro is mal-formatted', () => { - win.location = 'https://www.example.com/?a=123&b=567'; + win.location = 'https://www.example.test/?a=123&b=567'; const cookieWriter = new CookieWriter( win, win.document.body, diff --git a/extensions/amp-analytics/0.1/test/test-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/test/test-iframe-transport-message-queue.js index 6a169e15829a..d28361c6f605 100644 --- a/extensions/amp-analytics/0.1/test/test-iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/test/test-iframe-transport-message-queue.js @@ -29,7 +29,7 @@ describes.realWin( 'sandbox': 'allow-scripts', 'name': 'some_name', }); - frame.src = 'http://example.com'; + frame.src = 'http://example.test'; frame.sentinel = '42'; queue = new IframeTransportMessageQueue(env.win, frame); }); diff --git a/extensions/amp-analytics/0.1/test/test-iframe-transport.js b/extensions/amp-analytics/0.1/test/test-iframe-transport.js index 14ec432638e2..c8ef17896b5e 100644 --- a/extensions/amp-analytics/0.1/test/test-iframe-transport.js +++ b/extensions/amp-analytics/0.1/test/test-iframe-transport.js @@ -25,7 +25,7 @@ import {user} from '../../../../src/log'; describes.realWin('amp-analytics.iframe-transport', {amp: true}, env => { let iframeTransport; - const frameUrl = 'http://example.com'; + const frameUrl = 'http://example.test'; beforeEach(() => { iframeTransport = new IframeTransport( @@ -61,7 +61,7 @@ describes.realWin('amp-analytics.iframe-transport', {amp: true}, env => { }); it('enqueues event messages correctly', () => { - const url = 'https://example.com/test'; + const url = 'https://example.test/test'; const config = {iframe: url}; iframeTransport.sendRequest('hello, world!', config); const {queue} = IframeTransport.getFrameData(iframeTransport.getType()); @@ -74,8 +74,8 @@ describes.realWin('amp-analytics.iframe-transport', {amp: true}, env => { const iframeTransport2 = new IframeTransport( env.ampdoc.win, 'some_other_vendor_type', - {iframe: 'https://example.com/test2'}, - 'https://example.com/test2-2' + {iframe: 'https://example.test/test2'}, + 'https://example.test/test2-2' ); const frame1 = IframeTransport.getFrameData(iframeTransport.getType()); @@ -89,7 +89,7 @@ describes.realWin('amp-analytics.iframe-transport', {amp: true}, env => { }); it('correctly tracks usageCount and destroys iframes', () => { - const frameUrl2 = 'https://example.com/test2'; + const frameUrl2 = 'https://example.test/test2'; const iframeTransport2 = new IframeTransport( env.ampdoc.win, 'some_other_vendor_type', @@ -161,7 +161,7 @@ describes.realWin('amp-analytics.iframe-transport', {amp: true}, env => { expect(createPerformanceObserverSpy).to.not.be.called; // Create frame for a new vendor - const frameUrl2 = 'https://example.com/test2'; + const frameUrl2 = 'https://example.test/test2'; new IframeTransport( env.ampdoc.win, 'some_other_vendor_type', 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 43126b37e7e1..6f9fea0670bc 100644 --- a/extensions/amp-analytics/0.1/test/test-linker-manager.js +++ b/extensions/amp-analytics/0.1/test/test-linker-manager.js @@ -52,8 +52,8 @@ describes.realWin('Linker Manager', {amp: true}, env => { }); env.sandbox.stub(Services, 'documentInfoForDoc').returns({ - sourceUrl: 'https://amp.source.com/some/path?q=123', - canonicalUrl: 'https://www.canonical.com/some/path?q=123', + sourceUrl: 'https://amp.source.test/some/path?q=123', + canonicalUrl: 'https://www.canonical.test/some/path?q=123', }); element = doc.createElement('div'); @@ -136,7 +136,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { it('does not register anchor mutator if not on proxy', () => { windowInterface.getLocation.returns({ - origin: 'https://amp.source.com', + origin: 'https://amp.source.test', }); new LinkerManager( ampdoc, @@ -158,7 +158,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { it('registers anchor mutator if not on proxy but proxyOnly=false', () => { windowInterface.getLocation.returns({ - origin: 'https://amp.source.com', + origin: 'https://amp.source.test', }); new LinkerManager( ampdoc, @@ -188,7 +188,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { windowInterface.getUserLanguage.returns('en-US'); env.sandbox.useFakeTimers(1533329483292); env.sandbox.stub(Date.prototype, 'getTimezoneOffset').returns(420); - doc.title = 'TEST TITLE'; + doc.title = 'Test Title'; const config = { linkers: { enabled: true, @@ -213,11 +213,11 @@ describes.realWin('Linker Manager', {amp: true}, env => { return lm.init().then(() => { expect(anchorClickHandlers).to.have.length(1); expect(navigateToHandlers).to.have.length(1); - const origUrl = 'https://www.source.com/dest?a=1'; + const origUrl = 'https://www.source.test/dest?a=1'; const finalUrl = - 'https://www.source.com/dest' + + 'https://www.source.test/dest' + '?a=1' + - '&testLinker1=1*1pgvkob*_key*VEVTVCUyMFRJVExF*gclid*MjM0' + + '&testLinker1=1*drctf3*_key*VGVzdCBUaXRsZQ..*gclid*MjM0' + '&testLinker2=1*1u4ugj3*foo*YmFy'; expect(clickAnchor(origUrl)).to.equal(finalUrl); expect(navigateTo(origUrl)).to.equal(finalUrl); @@ -263,8 +263,8 @@ describes.realWin('Linker Manager', {amp: true}, env => { const lm = new LinkerManager(ampdoc, config, /* type */ null, element); return lm.init().then(() => { expect(anchorClickHandlers).to.have.length(1); - expect(clickAnchor('https://www.source.com/dest?a=1')).to.equal( - 'https://www.source.com/dest?a=1' + expect(clickAnchor('https://www.source.test/dest?a=1')).to.equal( + 'https://www.source.test/dest?a=1' ); }); }); @@ -286,7 +286,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { const lm = new LinkerManager(ampdoc, config, /* type */ null, element); return lm.init().then(() => { clock.tick(1000 * 60 * 5); // 5 minutes. - const linkerUrl = clickAnchor('https://www.source.com/dest?a=1'); + const linkerUrl = clickAnchor('https://www.source.test/dest?a=1'); windowInterface.history = {replaceState: () => {}}; windowInterface.location = {href: linkerUrl}; @@ -321,12 +321,12 @@ describes.realWin('Linker Manager', {amp: true}, env => { // testLinker1 should apply to both canonical and source // testLinker2 should not const canonicalDomainUrl = clickAnchor( - 'https://www.canonical.com/path' + 'https://www.canonical.test/path' ); expect(canonicalDomainUrl).to.contain('testLinker1='); expect(canonicalDomainUrl).to.not.contain('testLinker2='); - const sourceDomainUrl = clickAnchor('https://www.source.com/path'); + const sourceDomainUrl = clickAnchor('https://www.source.test/path'); expect(sourceDomainUrl).to.contain('testLinker1='); expect(sourceDomainUrl).to.not.contain('testLinker2='); @@ -453,11 +453,11 @@ describes.realWin('Linker Manager', {amp: true}, env => { const lm = new LinkerManager(ampdoc, config, /* type */ null, element); return lm.init().then(() => { - const url1 = clickAnchor('https://www.source.com/path'); + const url1 = clickAnchor('https://www.source.test/path'); const url2 = clickAnchor('https://foo.test.com'); const url3 = clickAnchor('https://test.com'); - const url4 = clickAnchor('https://canonical.com/path'); - const url5 = clickAnchor('https://amp.www.canonical.com/path'); + const url4 = clickAnchor('https://canonical.test/path'); + const url5 = clickAnchor('https://amp.www.canonical.test/path'); const url6 = clickAnchor('https://amp.google.com/path'); expect(url1).to.not.contain('testLinker1='); @@ -484,10 +484,10 @@ describes.realWin('Linker Manager', {amp: true}, env => { const lm = new LinkerManager(ampdoc, config, /* type */ null, element); return lm.init().then(() => { - const url1 = clickAnchor('https://www.source.com/path'); - const url2 = clickAnchor('https://amp.www.source.com/path'); - const url3 = clickAnchor('https://canonical.com/path'); - const url4 = clickAnchor('https://amp.www.canonical.com/path'); + const url1 = clickAnchor('https://www.source.test/path'); + const url2 = clickAnchor('https://amp.www.source.test/path'); + const url3 = clickAnchor('https://canonical.test/path'); + const url4 = clickAnchor('https://amp.www.canonical.test/path'); const url5 = clickAnchor('https://amp.google.com/path'); expect(url1).to.contain('testLinker1='); @@ -501,7 +501,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { it('should respect proxyOnly config', () => { windowInterface.getLocation.returns({ - origin: 'https://amp.source.com', + origin: 'https://amp.source.test', }); const config = { linkers: { @@ -523,7 +523,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { const lm = new LinkerManager(ampdoc, config, /* type */ null, element); return lm.init().then(() => { - const a = clickAnchor('https://www.source.com/path'); + const a = clickAnchor('https://www.source.test/path'); expect(a).to.contain('testLinker1='); expect(a).to.not.contain('testLinker2='); }); @@ -531,7 +531,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { it('proxyOnly should default to true', () => { windowInterface.getLocation.returns({ - origin: 'https://amp.source.com', + origin: 'https://amp.source.test', }); const config = { linkers: { @@ -552,7 +552,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { const lm = new LinkerManager(ampdoc, config, /* type */ null, element); return lm.init().then(() => { - const a = clickAnchor('https://www.source.com'); + const a = clickAnchor('https://www.source.test'); expect(a).to.not.contain('testLinker1='); expect(a).to.contain('testLinker2='); }); @@ -587,9 +587,9 @@ describes.realWin('Linker Manager', {amp: true}, env => { beforeEach(() => { windowInterface.getLocation.returns({ - origin: 'https://amp.source.com', + origin: 'https://amp.source.test', }); - windowInterface.getHostname.returns('amp.source.com'); + windowInterface.getHostname.returns('amp.source.test'); config = { linkers: { testLinker: { @@ -606,7 +606,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { it('should not add linker if same domain', () => { const lm = new LinkerManager(ampdoc, config, /* type */ null, element); return lm.init().then(() => { - const url = clickAnchor('https://amp.source.com/'); + const url = clickAnchor('https://amp.source.test/'); expect(url).to.not.contain('testLinker'); }); }); @@ -614,7 +614,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { it('should add linker if subdomain is different but friendly', () => { const lm = new LinkerManager(ampdoc, config, /* type */ null, element); return lm.init().then(() => { - const url = clickAnchor('https://m.source.com/'); + const url = clickAnchor('https://m.source.test/'); expect(url).to.contain('testLinker'); }); }); @@ -628,13 +628,13 @@ describes.realWin('Linker Manager', {amp: true}, env => { ids: { foo: 'bar', }, - destinationDomains: ['amp.source.com'], + destinationDomains: ['amp.source.test'], }, }, }; const lm = new LinkerManager(ampdoc, config, /* type */ null, element); return lm.init().then(() => { - const url = clickAnchor('https://amp.source.com/'); + const url = clickAnchor('https://amp.source.test/'); expect(url).to.contain('testLinker'); }); }); @@ -644,7 +644,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { return lm.init().then(() => { const a = { href: '#hello', - hostname: 'amp.source.com', + hostname: 'amp.source.test', }; anchorClickHandlers.forEach(handler => handler(a, {type: 'click'})); expect(a.href).to.not.contain('testLinker'); @@ -656,7 +656,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { return lm.init().then(() => { const a = { href: '/foo', - hostname: 'amp.source.com', + hostname: 'amp.source.test', }; anchorClickHandlers.forEach(handler => handler(a, {type: 'click'})); expect(a.href).to.not.contain('testLinker'); @@ -683,7 +683,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { }; const lm = new LinkerManager(ampdoc, config, 'googleanalytics', element); return lm.init().then(() => { - const a = clickAnchor('https://www.source.com/path'); + const a = clickAnchor('https://www.source.test/path'); expect(a).to.contain('testLinker1='); }); }); @@ -713,7 +713,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { element ).init(); return Promise.all([p1, p2]).then(() => { - const a = clickAnchor('https://www.source.com/path'); + const a = clickAnchor('https://www.source.test/path'); expect(a).to.not.match(/(testLinker1=.*){2}/); }); }); @@ -969,7 +969,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { it('should add multiple linker data to one form if not action-xhr', () => { windowInterface.getLocation.returns({ - origin: 'https://www.ampbyexample.com', + origin: 'https://www.example.test', }); const manager1 = new LinkerManager( @@ -1011,7 +1011,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { return Promise.all([p1, p2]).then(() => { const form = createForm(); - form.setAttribute('action', 'https://www.source.com'); + form.setAttribute('action', 'https://www.source.test'); const setterSpy = env.sandbox.spy(); manager1.handleFormSubmit_({form, actionXhrMutator: setterSpy}); manager2.handleFormSubmit_({form, actionXhrMutator: setterSpy}); @@ -1045,13 +1045,15 @@ describes.realWin('Linker Manager', {amp: true}, env => { describe('areFriendlyDomains', () => { it('should work', () => { - 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.test', 'www.source.test')).to.be.true; + expect(areFriendlyDomains('m.source.test', 'www.source.test')).to.be.true; + expect(areFriendlyDomains('amp.www.source.test', 'source.test')).to.be.true; + expect(areFriendlyDomains('amp.source.test', 'm.www.source.test')).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 + expect(areFriendlyDomains('amp.source.test', 'amp.google.test')).to.be + .false; + expect(areFriendlyDomains('web.amp.source.test', 'web.m.source.test')).to.be .false; }); }); diff --git a/extensions/amp-analytics/0.1/test/test-requests.js b/extensions/amp-analytics/0.1/test/test-requests.js index bfcd34e7b849..09826c045080 100644 --- a/extensions/amp-analytics/0.1/test/test-requests.js +++ b/extensions/amp-analytics/0.1/test/test-requests.js @@ -65,33 +65,33 @@ describes.realWin('Requests', {amp: 1}, env => { }); it('should prepend request origin', function*() { - const r = {'baseUrl': '/r1', 'origin': 'http://example.com'}; + const r = {'baseUrl': '/r1', 'origin': 'http://example.test'}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); handler.send({}, {}, expansionOptions, {}); yield macroTask(); - expect(spy).to.be.calledWith('http://example.com/r1'); + expect(spy).to.be.calledWith('http://example.test/r1'); }); it('handle trailing slash in request origin', function*() { - const r = {'baseUrl': '/r1', 'origin': 'http://example.com/'}; + const r = {'baseUrl': '/r1', 'origin': 'http://example.test/'}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); handler.send({}, {}, expansionOptions, {}); yield macroTask(); - expect(spy).to.be.calledWith('http://example.com/r1'); + expect(spy).to.be.calledWith('http://example.test/r1'); }); it('handle trailing path in request origin', function*() { - const r = {'baseUrl': '/r1', 'origin': 'http://example.com/test'}; + const r = {'baseUrl': '/r1', 'origin': 'http://example.test/test'}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); handler.send({}, {}, expansionOptions, {}); yield macroTask(); - expect(spy).to.be.calledWith('http://example.com/r1'); + expect(spy).to.be.calledWith('http://example.test/r1'); }); it('handle empty requestOrigin', function*() { @@ -117,20 +117,20 @@ describes.realWin('Requests', {amp: 1}, env => { it('handle baseUrl with no leading slash', function*() { const r = { 'baseUrl': 'r1', - 'origin': 'https://requestorigin.com', + 'origin': 'https://requestorigin.test', }; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); handler.send({}, {}, expansionOptions, {}); yield macroTask(); - expect(spy).to.be.calledWith('https://requestorigin.comr1'); + expect(spy).to.be.calledWith('https://requestorigin.testr1'); }); it('prepend request origin to absolute baseUrl', function*() { const r = { - 'baseUrl': 'https://baseurl.com', - 'origin': 'https://requestorigin.com', + 'baseUrl': 'https://baseurl.test', + 'origin': 'https://requestorigin.test', }; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); @@ -138,7 +138,7 @@ describes.realWin('Requests', {amp: 1}, env => { handler.send({}, {}, expansionOptions, {}); yield macroTask(); expect(spy).to.be.calledWith( - 'https://requestorigin.comhttps://baseurl.com' + 'https://requestorigin.testhttps://baseurl.test' ); }); @@ -161,12 +161,12 @@ describes.realWin('Requests', {amp: 1}, env => { const r = {'baseUrl': '/r2', 'origin': '${documentReferrer}'}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({ - 'documentReferrer': 'http://example.com', + 'documentReferrer': 'http://example.test', }); handler.send({}, {}, expansionOptions, {}); yield macroTask(); - expect(spy).to.be.calledWith('http://example.com/r2'); + expect(spy).to.be.calledWith('http://example.test/r2'); }); it('should expand nested request origin', function*() { @@ -174,12 +174,12 @@ describes.realWin('Requests', {amp: 1}, env => { const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({ 'a': '${b}', - 'b': 'http://example.com', + 'b': 'http://example.test', }); handler.send({}, {}, expansionOptions, {}); yield macroTask(); - expect(spy).to.be.calledWith('http://example.com/r3'); + expect(spy).to.be.calledWith('http://example.test/r3'); }); }); 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 23196d9e67ae..9aeb1e30693c 100644 --- a/extensions/amp-analytics/0.1/test/test-resource-timing.js +++ b/extensions/amp-analytics/0.1/test/test-resource-timing.js @@ -26,11 +26,11 @@ export function newResourceTimingSpec() { return { 'resources': { 'foo_bar': { - 'host': '(foo|bar).example.com', + 'host': '(foo|bar).example.test', 'path': '/lib.js', }, 'foo_style': { - 'host': 'example.com', + 'host': 'example.test', 'path': '.*.css', }, }, @@ -89,6 +89,7 @@ export function newPerformanceResourceTiming( describes.realWin('resourceTiming', {amp: true}, env => { let win; let ampdoc; + let element; /** * @param {!Array { expectedResult ) { env.sandbox.stub(win.performance, 'getEntriesByType').returns(fakeEntries); - return getResourceTiming(ampdoc, resourceTimingSpec, Date.now()).then( + return getResourceTiming(element, resourceTimingSpec, Date.now()).then( result => { expect(result).to.equal(expectedResult); } @@ -112,12 +113,15 @@ describes.realWin('resourceTiming', {amp: true}, env => { beforeEach(() => { win = env.win; ampdoc = env.ampdoc; + element = document.createElement('amp-analytics'); + element.getAmpDoc = () => ampdoc; + env.win.document.body.appendChild(element); installVariableServiceForTesting(ampdoc); installLinkerReaderService(win); }); it('should return empty if the performance API is not supported', () => { - return getResourceTiming(ampdoc, newResourceTimingSpec(), Date.now()).then( + return getResourceTiming(element, newResourceTimingSpec(), Date.now()).then( result => { expect(result).to.equal(''); } @@ -126,7 +130,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should return empty when resource timing is not supported', () => { // Performance API (ampdoc.performance) doesn't support resource timing. - return getResourceTiming(ampdoc, newResourceTimingSpec(), Date.now()).then( + return getResourceTiming(element, newResourceTimingSpec(), Date.now()).then( result => { expect(result).to.equal(''); } @@ -135,7 +139,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should return empty when start time has passed 1s', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -144,14 +148,16 @@ describes.realWin('resourceTiming', {amp: true}, env => { ); const spec = newResourceTimingSpec(); env.sandbox.stub(win.performance, 'getEntriesByType').returns([entry]); - return getResourceTiming(win, spec, Date.now() - 60 * 1000).then(result => { - expect(result).to.equal(''); - }); + return getResourceTiming(element, 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', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -163,7 +169,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should return empty if encoding spec is empty', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -177,7 +183,7 @@ 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', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -191,7 +197,7 @@ 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', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -205,7 +211,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should serialize matching entries', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -219,7 +225,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should serialize multiple matching entries', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -227,7 +233,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { false ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', + 'http://bar.example.test/lib.js', 'script', 700, 100, @@ -243,7 +249,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should match against the first spec', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -255,7 +261,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { // Note that both spec'd resources match. spec.resources = { 'foo_bar': { - 'host': '(foo|bar).example.com', + 'host': '(foo|bar).example.test', 'path': '/lib.js', }, 'any': {}, @@ -266,7 +272,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should accept empty per-resource specs', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -279,7 +285,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { spec.resources = { 'any': {}, 'foo_bar': { - 'host': '(foo|bar).example.com', + 'host': '(foo|bar).example.test', 'path': '/lib.js', }, }; @@ -289,7 +295,7 @@ 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', + 'http://foo.example.test/lib.js', 'script', 100, 500, @@ -297,7 +303,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { false ); const entry2 = newPerformanceResourceTiming( - 'http://baz.example.com/lib.js', + 'http://baz.example.test/lib.js', 'script', 700, 100, @@ -306,13 +312,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { ); const spec = newResourceTimingSpec(); - spec.resources = {'foo': {'host': 'foo.example.com'}}; + spec.resources = {'foo': {'host': 'foo.example.test'}}; return runSerializeTest([entry1, entry2], spec, 'foo-script-100-500-7200'); }); it('should should only report resources if the path matches', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js', + 'http://foo.example.test/lib.js', 'script', 100, 500, @@ -320,7 +326,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { false ); const entry2 = newPerformanceResourceTiming( - 'http://foo.example.com/extra.js', + 'http://foo.example.test/extra.js', 'script', 700, 100, @@ -330,14 +336,14 @@ describes.realWin('resourceTiming', {amp: true}, env => { const spec = newResourceTimingSpec(); spec.resources = { - 'foo': {'host': 'foo.example.com', 'path': 'lib.js'}, + 'foo': {'host': 'foo.example.test', 'path': 'lib.js'}, }; return runSerializeTest([entry1, entry2], spec, 'foo-script-100-500-7200'); }); it('should should only report resources if the query matches', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=200', + 'http://foo.example.test/lib.js?v=200', 'script', 100, 500, @@ -345,7 +351,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { false ); const entry2 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=test', + 'http://foo.example.test/lib.js?v=test', 'script', 700, 100, @@ -356,7 +362,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { const spec = newResourceTimingSpec(); spec.resources = { 'foo': { - 'host': 'foo.example.com', + 'host': 'foo.example.test', 'path': 'lib.js', 'query': '^\\?v=\\d+', }, @@ -366,7 +372,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${key} and ${initiatorType}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', + 'http://foo.example.test/style.css?v=200', 'link', 100, 500, @@ -380,7 +386,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${startTime} and ${duration}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', + 'http://foo.example.test/style.css?v=200', 'link', 100, 500, @@ -394,7 +400,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${domainLookupTime} and ${tcpConnectTime}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', + 'http://foo.example.test/style.css?v=200', 'link', 100, 500, @@ -408,7 +414,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${serverResponseTime} and ${networkTransferTime}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', + 'http://foo.example.test/style.css?v=200', 'link', 100, 500, @@ -422,7 +428,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${transferSize}, ${encodedBodySize}, ${decodedBodySize}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', + 'http://foo.example.test/style.css?v=200', 'link', 100, 500, @@ -437,7 +443,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should use the base specified in encoding', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', + 'http://foo.example.test/style.css?v=200', 'link', 100, 500, @@ -453,7 +459,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should reject invalid bases (over 36)', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', + 'http://foo.example.test/style.css?v=200', 'link', 100, 500, @@ -470,7 +476,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should not replace other analytics variables', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -478,7 +484,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { false ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', + 'http://bar.example.test/lib.js', 'script', 700, 100, @@ -493,7 +499,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should URL-encode the results', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 500, @@ -501,7 +507,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { false ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', + 'http://bar.example.test/lib.js', 'script', 700, 100, @@ -520,7 +526,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should only include resources downloaded after `responseAfter`', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', + 'http://foo.example.test/lib.js?v=123', 'script', 100, 200, @@ -528,7 +534,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { false ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', + 'http://bar.example.test/lib.js', 'script', 200, 200, @@ -536,7 +542,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { true ); const entry3 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', + 'http://bar.example.test/lib.js', 'script', 300, 200, @@ -556,7 +562,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should reject invalid (non-numeric) responseAfter fields', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', + 'http://foo.example.test/style.css?v=200', 'link', 100, 500, @@ -572,7 +578,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should update responseAfter', () => { const initialEntry = newPerformanceResourceTiming( - 'https://example.com/lib.css', + 'https://example.test/lib.css', 'link', 100, 400, @@ -580,7 +586,7 @@ describes.realWin('resourceTiming', {amp: true}, env => { false ); const laterEntry = newPerformanceResourceTiming( - 'https://bar.example.com/lib.js', + 'https://bar.example.test/lib.js', 'script', 200, 500, @@ -604,13 +610,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${initiatorType}.${startTime}.${duration}'; - return getResourceTiming(ampdoc, spec, Date.now()) + return getResourceTiming(element, 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(ampdoc, spec, Date.now()); + return getResourceTiming(element, spec, Date.now()); }) .then(result => { expect(result).to.equal('script.200.500'); @@ -650,7 +656,7 @@ 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', + 'http://foo.example.test/style.css?v=200', 'link', 100, 500, diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index c4a5bab7c65c..bc2c1a559a5e 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -52,53 +52,53 @@ describes.realWin( it('prefers beacon over xhrpost and image', () => { setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { + sendRequest(win, 'https://example.test/test', { beacon: true, xhrpost: true, image: true, }); - expectBeacon('https://example.com/test', ''); + expectBeacon('https://example.test/test', ''); expectNoXhr(); expectNoImagePixel(); }); it('prefers xhrpost over image', () => { setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { + sendRequest(win, 'https://example.test/test', { beacon: false, xhrpost: true, image: true, }); expectNoBeacon(); - expectXhr('https://example.com/test', ''); + expectXhr('https://example.test/test', ''); expectNoImagePixel(); }); it('reluctantly uses image if nothing else is enabled', () => { setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { + sendRequest(win, 'https://example.test/test', { image: true, }); expectNoBeacon(); - expectImagePixel('https://example.com/test'); + expectImagePixel('https://example.test/test'); expectNoXhr(); }); it('falls back to image setting suppressWarnings to true', () => { setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { + sendRequest(win, 'https://example.test/test', { beacon: false, xhrpost: false, image: {suppressWarnings: true}, }); expectNoBeacon(); expectNoXhr(); - expectImagePixel('https://example.com/test'); + expectImagePixel('https://example.test/test'); }); it('falls back to image setting referrerPolicy', () => { setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { + sendRequest(win, 'https://example.test/test', { beacon: true, xhrpost: true, image: true, @@ -106,48 +106,48 @@ describes.realWin( }); expectNoBeacon(); expectNoXhr(); - expectImagePixel('https://example.com/test', 'no-referrer'); + expectImagePixel('https://example.test/test', 'no-referrer'); }); it('falls back to xhrpost when enabled and beacon is not available', () => { setupStubs(false, true); - sendRequest(win, 'https://example.com/test', { + sendRequest(win, 'https://example.test/test', { beacon: true, xhrpost: true, image: true, }); expectNoBeacon(); - expectXhr('https://example.com/test', ''); + expectXhr('https://example.test/test', ''); expectNoImagePixel(); }); it('falls back to image when beacon not found and xhr disabled', () => { setupStubs(false, true); - sendRequest(win, 'https://example.com/test', { + sendRequest(win, 'https://example.test/test', { beacon: true, xhrpost: false, image: true, }); expectNoBeacon(); expectNoXhr(); - expectImagePixel('https://example.com/test'); + expectImagePixel('https://example.test/test'); }); it('falls back to image when beacon and xhr are not available', () => { setupStubs(false, false); - sendRequest(win, 'https://example.com/test', { + sendRequest(win, 'https://example.test/test', { beacon: true, xhrpost: true, image: true, }); expectNoBeacon(); expectNoXhr(); - expectImagePixel('https://example.com/test'); + expectImagePixel('https://example.test/test'); }); it('does not send a request when no transport methods are enabled', () => { setupStubs(true, true); - sendRequest(win, 'https://example.com/test', {}); + sendRequest(win, 'https://example.test/test', {}); expectNoBeacon(); expectNoXhr(); expectNoImagePixel(); @@ -318,7 +318,7 @@ describes.realWin( it('asserts that urls are https', () => { allowConsoleError(() => { expect(() => { - sendRequest(win, 'http://example.com/test', {image: true}); + sendRequest(win, 'http://example.test/test', {image: true}); }).to.throw(/https/); }); }); @@ -361,7 +361,7 @@ describes.realWin( it('iframe asserts that urls are https', () => { allowConsoleError(() => { expect(() => { - sendRequestUsingIframe(win, 'http://example.com/test'); + sendRequestUsingIframe(win, 'http://example.test/test'); }).to.throw(/https/); }); }); @@ -369,12 +369,12 @@ describes.realWin( it('forbids same origin', () => { const fakeWin = { location: { - href: 'https://example.com/abc', + href: 'https://example.test/abc', }, }; allowConsoleError(() => { expect(() => { - sendRequestUsingIframe(fakeWin, 'https://example.com/123'); + sendRequestUsingIframe(fakeWin, 'https://example.test/123'); }).to.throw(/Origin of iframe request/); }); }); diff --git a/extensions/amp-analytics/0.1/test/test-variables.js b/extensions/amp-analytics/0.1/test/test-variables.js index 5aac712754b1..7ee3cb294b50 100644 --- a/extensions/amp-analytics/0.1/test/test-variables.js +++ b/extensions/amp-analytics/0.1/test/test-variables.js @@ -28,6 +28,8 @@ import { linkerReaderServiceFor, } from '../linker-reader'; +const fakeElement = document.documentElement; + describes.fakeWin('amp-analytics.VariableService', {amp: true}, env => { let variables; @@ -58,28 +60,34 @@ describes.fakeWin('amp-analytics.VariableService', {amp: true}, env => { 'c': 'https://www.google.com/a?b=1&c=2', }; - function check(template, expected, vars) { - const actual = variables.expandTemplateSync( - template, - new ExpansionOptions(vars) - ); - expect(actual).to.equal(expected); + function check(template, expected, vars, opt_freeze) { + const expansion = new ExpansionOptions(vars); + if (opt_freeze) { + expansion.freezeVar(opt_freeze); + } + const actual = variables.expandTemplate(template, expansion, fakeElement); + return expect(actual).to.eventually.equal(expected); } it('expands nested vars (encode once)', () => { - check('${a}', 'https%3A%2F%2Fwww.google.com%2Fa%3Fb%3D1%26c%3D2', vars); + return check( + '${a}', + 'https%3A%2F%2Fwww.google.com%2Fa%3Fb%3D1%26c%3D2', + vars + ); }); it('expands nested vars (no encode)', () => { - const actual = variables.expandTemplateSync( + const actual = variables.expandTemplate( '${a}', - new ExpansionOptions(vars, undefined, true) + new ExpansionOptions(vars, undefined, true), + fakeElement ); - expect(actual).to.equal('https://www.google.com/a?b=1&c=2'); + expect(actual).to.eventually.equal('https://www.google.com/a?b=1&c=2'); }); it('expands complicated string', () => { - check('${foo}', 'HELLO%2FWORLD%2BWORLD%2BHELLO%2BHELLO', { + return check('${foo}', 'HELLO%2FWORLD%2BWORLD%2BHELLO%2BHELLO', { 'foo': '${a}+${b}+${c}+${hello}', 'a': '${hello}/${world}', 'b': '${world}', @@ -90,57 +98,105 @@ describes.fakeWin('amp-analytics.VariableService', {amp: true}, env => { }); it('expands zeros', () => { - check('${zero}', '0', {'zero': 0}); + return check('${zero}', '0', {'zero': 0}); }); it('drops unknown vars', () => { - check('a=${known}&b=${unknown}', 'a=KNOWN&b=', {'known': 'KNOWN'}); + return check('a=${known}&b=${unknown}', 'a=KNOWN&b=', {'known': 'KNOWN'}); }); it('does not expand macros', () => { - check('MACRO(a,b)', 'MACRO(a,b)', {}); + return check('MACRO(a,b)', 'MACRO(a,b)', {}); }); - it('supports macro args', () => { - check('${foo}', 'AAA(BBB(1))', { - 'foo': 'AAA(BBB(1))', - }); - - // TODO: fix this, should be 'AAA(BBB(1,2))' - check('${foo}', 'AAA(BBB(1%2C2))', { - 'foo': 'AAA(BBB(1,2))', - }); - - check('${foo}&${bar(3,4)}', 'FOO(1,2)&BAR(3,4)', { - '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)', - 'all': '${a(1,2)}&${b(3,4)}&${c}&${d}', + it('does not handle nested macros using ${} syntax', () => { + // VariableService.expandTemplate's regex cannot parse nested ${}. + return check('${a${b}}', '}', { + 'a': 'TIMESTAMP', + 'b': 'TIMESTAMP', }); }); + it('supports macro args', () => + check('${foo}', 'AAA(BBB(1))', { + 'foo': 'AAA(BBB(1))', + }) + .then(() => + // This is a result of `getNameArgs` strange behavior. Leaving here as + // pseudo documentation. We mostly avoid this problem, as now we expand any + // valid macros when they are seen in `Variables#expandTemplate`. + check('${foo}', 'AAA(BBB(1%2C2))', { + 'foo': 'AAA(BBB(1,2))', + }) + ) + .then(() => + check('${foo}', 'true', { + 'foo': '$EQUALS($SUBSTR(zyxabc,3),abc)', + }) + ) + .then(() => { + env.sandbox.useFakeTimers(123456789); + + // Arguments (3,4) and (5,TIMESTAMP) do not include parenthesis, + // so they are parsed and encoded correctly when sent to urlReplacements + return check( + '${foo}&${bar(3,4)}&${bar(5,TIMESTAMP)}', + 'FOO(1,2)&4&123456789', + { + 'foo': 'FOO(1,2)', + 'bar': 'QUERY_PARAM', + } + ); + }) + .then(() => + // Macros that take additonal arugments in the arglist (after expansion), + // and getNameArgs doesn't handle them + check( + '${foo}&${bar(2,$TOUPPERCASE(lowercase)}&${bar(5,QUERY_PARAM(6,7))}&${baz($NOT(true))}', + 'FOO(3,4)&(lowercase)(lowercase)&&', + { + 'foo': 'FOO(3,4)', + 'bar': 'QUERY_PARAM', + 'baz': '$TOUPPERCASE', + } + ) + ) + .then(() => + // See comment about getNameArgs above. + check('${all}', '2%264', { + 'a': 'QUERY_PARAM', + 'b': 'QUERY_PARAM(3,4)', + 'all': '${a(1,2)}&${b}', + }) + ) + .then(() => + check('${all}&${c}&${d}', 'CCC(5%2C6)%26DDD(7,8)&CCC(5,6)&DDD(7,8)', { + 'c': 'CCC(5,6)', + 'd': 'DDD(7,8)', + 'all': '${c}&${d}', + }) + ) + .then(() => + check('${nested}', 'default', { + 'nested': '${deeper}', + 'deeper': '$IF(true, QUERY_PARAM(foo, default), never)', + }) + )); + it('respect freeze variables', () => { - const vars = new ExpansionOptions({ - 'fooParam': 'QUERY_PARAM', - 'freeze': 'error', - }); - vars.freezeVar('freeze'); - const actual = variables.expandTemplateSync( + return check( '${fooParam(foo,bar)}${nonfreeze}${freeze}', - vars + 'bar${freeze}', + { + 'fooParam': 'QUERY_PARAM', + 'freeze': 'error', + }, + 'freeze' ); - expect(actual).to.equal('QUERY_PARAM(foo,bar)${freeze}'); }); it('expands array vars', () => { - check( + return check( '${array}', '123,xy%26x,MACRO(abc,def),MACRO(abc%2Cdef)%26123,bar,', { @@ -164,7 +220,7 @@ describes.fakeWin('amp-analytics.VariableService', {amp: true}, env => { }); it('handles empty var name', () => { - check('${}', '', {}); + return check('${}', '', {}); }); describe('should handle recursive vars', () => { @@ -179,18 +235,19 @@ describes.fakeWin('amp-analytics.VariableService', {amp: true}, env => { expectAsyncConsoleError( /Maximum depth reached while expanding variables/ ); - check('${1}', '123%24%7B4%7D', recursiveVars); + return check('${1}', '123%24%7B4%7D', recursiveVars); }); it('customize recursions to 5', () => { expectAsyncConsoleError( /Maximum depth reached while expanding variables/ ); - const actual = variables.expandTemplateSync( + const actual = variables.expandTemplate( '${1}', - new ExpansionOptions(recursiveVars, 5) + new ExpansionOptions(recursiveVars, 5), + fakeElement ); - expect(actual).to.equal('123412%24%7B3%7D'); + return expect(actual).to.eventually.equal('123412%24%7B3%7D'); }); }); }); @@ -224,7 +281,7 @@ describes.fakeWin('amp-analytics.VariableService', {amp: true}, env => { it('handles consecutive macros in inner arguments', () => { env.sandbox.useFakeTimers(123456789); - win.location.href = 'https://example.com/?test=yes'; + win.location.href = 'https://example.test/?test=yes'; return check( '$IF(QUERY_PARAM(test), 1.$SUBSTR(TIMESTAMP, 0, 10)QUERY_PARAM(test), ``)', '1.123456789yes' @@ -233,7 +290,7 @@ describes.fakeWin('amp-analytics.VariableService', {amp: true}, env => { it('handles consecutive macros w/o parens in inner arguments', () => { env.sandbox.useFakeTimers(123456789); - win.location.href = 'https://example.com/?test=yes'; + win.location.href = 'https://example.test/?test=yes'; return check('$IF(QUERY_PARAM(test), 1.TIMESTAMP, ``)', '1.123456789'); }); @@ -244,7 +301,7 @@ describes.fakeWin('amp-analytics.VariableService', {amp: true}, env => { it('should not trim right of string before macro', () => { env.sandbox.useFakeTimers(123456789); - win.location.href = 'https://example.com/?test=yes'; + win.location.href = 'https://example.test/?test=yes'; return check( '$IF(QUERY_PARAM(test), foo TIMESTAMP, ``)', 'foo%20123456789' @@ -405,6 +462,33 @@ describes.fakeWin('amp-analytics.VariableService', {amp: true}, env => { doc.cookie = ''; }); + it('should replace FIRST_CONTENTFUL_PAINT', () => { + env.sandbox.stub(Services, 'performanceFor').returns({ + getFirstContentfulPaint() { + return Promise.resolve(1); + }, + }); + return check('FIRST_CONTENTFUL_PAINT', '1'); + }); + + it('should replace FIRST_VIEWPORT_READY', () => { + env.sandbox.stub(Services, 'performanceFor').returns({ + getFirstViewportReady() { + return Promise.resolve(1); + }, + }); + return check('FIRST_VIEWPORT_READY', '1'); + }); + + it('should replace MAKE_BODY_VISIBLE', () => { + env.sandbox.stub(Services, 'performanceFor').returns({ + getMakeBodyVisible() { + return Promise.resolve(1); + }, + }); + return check('MAKE_BODY_VISIBLE', '1'); + }); + describe('$MATCH', () => { it('handles default index', () => { return check('$MATCH(thisisatest, thisisatest)', 'thisisatest'); diff --git a/extensions/amp-analytics/0.1/test/test-vendors-new.js b/extensions/amp-analytics/0.1/test/test-vendors-new.js index 3cd2ec937693..a7ab0cda26c6 100644 --- a/extensions/amp-analytics/0.1/test/test-vendors-new.js +++ b/extensions/amp-analytics/0.1/test/test-vendors-new.js @@ -16,7 +16,7 @@ import {AmpAnalytics} from '../amp-analytics'; import {AnalyticsConfig} from '../config'; -import {ExpansionOptions} from '../variables'; +import {ExpansionOptions, variableServiceForDoc} from '../variables'; import {IFRAME_TRANSPORTS} from '../iframe-transport-vendors'; import { ImagePixelVerifier, @@ -40,6 +40,7 @@ describes.realWin( function(env) { let win, doc; let requestVerifier; + let elementMacros; beforeEach(() => { win = env.win; @@ -47,6 +48,10 @@ describes.realWin( const wi = mockWindowInterface(env.sandbox); wi.getLocation.returns(win.location); requestVerifier = new ImagePixelVerifier(wi); + elementMacros = { + 'COOKIE': null, + 'CONSENT_STATE': null, + }; }); describe('Should not contain iframe transport if not whitelisted', () => { @@ -104,6 +109,32 @@ describes.realWin( config = analytics.config_; done(); }); + + // Have to get service after analytics element is created + const variableService = variableServiceForDoc(doc); + + window.sandbox + .stub(variableService, 'getMacros') + .callsFake(function() { + // Add all the macros in amp-analytics + const merged = {...this.macros_, ...elementMacros}; + + // Change the resolving function + const keys = Object.keys(merged); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + merged[key] = (...args) => { + let params = args + .filter(val => val !== undefined) + .join(','); + if (params) { + params = '(' + params + ')'; + } + return `_${key.replace('$', '').toLowerCase()}${params}_`; + }; + } + return /** @type {!JsonObject} */ (merged); + }); }); it('test requests', function*() { @@ -161,8 +192,8 @@ describes.realWin( // Write this out for easy copy pasting. if (url !== val) { throw new Error( - `Vendor ${vendor}, request ${name} doesn't match` + - `Expected value ${val}, get value ${url}` + `Vendor ${vendor}, request ${name} doesn't match. ` + + `Expected value ${val}, get value ${url}.` ); } expect(url).to.equal(val); diff --git a/extensions/amp-analytics/0.1/test/test-vendors.js b/extensions/amp-analytics/0.1/test/test-vendors.js index 21661b040b7d..d93d7c9d123a 100644 --- a/extensions/amp-analytics/0.1/test/test-vendors.js +++ b/extensions/amp-analytics/0.1/test/test-vendors.js @@ -16,7 +16,7 @@ import {ANALYTICS_CONFIG} from '../vendors'; import {AmpAnalytics} from '../amp-analytics'; -import {ExpansionOptions} from '../variables'; +import {ExpansionOptions, variableServiceForDoc} from '../variables'; //import {IFRAME_TRANSPORTS} from '../iframe-transport-vendors'; import { ImagePixelVerifier, @@ -61,6 +61,7 @@ describes.realWin.skip( function(env) { let win, doc; let requestVerifier; + let elementMacros; beforeEach(() => { win = env.win; @@ -68,6 +69,10 @@ describes.realWin.skip( const wi = mockWindowInterface(env.sandbox); wi.getLocation.returns(win.location); requestVerifier = new ImagePixelVerifier(wi); + elementMacros = { + 'COOKIE': null, + 'CONSENT_STATE': null, + }; }); function getAnalyticsTag(config, attrs) { @@ -169,6 +174,26 @@ describes.realWin.skip( analytics.buildCallback(); yield analytics.layoutCallback(); + // Have to get service after analytics element is created + const variableService = variableServiceForDoc(doc); + + window.sandbox + .stub(variableService, 'getMacros') + .callsFake(function() { + // Add all the macros in amp-analytics + const merged = {...this.macros_, ...elementMacros}; + + // Change the resolving function + const keys = Object.keys(merged); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + merged[key] = (opt_param, opt_param2, opt_param3) => { + return `_${key.replace('$', '')}_`; + }; + } + return /** @type {!JsonObject} */ (merged); + }); + // Wait for event queue to clear. yield macroTask(); 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 04aa434270c4..2464530169c5 100644 --- a/extensions/amp-analytics/0.1/test/test-visibility-manager.js +++ b/extensions/amp-analytics/0.1/test/test-visibility-manager.js @@ -764,6 +764,7 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { it('should listen on a resource', () => { clock.tick(1); const target = win.document.createElement('div'); + target.id = 'targetElementId'; const resource = { getLayoutBox() { return {top: 10, left: 11, width: 110, height: 111}; @@ -793,6 +794,7 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { clock.tick(11); return eventPromise.then(state => { expect(state.totalVisibleTime).to.equal(10); + expect(state.elementId).to.equal('targetElementId'); expect(state.elementY).to.equal(10); expect(state.elementX).to.equal(11); expect(state.elementWidth).to.equal(110); @@ -1697,7 +1699,7 @@ describes.realWin( top.document.body.appendChild(host); shadowRoot = host.attachShadow({mode: 'open'}); shadowDoc = ampdocService.installShadowDoc( - 'https://example.com', + 'https://example.test', shadowRoot ); env.sandbox.stub(Services, 'resourcesForDoc').returns({}); diff --git a/extensions/amp-analytics/0.1/test/vendor-requests.json b/extensions/amp-analytics/0.1/test/vendor-requests.json index c9a39c8bb67c..cc4db2a3b643 100644 --- a/extensions/amp-analytics/0.1/test/vendor-requests.json +++ b/extensions/amp-analytics/0.1/test/vendor-requests.json @@ -50,7 +50,7 @@ "event": "//webcare.byside.com/BWA!webcareId/amp/signal.php?event_id=!eventId&event_label=!eventLabel&fields=!fields&webcare_id=!webcareId&bwch=!channel&lang=pt&fid=!fid&bwit=A&tuid=_client_id_&suid=&puid=_page_view_id_p_timestamp_&referrer=_document_referrer_&page=_source_url_&page=_ampdoc_url_&bwpt=_title_&bres=_viewport_width_x_viewport_height_&res=_screen_width_x_screen_height_&v=v20171116a&v=_amp_version_&viewer=_viewer_&ua=_user_agent_&r=_random_" }, "captainmetrics": { - "hit": "https://photon.captainmetrics.com/amp/?cId=_client_id_&sId=W2CX8IhWFDNI_pvEhheYkaODWeJu0zIDlM5YaCtUeKAvYaEfTW5Rhxy_13DvjsZy&ts=_timestamp_&rand=_random_&pId=!projectId&V=_amp_version_&v=1&lang=_browser_language_&ua=_user_agent_&res=_screen_height_x_screen_width_&aRes=_available_screen_height_x_available_screen_width_&off=_timezone_&tz=_timezone_code_&lp=_source_url_&ref=_external_referrer_&Ref=_ampdoc_url_&pageId=_page_view_id_" + "hit": "https://photon.captainmetrics.com/amp/?cId=_client_id_&sId=_hash(_page_view_id__client_id_)_&ts=_timestamp_&rand=_random_&pId=!projectId&V=_amp_version_&v=1&lang=_browser_language_&ua=_user_agent_&res=_screen_height_x_screen_width_&aRes=_available_screen_height_x_available_screen_width_&off=_timezone_&tz=_timezone_code_&lp=_source_url_&ref=_external_referrer_&Ref=_ampdoc_url_&pageId=_page_view_id_" }, "chartbeat": { "host": "https://ping.chartbeat.net", @@ -73,7 +73,7 @@ "comscore": { "host": "https://sb.scorecardresearch.com", "base": "https://sb.scorecardresearch.com/b?", - "pageview": "https://sb.scorecardresearch.com/b?c1=2&c2=1000001&cs_ucfr=&cs_amp_consent=&cs_pv=_page_view_id_&c12=_client_id_&rn=_random_&c8=_title_&c7=_canonical_url_&c9=_document_referrer_&cs_c7amp=_ampdoc_url_" + "pageview": "https://sb.scorecardresearch.com/b?c1=2&c2=1000001&cs_ucfr=_if(_equals(_consent_state_%2Csufficient)_%2C1)__if(_equals(_consent_state_%2Cinsufficient)_%2C0)__if(_equals(_consent_state_%2C)_%2C)_&cs_amp_consent=_consent_state_&cs_pv=_page_view_id_&c12=_client_id_&rn=_random_&c8=_title_&c7=_canonical_url_&c9=_document_referrer_&cs_c7amp=_ampdoc_url_" }, "cxense": { "host": "https://scomcluster.cxense.com", @@ -162,8 +162,7 @@ }, "ibeatanalytics": { "host": "https://ibeat.indiatimes.com", - "base": "https://ibeat.indiatimes.com/iBeat/pageTrendlogAmp.html", - "pageview": "https://ibeat.indiatimes.com/iBeat/pageTrendlogAmp.html?&h=!h&d=!h&url=!url&k=!key&ts=!time&ch=!channel&sid=!uid&at=!agentType&ref=_document_referrer_&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=_client_id_" + "track": "https://ibeat.indiatimes.com/ping-amp?url=_ampdoc_url_&sid=_client_id_&ua=_user_agent_&ref=_document_referrer_&at=_incremental_engaged_time_&tt=!totalTime&pid=_page_view_id_&d=!d&ct=!ct&pt=!pt&au=!au&ag=!ag&aid=!aid&cn=_canonical_url_&ctIds=!ctIds" }, "infonline": { "pageview": "!url?st=!st&sv=ke&ap=1&co=!co&cp=!cp&ps=!ps&host=_canonical_host_&path=_canonical_path_&type=pageview", @@ -413,11 +412,11 @@ "structEvent": "https://!collectorHost/i?url=_canonical_url_&page=_title_&res=_screen_width_x_screen_height_&stm=_timestamp_&tz=_timezone_code_&aid=!appId&p=web&tv=amp-0.3&cd=_screen_color_depth_&cs=_document_charset_&duid=_client_id_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ds=_scroll_width_x_scroll_height_&e=se&se_ca=!structEventCategory&se_ac=!structEventAction&se_la=!structEventLabel&se_pr=!structEventProperty&se_va=!structEventValue" }, "snowplow_v2": { - "basePrefix": "https://!collectorHost/i?p=web&tv=amp-1.0.0&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_query_param_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C!customContexts%2C%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D", - "pageView": "https://!collectorHost/i?p=web&tv=amp-1.0.0&e=pv&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_query_param_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C!customContexts%2C%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D", - "structEvent": "https://!collectorHost/i?p=web&tv=amp-1.0.0&e=se&se_ca=!structEventCategory&se_ac=!structEventAction&se_la=!structEventLabel&se_pr=!structEventProperty&se_va=!structEventValue&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_query_param_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C!customContexts%2C%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D", - "pagePing": "https://!collectorHost/i?p=web&tv=amp-1.0.0&e=pp&pp_mix=_scroll_left_&pp_miy=_scroll_top_&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_query_param_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C!customContexts%2C%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D", - "selfDescribingEvent": "https://!collectorHost/i?p=web&tv=amp-1.0.0&e=ue&ue_pr=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Funstruct_event%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22schema%22%3A%22iglu%3A!customEventSchemaVendor%2F!customEventSchemaName%2Fjsonschema%2F!customEventSchemaVersion%22%2C%22data%22%3A!customEventSchemaData%7D%7D&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_query_param_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C!customContexts%2C%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D" + "basePrefix": "https://!collectorHost/i?p=web&tv=amp-1.0.0&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_cookie(_sp_duid)_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C_replace(!customContexts%2C%5E%2C*(.%2B%3F)%2C*%24%2C%241%2C)_%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D", + "pageView": "https://!collectorHost/i?p=web&tv=amp-1.0.0&e=pv&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_cookie(_sp_duid)_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C_replace(!customContexts%2C%5E%2C*(.%2B%3F)%2C*%24%2C%241%2C)_%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D", + "structEvent": "https://!collectorHost/i?p=web&tv=amp-1.0.0&e=se&se_ca=!structEventCategory&se_ac=!structEventAction&se_la=!structEventLabel&se_pr=!structEventProperty&se_va=_if(!structEventValue%2C!structEventValue%2Cnull)_&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_cookie(_sp_duid)_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C_replace(!customContexts%2C%5E%2C*(.%2B%3F)%2C*%24%2C%241%2C)_%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D", + "pagePing": "https://!collectorHost/i?p=web&tv=amp-1.0.0&e=pp&pp_mix=_scroll_left_&pp_miy=_scroll_top_&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_cookie(_sp_duid)_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C_replace(!customContexts%2C%5E%2C*(.%2B%3F)%2C*%24%2C%241%2C)_%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D", + "selfDescribingEvent": "https://!collectorHost/i?p=web&tv=amp-1.0.0&e=ue&ue_pr=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Funstruct_event%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22schema%22%3A%22iglu%3A!customEventSchemaVendor%2F!customEventSchemaName%2Fjsonschema%2F!customEventSchemaVersion%22%2C%22data%22%3A!customEventSchemaData%7D%7D&url=_ampdoc_url_&page=_title_&res=_screen_width_x_screen_height_&dtm=_timestamp_&tz=_timezone_code_&aid=!appId&cd=_screen_color_depth_&cs=_document_charset_&lang=_browser_language_&refr=_document_referrer_&vp=_viewport_width_x_viewport_height_&ua=_user_agent_&ds=_scroll_width_x_scroll_height_&uid=!userId&co=%7B%22schema%22%3A%22iglu%3Acom.snowplowanalytics.snowplow%2Fcontexts%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%5B%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_id%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampClientId%22%3A%22_client_id_%22%2C%20%22domainUserid%22%3A%20%22_cookie(_sp_duid)_%22%2C%20%22userId%22%3A%20%22!userId%22%7D%7D%2C_replace(!customContexts%2C%5E%2C*(.%2B%3F)%2C*%24%2C%241%2C)_%7B%22schema%22%3A%22iglu%3Adev.amp.snowplow%2Famp_web_page%2Fjsonschema%2F1-0-0%22%2C%22data%22%3A%7B%22ampPageViewId%22%3A%22_page_view_id_64_%22%7D%7D%5D%7D" }, "teaanalytics": { "domain": "https://!channel/v1/amp", diff --git a/extensions/amp-analytics/0.1/transport.js b/extensions/amp-analytics/0.1/transport.js index 1e488dfaf720..7c64bdaf547d 100644 --- a/extensions/amp-analytics/0.1/transport.js +++ b/extensions/amp-analytics/0.1/transport.js @@ -241,6 +241,9 @@ export class Transport { * @param {string|undefined} referrerPolicy */ static sendRequestUsingImage(win, request, suppressWarnings, referrerPolicy) { + if (!win) { + return; + } const image = createPixel(win, request.url, referrerPolicy); loadPromise(image) .then(() => { diff --git a/extensions/amp-analytics/0.1/variables.js b/extensions/amp-analytics/0.1/variables.js index 224ed6399588..1246a2637d14 100644 --- a/extensions/amp-analytics/0.1/variables.js +++ b/extensions/amp-analytics/0.1/variables.js @@ -15,6 +15,7 @@ */ import {Services} from '../../../src/services'; +import {asyncStringReplace} from '../../../src/string'; import {base64UrlEncodeFromString} from '../../../src/utils/base64'; import {cookieReader} from './cookie-reader'; import {dev, devAssert, user, userAssert} from '../../../src/log'; @@ -27,7 +28,6 @@ import { } from '../../../src/service'; import {isArray, isFiniteNumber} from '../../../src/types'; import {linkerReaderServiceFor} from './linker-reader'; -import {tryResolve} from '../../../src/utils/promise'; /** @const {string} */ const TAG = 'amp-analytics/variables'; @@ -206,11 +206,24 @@ export class VariableService { '$EQUALS', (firstValue, secValue) => firstValue === secValue ); - // 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) ); + + // Was set async before + this.register_('FIRST_CONTENTFUL_PAINT', () => { + return Services.performanceFor( + this.ampdoc_.win + ).getFirstContentfulPaint(); + }); + + this.register_('FIRST_VIEWPORT_READY', () => { + return Services.performanceFor(this.ampdoc_.win).getFirstViewportReady(); + }); + + this.register_('MAKE_BODY_VISIBLE', () => { + return Services.performanceFor(this.ampdoc_.win).getMakeBodyVisible(); + }); } /** @@ -228,6 +241,9 @@ export class VariableService { } /** + * TODO (micajuineho): If we add new synchronous macros, we + * will need to split this method and getMacros into sync and + * async version (currently all macros are async). * @param {string} name * @param {*} macro */ @@ -237,22 +253,17 @@ export class VariableService { } /** - * @param {string} template The template to expand - * @param {!ExpansionOptions} options configuration to use for expansion - * @return {!Promise} The expanded string + * Converts templates from ${} format to MACRO() and resolves any platform + * level macros when encountered. + * @param {string} template The template to expand. + * @param {!ExpansionOptions} options configuration to use for expansion. + * @param {!Element} element amp-analytics element. + * @param {!JsonObject=} opt_bindings + * @param {!Object=} opt_whitelist + * @return {!Promise} The expanded string. */ - expandTemplate(template, options) { - return tryResolve(this.expandTemplateSync.bind(this, template, options)); - } - - /** - * @param {string} template The template to expand - * @param {!ExpansionOptions} options configuration to use for expansion - * @return {string} The expanded string - * @visibleForTesting - */ - expandTemplateSync(template, options) { - return template.replace(/\${([^}]*)}/g, (match, key) => { + expandTemplate(template, options, element, opt_bindings, opt_whitelist) { + return asyncStringReplace(template, /\${([^}]*)}/g, (match, key) => { if (options.iterations < 0) { user().error( TAG, @@ -277,40 +288,75 @@ export class VariableService { let value = options.getVar(name); if (typeof value == 'string') { - value = this.expandValue_(value, options); + value = this.expandValue_( + value, + options, + element, + opt_bindings, + opt_whitelist + ); } else if (isArray(value)) { // Treat each value as a template and expand for (let i = 0; i < value.length; i++) { value[i] = typeof value[i] == 'string' - ? this.expandValue_(value[i], options) + ? this.expandValue_( + value[i], + options, + element, + opt_bindings, + opt_whitelist + ) : value[i]; } } - if (!options.noEncode) { - value = encodeVars(/** @type {string|?Array} */ (value)); - } - if (value) { - value += argList; - } - return value; + const bindings = opt_bindings || this.getMacros(element); + const urlReplacements = Services.urlReplacementsForDoc(element); + + return Promise.resolve(value) + .then(value => { + if (isArray(value)) { + return Promise.all( + value.map(item => + urlReplacements.expandStringAsync(item, bindings, opt_whitelist) + ) + ); + } + return urlReplacements.expandStringAsync( + value + argList, + bindings, + opt_whitelist + ); + }) + .then(value => { + if (!options.noEncode) { + value = encodeVars(/** @type {string|?Array} */ (value)); + } + return value; + }); }); } /** * @param {string} value * @param {!ExpansionOptions} options - * @return {string} + * @param {!Element} element amp-analytics element. + * @param {!JsonObject=} opt_bindings + * @param {!Object=} opt_whitelist + * @return {Promise} */ - expandValue_(value, options) { - return this.expandTemplateSync( + expandValue_(value, options, element, opt_bindings, opt_whitelist) { + return this.expandTemplate( value, new ExpansionOptions( options.vars, options.iterations - 1, true /* noEncode */ - ) + ), + element, + opt_bindings, + opt_whitelist ); } diff --git a/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js b/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js index 27c1484cdf09..11f7ebb04b54 100644 --- a/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js @@ -19,36 +19,31 @@ import {jsonLiteral} from '../../../../src/json'; const IBEATANALYTICS_CONFIG = jsonLiteral({ '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}' + + 'track': + '${host}/ping-amp?url=${ampdocUrl}' + + '&sid=${clientId(_iibeat_session)}' + + '&ua=${userAgent}' + '&ref=${documentReferrer}' + + '&at=${incrementalEngagedTime}' + + '&tt=${totalTime}' + + '&pid=${pageViewId}' + + '&d=${d}' + + '&ct=${ct}' + + '&pt=${pt}' + + '&au=${au}' + + '&ag=${ag}' + '&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)}', + '&cn=${canonicalUrl}' + + '&ctIds=${ctIds}', }, 'triggers': { - 'defaultPageview': { - 'on': 'visible', - 'request': 'pageview', + 'trackInterval': { + 'on': 'timer', + 'timerSpec': { + 'interval': 15, + 'maxTimerLength': 7200, + }, + 'request': 'track', }, }, }); diff --git a/extensions/amp-analytics/0.1/vendors/ibeatanalytics.json b/extensions/amp-analytics/0.1/vendors/ibeatanalytics.json index 3d1bf9d07a01..735c3e546324 100644 --- a/extensions/amp-analytics/0.1/vendors/ibeatanalytics.json +++ b/extensions/amp-analytics/0.1/vendors/ibeatanalytics.json @@ -1,13 +1,16 @@ { "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)}" + "track": "${host}/ping-amp?url=${ampdocUrl}&sid=${clientId(_iibeat_session)}&ua=${userAgent}&ref=${documentReferrer}&at=${incrementalEngagedTime}&tt=${totalTime}&pid=${pageViewId}&d=${d}&ct=${ct}&pt=${pt}&au=${au}&ag=${ag}&aid=${aid}&cn=${canonicalUrl}&ctIds=${ctIds}" }, "triggers": { - "defaultPageview": { - "on": "visible", - "request": "pageview" + "trackInterval": { + "on": "timer", + "timerSpec": { + "interval": 15, + "maxTimerLength": 7200 + }, + "request": "track" } } } diff --git a/extensions/amp-analytics/0.1/visibility-manager.js b/extensions/amp-analytics/0.1/visibility-manager.js index 0d810822e6e8..b62bbbba7e55 100644 --- a/extensions/amp-analytics/0.1/visibility-manager.js +++ b/extensions/amp-analytics/0.1/visibility-manager.js @@ -468,6 +468,7 @@ export class VisibilityManager { // Optionally, element-level state. let layoutBox; if (opt_element) { + state['elementId'] = opt_element.id; state['opacity'] = getMinOpacity(opt_element); const resource = this.resources_.getResourceForElementOptional( opt_element diff --git a/extensions/amp-analytics/analytics-vars.md b/extensions/amp-analytics/analytics-vars.md index e1280bc444a7..5d10e0a3e702 100644 --- a/extensions/amp-analytics/analytics-vars.md +++ b/extensions/amp-analytics/analytics-vars.md @@ -50,13 +50,50 @@ And in the request url the token would be of the format `${eventId}` (follows ca When the same `var` is defined in multiple locations, the value is picked in the order remote config > element level data attributes > triggers > top level > platform. Thus, if the remote config defined `clientId` as `12332312` in the example above, the values of various vars will be as follows: -| var | Value | Defined by | -| ------------ | ----------------------------------- | ---------------- | -| canonicalUrl | http://example.com/path/to/the/page | Platform | -| title | My homepage | Trigger | -| account | ABC123 | Top level config | -| clientId | 12332312 | Remote config | +| var | Value | Defined by | +| ------------ | -------------------------------------- | ---------------- | +| canonicalUrl | `https://example.com/path/to/the/page` | Platform | +| title | My homepage | Trigger | +| account | ABC123 | Top level config | +| clientId | 12332312 | Remote config | ## Variables -For a list of variables supported in `amp-analytics`, see [Variable Substitutions](../../spec/amp-var-substitutions.md). +For a list of variables supported in `amp-analytics`, see [Variable Substitutions](../../spec/amp-var-substitutions.md). These variables may be substituted as well as nested within each other. For example, a variable that is substituted to + +```javascript +{ + "request": { + "base": "example.com/${nested}${nested2}" + }, + "vars": { + "nested": "QUERY_PARAM(foo,QUERY_PARAM(bar,default))", + "nested2": "abc" + } +} +``` + +will resolve to `default` if `foo` and `bar` are not parameters. + +One known caveat is when substituting a variable for a macro (not including its argument list), where the argument list contains macros with arguments: + +```javascript +{ + "request": { + "base": "example.com/${a(1,TIMEZONE_CODE)}${a(RANDOM,$NOT(true))}" + }, + "vars": { + "a": "QUERY_PARAM" + } +} +``` + +This will result in the first QUERY_PARAM working as intended but the second QUERY_PARAM will not get parsed corretly due to the second pair of parenthesis from the macros within. + +Similar to this, another restriction is nesting analytics variables within each other: + +`${queryParam(${title},${canonicalUrl})}` + +Instead, use the platform version (or a combination): + +`QUERY_PARAM(TITLE,${myVariable})` diff --git a/extensions/amp-analytics/integrating-analytics.md b/extensions/amp-analytics/integrating-analytics.md index 10d5d50bdfb5..8263efe18925 100644 --- a/extensions/amp-analytics/integrating-analytics.md +++ b/extensions/amp-analytics/integrating-analytics.md @@ -22,9 +22,18 @@ Before you can add your analytics service to AMP HTML runtime, you may need to: ## Adding your configuration to the AMP HTML runtime 1. Create an [Intent-To-Implement issue](../../CONTRIBUTING.md#contributing-features) stating that you'll be adding your analytics service's configuration to AMP HTML's runtime. Be sure to include **cc @ampproject/wg-analytics** in your description. -1. Develop a patch that implements the following: 1. A new configuration file in the vendors [folder](https://github.com/ampproject/amphtml/tree/master/extensions/amp-analytics/0.1/vendors) including any options above and beyond the default, such as: 1. `"vars": {}` for additional default variables. 1. `"requests": {}` for requests that your service will use. 1. `"optout":` if needed. We currently don't have a great opt-out system, so please reach out to help us design one that works well for you. 1. `"warningMessage":` if needed. Displays warning information from the vendor (such as deprecation or migration) in the console. 1. Import your configuration and include it in ANALYTICS_CONFIG in [vendors.js](0.1/vendors.js). 1. If you are using iframe transport, add a new line to ANALYTICS_IFRAME_TRANSPORT_CONFIG in iframe-transport-vendors.js containing `"*vendor-name*": "*url*"` 1. An example in the [examples/analytics-vendors.amp.html](../../examples/analytics-vendors.amp.html) - reference. 1. A test in the [extensions/amp-analytics/0.1/test/vendor-requests.json - ](../../extensions/amp-analytics/0.1/test/vendor-requests.json) file. 1. A new batch plugin if required. Please refer to [Add Batch Plugin](#add-batch-plugin) for instructions. +1. Develop a patch that implements the following: + 1. A new configuration json file `${vendorName}.json` in the vendors [folder](https://github.com/ampproject/amphtml/tree/master/extensions/amp-analytics/0.1/vendors) including any options above and beyond the default, such as: + 1. `"vars": {}` for additional default variables. + 1. `"requests": {}` for requests that your service will use. + 1. `"optout":` if needed. We currently don't have a great opt-out system, so please reach out to help us design one that works well for you. + 1. `"warningMessage":` if needed. Displays warning information from the vendor (such as deprecation or migration) in the console. + 1. If you are using iframe transport, also add a new line to ANALYTICS_IFRAME_TRANSPORT_CONFIG in iframe-transport-vendors.js containing `"*vendor-name*": "*url*"` + 1. An example in the [examples/analytics-vendors.amp.html](../../examples/analytics-vendors.amp.html) + reference. + 1. A test in the [extensions/amp-analytics/0.1/test/vendor-requests.json + ](../../extensions/amp-analytics/0.1/test/vendor-requests.json) file. +1. If a new batch plugin if required. Please refer to [Add Batch Plugin](#add-batch-plugin) for instructions. 1. Test the new example you put in [examples/analytics-vendors.amp.html](../../examples/analytics-vendors.amp.html) to ensure the hits from the example are working as expected. For example, the data needed is being collected and displayed in your analytics dashboard. 1. Submit a Pull Request with this patch, referencing the Intent-To-Implement issue. 1. Add your analytics service to the [list of supported Analytics Vendors](https://github.com/ampproject/docs/blob/master/content/docs/analytics/analytics-vendors.md) by submitting a Pull Request to the [ampproject/docs](https://github.com/ampproject/docs) repo. Include the type, description, and link to your usage documentation. @@ -52,11 +61,6 @@ To take this approach, review the documentation for publishers' integration with - Deep Dive: [Why not just use an iframe?](why-not-iframe.md) - Deep Dive: [Managing non-authenticated user state with AMP](https://github.com/ampproject/amphtml/blob/master/spec/amp-managing-user-state.md) -- Review pull requests from other AMP Analytics providers: -- [AT Internet](https://github.com/ampproject/amphtml/pull/1672) -- [Piano](https://github.com/ampproject/amphtml/pull/1652) -- [comScore](https://github.com/ampproject/amphtml/pull/1608) -- [Parsely](https://github.com/ampproject/amphtml/pull/1595) - [amp-analytics sample](https://github.com/ampproject/amp-publisher-sample#amp-analytics-sample) - [amp-analytics](https://amp.dev/documentation/components/amp-analytics) reference documentation - [amp-analytics variables](analytics-vars.md) reference documentation diff --git a/extensions/amp-apester-media/0.1/monetization/companion/display.js b/extensions/amp-apester-media/0.1/monetization/companion/display.js index 2077eac6c2dd..9a69abda08cb 100644 --- a/extensions/amp-apester-media/0.1/monetization/companion/display.js +++ b/extensions/amp-apester-media/0.1/monetization/companion/display.js @@ -14,6 +14,7 @@ * limitations under the License. */ +import {Services} from '../../../../../src/services'; import {createElementWithAttributes} from '../../../../../src/dom'; import {getValueForExpr} from '../../../../../src/json'; const ALLOWED_AD_PROVIDER = 'gdt'; @@ -84,6 +85,10 @@ function constructCompanionDisplayAd(slot, bannerSizes, apesterElement) { ); ampAd.classList.add('amp-apester-companion'); apesterElement.parentNode.insertBefore(ampAd, apesterElement.nextSibling); - apesterElement.getResources().attemptChangeSize(ampAd, maxHeight); + Services.mutatorForDoc(apesterElement).attemptChangeSize( + ampAd, + maxHeight, + /* newWidth */ undefined + ); return ampAd; } diff --git a/extensions/amp-apester-media/0.1/monetization/companion/video.js b/extensions/amp-apester-media/0.1/monetization/companion/video.js index 69edb960eec1..9275965b3749 100644 --- a/extensions/amp-apester-media/0.1/monetization/companion/video.js +++ b/extensions/amp-apester-media/0.1/monetization/companion/video.js @@ -112,7 +112,11 @@ function addCompanionSrElement(videoTag, position, macros, apesterElement) { position === 'below' ? apesterElement.nextSibling : apesterElement; apesterElement.parentNode.insertBefore(ampBladeAd, relativeElement); - apesterElement.getResources().attemptChangeSize(ampBladeAd, size.height); + Services.mutatorForDoc(apesterElement).attemptChangeSize( + ampBladeAd, + size.height, + /* newWidth */ undefined + ); } /** diff --git a/extensions/amp-apester-media/0.1/test/test-amp-apester-monetization.js b/extensions/amp-apester-media/0.1/test/test-amp-apester-monetization.js index 624b2b2b27dd..3769d034dae9 100644 --- a/extensions/amp-apester-media/0.1/test/test-amp-apester-monetization.js +++ b/extensions/amp-apester-media/0.1/test/test-amp-apester-monetization.js @@ -14,6 +14,7 @@ * limitations under the License. */ +import {Services} from '../../../../src/services'; import {handleCompanionAds} from '../monetization/index'; import {installDocService} from '../../../../src/service/ampdoc-impl'; import { @@ -23,7 +24,6 @@ import { describes.realWin('amp-apester-media-monetization', {}, env => { let win, doc; let baseElement; - let resources; let docInfo; const queryAmpAdBladeSelector = myDoc => myDoc.querySelector('amp-ad[type=blade]'); @@ -36,11 +36,10 @@ describes.realWin('amp-apester-media-monetization', {}, env => { baseElement = doc.createElement('amp-apester-media'); - resources = { + const mutator = { attemptChangeSize: () => env.sandbox.stub(), }; - - baseElement.getResources = () => resources; + env.sandbox.stub(Services, 'mutatorForDoc').returns(mutator); doc.body.appendChild(baseElement); docInfo = { @@ -49,7 +48,7 @@ describes.realWin('amp-apester-media-monetization', {}, env => { }; installDocService(win, /* isSingleDoc */ true); resetServiceForTesting(win, 'documentInfo'); - return registerServiceBuilderForDoc(doc, 'documentInfo', () => { + return registerServiceBuilderForDoc(doc, 'documentInfo', function() { return { get: () => docInfo, }; diff --git a/extensions/amp-auto-lightbox/0.1/amp-auto-lightbox.js b/extensions/amp-auto-lightbox/0.1/amp-auto-lightbox.js index cc72ba866b22..73325008eeae 100644 --- a/extensions/amp-auto-lightbox/0.1/amp-auto-lightbox.js +++ b/extensions/amp-auto-lightbox/0.1/amp-auto-lightbox.js @@ -212,7 +212,7 @@ export function meetsSizingCriteria( * @return {!Promise} */ function markAsVisited(candidate) { - return Mutation.mutate(candidate, () => { + return Services.mutatorForDoc(candidate).mutateElement(candidate, () => { candidate.setAttribute(VISITED_ATTR, ''); }); } @@ -305,24 +305,6 @@ export class DocMetaAnnotations { } } -/** - * Wrapper for an element-implementation-mutate sequence for readability and - * mocking in tests. - * @visibleForTesting - */ -export class Mutation { - /** - * @param {!Element} ampEl - * @param {function()} mutator - * @return {!Promise} - */ - static mutate(ampEl, mutator) { - return whenUpgradedToCustomElement(ampEl).then(ampEl => - ampEl.getResources().mutateElement(ampEl, mutator) - ); - } -} - /** * Determines whether a document uses `amp-lightbox-gallery` explicitly by * including the extension and explicitly lightboxing at least one element. @@ -377,9 +359,11 @@ function generateLightboxUid() { * @visibleForTesting */ export function apply(ampdoc, element) { - return Mutation.mutate(element, () => { + const mutator = Services.mutatorForDoc(ampdoc); + const mutatePromise = mutator.mutateElement(element, () => { element.setAttribute(LIGHTBOXABLE_ATTR, generateLightboxUid()); - }).then(() => { + }); + return mutatePromise.then(() => { Services.extensionsFor(ampdoc.win).installExtensionForDoc( ampdoc, REQUIRED_EXTENSION @@ -399,6 +383,11 @@ export function apply(ampdoc, element) { export function runCandidates(ampdoc, candidates) { return candidates.map(candidate => whenLoaded(candidate).then(() => { + // will change the img's src inline data on unlayout and remove + // it from DOM, but a LOAD_END event would still be triggered afterwards. + if (candidate.signals().get(CommonSignals.UNLOAD)) { + return; + } if (!Criteria.meetsAll(candidate)) { return; } diff --git a/extensions/amp-auto-lightbox/0.1/test/test-amp-auto-lightbox.js b/extensions/amp-auto-lightbox/0.1/test/test-amp-auto-lightbox.js index a469500b947c..5039ec955f50 100644 --- a/extensions/amp-auto-lightbox/0.1/test/test-amp-auto-lightbox.js +++ b/extensions/amp-auto-lightbox/0.1/test/test-amp-auto-lightbox.js @@ -22,7 +22,6 @@ import { ENABLED_LD_JSON_TYPES, ENABLED_OG_TYPE_ARTICLE, LIGHTBOXABLE_ATTR, - Mutation, RENDER_AREA_RATIO, REQUIRED_EXTENSION, Scanner, @@ -116,13 +115,17 @@ describes.realWin( return element; } + function stubMutatorForDoc() { + env.sandbox.stub(Services, 'mutatorForDoc').returns({ + mutateElement: (_, fn) => tryResolve(fn), + }); + } + beforeEach(() => { any = env.sandbox.match.any; html = htmlFor(env.win.document); - env.sandbox - .stub(Mutation, 'mutate') - .callsFake((_, mutator) => tryResolve(mutator)); + stubMutatorForDoc(); }); describe('meetsTreeShapeCriteria', () => { @@ -544,6 +547,21 @@ describes.realWin( }); describe('runCandidates', () => { + it('ignores amp-img load signal after being unlaid out', async () => { + const img = html` + + `; + + const signals = new Signals(); + img.signals = () => signals; + + signals.signal(CommonSignals.UNLOAD); + signals.signal(CommonSignals.LOAD_END); + + const elected = await Promise.all(runCandidates(env.ampdoc, [img])); + expect(elected[0]).to.be.undefined; + }); + it('filters out candidates that fail to load', async () => { const shouldNotLoad = mockLoadedSignal( html` diff --git a/extensions/amp-autocomplete/0.1/amp-autocomplete.js b/extensions/amp-autocomplete/0.1/amp-autocomplete.js index 8453108e89a5..8674d4de5629 100644 --- a/extensions/amp-autocomplete/0.1/amp-autocomplete.js +++ b/extensions/amp-autocomplete/0.1/amp-autocomplete.js @@ -28,6 +28,7 @@ import { batchFetchJsonFor, requestForBatchFetch, } from '../../../src/batched-json'; +import {addParamToUrl} from '../../../src/url'; import {createCustomEvent} from '../../../src/event-helper'; import {dev, user, userAssert} from '../../../src/log'; import {dict, hasOwn, map, ownProperty} from '../../../src/utils/object'; @@ -249,16 +250,9 @@ export class AmpAutocomplete extends AMP.BaseElement { this.inputElement_.setAttribute('aria-autocomplete', 'both'); this.inputElement_.setAttribute('role', 'combobox'); - userAssert( - this.inputElement_.form, - '%s should be inside a
tag. %s', - TAG, - this.element - ); - if (this.inputElement_.form.hasAttribute('autocomplete')) { - this.initialAutocompleteAttr_ = this.inputElement_.form.getAttribute( - 'autocomplete' - ); + const form = this.getFormOrNull_(); + if (form && form.hasAttribute('autocomplete')) { + this.initialAutocompleteAttr_ = form.getAttribute('autocomplete'); } // When SSR is supported, it is required. @@ -333,6 +327,13 @@ export class AmpAutocomplete extends AMP.BaseElement { return /** @type {!HTMLInputElement} */ (possibleElements[0]); } + /** + * @return {?HTMLFormElement} + */ + getFormOrNull_() { + return this.inputElement_.form || null; + } + /** * Creates a binding associated with singular autocomplete or * inline autocomplete depending on the presence of the given element's "inline" attribute. @@ -439,9 +440,7 @@ export class AmpAutocomplete extends AMP.BaseElement { * @private */ generateSrc_(opt_query = '') { - const encodedQueryKey = encodeURIComponent(this.queryKey_); - const encodedQuery = encodeURIComponent(opt_query); - return `${this.srcBase_}?${encodedQueryKey}=${encodedQuery}`; + return addParamToUrl(this.srcBase_, this.queryKey_, opt_query); } /** @@ -903,16 +902,16 @@ export class AmpAutocomplete extends AMP.BaseElement { * @private */ toggleResultsHandler_(display) { - // Set/reset "autocomplete" attribute on the ancestor. - if (display) { - this.inputElement_.form.setAttribute('autocomplete', 'off'); - } else if (this.initialAutocompleteAttr_) { - this.inputElement_.form.setAttribute( - 'autocomplete', - this.initialAutocompleteAttr_ - ); - } else { - this.inputElement_.form.removeAttribute('autocomplete'); + // Set/reset "autocomplete" attribute on ancestor if present. + const form = this.getFormOrNull_(); + if (form) { + if (display) { + form.setAttribute('autocomplete', 'off'); + } else if (this.initialAutocompleteAttr_) { + form.setAttribute('autocomplete', this.initialAutocompleteAttr_); + } else { + form.removeAttribute('autocomplete'); + } } // Toggle results. @@ -1203,10 +1202,10 @@ export class AmpAutocomplete extends AMP.BaseElement { } return this.updateActiveItem_(-1); case Keys.ENTER: - const shouldPreventSubmit = this.binding_.shouldPreventFormSubmissionOnEnter( + const shouldPreventDefault = this.binding_.shouldPreventDefaultOnEnter( !!this.activeElement_ ); - if (this.areResultsDisplayed_() && shouldPreventSubmit) { + if (this.areResultsDisplayed_() && shouldPreventDefault) { event.preventDefault(); } this.binding_.removeSelectionHighlighting(this.inputElement_); diff --git a/extensions/amp-autocomplete/0.1/autocomplete-binding-def.js b/extensions/amp-autocomplete/0.1/autocomplete-binding-def.js index d92c4a86d53f..cd328a25653c 100644 --- a/extensions/amp-autocomplete/0.1/autocomplete-binding-def.js +++ b/extensions/amp-autocomplete/0.1/autocomplete-binding-def.js @@ -87,5 +87,5 @@ export class AutocompleteBindingDef { * @param {boolean} unusedActiveElement * @return {boolean} */ - shouldPreventFormSubmissionOnEnter(unusedActiveElement) {} + shouldPreventDefaultOnEnter(unusedActiveElement) {} } diff --git a/extensions/amp-autocomplete/0.1/autocomplete-binding-inline.js b/extensions/amp-autocomplete/0.1/autocomplete-binding-inline.js index b02d0f63137a..36a9d3c1ab00 100644 --- a/extensions/amp-autocomplete/0.1/autocomplete-binding-inline.js +++ b/extensions/amp-autocomplete/0.1/autocomplete-binding-inline.js @@ -190,7 +190,7 @@ export class AutocompleteBindingInline { * @param {boolean} activeElement * @return {boolean} */ - shouldPreventFormSubmissionOnEnter(activeElement) { + shouldPreventDefaultOnEnter(activeElement) { return activeElement; } } diff --git a/extensions/amp-autocomplete/0.1/autocomplete-binding-single.js b/extensions/amp-autocomplete/0.1/autocomplete-binding-single.js index 7e4a8d71fc95..6f0dc7f0c686 100644 --- a/extensions/amp-autocomplete/0.1/autocomplete-binding-single.js +++ b/extensions/amp-autocomplete/0.1/autocomplete-binding-single.js @@ -141,7 +141,7 @@ export class AutocompleteBindingSingle { * @param {boolean} unusedActiveElement * @return {boolean} */ - shouldPreventFormSubmissionOnEnter(unusedActiveElement) { + shouldPreventDefaultOnEnter(unusedActiveElement) { return !this.submitOnEnter_; } } diff --git a/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete-bindings.js b/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete-bindings.js index 65950131ecfb..2d3e61a47556 100644 --- a/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete-bindings.js +++ b/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete-bindings.js @@ -117,14 +117,14 @@ describes.realWin( }); it('should prevent submission when "submit-on-enter" is absent', () => { - expect(binding.shouldPreventFormSubmissionOnEnter(true)).to.be.true; - expect(binding.shouldPreventFormSubmissionOnEnter(false)).to.be.true; + expect(binding.shouldPreventDefaultOnEnter(true)).to.be.true; + expect(binding.shouldPreventDefaultOnEnter(false)).to.be.true; }); it('should not prevent submission when "submit-on-enter" is true', () => { binding = getBindingSingle({'submit-on-enter': 'true'}); - expect(binding.shouldPreventFormSubmissionOnEnter(true)).to.be.false; - expect(binding.shouldPreventFormSubmissionOnEnter(false)).to.be.false; + expect(binding.shouldPreventDefaultOnEnter(true)).to.be.false; + expect(binding.shouldPreventDefaultOnEnter(false)).to.be.false; }); }); @@ -209,8 +209,8 @@ describes.realWin( }); it('should prevent default whenever there are active suggestions shown', () => { - expect(binding.shouldPreventFormSubmissionOnEnter(true)).to.be.true; - expect(binding.shouldPreventFormSubmissionOnEnter(false)).to.be.false; + expect(binding.shouldPreventDefaultOnEnter(true)).to.be.true; + expect(binding.shouldPreventDefaultOnEnter(false)).to.be.false; }); }); } diff --git a/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete-init.js b/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete-init.js index 04b7cab4c961..fb7f36f9187c 100644 --- a/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete-init.js +++ b/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete-init.js @@ -348,14 +348,10 @@ describes.realWin( }); }); - it('should error without the form ancestor', () => { - return allowConsoleError(() => { - const autocomplete = setupAutocomplete({'filter': 'substring'}); - doc.body.appendChild(autocomplete); - return expect(autocomplete.build()).to.be.rejectedWith( - 'amp-autocomplete should be inside a tag' - ); - }); + it('should not require a form ancestor', () => { + const autocomplete = setupAutocomplete({'filter': 'substring'}); + doc.body.appendChild(autocomplete); + return expect(autocomplete.build()).to.be.fulfilled; }); it('should read the autocomplete attribute on the form as null', () => { diff --git a/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete.js b/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete.js index 5c867924482f..d5a97c8539ce 100644 --- a/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete.js +++ b/extensions/amp-autocomplete/0.1/test/test-amp-autocomplete.js @@ -39,7 +39,6 @@ describes.realWin( }); function buildAmpAutocomplete(wantSsr) { - const form = doc.createElement('form'); const element = createElementWithAttributes(doc, 'amp-autocomplete', { layout: 'container', filter: 'substring', @@ -54,8 +53,7 @@ describes.realWin( script.innerHTML = '{ "items" : ["apple", "banana", "orange"] }'; element.appendChild(script); - form.appendChild(element); - doc.body.appendChild(form); + doc.body.appendChild(element); if (wantSsr) { element.removeAttribute('filter'); @@ -678,7 +676,7 @@ describes.realWin( impl.activeElement_ = impl.createElementFromItem_('abc'); env.sandbox.stub(impl, 'areResultsDisplayed_').returns(true); env.sandbox - .stub(impl.binding_, 'shouldPreventFormSubmissionOnEnter') + .stub(impl.binding_, 'shouldPreventDefaultOnEnter') .returns(true); return impl.keyDownHandler_(event); }) @@ -777,6 +775,9 @@ describes.realWin( it('should call toggleResultsHandler_()', () => { const toggleResultsSpy = env.sandbox.spy(impl, 'toggleResults_'); const resetSpy = env.sandbox.spy(impl, 'resetActiveElement_'); + const form = doc.createElement('form'); + form.appendChild(impl.element); + doc.body.appendChild(form); return impl .layoutCallback() .then(() => { @@ -784,7 +785,7 @@ describes.realWin( }) .then(() => { expect(toggleResultsSpy).to.have.been.calledOnce; - expect(impl.inputElement_.form.getAttribute('autocomplete')).to.equal( + expect(impl.getFormOrNull_().getAttribute('autocomplete')).to.equal( 'off' ); expect(resetSpy).not.to.have.been.called; @@ -995,5 +996,18 @@ describes.realWin( ); }); }); + + it('should preserve existing query parameters when generating src values from "query" attribute', () => { + return impl.layoutCallback().then(() => { + impl.queryKey_ = 'q'; + impl.srcBase_ = 'https://www.data.com/?param=1'; + expect(impl.generateSrc_('')).to.equal( + 'https://www.data.com/?param=1&q=' + ); + expect(impl.generateSrc_('abc')).to.equal( + 'https://www.data.com/?param=1&q=abc' + ); + }); + }); } ); diff --git a/extensions/amp-bind/amp-bind.md b/extensions/amp-bind/amp-bind.md index 0402e82baae4..6f98641c0660 100644 --- a/extensions/amp-bind/amp-bind.md +++ b/extensions/amp-bind/amp-bind.md @@ -28,9 +28,11 @@ limitations under the License. Adds custom interactivity with data binding and expressions. -## Overview +## Usage -The `amp-bind` component allows you to add custom stateful interactivity to your AMP pages via data binding and JS-like expressions. +The `amp-bind` component enables custom stateful interactivity on AMP pages. + +For performance, and to avoid the risk of unexpected content jumping, `amp-bind` does not evaluate expressions on page load. This means visual elements should be given a default state and not rely on `amp-bind` for initial render.
Watch this video for an introduction to amp-bind.
-### A simple example +`amp-bind` has three main concepts: + +1. [State](#state): A document-scope, mutable JSON state. State variables update in response to user actions. `amp-bind` does not evaluate expressions on page load. Visual elements should have their default "state" defined and not rely `amp-bind` for initial render. +2. [Expressions](#expressions): JavaScript-like expressions that can reference the **state**. +3. [Bindings](#bindings): Special attributes that link an element's property to a **state** via an **expression**. A property is bound by wrapping it inside brackets, in the form of `[property]`. -In the following example, tapping the button changes the `

` element's text from "Hello World" to "Hello amp-bind". +### Example without declared state + +[example preview="inline" playground="true" imports="amp-bind"] ```html

Hello World

- + ``` -{% call callout('Note', type='note') %} -For performance and to avoid the risk of unexpected content jumping, `amp-bind` does not evaluate expressions on page load. This means that the visual elements should be given a default state and not rely `amp-bind` for initial render. -{% endcall %} +[/example] + +In the example above: -### How does it work? +- The **state** begins as empty. +- It has a single **binding** to `[text]`, the text content of a node, on the `

` element. +- The `[text]` value contains the **expression**, `'Hello ' + foo`. This expression concatenates the string 'Hello ' and the value of the **state variable** foo. -`amp-bind` has three main components: +When the user taps/clicks the button: -1. [State](#state): A document-scope, mutable JSON state. In the example above, the state is empty before tapping the button. After tapping the button, the state is `{foo: 'amp-bind'}`. -2. [Expressions](#expressions): These are JavaScript-like expressions that can reference the **state**. The example above has a single expression, `'Hello ' + foo`, which concatenates the string literal `'Hello '` and the state variable `foo`. - There is a limit of 100 operands what can be used in an expression. -3. [Bindings](#bindings): These are special attributes of the form `[property]` that link an element's property to an **expression**. The example above has a single binding, `[text]`, which updates the `

` element's text every time the expression's value changes. +1. It triggers the `tap` event. +1. The `tap` event invokes the `AMP.setState()` method. +1. The `AMP.setState()` methods sets the `foo` **state variable** to the value of `Interactivity`. +1. The state is no longer empty, so the page updates the bound property to its state. -`amp-bind` takes special care to ensure speed, security and performance on AMP pages. +[tip type="note"] +Calling `AMP.setState()` in some examples may set or change states of other examples on page. Refresh this page to see examples before `AMP.setState()`. +[/tip] -### A slightly more complex example +### Example with declared state [filter formats="websites, stories, ads"] +[example preview="top-frame" playground="true" imports="amp-bind"] + ```html - - + + + + + + + +

+

Each food has a different border color.

+

I want to eat cupcakes.

+ + + + +
+ +``` + +[/example] + +In the example above: + +- The `` component declares state using JSON. The `` element has an `id` of `theFood` to allow us to reference the defined data. But because `` does not evaluate `` on page load, the **state** is empty. +- The page loads with visual defaults. + - The `
` element has `class="greenBorder"` defined. + - The second `

` element has "I want cupcakes." defined within the tags. + - The `` `src` points to a url. +- Changeable elements have **bindings** that point to **expressions**. + - The `[class]` attribute on the `

` is bound to the `theFood[currentMeal].style` **expression**. + - The `[text]` attribute on the second `

` is bound to the `'I want to eat ' + currentMeal + '.'` **expression**. + - The `[src]` attribute is bound to the `theFood[currentMeal].imageUrl` **expression**. + +If a user clicks the "Set to sushi" button: + +1. The `tap` event trigger the `AMP.setState` action. +1. The setState action turns `currentMeal` into a state and sets it to `sushi`. +1. AMP evaluates **bindings** with **expressions** that contain the state `currentMeal`. +1. `[class]="theFood[currentMeal].style"` updates `class` to `redBorder`. +1. `[text]="'I want to eat ' + currentMeal + '.'"` updates the inner text of the second `

` element to "I want to eat sushi". +1. `[src]="theFood[currentMeal].imageUrl` updates the `src` of `` to `https://amp.dev/static/samples/img/image3.jpg` + +Using `[class]="theFood[currentMeal].style"` as an example of **expression** syntax evaluation: + +- `[class]` is the property to update. +- `theFood` is the id of the `` component. +- `currentMeal` is the state name. In the case of `theFood` it will be `cupcakes` or `sushi`. +- `style` is the **state variable**. It corresponds to the matching JSON key, and sets the bound property to that key's value. + +[/filter] + +[filter formats="email"] + +[example preview="top-frame" playground="true" imports="amp-bind"] + +```html + + + + +

+

Each food has a different border color.

+

I want to eat cupcakes.

+ + +
+``` -

This is a dog.

+[/example] - -

- Each animal has a different background color. -

+- The `` component declares state using a JSON object. It has an `id` of `theFood` to allow us to reference the defined data. But because `` does not evaluate `` on email load, the **state** is empty. +- The page loads with visual defaults. +- The `
` element has `class="greenBorder"` defined. +- The second `

` element has "I want cupcakes." defined within the tags. +- The `` `src` points to a url. +- Changeable elements have **bindings** that point to **expressions**. +- The `[class]` attribute on the `

` is bound to the `theFood[currentMeal].style` **expression**. +- The `[text]` attribute on the second `

` is bound to the `theFood[currentMeal].text` **expression**. - - - +If a user clicks the "Set to sushi" button: + +1. The `tap` event trigger the `AMP.setState` action. +1. The setState action turns `currentMeal` into a state and sets it to `sushi`. +1. AMP evaluates **bindings** with **expressions** that contain the state `currentMeal`. +1. `[class]="theFood[currentMeal].style` updates `class` to `redBorder`. +1. `theFood[currentMeal].text` updates the inner text of the second `

` element to "Actually, I want to eat sushi.". + +Using `[class]="theFood[currentMeal].style"` as an example of **expression** syntax evaluation: + +- `[class]` is the property to update +- `theFood` is the id of the `` component. +- `currentMeal` is the state name. In the case of `theFood` it will be `cupcakes` or `sushi`. +- `style` is the **state variable**. It corresponds to the matching JSON key, and sets the bound property to that key's value. + +[/filter] + +### `` specification - +[filter formats="websites, stories, ads"] +An `amp-state` element may contain either a child ` + + + ``` [/filter] [filter formats="email"] +An `amp-state` element must contain a child ` - -

This is a dog.

- - -

- Each animal has a different background color. -

``` [/filter] -When the button is pressed: +[filter formats="websites, stories, ads"] -1. **State** is updated with `currentAnimal` defined as `'cat'`. +#### Attributes -2. **Expressions** that depend on `currentAnimal` are evaluated: +[filter formats="websites, stories, ads"] - - `'This is a ' + currentAnimal + '.'` => `'This is a cat.'` - - `myAnimals[currentAnimal].style` => `'redBackground'` - - `myAnimals[currentAnimal].imageUrl` => `/img/cat.jpg` +##### `src` (optional) -3. **Bindings** that depend on the changed expressions are updated: - - The first `

` element's text will read "This is a cat." - - The second `

` element's `class` attribute will be "redBackground". - - The `amp-img` element will show the image of a cat. +The URL of the remote endpoint that must return JSON, which is used to this `amp-state`. This must be a HTTP service with a proper CORS configuration for the page. The `src` attribute allows all standard URL variable substitutions. See the [Substitutions Guide](../../spec/amp-var-substitutions.md) for more info. -{% call callout('Tip', type='success') %} -[Try out the **live demo**](https://amp.dev/documentation/examples/components/amp-bind/) for this example with code annotations! -{% endcall %} +[tip type="important"] +The endpoint must implement the requirements specified in the [CORS Requests in AMP](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests) spec. +[/tip] + +##### `credentials` (optional) + +Defines a `credentials` option as specified by the [Fetch API](https://fetch.spec.whatwg.org/). + +- Supported values: `omit`, `include` +- Default: `omit` + +To send credentials, pass the value of `include`. If this value is set, the response must follow the [AMP CORS security guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/#cors-security-in-amp). +[/filter] + +[filter formats="email"] -## Details +#### Actions -### State +##### `refresh` + +The `refresh` action refetches data from data point the `src` attribute points to. This action will make a network request bypassing the browser's caching mechanisms. + +[example preview="inline" playground="true" imports="amp-bind"] + +```html + + +

+``` + +[/example] + +We recommend [`amp-script`](../amp-script/amp-script.md) for most use cases working with live content. In a subset of cases, `refresh` with `amp-bind` will work. + +#### XHR batching + +AMP batches XMLHttpRequests (XHRs) to JSON endpoints, that is, you can use a single JSON data request as a data source for multiple consumers (e.g., multiple `amp-state` elements) on an AMP page. + +For example, if your `amp-state` element makes an XHR to an endpoint, while the XHR is in flight, all subsequent XHRs to the same endpoint won't trigger and will instead return the results from the first XHR. + +[/filter] + +## State Each AMP document that uses `amp-bind` has document-scope mutable JSON data, or **state**. -#### Initializing state with `amp-state` +### Size -`amp-bind`'s state can be initialized with the `amp-state` component: +An `` element's JSON data has a maximum size of 100KB. + +### Defining and initializing state with `` + +Expressions are not evaluates on page load, but you may define an initial state. The `` component contains different **states** and their **state variables**. While this defines a **states**, it will not reflect on the page until after a user interacts. + +[example preview="inline" playground="true" imports="amp-bind"] ```html - + +

+ ``` -[Expressions](#expressions) can reference state variables via dot syntax. In this example, `myState.foo` will evaluate to `"bar"`. - -- An `` element's child JSON has a maximum size of 100KB. -- An `` element can also specify a CORS URL instead of a child JSON script. See the [Appendix](#amp-state-specification) for details. +[/example] -#### Refreshing state +Use [expressions](#expressions) to reference **state variables**. If the JSON data is not nested in the `` component, reference the states via dot syntax. In the above example, `myState.foo` evaluates to "bar". -The `refresh` action is supported by this component and can be used to refresh the -state's contents. +An `` element can also specify a CORS URL instead of a child JSON script. See the [`` specification](#amp-state-specification) for details. ```html - - - + + ``` -#### Updating state with `AMP.setState()` +### Updating state variables with `AMP.setState()` -The [`AMP.setState()`](../../spec/amp-actions-and-events.md#amp) action merges an object literal into the state. For example, when the below button is pressed, `AMP.setState()` will [deep-merge](#deep-merge-with-ampsetstate) the object literal with the state. +The [`AMP.setState()`](../../spec/amp-actions-and-events.md#amp) action merges an object literal into the state. This means you can update the value of a defined state variable. + +[example preview="inline" playground="true" imports="amp-bind"] ```html + + + +

+

+ - + + ``` -In general, nested objects will be merged up to a maximum depth of 10. All variables, including those introduced by `amp-state`, can be overidden. +[/example] + +In the example above, triggering the `AMP.setState({})` action on the first button evaluates the `[text]` binding expression. It then inserts the defined **state variable's** value into the `

` tag. + +When the clicking the second button, with `AMP.setState({myState:{baz: myState.foo}})` action defined, it [deep-merges](#deep-merge-with-ampsetstate) the "baz" **state variable** value to the same as the "foo" **state variable** value. Both `

` tags display "bar". -When triggered by certain events, `AMP.setState()` also can access event-related data on the `event` property. +**State variable** values can update to values not defined in the initial state. When clicking the third button, with `"tap:AMP.setState({myState:{baz: 'world'}})"` action defined, it deep merges the "baz" **state variable** value, overriding it to "world". + +Clicking the first button after the other two sets the current state. Nothing will change. + +The **state variables** reverts back to the defined JSON in `` on page refresh. + +##### Event triggering and data + +When triggered by certain events, `AMP.setState()` can access event-related data on the `event` property. + +[example preview="inline" playground="true" imports="amp-bind"] ```html - + +

+ +``` + +[/example] + +#### Updating nested variables + +Nested objects are generally merged to a maximum depth of 10. All variables, including those defined in ``, can be overidden. + +[example preview="inline" playground="true" imports="amp-bind"] + +```html + + + +

+

+ + +``` + +[/example] + +#### Circular references + +`AMP.setState(object)` throws an error if `object` contains a circular reference. + +#### Removing a variable + +Remove an existing state variable by setting its value to `null` in `AMP.setState()`. + +```html + +``` + +#### Deep-merge with `AMP.setState()` + +Calling `AMP.setState()` deep-merges the provided object literal with the current state. `amp-bind` writes all literals to the state directly, except for nested objects, which are recursively merged. Primitives and arrays are in the state are always overwritten by variables of the same name in the object literal. + +[example preview="inline" playground="true" imports="amp-bind"] + +```html +

Name

+

Age

+

Vehicle

+ + + + + + + + +``` + +[/example] + +### Modifying history with `AMP.pushState()` + +`AMP.pushState()` writes state changes to the history. Navigating back, will restore the previous state. To test this, increase the count in the example below and use your browser's back button to decrease the count. + +[example preview="inline" playground="true" imports="amp-bind"] + +```html + + + +
Item 1
+ ``` -#### Modifying history with `AMP.pushState()` +[/example] + +Using `AMP.pushState()` sets the current state to the most recent pushed state. + +## Expressions + +`amp-bind` uses JavaScript-like expressions that can reference the state. + +### Differences from JavaScript + +- Expressions may only access the containing document's [state](#state). +- Expressions **do not** have access to `window` or `document`. `global` references the top-level state. +- Only `amp-bind` [allowed-listed functions](#allowed-listed functions) and operators are usable. are usable. Use of arrow functions are allowed as function parameters, e.g. `[1, 2, 3].map(x => x + 1)`. + - Custom functions, classes and loops are disallowed. +- Undefined variables and array-index-out-of-bounds return `null` instead of `undefined` or throwing errors. +- A single expression is currently capped at 50 operands for performance. Please [contact us](https://github.com/ampproject/amphtml/issues/new) if this is insufficient for your use case. + +The following are all valid expressions: + +[example preview="inline" playground="true" imports="amp-bind"] + +```html +

+ + + + + + + + + + +``` + +[/example] + +Find the full expression grammar and implementation in [bind-expr-impl.jison](./0.1/bind-expr-impl.jison) and [bind-expression.js](./0.1/bind-expression.js). + +### Allowed-listed functions + +#### [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Methods) + +Single-parameter arrow functions can't have parentheses, e.g. use `x => x + 1` instead of `(x) => x + 1`. `sort()` and `splice()` return modified copies instead of operating in-place. + +- [concat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) +- [filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) +- [includes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) +- [indexOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) +- [join](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join) +- [lastIndexOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf) +- [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) +- [reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) +- [slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) +- [some](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) +- [sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) +- [splice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) (not-in-place) + +[example preview="inline" playground="true" imports="amp-bind"] + +```html + + + +

concat: 1, 2, 3

+

+ filter: words with less than three letter +

+

+ includes: "hello" or "world" +

+

indexOf: "world"

+

+ join: all words with a dash +

+

+ lastIndexOf: "amp-bind" +

+

+ map: add each number to previous number +

+

+ reduce: add all numbers in array together +

+

+ slice: return words at index 1 and 3 +

+

+ some: some numbers are less than 2 +

+

+ sort: place words in alphabetical order +

+

+ splice: place "amp-bind" at index 2 +

+ +``` + +[/example] + +#### [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#Methods) + +- [toExponential](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential) +- [toFixed](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) +- [toPrecision](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision) +- [toString](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString) + +[example preview="inline" playground="true" imports="amp-bind"] + +```html +

+ toExponential: 100 to the exponent of 5 +

+

+ toFixed: 1.99 rounded and fixed to first decimal +

+

+ toPrecision: 1.234567 returned as a string to the third digit +

+

+ toString: 3.14 returned as a string +

+ +``` + +[/example] + +#### [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#Methods) + +- [charAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt) +- [charCodeAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt) +- [concat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat) +- [indexOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf) +- [lastIndexOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf) +- [replace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace) +- [slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) +- [split](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split) +- [substr](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr) +- [toLowerCase](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase) +- [toUpperCase](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) + +[example preview="inline" playground="true" imports="amp-bind"] + +```html + + + +

+ charAt: The character at index 6 +

+

+ charCodeAt: The UTF-16 code unit of the character at index 6 +

+

+ concat: Combine foo and bar +

+

+ lastIndexOf: The index of "w" +

+

+ replace: Replace "world" with "amp-bind" +

+

+ slice: Extract the first 5 characters +

+

+ split: Split words at space and return as array +

+

+ toLowerCase: Make all letters lower case +

+

+ toUpperCase: Make all letters upper case +

+ +``` + +[/example] + +#### [`Math`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math) + +Static functions are not namespaced, e.g. use `abs(-1)` instead of `Math.abs(-1)` -The [`AMP.pushState()`](../../spec/amp-actions-and-events.md#amp) action is similar to `AMP.setState()` except it also pushes a new entry -onto the browser history stack. Popping this history entry (e.g. by navigating back) restores -the previous value of variables set by `AMP.pushState()`. +- [abs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs) +- [ceil](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil) +- [floor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor) +- [max](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max) +- [min](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min) +- [pow](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow) +- [random](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) +- [round](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round) +- [sign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign) -For example: +[example preview="inline" playground="true" imports="amp-bind"] ```html - +

abs: absolute number of 5 - 9

+

+ abs: round 1.01 up to the next largest whole number +

+

floor: round 1.99 down to a whole number

+

max: return largest number

+

min: return smalled number

+

pow: return 5 to the power of 3

+

+ random: return a number greater than 0 and less than 1 +

+

round: round 1.51

+

sign: evaluate if positive or negative

+ ``` -- Tapping the button will set variable `foo` to 123 and push a new history entry. -- Navigating back will restore `foo` to its previous value, "bar" (equivalent to calling `AMP.setState({foo: 'bar'})`. +[/example] -### Expressions +#### [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) -Expressions are similar to JavaScript with some important differences. +Static functions are not namespaced, e.g. use `keys(Object)` instead of `Object.abs(Object)` -#### Differences from JavaScript +- [keys](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) +- [values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values) -- Expressions may only access the containing document's [state](#state). -- Expressions **do not** have access to `window` or `document`. `global` references the top-level state. -- Only [white-listed functions](#white-listed-functions) and operators may be used. Custom functions, classes and loops are disallowed. Arrow functions are allowed as function parameters e.g. `[1, 2, 3].map(x => x + 1)`. -- Undefined variables and array-index-out-of-bounds return `null` instead of `undefined` or throwing errors. -- A single expression is currently capped at 50 operands for performance. Please [contact us](https://github.com/ampproject/amphtml/issues/new) if this is insufficient for your use case. +[example preview="inline" playground="true" imports="amp-bind"] -The full expression grammar and implementation can be found in [bind-expr-impl.jison](./0.1/bind-expr-impl.jison) and [bind-expression.js](./0.1/bind-expression.js). +```html + + + +

+ keys: myObjectState JSON object keys +

+

+ values: myObjectState JSON object values +

+ +``` -#### Examples +[/example] -The following are all valid expressions: +#### [`Global`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects) -```javascript -1 + '1'; // 11 -1 + +'1'; // 2 -!0; // true -null || 'default'; // 'default' -``` +- [encodeURI](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) +- [encodeURIComponent](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) -#### White-listed functions +[example preview="inline" playground="true" imports="amp-bind"] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Object type Function(s)Example
Array1 - concat
- filter
- includes
- indexOf
- join
- lastIndexOf
- map
- reduce
- slice
- some
- sort (not-in-place)
- splice (not-in-place)
-
-
// Returns [1, 2, 3].
-[3, 2, 1].sort()
-
// Returns [1, 3, 5].
-[1, 2, 3].map((x, i) => x + i)
-
// Returns 6.
-[1, 2, 3].reduce((x, y) => x + y)
-
Number - toExponential
- toFixed
- toPrecision
- toString -
-
// Returns 3.
-(3.14).toFixed()
-
// Returns '3.14'.
-(3.14).toString()
-
String - charAt
- charCodeAt
- concat
- indexOf
- lastIndexOf
- replace
- slice
- split
- substr
- substring
- toLowerCase
- toUpperCase
-
// Returns 'abcdef'.
-'abc'.concat('def')
-
Math2 - abs
- ceil
- floor
- max
- min
- pow
- random
- round
- sign
-
// Returns 1.
-abs(-1)
-
Object2 - keys
- values -
-
// Returns ['a', 'b'].
-keys({a: 1, b: 2})
-
// Returns [1, 2].
-values({a: 1, b: 2}
-
- Global2 - - encodeURI
- encodeURIComponent -
-
// Returns 'Hello%20world'.
-encodeURIComponent('Hello world')
-
+```html +

+ encodeURI: Encode a URI and ignore protocol prefix +

+

+ encodeURIComponent: Encode a URI +

+ +``` -1Single-parameter arrow functions can't have parentheses, e.g. use `x => x + 1` instead of `(x) => x + 1`. Also, `sort()` and `splice()` return modified copies instead of operating in-place.
-2Static functions are not namespaced, e.g. use `abs(-1)` instead of `Math.abs(-1)`. +[/example] -#### Defining macros with `amp-bind-macro` +### Defining macros with `amp-bind-macro` -`amp-bind` expression fragments can be reused by defining an `amp-bind-macro`. The `amp-bind-macro` element allows you to define an expression that takes zero or more arguments and references the current state. A macro can be invoked like a function by referencing its `id` attribute value from anywhere in your doc. +Reuse `amp-bind` expression fragments by defining an `amp-bind-macro`. The `amp-bind-macro` element allows an expression that takes zero or more arguments and references the current state. Invoke `amp-bind-macros` like a function, referencing the `id` attribute value from anywhere in the document. [example preview="inline" playground="true" imports="amp-bind"] @@ -398,66 +851,162 @@ encodeURIComponent('Hello world') A macro can also call other macros defined before itself. A macro cannot call itself recursively. -### Bindings +## Bindings -A **binding** is a special attribute of the form `[property]` that links an element's property to an [expression](#expressions). An alternative, XML-compatible syntax can also be used in the form of `data-amp-bind-property`. +A **binding** is a special attribute of the form `[property]` that links an element's property to an [expression](#expressions). Use the alternative,[XML-compatible](#xml-compatibility) syntax if developing in XML. -When the **state** changes, expressions are re-evaluated and the bound elements' properties are updated with the new expression results. +When the **state** changes, expressions tied to that state are evaluated. The element properties **bound** to the **state** are updated with the new expression results. -`amp-bind` supports data bindings on five types of element state: +Boolean expression results toggle boolean attributes. For example: ``. When `expr` evaluates to `true`, the `` element has the `controls` attribute. When `expr` evaluates to `false`, the `controls` attribute is removed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeAttribute(s)Details
Node.textContent[text]Supported on most text elements.
CSS classes[class]Expression result must be a space-delimited string.
The hidden attribute[hidden]Should be a boolean expression.
Size of AMP elements[width]
[height]
Changes the width and/or height of the AMP element.
Accessibility states and properties[aria-hidden]
[aria-label]
etc.
Used for dynamically updating information available to assistive technologies like screen readers.
Element-specific attributesVarious
+[example preview="inline" playground="true" imports="amp-bind, amp-video"] + +```html + + + +
+

This browser does not support the video element.

+
+
+ + +``` + +[/example] + +### React and XML compatibility + +If developing with React or XML, use the alternative `data-amp-bind-property` syntax. The `[` and `]` characters in attribute names is invalid XML, making the `[property]` syntax unavailable. + +Replace the `property` field with the name of the property you would like to define in `data-amp-bind-property`. + +For example, `[text]="myState.foo"` would become `data-amp-bind-text="myState.foo"`. + +### Binding types + +`amp-bind` supports data bindings on five types of element state. + +[**Node.textContent**](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) + +Bind `Node.textContent` using the `[text]` attribute. The `[text]` attribute is supported on most text elements. + +```html +

Hello World

+

+``` + +**CSS classes** + +Bind an element's `class` using the `[class]` attribute. A `[class]` expression must result in a space-delimited string. Meaning, if you are binding multiple classes, use a space between names. A comma or dash will be evaluated as the class name. + +[example preview="inline" playground="true" imports="amp-bind"] + +```html + +
Hello World
+ + + + + + +``` + +[/example] + +[**the `hidden` attribute**](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden) + +Hide and reveal and element using the `[hidden]` attribute. A `[hidden]` expression should be a boolean expression. + +[example preview="inline" playground="true" imports="amp-bind"] + +```html +

Hello there!

+ + +``` + +[/example] + +**Size of [AMP components](https://www.ampproject.org/docs/reference/components)** + +Change the `width` and `height` using the `[width]` and `[height]` attributes. + +[example preview="inline" playground="true" imports="amp-bind"] -Notes on bindings: +```html + + + +``` + +[/example] + +**Accessibility states and properties** -- For security reasons, binding to `innerHTML` is disallowed. -- All attribute bindings are sanitized for unsafe values (e.g., `javascript:`). -- Boolean expression results toggle boolean attributes. For example: ``. When `expr` evaluates to `true`, the `` element has the `controls` attribute. When `expr` evaluates to `false`, the `controls` attribute is removed. -- Bracket characters `[` and `]` in attribute names can be problematic when writing XML (e.g. XHTML, JSX) or writing attributes via DOM APIs. In these cases, use the alternative syntax `data-amp-bind-x="foo"` instead of `[x]="foo"`. +Use to dynamically update information available to assistive technologies, such as screen readers. All `[aria-*]` and `[data-*]` are bindable. See the [full list here](https://www.w3.org/WAI/PF/aria-1.1/states_and_properties). -#### Element-specific attributes +**AMP Component specific and HTML attributes** -Binding to the following components and attributes are allowed: +Some AMP components and HTML elements have specific bindable attributes. They are listed below. + +### AMP component specific attributes [filter formats="websites"] -##### `` +**``** - `[data-account]` - `[data-embed]` @@ -467,7 +1016,7 @@ Binding to the following components and attributes are allowed: - `[data-video-id]` Changes the displayed Brightcove video. [/filter] -##### `` +**``** - `[slide]` Changes the currently displayed slide index. @@ -475,24 +1024,24 @@ Binding to the following components and attributes are allowed: [filter formats="websites"] -##### `` +**``** - `[min]` Sets the earliest selectable date - `[max]` Sets the latest selectable date -##### `` +**``** - `[src]` Displays the document at the updated URL. - `[title]` Changes the document's title. -##### `` +**``** - `[src]` Changes the iframe's source URL. [/filter] [filter formats="websites, ads"] -##### `` +**``** - `[alt]` - `[attribution]` @@ -503,13 +1052,13 @@ Bind to `[srcset]` instead of `[src]` to support responsive images. See correspo [/filter] [filter formats="email"] -##### `` +**``** - `[alt]` - `[attribution]` [/filter] -##### `` +**``** - `[open]` Toggles display of the lightbox. @@ -519,7 +1068,7 @@ Use `on="lightboxClose: AMP.setState(...)"` to update variables when the lightbo [filter formats="websites, stories"] -##### `` +**``** - `[src]` @@ -528,7 +1077,7 @@ If the expression is a string, it fetches and renders JSON from the string URL. [filter formats="websites, email"] -##### `` +**``** - `[selected]` Changes the currently selected children element(s) identified by their `option` attribute values. Supports a comma-separated list of values for multiple selection. [See an example](https://amp.dev/documentation/examples/multimedia-animations/image_galleries_with_amp-carousel/?format=email#linking-carousels-with-amp-bind). - `[disabled]` @@ -541,7 +1090,7 @@ If the expression is a string, it fetches and renders JSON from the string URL. [filter formats="websites, stories, ads"] -##### `` +**``** - `[src]` @@ -550,7 +1099,7 @@ Fetches JSON from the new URL and merges it into the existing state. The followi [filter formats="websites, stories"] -##### `` +**``** - `[data-tweetid]` Changes the displayed Tweet. @@ -558,7 +1107,7 @@ Fetches JSON from the new URL and merges it into the existing state. The followi [filter formats="websites, stories, ads"] -##### `` +**``** - `[alt]` - `[attribution]` @@ -573,7 +1122,7 @@ See corresponding [`amp-video` attributes](../amp-video/amp-video.md#attributes) [filter formats="websites, ads"] -##### `` +**``** - `[data-videoid]` Changes the displayed YouTube video. @@ -581,11 +1130,13 @@ See corresponding [`amp-video` attributes](../amp-video/amp-video.md#attributes) [filter formats="websites, stories, ads"] -##### `` +### HTML attributes + +**``** - `[href]` Changes the link. -##### `