diff --git a/tools/experiments/experiments.html b/tools/experiments/experiments.html index 27ab54fb922d..ffeb12c174c8 100644 --- a/tools/experiments/experiments.html +++ b/tools/experiments/experiments.html @@ -81,6 +81,21 @@ min-width: 60px; } + details { + border: solid 1px gray; + margin-top: 5px; + padding: 10px; + } + + details summary { + color: gray; + cursor: pointer; + } + + details[open] summary { + color: inherit; + } + #subdomain { color: gray; font-family: 'Courier New', Courier, monospace; @@ -112,6 +127,21 @@ display: block; } + #rtv { + border: solid 1px gray; + width: calc(100% - 150px); + padding: 10px; + } + + #rtv-submit { + background: #aaa; + margin: 5px; + } + + #rtv-submit[disabled] { + color: #777; + } + #redirect > input { border: solid 1px gray; width: calc(100% - 20px); @@ -218,6 +248,20 @@

Release channels

+
+ Advanced +

+ You can manually opt in to a previously released RTV using this form. +

+

+ Warning: This is an advanced setting. It is intended for use while debugging AMP runtime code, and is + not meant to be used to publish AMP documents. To opt out of this setting, clear the RTV in the input field. +

+
+ + +
+
diff --git a/tools/experiments/experiments.js b/tools/experiments/experiments.js index c4b0404a2f1d..f398b635c73a 100644 --- a/tools/experiments/experiments.js +++ b/tools/experiments/experiments.js @@ -34,6 +34,10 @@ setReportError(reportError); const COOKIE_MAX_AGE_DAYS = 180; // 6 month const MS_PER_DAY = 24 * 60 * 60 * 1000; const COOKIE_MAX_AGE_MS = COOKIE_MAX_AGE_DAYS * MS_PER_DAY; +const RTV_COOKIE_MAX_AGE_MS = MS_PER_DAY / 2; + +const RTV_PATTERN = /^\d{15}$/; + /** * @typedef {{ * id: string, @@ -49,8 +53,9 @@ let ExperimentDef; * interpreted by the server to deliver a different version of the AMP * JS libraries. */ -const CANARY_EXPERIMENT_ID = 'dev-channel'; -const RC_EXPERIMENT_ID = 'rc-channel'; +const EXPERIMENTAL_CHANNEL_ID = 'experimental-channel'; +const BETA_CHANNEL_ID = 'beta-channel'; +const RTV_CHANNEL_ID = 'rtv-channel'; /** * The different states of the AMP_CANARY cookie. @@ -65,7 +70,7 @@ const AMP_CANARY_COOKIE = { const CHANNELS = [ // Experimental Channel { - id: CANARY_EXPERIMENT_ID, + id: EXPERIMENTAL_CHANNEL_ID, name: 'AMP Experimental Channel (more info)', spec: 'https://github.com/ampproject/amphtml/blob/master/' + @@ -73,7 +78,7 @@ const CHANNELS = [ }, // Beta Channel { - id: RC_EXPERIMENT_ID, + id: BETA_CHANNEL_ID, name: 'AMP Beta Channel (more info)', spec: 'https://github.com/ampproject/amphtml/blob/master/' + @@ -138,6 +143,32 @@ function build() { } else { redirect.setAttribute('hidden', ''); } + + const rtvInput = document.getElementById('rtv'); + const rtvButton = document.getElementById('rtv-submit'); + rtvInput.addEventListener('input', () => { + rtvButton.disabled = rtvInput.value && !RTV_PATTERN.test(rtvInput.value); + rtvButton.textContent = rtvInput.value ? 'opt-in' : 'opt-out'; + }); + rtvButton.addEventListener('click', () => { + if (!rtvInput.value) { + showConfirmation_( + 'Do you really want to opt out of RTV?', + setAmpCanaryCookie_.bind(null, AMP_CANARY_COOKIE.DISABLED) + ); + } else if (RTV_PATTERN.test(rtvInput.value)) { + showConfirmation_( + `Do you really want to opt in to RTV ${rtvInput.value}?`, + setAmpCanaryCookie_.bind(null, rtvInput.value) + ); + } + }); + + if (isExperimentOn_(RTV_CHANNEL_ID)) { + rtvInput.value = getCookie(window, 'AMP_CANARY'); + rtvInput.dispatchEvent(new Event('input')); + document.getElementById('rtv-details').open = true; + } } /** @@ -237,24 +268,31 @@ function updateExperimentRow(experiment) { * @return {boolean} */ function isExperimentOn_(id) { - if (id == CANARY_EXPERIMENT_ID) { - return getCookie(window, 'AMP_CANARY') == AMP_CANARY_COOKIE.CANARY; - } else if (id == RC_EXPERIMENT_ID) { - return getCookie(window, 'AMP_CANARY') == AMP_CANARY_COOKIE.RC; + switch (id) { + case EXPERIMENTAL_CHANNEL_ID: + return getCookie(window, 'AMP_CANARY') == AMP_CANARY_COOKIE.CANARY; + case BETA_CHANNEL_ID: + return getCookie(window, 'AMP_CANARY') == AMP_CANARY_COOKIE.RC; + case RTV_CHANNEL_ID: + return RTV_PATTERN.test(getCookie(window, 'AMP_CANARY')); + default: + return isExperimentOn(window, /*OK*/ id); } - return isExperimentOn(window, /*OK*/ id); } /** * Opts in to / out of the "canary" or "rc" runtime types by setting the * AMP_CANARY cookie. - * @param {string} cookieState One of AMP_CANARY_COOKIE.{DISABLED|CANARY|RC} + * @param {string} cookieState One of AMP_CANARY_COOKIE.{DISABLED|CANARY|RC} or + * a 15-digit RTV. */ function setAmpCanaryCookie_(cookieState) { - const validUntil = - cookieState != AMP_CANARY_COOKIE.DISABLED - ? Date.now() + COOKIE_MAX_AGE_MS - : 0; + let validUntil = 0; + if (RTV_PATTERN.test(cookieState)) { + validUntil = Date.now() + RTV_COOKIE_MAX_AGE_MS; + } else if (cookieState != AMP_CANARY_COOKIE.DISABLED) { + validUntil = Date.now() + COOKIE_MAX_AGE_MS; + } const cookieOptions = { // Set explicit domain, so the cookie gets sent to sub domains. domain: location.hostname, @@ -279,17 +317,17 @@ function setAmpCanaryCookie_(cookieState) { function toggleExperiment_(id, name, opt_on) { const currentlyOn = isExperimentOn_(id); const on = opt_on === undefined ? !currentlyOn : opt_on; - // Protect against click jacking. + // Protect against accidental choice. const confirmMessage = on - ? 'Do you really want to activate the AMP experiment' - : 'Do you really want to deactivate the AMP experiment'; + ? 'Do you really want to activate the AMP experiment?' + : 'Do you really want to deactivate the AMP experiment?'; showConfirmation_(`${confirmMessage}: "${name}"`, () => { - if (id == CANARY_EXPERIMENT_ID) { + if (id == EXPERIMENTAL_CHANNEL_ID) { setAmpCanaryCookie_( on ? AMP_CANARY_COOKIE.CANARY : AMP_CANARY_COOKIE.DISABLED ); - } else if (id == RC_EXPERIMENT_ID) { + } else if (id == BETA_CHANNEL_ID) { setAmpCanaryCookie_( on ? AMP_CANARY_COOKIE.RC : AMP_CANARY_COOKIE.DISABLED ); @@ -348,7 +386,7 @@ function getAmpConfig() { xhr.send(null); return promise .then(text => { - const match = text.match(/self\.AMP_CONFIG=([^;]+)/); + const match = text.match(/self\.AMP_CONFIG=(\{.+?\})/); if (!match) { throw new Error("Can't find AMP_CONFIG in: " + text); }