diff --git a/build-system/global-configs/experiments-config.json b/build-system/global-configs/experiments-config.json
index f061431de029..62f25fecdde8 100644
--- a/build-system/global-configs/experiments-config.json
+++ b/build-system/global-configs/experiments-config.json
@@ -1,5 +1,11 @@
{
- "experimentA": {},
+ "experimentA": {
+ "name": "A4A No Signing RTV Experiment",
+ "environment": "AMP",
+ "issue": "https://github.com/ampproject/amphtml/issues/27189",
+ "expiration_date_utc": "2020-12-30",
+ "define_experiment_constant": "NO_SIGNING_RTV"
+ },
"experimentB": {},
"experimentC": {}
}
diff --git a/build-system/tasks/e2e/amp-driver.js b/build-system/tasks/e2e/amp-driver.js
index 316515bb77f8..1b7727e69be8 100644
--- a/build-system/tasks/e2e/amp-driver.js
+++ b/build-system/tasks/e2e/amp-driver.js
@@ -22,7 +22,6 @@ const AmpdocEnvironment = {
// AMPHTML ads environments
A4A_FIE: 'a4a-fie',
- A4A_FIE_NO_SIGNING: 'a4a-fie-no-signing',
A4A_INABOX: 'a4a-inabox',
A4A_INABOX_FRIENDLY: 'a4a-inabox-friendly',
A4A_INABOX_SAFEFRAME: 'a4a-inabox-safeframe',
@@ -99,21 +98,6 @@ const EnvironmentBehaviorMap = {
},
},
- [AmpdocEnvironment.A4A_FIE_NO_SIGNING]: {
- async ready(controller) {
- return controller
- .findElement('amp-ad > iframe')
- .then((frame) => controller.switchToFrame(frame));
- },
-
- url(url) {
- const a4aUrl = url.replace(HOST, HOST + '/a4a');
- // Exp value is from extensions/amp-a4a/0.1/amp-a4a.js
- // NO_SIGNING_EXP.experiment
- return `${a4aUrl}?eid=a4a-no-signing:21066325`;
- },
- },
-
[AmpdocEnvironment.A4A_INABOX]: {
async ready(controller) {
return controller
diff --git a/build-system/tasks/e2e/describes-e2e.js b/build-system/tasks/e2e/describes-e2e.js
index 4c64d94176af..06d49b92c517 100644
--- a/build-system/tasks/e2e/describes-e2e.js
+++ b/build-system/tasks/e2e/describes-e2e.js
@@ -254,10 +254,6 @@ const EnvironmentVariantMap = {
name: 'AMPHTML ads FIE environment',
value: {environment: 'a4a-fie'},
},
- [AmpdocEnvironment.A4A_FIE_NO_SIGNING]: {
- name: 'AMPHTML ads FIE environment with no-signing exp enabled',
- value: {environment: 'a4a-fie-no-signing'},
- },
[AmpdocEnvironment.A4A_INABOX]: {
name: 'AMPHTML ads inabox environment',
value: {environment: 'a4a-inabox'},
@@ -280,7 +276,6 @@ const envPresets = {
],
'amp4ads-preset': [
AmpdocEnvironment.A4A_FIE,
- AmpdocEnvironment.A4A_FIE_NO_SIGNING,
AmpdocEnvironment.A4A_INABOX,
AmpdocEnvironment.A4A_INABOX_FRIENDLY,
AmpdocEnvironment.A4A_INABOX_SAFEFRAME,
diff --git a/extensions/amp-a4a/0.1/amp-a4a.js b/extensions/amp-a4a/0.1/amp-a4a.js
index 8918c320c72c..a502e0ed7b92 100644
--- a/extensions/amp-a4a/0.1/amp-a4a.js
+++ b/extensions/amp-a4a/0.1/amp-a4a.js
@@ -50,7 +50,6 @@ import {
getConsentPolicyState,
} from '../../../src/consent';
import {getContextMetadata} from '../../../src/iframe-attributes';
-import {getExperimentBranch} from '../../../src/experiments';
import {getMode} from '../../../src/mode';
import {insertAnalyticsElement} from '../../../src/extension-analytics';
import {
@@ -179,15 +178,6 @@ const LIFECYCLE_STAGE_TO_ANALYTICS_TRIGGER = {
'crossDomainIframeLoaded': AnalyticsTrigger.AD_IFRAME_LOADED,
};
-/**
- * @const @enum {string}
- */
-export const NO_SIGNING_EXP = {
- id: 'a4a-no-signing',
- control: '21066324',
- experiment: '21066325',
-};
-
/** @const @enum {string} */
export const MODULE_NOMODULE_PARAMS_EXP = {
ID: 'module-nomodule',
@@ -870,10 +860,8 @@ export class AmpA4A extends AMP.BaseElement {
* @return {boolean}
*/
isInNoSigningExp() {
- return (
- getExperimentBranch(this.win, NO_SIGNING_EXP.id) ===
- NO_SIGNING_EXP.experiment
- );
+ // eslint-disable-next-line no-undef
+ return !!NO_SIGNING_RTV;
}
/**
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 8b44278995b0..fbd35b152c00 100644
--- a/extensions/amp-a4a/0.1/test/test-amp-a4a.js
+++ b/extensions/amp-a4a/0.1/test/test-amp-a4a.js
@@ -31,7 +31,6 @@ import {
DEFAULT_SAFEFRAME_VERSION,
EXPERIMENT_FEATURE_HEADER_NAME,
INVALID_SPSA_RESPONSE,
- NO_SIGNING_EXP,
RENDERING_TYPE_HEADER,
SAFEFRAME_VERSION_HEADER,
assignAdUrlToError,
@@ -56,10 +55,6 @@ import {cancellation} from '../../../../src/error';
import {createElementWithAttributes} from '../../../../src/dom';
import {createIframePromise} from '../../../../testing/iframe';
import {dev, user} from '../../../../src/log';
-import {
- forceExperimentBranch,
- toggleExperiment,
-} from '../../../../src/experiments';
import {
incrementLoadingAds,
is3pThrottled,
@@ -70,1153 +65,1914 @@ import {data as testFragments} from './testdata/test_fragments';
import {toggleAmpdocFieForTesting} from '../../../../src/ampdoc-fie';
import {data as validCSSAmp} from './testdata/valid_css_at_rules_amp.reserialized';
-describes.realWin('no signing', {amp: true}, (env) => {
- let doc;
- let element;
- let a4a;
+// eslint-disable-next-line no-undef
+if (NO_SIGNING_RTV) {
+ describes.realWin('no signing', {amp: true}, (env) => {
+ let doc;
+ let element;
+ let a4a;
- beforeEach(() => {
- doc = env.win.document;
- toggleExperiment(env.win, 'a4a-no-signing', true);
- forceExperimentBranch(
- env.win,
- NO_SIGNING_EXP.id,
- NO_SIGNING_EXP.experiment
- );
- element = createElementWithAttributes(env.win.document, 'amp-ad', {
- 'width': '300',
- 'height': '250',
- 'type': 'doubleclick',
- 'layout': 'fixed',
+ beforeEach(() => {
+ doc = env.win.document;
+ element = createElementWithAttributes(env.win.document, 'amp-ad', {
+ 'width': '300',
+ 'height': '250',
+ 'type': 'doubleclick',
+ 'layout': 'fixed',
+ });
+ doc.body.appendChild(element);
+ a4a = new AmpA4A(element);
+ // Make the ad think it has size.
+ env.sandbox.stub(a4a, 'getIntersectionElementLayoutBox').returns({
+ height: 250,
+ width: 300,
+ });
+ env.sandbox.stub(a4a, 'getAdUrl').returns('https://adnetwork.com');
+ env.fetchMock.mock(
+ 'begin:https://adnetwork.com',
+ validCSSAmp.minifiedCreative
+ );
+ });
+
+ it('should contain the correct security features', async () => {
+ await a4a.buildCallback();
+ a4a.onLayoutMeasure();
+ await a4a.layoutCallback();
+ const fie = doc.body.querySelector('iframe[srcdoc]');
+ expect(fie.getAttribute('sandbox')).to.equal(
+ 'allow-forms allow-popups allow-popups-to-escape-sandbox ' +
+ 'allow-same-origin allow-top-navigation'
+ );
+ const cspMeta = fie.contentDocument.querySelector(
+ 'meta[http-equiv=Content-Security-Policy]'
+ );
+ expect(cspMeta).to.be.ok;
+ expect(cspMeta.content).to.include('img-src * data:;');
+ expect(cspMeta.content).to.include('media-src *;');
+ expect(cspMeta.content).to.include('font-src *;');
+ expect(cspMeta.content).to.include('connect-src *;');
+ expect(cspMeta.content).to.include("script-src 'none';");
+ expect(cspMeta.content).to.include("object-src 'none';");
+ expect(cspMeta.content).to.include("child-src 'none';");
+ expect(cspMeta.content).to.include("default-src 'none';");
+ expect(cspMeta.content).to.include(
+ 'style-src ' +
+ 'https://cdn.materialdesignicons.com ' +
+ 'https://cloud.typography.com ' +
+ 'https://fast.fonts.net ' +
+ 'https://fonts.googleapis.com ' +
+ 'https://maxcdn.bootstrapcdn.com https://p.typekit.net https://pro.fontawesome.com ' +
+ 'https://use.fontawesome.com ' +
+ 'https://use.typekit.net ' +
+ "'unsafe-inline';"
+ );
+ });
+
+ it('FIE should contain with adurl', async () => {
+ await a4a.buildCallback();
+ a4a.onLayoutMeasure();
+ await a4a.layoutCallback();
+ const fie = doc.body.querySelector('iframe[srcdoc]');
+ const base = fie.contentDocument.querySelector('base');
+ expect(base).to.be.ok;
+ expect(base.href).to.equal('https://adnetwork.com/');
+ });
+
+ it('should complete the rendering FIE', async () => {
+ const prioritySpy = env.sandbox.spy(a4a, 'updateLayoutPriority');
+ await a4a.buildCallback();
+ a4a.onLayoutMeasure();
+ await a4a.layoutCallback();
+ const fie = doc.body.querySelector('iframe[srcdoc]');
+ expect(fie).to.be.ok;
+ expect(fie.contentDocument.body.textContent).to.contain.string(
+ 'Hello, world.'
+ );
+ expect(prioritySpy).to.be.calledWith(LayoutPriority.CONTENT);
+ });
+
+ it('should collapse on no content', async () => {
+ env.fetchMock.config.overwriteRoutes = true;
+ env.fetchMock.mock('begin:https://adnetwork.com', ''); // no content.
+ const iframe3pInit = env.sandbox
+ .stub(AmpAdXOriginIframeHandler.prototype, 'init')
+ .resolves();
+ const collapseSpy = env.sandbox.spy(a4a, 'forceCollapse');
+
+ await a4a.buildCallback();
+ a4a.onLayoutMeasure();
+ await a4a.layoutCallback();
+ expect(collapseSpy).to.be.called;
+ expect(iframe3pInit).not.to.be.called;
+ });
+
+ it('should fallback to x-domain without ⚡️4ads', async () => {
+ env.fetchMock.config.overwriteRoutes = true;
+ env.fetchMock.mock(
+ 'begin:https://adnetwork.com',
+ testFragments.minimalDocOneStyle
+ );
+ const iframe3pInit = env.sandbox
+ .stub(AmpAdXOriginIframeHandler.prototype, 'init')
+ .resolves();
+ await a4a.buildCallback();
+ a4a.onLayoutMeasure();
+ await a4a.layoutCallback();
+ expect(iframe3pInit).to.be.called;
});
- doc.body.appendChild(element);
- a4a = new AmpA4A(element);
- // Make the ad think it has size.
- env.sandbox.stub(a4a, 'getIntersectionElementLayoutBox').returns({
- height: 250,
- width: 300,
- });
- env.sandbox.stub(a4a, 'getAdUrl').returns('https://adnetwork.com');
- env.fetchMock.mock(
- 'begin:https://adnetwork.com',
- validCSSAmp.minifiedCreative
- );
});
+}
+
+describe('amp-a4a', () => {
+ const IFRAME_SANDBOXING_FLAGS = [
+ 'allow-forms',
+ 'allow-modals',
+ 'allow-pointer-lock',
+ 'allow-popups',
+ 'allow-popups-to-escape-sandbox',
+ 'allow-same-origin',
+ 'allow-scripts',
+ 'allow-top-navigation-by-user-activation',
+ ];
+
+ let fetchMock;
+ let getSigningServiceNamesMock;
+ let whenVisibleMock;
+ let adResponse;
+ let onCreativeRenderSpy;
+ let getResourceStub;
- it('should contain the correct security features', async () => {
- await a4a.buildCallback();
- a4a.onLayoutMeasure();
- await a4a.layoutCallback();
- const fie = doc.body.querySelector('iframe[srcdoc]');
- expect(fie.getAttribute('sandbox')).to.equal(
- 'allow-forms allow-popups allow-popups-to-escape-sandbox ' +
- 'allow-same-origin allow-top-navigation'
- );
- const cspMeta = fie.contentDocument.querySelector(
- 'meta[http-equiv=Content-Security-Policy]'
+ beforeEach(() => {
+ fetchMock = null;
+ getSigningServiceNamesMock = window.sandbox.stub(
+ AmpA4A.prototype,
+ 'getSigningServiceNames'
);
- expect(cspMeta).to.be.ok;
- expect(cspMeta.content).to.include('img-src * data:;');
- expect(cspMeta.content).to.include('media-src *;');
- expect(cspMeta.content).to.include('font-src *;');
- expect(cspMeta.content).to.include('connect-src *;');
- expect(cspMeta.content).to.include("script-src 'none';");
- expect(cspMeta.content).to.include("object-src 'none';");
- expect(cspMeta.content).to.include("child-src 'none';");
- expect(cspMeta.content).to.include("default-src 'none';");
- expect(cspMeta.content).to.include(
- 'style-src ' +
- 'https://cdn.materialdesignicons.com ' +
- 'https://cloud.typography.com ' +
- 'https://fast.fonts.net ' +
- 'https://fonts.googleapis.com ' +
- 'https://maxcdn.bootstrapcdn.com https://p.typekit.net https://pro.fontawesome.com ' +
- 'https://use.fontawesome.com ' +
- 'https://use.typekit.net ' +
- "'unsafe-inline';"
+ onCreativeRenderSpy = window.sandbox.spy(
+ AmpA4A.prototype,
+ 'onCreativeRender'
);
+ getSigningServiceNamesMock.returns(['google']);
+ whenVisibleMock = window.sandbox.stub(AmpDoc.prototype, 'whenFirstVisible');
+ whenVisibleMock.returns(Promise.resolve());
+ getResourceStub = window.sandbox.stub(AmpA4A.prototype, 'getResource');
+ getResourceStub.returns({
+ getUpgradeDelayMs: () => 12345,
+ });
+ adResponse = {
+ headers: {
+ 'AMP-Fast-Fetch-Signature': validCSSAmp.signatureHeader,
+ },
+ body: validCSSAmp.reserialized,
+ };
+ adResponse.headers[AMP_SIGNATURE_HEADER] = validCSSAmp.signatureHeader;
});
- it('FIE should contain with adurl', async () => {
- await a4a.buildCallback();
- a4a.onLayoutMeasure();
- await a4a.layoutCallback();
- const fie = doc.body.querySelector('iframe[srcdoc]');
- const base = fie.contentDocument.querySelector('base');
- expect(base).to.be.ok;
- expect(base.href).to.equal('https://adnetwork.com/');
+ afterEach(() => {
+ if (fetchMock) {
+ fetchMock./*OK*/ restore();
+ fetchMock = null;
+ }
+ resetScheduledElementForTesting(window, 'amp-a4a');
});
- it('should complete the rendering FIE', async () => {
- const prioritySpy = env.sandbox.spy(a4a, 'updateLayoutPriority');
- await a4a.buildCallback();
- a4a.onLayoutMeasure();
- await a4a.layoutCallback();
- const fie = doc.body.querySelector('iframe[srcdoc]');
- expect(fie).to.be.ok;
- expect(fie.contentDocument.body.textContent).to.contain.string(
- 'Hello, world.'
+ /**
+ * Sets up testing by loading iframe within which test runs.
+ * @param {FixtureInterface} fixture
+ */
+ function setupForAdTesting(fixture) {
+ expect(fetchMock).to.be.null;
+ fetchMock = new FetchMock(fixture.win);
+ fetchMock.getOnce(
+ 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json',
+ {
+ body: validCSSAmp.publicKeyset,
+ status: 200,
+ headers: {'Content-Type': 'application/jwk-set+json'},
+ }
);
- expect(prioritySpy).to.be.calledWith(LayoutPriority.CONTENT);
- });
+ installDocService(fixture.win, /* isSingleDoc */ true);
+ const {doc} = fixture;
+ // TODO(a4a-cam@): This is necessary in the short term, until A4A is
+ // smarter about host document styling. The issue is that it needs to
+ // inherit the AMP runtime style element in order for shadow DOM-enclosed
+ // elements to behave properly. So we have to set up a minimal one here.
+ const ampStyle = doc.createElement('style');
+ ampStyle.setAttribute('amp-runtime', 'scratch-fortesting');
+ doc.head.appendChild(ampStyle);
+ }
+
+ /**
+ * @param {!Document} doc
+ * @param {Rect=} opt_rect
+ * @param {Element=} opt_body
+ */
+ function createA4aElement(doc, opt_rect, opt_body) {
+ const element = createElementWithAttributes(doc, 'amp-a4a', {
+ 'width': opt_rect ? String(opt_rect.width) : '200',
+ 'height': opt_rect ? String(opt_rect.height) : '50',
+ 'type': 'adsense',
+ });
+ element.getAmpDoc = () => {
+ const ampdocService = Services.ampdocServiceFor(doc.defaultView);
+ return ampdocService.getAmpDoc(element);
+ };
+ element.isBuilt = () => {
+ return true;
+ };
+ element.getLayoutBox = () => {
+ return opt_rect || layoutRectLtwh(0, 0, 200, 50);
+ };
+ element.getPageLayoutBox = () => {
+ return element.getLayoutBox.apply(element, arguments);
+ };
+ element.getIntersectionChangeEntry = () => {
+ return null;
+ };
+ const signals = new Signals();
+ element.signals = () => signals;
+ element.renderStarted = () => {
+ signals.signal('render-start');
+ };
+ (opt_body || doc.body).appendChild(element);
+ return element;
+ }
+
+ /**
+ * @param {Object=} opt_additionalInfo
+ * @return {string}
+ */
+ function buildCreativeString(opt_additionalInfo) {
+ const baseTestDoc = testFragments.minimalDocOneStyle;
+ const offsets = {...(opt_additionalInfo || {})};
+ offsets.ampRuntimeUtf16CharOffsets = [
+ baseTestDoc.indexOf('