From 9a2c05ebd2d2f70b45205f8e20b88fc217208367 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 13 Jul 2017 16:59:45 -0400 Subject: [PATCH 01/85] Adds 3p iframe to amp analytics. Uses Subscription API. Event message queue much simpler than previous PR. Response functionality is included, but extraData is currently commented out - will add again before PR --- examples/analytics-3p.amp.html | 26 ++++ .../data/fake_amp_ad_with_3p_analytics.html | 58 ++++++++ .../0.1/amp-analytics-3p-message-queue.js | 128 +++++++++++++++++ extensions/amp-analytics/0.1/amp-analytics.js | 44 +++++- src/3p-analytics-common.js | 136 ++++++++++++++++++ src/service/ampdoc-impl.js | 13 ++ 6 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 examples/analytics-3p.amp.html create mode 100644 extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html create mode 100644 extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js create mode 100644 src/3p-analytics-common.js diff --git a/examples/analytics-3p.amp.html b/examples/analytics-3p.amp.html new file mode 100644 index 000000000000..b80ed73d4635 --- /dev/null +++ b/examples/analytics-3p.amp.html @@ -0,0 +1,26 @@ + + + + + 3P AMP Analytics Example + + + + + + + + + + Here is some text above the ad.
+ +
Loading...
+
Could not display the fake ad :(
+
+
+ Here is some text below the ad.
+ + diff --git a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html new file mode 100644 index 000000000000..0d2f435fb7ba --- /dev/null +++ b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + +

+ By Golden Trvs Gol twister (Own work) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons +

+
+ + + + + + + diff --git a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js new file mode 100644 index 000000000000..4d87650628e7 --- /dev/null +++ b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js @@ -0,0 +1,128 @@ +/** + * 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. + */ + +import {dev} from '../../../src/log'; +import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../../../src/3p-analytics-common'; +import {SubscriptionApi} from '../../../src/iframe-helper'; + +/** @private @const {string} */ +const TAG_ = 'amp-analytics.CrossDomainIframeMessageQueue'; + +/** @private @const {number} */ +const MAX_QUEUE_SIZE_ = 100; + +/** + * @visibleForTesting + */ +export class AmpAnalytics3pEventMessageQueue { + /** + * Constructor + * @param {!Window} win The window element + * @param {!HTMLIFrameElement} frame The cross-domain iframe to send + * messages to + */ + constructor(win, frame) { + /** @private {!Window} */ + this.win_ = win; + + /** @private {!HTMLIFrameElement} */ + this.frame_ = frame; + + /** @private {boolean} */ + this.isReady_ = false; + + /** @private {!Object>} */ + this.creativeToPendingMessages_ = {}; + + /** @private {!Object} */ + this.creativeToPendingExtraData_ = {}; + + /** @private {string} */ + this.messageType_ = this.frame_.getAttribute('data-amp-3p-sentinel') + + AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT; + + /** @private {!../../../src/iframe-helper.SubscriptionApi} */ + this.postMessageApi_ = new SubscriptionApi(this.frame_, + this.messageType_, + false, + () => { + this.isReady_ = true; + this.flushQueue_(); + }); + } + + /** + * Returns whether the queue has been marked as ready yet + * @return {boolean} + * @VisibleForTesting + */ + isReady() { + return this.isReady_; + } + + /** + * Returns how many creativeId -> message(s) mappings there are + * @return {number} + * @VisibleForTesting + */ + queueSize() { + return Object.keys(this.creativeToPendingMessages_).length; + } + + /** + * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain + * iframe. + * @param {!string} creativeId Identifies which creative is sending the message + * @param {!string} data The data to be enqueued and then sent to the iframe + */ + enqueue(creativeId, data) { + this.creativeToPendingMessages_[creativeId] = + this.creativeToPendingMessages_[creativeId] || []; + if (this.queueSize() >= MAX_QUEUE_SIZE_) { + dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + creativeId); + this.creativeToPendingMessages_[creativeId].shift(); + } + this.creativeToPendingMessages_[creativeId].push(data); + this.flushQueue_(); + } + + /** + * Send queued data (if there is any) to a cross-domain iframe + * @private + */ + flushQueue_() { + if (this.isReady() && this.queueSize()) { + // TODO(jonkeller): Send extraData. Create enqueueExtraData(). + this.postMessageApi_.send(this.messageType_, + /** @type {!JsonObject} */ + (this.creativeToPendingMessages_)); + this.creativeToPendingMessages_ = {}; + } + } + + /** + * Test method to see which messages (if any) are associated with a given + * creativeId + * @param {!string} creativeId Identifies which creative is sending the message + * @return {Array} + * @VisibleForTesting + */ + messagesFor(creativeId) { + return /** @type {Array} */ ( + this.creativeToPendingMessages_[creativeId]); + } +} + diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index b751be5686f9..1e74023aebbd 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -17,12 +17,14 @@ import {isJsonScriptTag} from '../../../src/dom'; import {assertHttpsUrl, appendEncodedParamStringToUrl} from '../../../src/url'; import {dev, rethrowAsync, user} from '../../../src/log'; +import {getMode} from '../../../src/mode'; import {expandTemplate} from '../../../src/string'; import {isArray, isObject} from '../../../src/types'; import {dict, hasOwn, map} from '../../../src/utils/object'; import {sendRequest, sendRequestUsingIframe} from './transport'; import {IframeTransport} from './iframe-transport'; import {Services} from '../../../src/services'; +import {ResponseMap} from '../../../src/3p-analytics-common'; import {toggle} from '../../../src/style'; import {isEnumValue} from '../../../src/types'; import {parseJson} from '../../../src/json'; @@ -162,6 +164,15 @@ export class AmpAnalytics extends AMP.BaseElement { return this.ensureInitialized_(); } + /** @override */ + unlayoutCallback() { + const ampDoc = this.getAmpDoc(); + Transport.doneUsingCrossDomainIframe(ampDoc.win.document, + this.config_['transport']); + ResponseMap.remove(ampDoc, this.config_['transport']['type']); + return true; + } + /** @override */ detachedCallback() { if (this.analyticsGroup_) { @@ -289,6 +300,20 @@ export class AmpAnalytics extends AMP.BaseElement { return Promise.all(promises); } + /** + * Receives any response that may be sent from the cross-domain iframe. + * @param {!string} type The type parameter of the cross-domain iframe + * @param {!../../../src/3p-analytics-common.AmpAnalytics3pResponse} response + * The response message from the iframe that was specified in the + * amp-analytics config + */ + processCrossDomainIframeResponse_(type, response) { + ResponseMap.add(this.getAmpDoc(), + type, + /** @type {string} */ (this.win.document.baseURI), + response.data); + } + /** * Calls `AnalyticsGroup.addTrigger` and reports any errors. "NoInline" is * to avoid inlining this method so that `try/catch` does it veto @@ -413,6 +438,22 @@ export class AmpAnalytics extends AMP.BaseElement { } const typeConfig = this.predefinedConfig_[type] || {}; + // transport.iframe is only allowed to be specified in typeConfig, not + // the others. Allowed when running locally for testing purposes. + [defaultConfig, inlineConfig, this.remoteConfig_].forEach(config => { + if (config && config.transport && config.transport.iframe) { + const TAG = this.getName_(); + if (getMode().localDev) { + user().warn(TAG, 'Only typeConfig may specify iframe transport,' + + ' but in local dev mode, so okay', config); + } else { + user().error(TAG, 'Only typeConfig may specify iframe transport', + config); + return; + } + } + }); + this.mergeObjects_(defaultConfig, config); this.mergeObjects_(typeConfig, config, /* predefined */ true); if (typeConfig) { @@ -756,7 +797,8 @@ export class AmpAnalytics extends AMP.BaseElement { this.config_['transport']['iframe']) { this.iframeTransport_.sendRequest(request); } else { - sendRequest(this.win, request, this.config_['transport'] || {}); + this.transport_.sendRequest(this.win, request, + this.config_['transport'] || {}); } } diff --git a/src/3p-analytics-common.js b/src/3p-analytics-common.js new file mode 100644 index 000000000000..5939b1ceaab5 --- /dev/null +++ b/src/3p-analytics-common.js @@ -0,0 +1,136 @@ +/** + * 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. + */ + +/** @enum {string} */ +export const AMP_ANALYTICS_3P_MESSAGE_TYPE = { + READY: 'R', + CREATIVE: 'C', + EVENT: 'E', + RESPONSE: 'A', +}; + +/** @typedef {{ + * sentinel: (string|undefined), + * type: !string + * }} */ +export let AmpAnalytics3pReadyMessage; +// Example: +// { +// "sentinel":"20354662305315974", +// "type":AMP_ANALYTICS_3P_MESSAGE_TYPE.READY +// } +// The sentinel value will be present when received but the sender doesn't +// need to add it, this is done by iframe-messaging-client. + +/** @typedef {{ + * sentinel: (string|undefined), + * type: !string, + * data: !Object + * }} */ +export let AmpAnalytics3pNewCreative; +// Example: +// { +// "sentinel":"20354662305315974", +// "type":AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, +// "data": { +// "8117602251459417": "ThisIsExtraData", +// ... +// } +// } +// The sentinel value will be present when received but the sender doesn't +// need to add it, this is done by iframe-messaging-client. + +/** @typedef {{ + * sentinel: (string|undefined), + * type: !string, + * data: !Object> + * }} */ +export let AmpAnalytics3pEvent; +// Example: +// { +// "sentinel":"20354662305315974", +// "type":AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, +// "data":{ +// "8117602251459417": ["viewed=true&...etc.", ... ], +// ... +// } +// } +// The sentinel value will be present when received but the sender doesn't +// need to add it, this is done by iframe-messaging-client. + +/** @typedef {{ + * sentinel: (string|undefined), + * destination: !string, + * type: !string, + * data: ?Object + * }} */ +export let AmpAnalytics3pResponse; +// Example: +// { +// "sentinel":"20354662305315974", +// "destination":"8117602251459417", +// "type":AMP_ANALYTICS_3P_MESSAGE_TYPE.RESPONSE, +// "data":{"status":"received","somethingElse":"42"} +// } +// The sentinel value will be present when received but the sender doesn't +// need to add it, this is done by iframe-messaging-client. + +/** + * A class for holding AMP Analytics third-party vendors responses to frames. + */ +export class ResponseMap { + /** + * Add a response + * @param {!string} frameType The identifier for the third-party frame that + * responded + * @param {!string} creativeUrl The URL of the creative being responded to + * @param {Object} response What the response was + */ + static add(ampDoc, frameType, creativeUrl, response) { + const map = ampDoc.getAnchorClickListenerBinding(); + map[frameType] = map[frameType] || {}; + map[frameType][creativeUrl] = response; + } + + /** + * Gets the most recent response given by a certain frame to a certain + * creative + * @param {!string} frameType The identifier for the third-party frame + * whose response is sought + * @param {!string} creativeUrl The URL of the creative that the sought + * response was about + * @returns {?Object} + */ + static get(ampDoc, frameType, creativeUrl) { + const map = ampDoc.getAnchorClickListenerBinding(); + if (map[frameType] && map[frameType][creativeUrl]) { + return map[frameType][creativeUrl]; + } + return {}; + } + + /** + * Remove a response, for instance if a third-party frame is being destroyed + * @param {!string} frameType The identifier for the third-party frame + * whose responses are to be removed + */ + static remove(ampDoc, frameType) { + const map = ampDoc.getAnchorClickListenerBinding(); + if (map[frameType]) { + delete map[frameType]; + } + } +} diff --git a/src/service/ampdoc-impl.js b/src/service/ampdoc-impl.js index 75e1634d8aea..96d597f43905 100644 --- a/src/service/ampdoc-impl.js +++ b/src/service/ampdoc-impl.js @@ -187,6 +187,9 @@ export class AmpDoc { /** @private @const */ this.signals_ = new Signals(); + + /** @private {!Object>} */ + this.anchorClickListenerBinding_ = {}; } /** @@ -300,6 +303,16 @@ export class AmpDoc { contains(node) { return this.getRootNode().contains(node); } + + /** + * Binding of macro to function used as part of any installed anchor click + * listener. + * @return {!Object>} + * @see src/anchor-click-interceptor#installAnchorClickInterceptor + */ + getAnchorClickListenerBinding() { + return this.anchorClickListenerBinding_; + } } From c539c666e78bbc16de0046236e36e108acf0def0 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 13 Jul 2017 21:39:35 -0400 Subject: [PATCH 02/85] Fix SubscriptionAPI --- examples/analytics-3p.amp.html | 2 +- .../0.1/amp-analytics-3p-message-queue.js | 43 ++++++++++++++----- extensions/amp-analytics/0.1/amp-analytics.js | 4 +- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/examples/analytics-3p.amp.html b/examples/analytics-3p.amp.html index b80ed73d4635..c0a7298fc028 100644 --- a/examples/analytics-3p.amp.html +++ b/examples/analytics-3p.amp.html @@ -15,7 +15,7 @@ Here is some text above the ad.
Loading...
Could not display the fake ad :(
diff --git a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js index 4d87650628e7..b2e485e29c35 100644 --- a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js @@ -27,7 +27,7 @@ const MAX_QUEUE_SIZE_ = 100; /** * @visibleForTesting */ -export class AmpAnalytics3pEventMessageQueue { +export class AmpAnalytics3pMessageQueue { /** * Constructor * @param {!Window} win The window element @@ -47,6 +47,9 @@ export class AmpAnalytics3pEventMessageQueue { /** @private {!Object>} */ this.creativeToPendingMessages_ = {}; + /** @private {!Object} */ + this.creativeToExtraData_ = {}; + /** @private {!Object} */ this.creativeToPendingExtraData_ = {}; @@ -57,7 +60,7 @@ export class AmpAnalytics3pEventMessageQueue { /** @private {!../../../src/iframe-helper.SubscriptionApi} */ this.postMessageApi_ = new SubscriptionApi(this.frame_, this.messageType_, - false, + true, () => { this.isReady_ = true; this.flushQueue_(); @@ -82,20 +85,31 @@ export class AmpAnalytics3pEventMessageQueue { return Object.keys(this.creativeToPendingMessages_).length; } + /** + * Sets extra config data to be sent to a cross-domain iframe. + * @param {!string} creativeId Identifies which creative is sending the message + * @param {!string} extraData The event to be enqueued and then sent to the + * iframe + */ + setExtraData(creativeId, extraData) { + this.creativeToExtraData_[creativeId] = extraData; + this.flushQueue_(); + } + /** * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain * iframe. * @param {!string} creativeId Identifies which creative is sending the message - * @param {!string} data The data to be enqueued and then sent to the iframe + * @param {!string} event The event to be enqueued and then sent to the iframe */ - enqueue(creativeId, data) { + enqueue(creativeId, event) { this.creativeToPendingMessages_[creativeId] = this.creativeToPendingMessages_[creativeId] || []; if (this.queueSize() >= MAX_QUEUE_SIZE_) { dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + creativeId); this.creativeToPendingMessages_[creativeId].shift(); } - this.creativeToPendingMessages_[creativeId].push(data); + this.creativeToPendingMessages_[creativeId].push(event); this.flushQueue_(); } @@ -104,12 +118,19 @@ export class AmpAnalytics3pEventMessageQueue { * @private */ flushQueue_() { - if (this.isReady() && this.queueSize()) { - // TODO(jonkeller): Send extraData. Create enqueueExtraData(). - this.postMessageApi_.send(this.messageType_, - /** @type {!JsonObject} */ - (this.creativeToPendingMessages_)); - this.creativeToPendingMessages_ = {}; + if (this.isReady()) { + if (Object.keys(this.creativeToExtraData_).length) { + this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, + /** @type {!JsonObject} */ + ({data: this.creativeToExtraData_})); + this.creativeToExtraData_ = {}; + } + if (this.queueSize()) { + this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + /** @type {!JsonObject} */ + ({data: this.creativeToPendingMessages_})); + this.creativeToPendingMessages_ = {}; + } } } diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index 1e74023aebbd..e43567f3b03b 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -164,12 +164,14 @@ export class AmpAnalytics extends AMP.BaseElement { return this.ensureInitialized_(); } - /** @override */ + /* @override */ unlayoutCallback() { + /* const ampDoc = this.getAmpDoc(); Transport.doneUsingCrossDomainIframe(ampDoc.win.document, this.config_['transport']); ResponseMap.remove(ampDoc, this.config_['transport']['type']); + */ return true; } From 2f892c3918ba58c06b0ffd5dded01b2d5edfc9b8 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 13 Jul 2017 21:48:15 -0400 Subject: [PATCH 03/85] Brings in previous chanages to anchor-click-interceptor.js --- src/anchor-click-interceptor.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/anchor-click-interceptor.js b/src/anchor-click-interceptor.js index 0ffa470ec2a5..fecba905fc35 100644 --- a/src/anchor-click-interceptor.js +++ b/src/anchor-click-interceptor.js @@ -19,6 +19,7 @@ import { } from './dom'; import {dev} from './log'; import {Services} from './services'; +import {ResponseMap} from './3p-analytics-common'; /** @private @const {string} */ const ORIG_HREF_ATTRIBUTE = 'data-a4a-orig-href'; @@ -59,6 +60,14 @@ function maybeExpandUrlParams(ampdoc, e) { 'CLICK_Y': () => { return e.pageY; }, + '3PANALYTICS': (frameType, key) => { + const responses = ResponseMap.get(ampdoc, frameType, + /** @type {!string} */ (target.baseURI)); + if (responses && responses[key]) { + return responses[key]; + } + return ''; + }, }; const newHref = Services.urlReplacementsForDoc(ampdoc).expandSync( hrefToExpand, vars, undefined, /* opt_whitelist */ { @@ -67,6 +76,7 @@ function maybeExpandUrlParams(ampdoc, e) { // NOTE: Addition to this whitelist requires additional review. 'CLICK_X': true, 'CLICK_Y': true, + '3PANALYTICS': true, }); if (newHref != hrefToExpand) { // Store original value so that later clicks can be processed with @@ -81,3 +91,4 @@ function maybeExpandUrlParams(ampdoc, e) { export function maybeExpandUrlParamsForTesting(ampdoc, e) { maybeExpandUrlParams(ampdoc, e); } + From e75452273ff3538ea43dbd0d6c1101fc28b3a635 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 13 Jul 2017 23:39:01 -0400 Subject: [PATCH 04/85] Adds unit tests --- .../0.1/amp-analytics-3p-message-queue.js | 30 ++- .../test-amp-analytics-3p-message-queue.js | 107 +++++++++++ .../amp-analytics/0.1/test/test-transport.js | 179 +++++++++++++++--- 3 files changed, 286 insertions(+), 30 deletions(-) create mode 100644 extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js diff --git a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js index b2e485e29c35..0a7c3fcfafdc 100644 --- a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js @@ -62,7 +62,7 @@ export class AmpAnalytics3pMessageQueue { this.messageType_, true, () => { - this.isReady_ = true; + this.setIsReady(); this.flushQueue_(); }); } @@ -76,6 +76,16 @@ export class AmpAnalytics3pMessageQueue { return this.isReady_; } + /** + * Indicate that a cross-domain frame is ready to receive messages, and + * send all messages that were previously queued for it. + * @VisibleForTesting + */ + setIsReady() { + this.isReady_ = true; + this.flushQueue_(); + } + /** * Returns how many creativeId -> message(s) mappings there are * @return {number} @@ -87,15 +97,27 @@ export class AmpAnalytics3pMessageQueue { /** * Sets extra config data to be sent to a cross-domain iframe. - * @param {!string} creativeId Identifies which creative is sending the message - * @param {!string} extraData The event to be enqueued and then sent to the - * iframe + * @param {!string} creativeId Identifies which creative is sending the + * extra data + * @param {!string} extraData The extra config data */ setExtraData(creativeId, extraData) { + dev().assert(!this.creativeToExtraData_[creativeId], + 'Replacing existing extra data for ' + creativeId); this.creativeToExtraData_[creativeId] = extraData; this.flushQueue_(); } + /** + * Test method to get extra config data to be sent to a cross-domain iframe. + * @param {!string} creativeId Identifies which creative is sending the + * extra data + * @returns {string} The extra config data + */ + getExtraData(creativeId) { + return this.creativeToExtraData_[creativeId]; + } + /** * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain * iframe. diff --git a/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js new file mode 100644 index 000000000000..6d89914595cc --- /dev/null +++ b/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js @@ -0,0 +1,107 @@ +/** + * 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. + */ + +import { + AmpAnalytics3pMessageQueue, +} from '../amp-analytics-3p-message-queue'; +import { + AMP_ANALYTICS_3P_MESSAGE_TYPE, +} from '../../../../src/3p-analytics-common'; +import {SubscriptionApi} from '../../../../src/iframe-helper'; +import {Timer} from '../../../../src/service/timer-impl'; +import {adopt} from '../../../../src/runtime'; +import * as sinon from 'sinon'; + +adopt(window); + +describe('amp-analytics.amp-analytics-3p-message-queue', () => { + let sandbox; + let sentinel = '42'; + let frame; + let queue; + let timer; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + frame = { + getAttribute: function(name) { return 'some_value'; }, + src: 'http://localhost', + ownerDocument: { + defaultView: window, + }, + }; + queue = new AmpAnalytics3pMessageQueue(window, frame); + timer = new Timer(window); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('is empty when first created ', () => { + expect(queue.queueSize()).to.equal(0); + }); + + it('is not ready until setIsReady() is called ', () => { + expect(queue.isReady()).to.be.false; + queue.setIsReady(); + expect(queue.isReady()).to.be.true; + }); + + it('queues messages when not ready to send ', () => { + const beforeCount = queue.queueSize(); + queue.enqueue('some_senderId', 'some_data'); + queue.enqueue('another_senderId', 'some_data'); + const afterCount = queue.queueSize(); + expect(afterCount - beforeCount).to.equal(2); + }); + + it('flushes the queue when ready to send ', () => { + queue.enqueue('some_senderId', 'some_data'); + queue.setIsReady(); + const afterCount = queue.queueSize(); + expect(afterCount).to.equal(0); + }); + + it('groups messages from same sender ', () => { + queue.enqueue('letter_sender', 'A'); + queue.enqueue('letter_sender', 'B'); + queue.enqueue('letter_sender', 'C'); + queue.enqueue('number_sender', '1'); + queue.enqueue('number_sender', '2'); + queue.enqueue('number_sender', '3'); + queue.enqueue('number_sender', '4'); + const letterCount = queue.messagesFor('letter_sender').length; + const numberCount = queue.messagesFor('number_sender').length; + expect(queue.queueSize()).to.equal(2); + expect(letterCount).to.equal(3); + expect(numberCount).to.equal(4); + }); + + it('only allows extraData to be set once per sender ', () => { + queue.setExtraData('letter_sender', 'A'); + queue.setExtraData('number_sender', '1'); + + expect(() => { + queue.setExtraData('letter_sender', 'B'); + }).to.throw(/Replacing existing extra data/); + + expect(() => { + queue.setExtraData('number_sender', '2'); + }).to.throw(/Replacing existing extra data/); + }); +}); + diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index 08333905a377..0a1bf35a9ed9 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {sendRequest, sendRequestUsingIframe, Transport} from '../transport'; +import {sendRequestUsingIframe, Transport} from '../transport'; import {adopt} from '../../../../src/runtime'; import {loadPromise} from '../../../../src/event-helper'; import * as sinon from 'sinon'; @@ -24,22 +24,31 @@ adopt(window); describe('amp-analytics.transport', () => { let sandbox; + const transport = new Transport(); beforeEach(() => { sandbox = sinon.sandbox.create(); }); afterEach(() => { + Transport.resetCrossDomainIframes(); sandbox.restore(); }); - function setupStubs(beaconRetval, xhrRetval) { - sandbox.stub(Transport, 'sendRequestUsingImage'); + function setupStubs(crossDomainIframeRetval, imageRetval, + beaconRetval, xhrRetval) { + sandbox.stub(transport, 'sendRequestUsingCrossDomainIframe').returns( + crossDomainIframeRetval); + sandbox.stub(Transport, 'sendRequestUsingImage').returns(imageRetval); sandbox.stub(Transport, 'sendRequestUsingBeacon').returns(beaconRetval); sandbox.stub(Transport, 'sendRequestUsingXhr').returns(xhrRetval); } function assertCallCounts( + expectedCrossDomainIframeCalls, expectedBeaconCalls, expectedXhrCalls, expectedImageCalls) { + expect(transport.sendRequestUsingCrossDomainIframe.callCount, + 'sendRequestUsingCrossDomainIframe call count').to.equal( + expectedCrossDomainIframeCalls); expect(Transport.sendRequestUsingBeacon.callCount, 'sendRequestUsingBeacon call count').to.equal(expectedBeaconCalls); expect(Transport.sendRequestUsingXhr.callCount, @@ -48,69 +57,187 @@ describe('amp-analytics.transport', () => { 'sendRequestUsingImage call count').to.equal(expectedImageCalls); } + function expectAllUnique(numArray) { + if (!numArray) { + return; + } + expect(numArray.length).to.equal(new Set(numArray).size); + } + + it('prefers cross-domain iframe over beacon, xhrpost, and image', () => { + setupStubs(true, true, true, true); + transport.sendRequest(window, 'https://example.com/test', { + iframe: 'https://example.com/test', + }); + assertCallCounts(1, 0, 0, 0); + }); + it('prefers beacon over xhrpost and image', () => { - setupStubs(true, true); - sendRequest(window, 'https://example.com/test', { + setupStubs(true, true, true, true); + transport.sendRequest(window, 'https://example.com/test', { beacon: true, xhrpost: true, image: true, }); - assertCallCounts(1, 0, 0); + assertCallCounts(0, 1, 0, 0); }); it('prefers xhrpost over image', () => { - setupStubs(true, true); - sendRequest(window, 'https://example.com/test', { + setupStubs(true, true, true, true); + transport.sendRequest(window, 'https://example.com/test', { beacon: false, xhrpost: true, image: true, }); - assertCallCounts(0, 1, 0); + assertCallCounts(0, 0, 1, 0); }); it('reluctantly uses image if nothing else is enabled', () => { - setupStubs(true, true); - sendRequest(window, 'https://example.com/test', { + setupStubs(true, true, true, true); + transport.sendRequest(window, 'https://example.com/test', { image: true, }); - assertCallCounts(0, 0, 1); + assertCallCounts(0, 0, 0, 1); }); it('falls back to xhrpost when enabled and beacon is not available', () => { - setupStubs(false, true); - sendRequest(window, 'https://example.com/test', { + setupStubs(false, false, false, true); + transport.sendRequest(window, 'https://example.com/test', { beacon: true, xhrpost: true, image: true, }); - assertCallCounts(1, 1, 0); + assertCallCounts(0, 1, 1, 0); }); it('falls back to image when beacon not found and xhr disabled', () => { - setupStubs(false, true); - sendRequest(window, 'https://example.com/test', { + setupStubs(false, false, false, true); + transport.sendRequest(window, 'https://example.com/test', { beacon: true, xhrpost: false, image: true, }); - assertCallCounts(1, 0, 1); + assertCallCounts(0, 1, 0, 1); }); it('falls back to image when beacon and xhr are not available', () => { - setupStubs(false, false); - sendRequest(window, 'https://example.com/test', { + setupStubs(false, false, false, false); + transport.sendRequest(window, 'https://example.com/test', { beacon: true, xhrpost: true, image: true, }); - assertCallCounts(1, 1, 1); + assertCallCounts(0, 1, 1, 1); + }); + + it('must create xframe before sending message to it', () => { + expect(() => { + transport.sendRequest(window, 'https://example.com/test', { + iframe: 'https://example.com/test', + }); + }).to.throw(/send message to non-existent/); + }); + + it('reuses cross-domain iframe', () => { + const config = { + iframe: 'https://example.com/test', + }; + sandbox.spy(transport, 'createCrossDomainIframe'); + transport.processCrossDomainIframe(window, config); + + expect(transport.createCrossDomainIframe.calledOnce).to.be.true; + expect(Transport.hasCrossDomainIframe(config.iframe)).to.be.true; + + transport.processCrossDomainIframe(window, config); + expect(transport.createCrossDomainIframe.calledOnce).to.be.true; + }); + + it('sends extra data to iframe', () => { + const url = 'https://example.com/test'; + const extraData = 'some extra data'; + const config = { + iframe: url, + extraData, + }; + transport.processCrossDomainIframe(window, config); + const queue = Transport.getFrameData(url).queue; + expect(queue.getExtraData(transport.getId())).to.equal(extraData); + expect(queue.queueSize()).to.equal(0); + }); + + it('enqueues event messages correctly', () => { + const url = 'https://example.com/test'; + const config = {iframe: url}; + transport.processCrossDomainIframe(window, config); + transport.sendRequest(window, 'hello, world!', config); + const queue = Transport.getFrameData(url).queue; + expect(queue.messagesFor(transport.getId()).length).to.equal(1); + transport.sendRequest(window, 'hello again, world!', config); + expect(queue.messagesFor(transport.getId()).length).to.equal(2); + }); + + it('does not cause sentinel collisions', () => { + const url1 = 'https://example.com/test'; + const url2 = 'https://example.com/test2'; + const url3 = 'https://example.com/test3'; + const url4 = 'https://example.com/test4'; + const transport2 = new Transport(); + + transport.processCrossDomainIframe(window, {iframe: url1}); + transport.processCrossDomainIframe(window, {iframe: url2}); + transport2.processCrossDomainIframe(window, {iframe: url3}); + transport2.processCrossDomainIframe(window, {iframe: url4}); + const frame1 = Transport.getFrameData(url1); + const frame2 = Transport.getFrameData(url2); + const frame3 = Transport.getFrameData(url3); + const frame4 = Transport.getFrameData(url4); + expectAllUnique([transport.getId(), transport2.getId(), frame1.sentinel, + frame2.sentinel, frame3.sentinel, frame4.sentinel]); + }); + + it('correctly tracks usageCount and destroys iframes', () => { + // Add 2 iframes. + const url1 = 'https://example.com/usageCountTest1'; + const url2 = 'https://example.com/usageCountTest2'; + transport.processCrossDomainIframe(window, {iframe: url1}); + transport.processCrossDomainIframe(window, {iframe: url2}); + const frame1 = Transport.getFrameData(url1); + const frame2 = Transport.getFrameData(url2); + expect(frame1.usageCount).to.equal(1); + expect(frame2.usageCount).to.equal(1); + expect(window.document.getElementsByTagName('IFRAME').length).to.equal(2); + + // Mark the iframes as used multiple times each. + transport.processCrossDomainIframe(window, {iframe: url1}); + transport.processCrossDomainIframe(window, {iframe: url1}); + transport.processCrossDomainIframe(window, {iframe: url2}); + transport.processCrossDomainIframe(window, {iframe: url2}); + transport.processCrossDomainIframe(window, {iframe: url2}); + expect(frame1.usageCount).to.equal(3); + expect(frame2.usageCount).to.equal(4); + + // Stop using the iframes, make sure usage counts go to zero and they are + // removed from the DOM. + Transport.doneUsingCrossDomainIframe(window.document, {iframe: url1}); + expect(frame1.usageCount).to.equal(2); + Transport.doneUsingCrossDomainIframe(window.document, {iframe: url1}); + Transport.doneUsingCrossDomainIframe(window.document, {iframe: url1}); + expect(frame1.usageCount).to.equal(0); + expect(frame2.usageCount).to.equal(4); // (Still) + expect(window.document.getElementsByTagName('IFRAME').length).to.equal(1); + Transport.doneUsingCrossDomainIframe(window.document, {iframe: url2}); + Transport.doneUsingCrossDomainIframe(window.document, {iframe: url2}); + Transport.doneUsingCrossDomainIframe(window.document, {iframe: url2}); + Transport.doneUsingCrossDomainIframe(window.document, {iframe: url2}); + expect(frame2.usageCount).to.equal(0); + expect(window.document.getElementsByTagName('IFRAME').length).to.equal(0); }); it('does not send a request when no transport methods are enabled', () => { - setupStubs(true, true); - sendRequest(window, 'https://example.com/test', {}); - assertCallCounts(0, 0, 0); + setupStubs(true, true, true, true); + transport.sendRequest(window, 'https://example.com/test', {}); + assertCallCounts(0, 0, 0, 0); }); it('asserts that urls are https', () => { expect(() => { - sendRequest(window, 'http://example.com/test'); + transport.sendRequest(window, 'http://example.com/test'); }).to.throw(/https/); }); it('should NOT allow __amp_source_origin', () => { expect(() => { - sendRequest(window, 'https://twitter.com?__amp_source_origin=1'); + transport.sendRequest(window, 'https://twitter.com?__amp_source_origin=1'); }).to.throw(/Source origin is not allowed in/); }); From be880d693d643d300cbcc3e0aaf68bcb5344987b Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 07:47:47 -0400 Subject: [PATCH 05/85] Updates queue unit tests --- .../0.1/amp-analytics-3p-message-queue.js | 2 +- .../0.1/test/test-amp-analytics-3p-message-queue.js | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js index 0a7c3fcfafdc..4d42c4677102 100644 --- a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js @@ -103,7 +103,7 @@ export class AmpAnalytics3pMessageQueue { */ setExtraData(creativeId, extraData) { dev().assert(!this.creativeToExtraData_[creativeId], - 'Replacing existing extra data for ' + creativeId); + 'Replacing existing extra data for ' + creativeId); this.creativeToExtraData_[creativeId] = extraData; this.flushQueue_(); } diff --git a/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js index 6d89914595cc..d861315b304f 100644 --- a/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js +++ b/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js @@ -17,11 +17,6 @@ import { AmpAnalytics3pMessageQueue, } from '../amp-analytics-3p-message-queue'; -import { - AMP_ANALYTICS_3P_MESSAGE_TYPE, -} from '../../../../src/3p-analytics-common'; -import {SubscriptionApi} from '../../../../src/iframe-helper'; -import {Timer} from '../../../../src/service/timer-impl'; import {adopt} from '../../../../src/runtime'; import * as sinon from 'sinon'; @@ -29,22 +24,19 @@ adopt(window); describe('amp-analytics.amp-analytics-3p-message-queue', () => { let sandbox; - let sentinel = '42'; let frame; let queue; - let timer; beforeEach(() => { sandbox = sinon.sandbox.create(); frame = { - getAttribute: function(name) { return 'some_value'; }, + getAttribute: () => 'some_value', src: 'http://localhost', ownerDocument: { defaultView: window, }, }; queue = new AmpAnalytics3pMessageQueue(window, frame); - timer = new Timer(window); }); afterEach(() => { From 8c76838492b23f3d6cb390d12c3e7bc5459a142e Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 11:54:36 -0400 Subject: [PATCH 06/85] Updates custom types and unit tests --- .../0.1/amp-analytics-3p-message-queue.js | 5 +- extensions/amp-analytics/0.1/amp-analytics.js | 2 +- .../test-amp-analytics-3p-message-queue.js | 23 ++++--- src/3p-analytics-common.js | 64 +++---------------- 4 files changed, 26 insertions(+), 68 deletions(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js index 4d42c4677102..08c4565db2c7 100644 --- a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js @@ -47,10 +47,11 @@ export class AmpAnalytics3pMessageQueue { /** @private {!Object>} */ this.creativeToPendingMessages_ = {}; - /** @private {!Object} */ + /** @private + * {!../../../src/3p-analytics-common.AmpAnalytics3pNewCreative} */ this.creativeToExtraData_ = {}; - /** @private {!Object} */ + /** @private {!../../../src/3p-analytics-common.AmpAnalytics3pEvent} */ this.creativeToPendingExtraData_ = {}; /** @private {string} */ diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index e43567f3b03b..f7915988d3e6 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -313,7 +313,7 @@ export class AmpAnalytics extends AMP.BaseElement { ResponseMap.add(this.getAmpDoc(), type, /** @type {string} */ (this.win.document.baseURI), - response.data); + response['data']); } /** diff --git a/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js index d861315b304f..ec93fce7d247 100644 --- a/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js +++ b/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js @@ -14,9 +14,7 @@ * limitations under the License. */ -import { - AmpAnalytics3pMessageQueue, -} from '../amp-analytics-3p-message-queue'; +import {AmpAnalytics3pMessageQueue} from '../amp-analytics-3p-message-queue'; import {adopt} from '../../../../src/runtime'; import * as sinon from 'sinon'; @@ -29,13 +27,18 @@ describe('amp-analytics.amp-analytics-3p-message-queue', () => { beforeEach(() => { sandbox = sinon.sandbox.create(); - frame = { - getAttribute: () => 'some_value', - src: 'http://localhost', - ownerDocument: { - defaultView: window, - }, - }; + // frame = { + // getAttribute: () => 'some_value', + // src: 'http://localhost', + // ownerDocument: { + // defaultView: window, + // }, + // }; + frame = window.document.createElement('iframe'); + frame.setAttribute('sandbox', 'allow-scripts allow-same-origin'); + frame.setAttribute('name', 'some_name'); + frame.setAttribute('src', 'some_url'); + frame.setAttribute('data-amp-3p-sentinel', '42'); queue = new AmpAnalytics3pMessageQueue(window, frame); }); diff --git a/src/3p-analytics-common.js b/src/3p-analytics-common.js index 5939b1ceaab5..4a3550eb210f 100644 --- a/src/3p-analytics-common.js +++ b/src/3p-analytics-common.js @@ -16,77 +16,31 @@ /** @enum {string} */ export const AMP_ANALYTICS_3P_MESSAGE_TYPE = { - READY: 'R', CREATIVE: 'C', EVENT: 'E', - RESPONSE: 'A', + RESPONSE: 'R', }; -/** @typedef {{ - * sentinel: (string|undefined), - * type: !string - * }} */ -export let AmpAnalytics3pReadyMessage; -// Example: -// { -// "sentinel":"20354662305315974", -// "type":AMP_ANALYTICS_3P_MESSAGE_TYPE.READY -// } -// The sentinel value will be present when received but the sender doesn't -// need to add it, this is done by iframe-messaging-client. - -/** @typedef {{ - * sentinel: (string|undefined), - * type: !string, - * data: !Object - * }} */ +/** @typedef {!Object} */ export let AmpAnalytics3pNewCreative; // Example: // { -// "sentinel":"20354662305315974", -// "type":AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, -// "data": { -// "8117602251459417": "ThisIsExtraData", -// ... -// } +// "2": "ThisIsExtraData", +// ... // } -// The sentinel value will be present when received but the sender doesn't -// need to add it, this is done by iframe-messaging-client. -/** @typedef {{ - * sentinel: (string|undefined), - * type: !string, - * data: !Object> - * }} */ +/** @typedef {!Object>} */ export let AmpAnalytics3pEvent; // Example: // { -// "sentinel":"20354662305315974", -// "type":AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, -// "data":{ -// "8117602251459417": ["viewed=true&...etc.", ... ], -// ... -// } +// "2": ["viewed=true&...etc.", ... ], +// ... // } -// The sentinel value will be present when received but the sender doesn't -// need to add it, this is done by iframe-messaging-client. -/** @typedef {{ - * sentinel: (string|undefined), - * destination: !string, - * type: !string, - * data: ?Object - * }} */ +/** @typedef {JsonObject} */ export let AmpAnalytics3pResponse; // Example: -// { -// "sentinel":"20354662305315974", -// "destination":"8117602251459417", -// "type":AMP_ANALYTICS_3P_MESSAGE_TYPE.RESPONSE, -// "data":{"status":"received","somethingElse":"42"} -// } -// The sentinel value will be present when received but the sender doesn't -// need to add it, this is done by iframe-messaging-client. +// {"status":"received","somethingElse":"42"} /** * A class for holding AMP Analytics third-party vendors responses to frames. From dbf29603647415cf3d1c8b865922837f1179b1f0 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 12:17:41 -0400 Subject: [PATCH 07/85] Adds misc small fixes/nits from self-review --- .../0.1/data/fake_amp_ad_with_3p_analytics.html | 11 ----------- .../0.1/amp-analytics-3p-message-queue.js | 8 ++------ .../0.1/test/test-amp-analytics-3p-message-queue.js | 7 ------- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html index 0d2f435fb7ba..65e38037c887 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html +++ b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html @@ -42,17 +42,6 @@ } - diff --git a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js index 08c4565db2c7..aae16e7a0a99 100644 --- a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js @@ -19,7 +19,7 @@ import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../../../src/3p-analytics-common'; import {SubscriptionApi} from '../../../src/iframe-helper'; /** @private @const {string} */ -const TAG_ = 'amp-analytics.CrossDomainIframeMessageQueue'; +const TAG_ = 'amp-analytics.3pMessageQueue'; /** @private @const {number} */ const MAX_QUEUE_SIZE_ = 100; @@ -44,16 +44,13 @@ export class AmpAnalytics3pMessageQueue { /** @private {boolean} */ this.isReady_ = false; - /** @private {!Object>} */ + /** @private {!../../../src/3p-analytics-common.AmpAnalytics3pEvent} */ this.creativeToPendingMessages_ = {}; /** @private * {!../../../src/3p-analytics-common.AmpAnalytics3pNewCreative} */ this.creativeToExtraData_ = {}; - /** @private {!../../../src/3p-analytics-common.AmpAnalytics3pEvent} */ - this.creativeToPendingExtraData_ = {}; - /** @private {string} */ this.messageType_ = this.frame_.getAttribute('data-amp-3p-sentinel') + AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT; @@ -64,7 +61,6 @@ export class AmpAnalytics3pMessageQueue { true, () => { this.setIsReady(); - this.flushQueue_(); }); } diff --git a/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js index ec93fce7d247..15c8ea98e083 100644 --- a/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js +++ b/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js @@ -27,13 +27,6 @@ describe('amp-analytics.amp-analytics-3p-message-queue', () => { beforeEach(() => { sandbox = sinon.sandbox.create(); - // frame = { - // getAttribute: () => 'some_value', - // src: 'http://localhost', - // ownerDocument: { - // defaultView: window, - // }, - // }; frame = window.document.createElement('iframe'); frame.setAttribute('sandbox', 'allow-scripts allow-same-origin'); frame.setAttribute('name', 'some_name'); From 7e58280a5163e89a845847460232815392572b4b Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 12:25:43 -0400 Subject: [PATCH 08/85] Changes 2 URLs in example from absolute to relative --- .../0.1/data/fake_amp_ad_with_3p_analytics.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html index 65e38037c887..dbb46b264af1 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html +++ b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html @@ -8,7 +8,7 @@ - +

By Golden Trvs Gol twister (Own work) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons @@ -36,7 +36,7 @@ } }, "transport": { - "iframe": "http://localhost:8000/examples/analytics-3p-remote-frame.html", + "iframe": "/examples/analytics-3p-remote-frame.html", "extraData":"ThisIsExtraData" } } From 5d46a9d353cf6ab96b25329690a0dbbc801bf4b8 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 12:34:18 -0400 Subject: [PATCH 09/85] Comments only --- extensions/amp-analytics/0.1/amp-analytics.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index f7915988d3e6..db73c1b4ce1c 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -164,8 +164,10 @@ export class AmpAnalytics extends AMP.BaseElement { return this.ensureInitialized_(); } - /* @override */ + /** @override */ unlayoutCallback() { + //TODO(jonkeller): Figure out why unlayoutCallback is getting called + //immediately, fix that, then add the following code back in. /* const ampDoc = this.getAmpDoc(); Transport.doneUsingCrossDomainIframe(ampDoc.win.document, From b6351f2f3cb1510bb9605a091f98813f4a8b75ee Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 14:29:57 -0400 Subject: [PATCH 10/85] Fixes TODO re: unlayoutCallback destroying 3p iframe --- extensions/amp-analytics/0.1/amp-analytics.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index db73c1b4ce1c..8aef2ccd2ba5 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -161,19 +161,16 @@ export class AmpAnalytics extends AMP.BaseElement { layoutCallback() { // Now that we are rendered, stop rendering the element to reduce // resource consumption. + this.element.getResources().setOwner(this.element, this.element.parentElement); return this.ensureInitialized_(); } /** @override */ unlayoutCallback() { - //TODO(jonkeller): Figure out why unlayoutCallback is getting called - //immediately, fix that, then add the following code back in. - /* const ampDoc = this.getAmpDoc(); Transport.doneUsingCrossDomainIframe(ampDoc.win.document, this.config_['transport']); ResponseMap.remove(ampDoc, this.config_['transport']['type']); - */ return true; } From d15b9c5878f239e7c72ad9ae6c7ef408588c0c0d Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 14:58:35 -0400 Subject: [PATCH 11/85] Fixes an 80-char line length violation introduced in previous PR --- extensions/amp-analytics/0.1/amp-analytics.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index 8aef2ccd2ba5..0898cceee25e 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -161,7 +161,8 @@ export class AmpAnalytics extends AMP.BaseElement { layoutCallback() { // Now that we are rendered, stop rendering the element to reduce // resource consumption. - this.element.getResources().setOwner(this.element, this.element.parentElement); + this.element.getResources().setOwner(this.element, + this.element.parentElement); return this.ensureInitialized_(); } From fb539c849b9cff564311ed2c9f34e3b2643f0df4 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 15:40:15 -0400 Subject: [PATCH 12/85] Implements review feedback --- .../0.1/amp-analytics-3p-message-queue.js | 2 +- extensions/amp-analytics/0.1/amp-analytics.js | 16 -- .../0.1/amp-iframe-transport-message-queue.js | 168 ++++++++++++++++++ ...test-amp-iframe-transport-message-queue.js | 95 ++++++++++ 4 files changed, 264 insertions(+), 17 deletions(-) create mode 100644 extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js create mode 100644 extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js diff --git a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js index aae16e7a0a99..220b79e0b01c 100644 --- a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js @@ -123,7 +123,7 @@ export class AmpAnalytics3pMessageQueue { */ enqueue(creativeId, event) { this.creativeToPendingMessages_[creativeId] = - this.creativeToPendingMessages_[creativeId] || []; + this.creativeToPendingMessages_[creativeId] || []; if (this.queueSize() >= MAX_QUEUE_SIZE_) { dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + creativeId); this.creativeToPendingMessages_[creativeId].shift(); diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index 0898cceee25e..8afa64f9af14 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -440,22 +440,6 @@ export class AmpAnalytics extends AMP.BaseElement { } const typeConfig = this.predefinedConfig_[type] || {}; - // transport.iframe is only allowed to be specified in typeConfig, not - // the others. Allowed when running locally for testing purposes. - [defaultConfig, inlineConfig, this.remoteConfig_].forEach(config => { - if (config && config.transport && config.transport.iframe) { - const TAG = this.getName_(); - if (getMode().localDev) { - user().warn(TAG, 'Only typeConfig may specify iframe transport,' + - ' but in local dev mode, so okay', config); - } else { - user().error(TAG, 'Only typeConfig may specify iframe transport', - config); - return; - } - } - }); - this.mergeObjects_(defaultConfig, config); this.mergeObjects_(typeConfig, config, /* predefined */ true); if (typeConfig) { diff --git a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js new file mode 100644 index 000000000000..220b79e0b01c --- /dev/null +++ b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js @@ -0,0 +1,168 @@ +/** + * 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. + */ + +import {dev} from '../../../src/log'; +import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../../../src/3p-analytics-common'; +import {SubscriptionApi} from '../../../src/iframe-helper'; + +/** @private @const {string} */ +const TAG_ = 'amp-analytics.3pMessageQueue'; + +/** @private @const {number} */ +const MAX_QUEUE_SIZE_ = 100; + +/** + * @visibleForTesting + */ +export class AmpAnalytics3pMessageQueue { + /** + * Constructor + * @param {!Window} win The window element + * @param {!HTMLIFrameElement} frame The cross-domain iframe to send + * messages to + */ + constructor(win, frame) { + /** @private {!Window} */ + this.win_ = win; + + /** @private {!HTMLIFrameElement} */ + this.frame_ = frame; + + /** @private {boolean} */ + this.isReady_ = false; + + /** @private {!../../../src/3p-analytics-common.AmpAnalytics3pEvent} */ + this.creativeToPendingMessages_ = {}; + + /** @private + * {!../../../src/3p-analytics-common.AmpAnalytics3pNewCreative} */ + this.creativeToExtraData_ = {}; + + /** @private {string} */ + this.messageType_ = this.frame_.getAttribute('data-amp-3p-sentinel') + + AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT; + + /** @private {!../../../src/iframe-helper.SubscriptionApi} */ + this.postMessageApi_ = new SubscriptionApi(this.frame_, + this.messageType_, + true, + () => { + this.setIsReady(); + }); + } + + /** + * Returns whether the queue has been marked as ready yet + * @return {boolean} + * @VisibleForTesting + */ + isReady() { + return this.isReady_; + } + + /** + * Indicate that a cross-domain frame is ready to receive messages, and + * send all messages that were previously queued for it. + * @VisibleForTesting + */ + setIsReady() { + this.isReady_ = true; + this.flushQueue_(); + } + + /** + * Returns how many creativeId -> message(s) mappings there are + * @return {number} + * @VisibleForTesting + */ + queueSize() { + return Object.keys(this.creativeToPendingMessages_).length; + } + + /** + * Sets extra config data to be sent to a cross-domain iframe. + * @param {!string} creativeId Identifies which creative is sending the + * extra data + * @param {!string} extraData The extra config data + */ + setExtraData(creativeId, extraData) { + dev().assert(!this.creativeToExtraData_[creativeId], + 'Replacing existing extra data for ' + creativeId); + this.creativeToExtraData_[creativeId] = extraData; + this.flushQueue_(); + } + + /** + * Test method to get extra config data to be sent to a cross-domain iframe. + * @param {!string} creativeId Identifies which creative is sending the + * extra data + * @returns {string} The extra config data + */ + getExtraData(creativeId) { + return this.creativeToExtraData_[creativeId]; + } + + /** + * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain + * iframe. + * @param {!string} creativeId Identifies which creative is sending the message + * @param {!string} event The event to be enqueued and then sent to the iframe + */ + enqueue(creativeId, event) { + this.creativeToPendingMessages_[creativeId] = + this.creativeToPendingMessages_[creativeId] || []; + if (this.queueSize() >= MAX_QUEUE_SIZE_) { + dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + creativeId); + this.creativeToPendingMessages_[creativeId].shift(); + } + this.creativeToPendingMessages_[creativeId].push(event); + this.flushQueue_(); + } + + /** + * Send queued data (if there is any) to a cross-domain iframe + * @private + */ + flushQueue_() { + if (this.isReady()) { + if (Object.keys(this.creativeToExtraData_).length) { + this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, + /** @type {!JsonObject} */ + ({data: this.creativeToExtraData_})); + this.creativeToExtraData_ = {}; + } + if (this.queueSize()) { + this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + /** @type {!JsonObject} */ + ({data: this.creativeToPendingMessages_})); + this.creativeToPendingMessages_ = {}; + } + } + } + + /** + * Test method to see which messages (if any) are associated with a given + * creativeId + * @param {!string} creativeId Identifies which creative is sending the message + * @return {Array} + * @VisibleForTesting + */ + messagesFor(creativeId) { + return /** @type {Array} */ ( + this.creativeToPendingMessages_[creativeId]); + } +} + diff --git a/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js new file mode 100644 index 000000000000..15c8ea98e083 --- /dev/null +++ b/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js @@ -0,0 +1,95 @@ +/** + * 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. + */ + +import {AmpAnalytics3pMessageQueue} from '../amp-analytics-3p-message-queue'; +import {adopt} from '../../../../src/runtime'; +import * as sinon from 'sinon'; + +adopt(window); + +describe('amp-analytics.amp-analytics-3p-message-queue', () => { + let sandbox; + let frame; + let queue; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + frame = window.document.createElement('iframe'); + frame.setAttribute('sandbox', 'allow-scripts allow-same-origin'); + frame.setAttribute('name', 'some_name'); + frame.setAttribute('src', 'some_url'); + frame.setAttribute('data-amp-3p-sentinel', '42'); + queue = new AmpAnalytics3pMessageQueue(window, frame); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('is empty when first created ', () => { + expect(queue.queueSize()).to.equal(0); + }); + + it('is not ready until setIsReady() is called ', () => { + expect(queue.isReady()).to.be.false; + queue.setIsReady(); + expect(queue.isReady()).to.be.true; + }); + + it('queues messages when not ready to send ', () => { + const beforeCount = queue.queueSize(); + queue.enqueue('some_senderId', 'some_data'); + queue.enqueue('another_senderId', 'some_data'); + const afterCount = queue.queueSize(); + expect(afterCount - beforeCount).to.equal(2); + }); + + it('flushes the queue when ready to send ', () => { + queue.enqueue('some_senderId', 'some_data'); + queue.setIsReady(); + const afterCount = queue.queueSize(); + expect(afterCount).to.equal(0); + }); + + it('groups messages from same sender ', () => { + queue.enqueue('letter_sender', 'A'); + queue.enqueue('letter_sender', 'B'); + queue.enqueue('letter_sender', 'C'); + queue.enqueue('number_sender', '1'); + queue.enqueue('number_sender', '2'); + queue.enqueue('number_sender', '3'); + queue.enqueue('number_sender', '4'); + const letterCount = queue.messagesFor('letter_sender').length; + const numberCount = queue.messagesFor('number_sender').length; + expect(queue.queueSize()).to.equal(2); + expect(letterCount).to.equal(3); + expect(numberCount).to.equal(4); + }); + + it('only allows extraData to be set once per sender ', () => { + queue.setExtraData('letter_sender', 'A'); + queue.setExtraData('number_sender', '1'); + + expect(() => { + queue.setExtraData('letter_sender', 'B'); + }).to.throw(/Replacing existing extra data/); + + expect(() => { + queue.setExtraData('number_sender', '2'); + }).to.throw(/Replacing existing extra data/); + }); +}); + From f252fe10d33181cb02e7095e9fdd9d35ecea2c5b Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 15:42:52 -0400 Subject: [PATCH 13/85] Implements review feedback --- .../0.1/amp-analytics-3p-message-queue.js | 168 ------------------ extensions/amp-analytics/0.1/amp-analytics.js | 1 - .../0.1/amp-iframe-transport-message-queue.js | 4 +- .../test-amp-analytics-3p-message-queue.js | 95 ---------- ...test-amp-iframe-transport-message-queue.js | 10 +- 5 files changed, 8 insertions(+), 270 deletions(-) delete mode 100644 extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js delete mode 100644 extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js diff --git a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js deleted file mode 100644 index 220b79e0b01c..000000000000 --- a/extensions/amp-analytics/0.1/amp-analytics-3p-message-queue.js +++ /dev/null @@ -1,168 +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. - */ - -import {dev} from '../../../src/log'; -import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../../../src/3p-analytics-common'; -import {SubscriptionApi} from '../../../src/iframe-helper'; - -/** @private @const {string} */ -const TAG_ = 'amp-analytics.3pMessageQueue'; - -/** @private @const {number} */ -const MAX_QUEUE_SIZE_ = 100; - -/** - * @visibleForTesting - */ -export class AmpAnalytics3pMessageQueue { - /** - * Constructor - * @param {!Window} win The window element - * @param {!HTMLIFrameElement} frame The cross-domain iframe to send - * messages to - */ - constructor(win, frame) { - /** @private {!Window} */ - this.win_ = win; - - /** @private {!HTMLIFrameElement} */ - this.frame_ = frame; - - /** @private {boolean} */ - this.isReady_ = false; - - /** @private {!../../../src/3p-analytics-common.AmpAnalytics3pEvent} */ - this.creativeToPendingMessages_ = {}; - - /** @private - * {!../../../src/3p-analytics-common.AmpAnalytics3pNewCreative} */ - this.creativeToExtraData_ = {}; - - /** @private {string} */ - this.messageType_ = this.frame_.getAttribute('data-amp-3p-sentinel') + - AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT; - - /** @private {!../../../src/iframe-helper.SubscriptionApi} */ - this.postMessageApi_ = new SubscriptionApi(this.frame_, - this.messageType_, - true, - () => { - this.setIsReady(); - }); - } - - /** - * Returns whether the queue has been marked as ready yet - * @return {boolean} - * @VisibleForTesting - */ - isReady() { - return this.isReady_; - } - - /** - * Indicate that a cross-domain frame is ready to receive messages, and - * send all messages that were previously queued for it. - * @VisibleForTesting - */ - setIsReady() { - this.isReady_ = true; - this.flushQueue_(); - } - - /** - * Returns how many creativeId -> message(s) mappings there are - * @return {number} - * @VisibleForTesting - */ - queueSize() { - return Object.keys(this.creativeToPendingMessages_).length; - } - - /** - * Sets extra config data to be sent to a cross-domain iframe. - * @param {!string} creativeId Identifies which creative is sending the - * extra data - * @param {!string} extraData The extra config data - */ - setExtraData(creativeId, extraData) { - dev().assert(!this.creativeToExtraData_[creativeId], - 'Replacing existing extra data for ' + creativeId); - this.creativeToExtraData_[creativeId] = extraData; - this.flushQueue_(); - } - - /** - * Test method to get extra config data to be sent to a cross-domain iframe. - * @param {!string} creativeId Identifies which creative is sending the - * extra data - * @returns {string} The extra config data - */ - getExtraData(creativeId) { - return this.creativeToExtraData_[creativeId]; - } - - /** - * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain - * iframe. - * @param {!string} creativeId Identifies which creative is sending the message - * @param {!string} event The event to be enqueued and then sent to the iframe - */ - enqueue(creativeId, event) { - this.creativeToPendingMessages_[creativeId] = - this.creativeToPendingMessages_[creativeId] || []; - if (this.queueSize() >= MAX_QUEUE_SIZE_) { - dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + creativeId); - this.creativeToPendingMessages_[creativeId].shift(); - } - this.creativeToPendingMessages_[creativeId].push(event); - this.flushQueue_(); - } - - /** - * Send queued data (if there is any) to a cross-domain iframe - * @private - */ - flushQueue_() { - if (this.isReady()) { - if (Object.keys(this.creativeToExtraData_).length) { - this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, - /** @type {!JsonObject} */ - ({data: this.creativeToExtraData_})); - this.creativeToExtraData_ = {}; - } - if (this.queueSize()) { - this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, - /** @type {!JsonObject} */ - ({data: this.creativeToPendingMessages_})); - this.creativeToPendingMessages_ = {}; - } - } - } - - /** - * Test method to see which messages (if any) are associated with a given - * creativeId - * @param {!string} creativeId Identifies which creative is sending the message - * @return {Array} - * @VisibleForTesting - */ - messagesFor(creativeId) { - return /** @type {Array} */ ( - this.creativeToPendingMessages_[creativeId]); - } -} - diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index 8afa64f9af14..91df24ca35f4 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -17,7 +17,6 @@ import {isJsonScriptTag} from '../../../src/dom'; import {assertHttpsUrl, appendEncodedParamStringToUrl} from '../../../src/url'; import {dev, rethrowAsync, user} from '../../../src/log'; -import {getMode} from '../../../src/mode'; import {expandTemplate} from '../../../src/string'; import {isArray, isObject} from '../../../src/types'; import {dict, hasOwn, map} from '../../../src/utils/object'; diff --git a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js index 220b79e0b01c..d68e1101ed5e 100644 --- a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js @@ -19,7 +19,7 @@ import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../../../src/3p-analytics-common'; import {SubscriptionApi} from '../../../src/iframe-helper'; /** @private @const {string} */ -const TAG_ = 'amp-analytics.3pMessageQueue'; +const TAG_ = 'amp-analytics.IframeTransportMessageQueue'; /** @private @const {number} */ const MAX_QUEUE_SIZE_ = 100; @@ -27,7 +27,7 @@ const MAX_QUEUE_SIZE_ = 100; /** * @visibleForTesting */ -export class AmpAnalytics3pMessageQueue { +export class AmpIframeTransportMessageQueue { /** * Constructor * @param {!Window} win The window element diff --git a/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js b/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js deleted file mode 100644 index 15c8ea98e083..000000000000 --- a/extensions/amp-analytics/0.1/test/test-amp-analytics-3p-message-queue.js +++ /dev/null @@ -1,95 +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. - */ - -import {AmpAnalytics3pMessageQueue} from '../amp-analytics-3p-message-queue'; -import {adopt} from '../../../../src/runtime'; -import * as sinon from 'sinon'; - -adopt(window); - -describe('amp-analytics.amp-analytics-3p-message-queue', () => { - let sandbox; - let frame; - let queue; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - frame = window.document.createElement('iframe'); - frame.setAttribute('sandbox', 'allow-scripts allow-same-origin'); - frame.setAttribute('name', 'some_name'); - frame.setAttribute('src', 'some_url'); - frame.setAttribute('data-amp-3p-sentinel', '42'); - queue = new AmpAnalytics3pMessageQueue(window, frame); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('is empty when first created ', () => { - expect(queue.queueSize()).to.equal(0); - }); - - it('is not ready until setIsReady() is called ', () => { - expect(queue.isReady()).to.be.false; - queue.setIsReady(); - expect(queue.isReady()).to.be.true; - }); - - it('queues messages when not ready to send ', () => { - const beforeCount = queue.queueSize(); - queue.enqueue('some_senderId', 'some_data'); - queue.enqueue('another_senderId', 'some_data'); - const afterCount = queue.queueSize(); - expect(afterCount - beforeCount).to.equal(2); - }); - - it('flushes the queue when ready to send ', () => { - queue.enqueue('some_senderId', 'some_data'); - queue.setIsReady(); - const afterCount = queue.queueSize(); - expect(afterCount).to.equal(0); - }); - - it('groups messages from same sender ', () => { - queue.enqueue('letter_sender', 'A'); - queue.enqueue('letter_sender', 'B'); - queue.enqueue('letter_sender', 'C'); - queue.enqueue('number_sender', '1'); - queue.enqueue('number_sender', '2'); - queue.enqueue('number_sender', '3'); - queue.enqueue('number_sender', '4'); - const letterCount = queue.messagesFor('letter_sender').length; - const numberCount = queue.messagesFor('number_sender').length; - expect(queue.queueSize()).to.equal(2); - expect(letterCount).to.equal(3); - expect(numberCount).to.equal(4); - }); - - it('only allows extraData to be set once per sender ', () => { - queue.setExtraData('letter_sender', 'A'); - queue.setExtraData('number_sender', '1'); - - expect(() => { - queue.setExtraData('letter_sender', 'B'); - }).to.throw(/Replacing existing extra data/); - - expect(() => { - queue.setExtraData('number_sender', '2'); - }).to.throw(/Replacing existing extra data/); - }); -}); - diff --git a/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js index 15c8ea98e083..c134b50c16a2 100644 --- a/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js @@ -14,13 +14,15 @@ * limitations under the License. */ -import {AmpAnalytics3pMessageQueue} from '../amp-analytics-3p-message-queue'; +import { + AmpIframeTransportMessageQueue, +} from '../amp-iframe-transport-message-queue'; import {adopt} from '../../../../src/runtime'; import * as sinon from 'sinon'; adopt(window); -describe('amp-analytics.amp-analytics-3p-message-queue', () => { +describe('amp-analytics.amp-iframe-transport-message-queue', () => { let sandbox; let frame; let queue; @@ -31,8 +33,8 @@ describe('amp-analytics.amp-analytics-3p-message-queue', () => { frame.setAttribute('sandbox', 'allow-scripts allow-same-origin'); frame.setAttribute('name', 'some_name'); frame.setAttribute('src', 'some_url'); - frame.setAttribute('data-amp-3p-sentinel', '42'); - queue = new AmpAnalytics3pMessageQueue(window, frame); + frame.sentinel = '42'; + queue = new AmpIframeTransportMessageQueue(window, frame); }); afterEach(() => { From ab3b882905aa3739b25fce1ea3bd65f2b5e5abcc Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 16:16:33 -0400 Subject: [PATCH 14/85] Addresses review feedback --- .../amp-analytics/0.1/amp-iframe-transport-message-queue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js index d68e1101ed5e..28bc7bd563af 100644 --- a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js @@ -52,7 +52,7 @@ export class AmpIframeTransportMessageQueue { this.creativeToExtraData_ = {}; /** @private {string} */ - this.messageType_ = this.frame_.getAttribute('data-amp-3p-sentinel') + + this.messageType_ = this.frame_.sentinel + AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT; /** @private {!../../../src/iframe-helper.SubscriptionApi} */ From 8b47fb05d8af78c9549e11a01ccc11350fdd774f Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 16:20:06 -0400 Subject: [PATCH 15/85] Reverts change to URL calculation, pending additional discussion --- extensions/amp-analytics/0.1/transport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/transport.js b/extensions/amp-analytics/0.1/transport.js index 0fdea88da445..c5cb0c52afff 100644 --- a/extensions/amp-analytics/0.1/transport.js +++ b/extensions/amp-analytics/0.1/transport.js @@ -29,7 +29,7 @@ import {setStyle} from '../../../src/style'; const TAG_ = 'amp-analytics.Transport'; /** - * @param {!Window} win + * @param {!Window} win * @param {string} request * @param {!Object} transportOptions */ From b291ea1e146c8ec8c2fc61e8f9bb60bf6c3bb40f Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 16:50:14 -0400 Subject: [PATCH 16/85] Fixes unit test --- extensions/amp-analytics/0.1/test/test-transport.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index 0a1bf35a9ed9..568560963af3 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -181,8 +181,9 @@ describe('amp-analytics.transport', () => { const frame2 = Transport.getFrameData(url2); const frame3 = Transport.getFrameData(url3); const frame4 = Transport.getFrameData(url4); - expectAllUnique([transport.getId(), transport2.getId(), frame1.sentinel, - frame2.sentinel, frame3.sentinel, frame4.sentinel]); + expectAllUnique([transport.getId(), transport2.getId(), + frame1.frame.sentinel, frame2.frame.sentinel, + frame3.frame.sentinel, frame4.frame.sentinel]); }); it('correctly tracks usageCount and destroys iframes', () => { From 8373e5506fe863bc02c2dfeb778f72f413b8eea1 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 16:58:49 -0400 Subject: [PATCH 17/85] Fixes indentation of 2 lines --- extensions/amp-analytics/0.1/test/test-transport.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index 568560963af3..7c7408e93b50 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -182,8 +182,8 @@ describe('amp-analytics.transport', () => { const frame3 = Transport.getFrameData(url3); const frame4 = Transport.getFrameData(url4); expectAllUnique([transport.getId(), transport2.getId(), - frame1.frame.sentinel, frame2.frame.sentinel, - frame3.frame.sentinel, frame4.frame.sentinel]); + frame1.frame.sentinel, frame2.frame.sentinel, + frame3.frame.sentinel, frame4.frame.sentinel]); }); it('correctly tracks usageCount and destroys iframes', () => { From e56dbed1fc08792453325f36e1625b3a8aa48fa2 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 21:36:41 -0400 Subject: [PATCH 18/85] Removes extraData, new creative message --- build-system/app.js | 5 +++ extensions/amp-analytics/0.1/amp-analytics.js | 29 +-------------- .../0.1/amp-iframe-transport-message-queue.js | 36 +------------------ ...test-amp-iframe-transport-message-queue.js | 13 ------- .../amp-analytics/0.1/test/test-transport.js | 13 ------- extensions/amp-analytics/0.1/transport.js | 2 +- src/3p-analytics-common.js | 9 ----- 7 files changed, 8 insertions(+), 99 deletions(-) diff --git a/build-system/app.js b/build-system/app.js index ca50bb95c1d0..7ad6e97f67ca 100644 --- a/build-system/app.js +++ b/build-system/app.js @@ -936,6 +936,11 @@ app.get(['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], next(); }); +app.get('/dist/ampanalytics-lib.js', (req, res, next) => { + req.url = req.url.replace(/dist/, 'dist.3p/current'); + next(); + }); + app.get('/dist/ampanalytics-lib.js', (req, res, next) => { req.url = req.url.replace(/dist/, 'dist.3p/current'); next(); diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index 91df24ca35f4..b751be5686f9 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -23,7 +23,6 @@ import {dict, hasOwn, map} from '../../../src/utils/object'; import {sendRequest, sendRequestUsingIframe} from './transport'; import {IframeTransport} from './iframe-transport'; import {Services} from '../../../src/services'; -import {ResponseMap} from '../../../src/3p-analytics-common'; import {toggle} from '../../../src/style'; import {isEnumValue} from '../../../src/types'; import {parseJson} from '../../../src/json'; @@ -160,20 +159,9 @@ export class AmpAnalytics extends AMP.BaseElement { layoutCallback() { // Now that we are rendered, stop rendering the element to reduce // resource consumption. - this.element.getResources().setOwner(this.element, - this.element.parentElement); return this.ensureInitialized_(); } - /** @override */ - unlayoutCallback() { - const ampDoc = this.getAmpDoc(); - Transport.doneUsingCrossDomainIframe(ampDoc.win.document, - this.config_['transport']); - ResponseMap.remove(ampDoc, this.config_['transport']['type']); - return true; - } - /** @override */ detachedCallback() { if (this.analyticsGroup_) { @@ -301,20 +289,6 @@ export class AmpAnalytics extends AMP.BaseElement { return Promise.all(promises); } - /** - * Receives any response that may be sent from the cross-domain iframe. - * @param {!string} type The type parameter of the cross-domain iframe - * @param {!../../../src/3p-analytics-common.AmpAnalytics3pResponse} response - * The response message from the iframe that was specified in the - * amp-analytics config - */ - processCrossDomainIframeResponse_(type, response) { - ResponseMap.add(this.getAmpDoc(), - type, - /** @type {string} */ (this.win.document.baseURI), - response['data']); - } - /** * Calls `AnalyticsGroup.addTrigger` and reports any errors. "NoInline" is * to avoid inlining this method so that `try/catch` does it veto @@ -782,8 +756,7 @@ export class AmpAnalytics extends AMP.BaseElement { this.config_['transport']['iframe']) { this.iframeTransport_.sendRequest(request); } else { - this.transport_.sendRequest(this.win, request, - this.config_['transport'] || {}); + sendRequest(this.win, request, this.config_['transport'] || {}); } } diff --git a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js index 28bc7bd563af..63063ea3aeb4 100644 --- a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js @@ -47,13 +47,8 @@ export class AmpIframeTransportMessageQueue { /** @private {!../../../src/3p-analytics-common.AmpAnalytics3pEvent} */ this.creativeToPendingMessages_ = {}; - /** @private - * {!../../../src/3p-analytics-common.AmpAnalytics3pNewCreative} */ - this.creativeToExtraData_ = {}; - /** @private {string} */ - this.messageType_ = this.frame_.sentinel + - AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT; + this.messageType_ = AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT; /** @private {!../../../src/iframe-helper.SubscriptionApi} */ this.postMessageApi_ = new SubscriptionApi(this.frame_, @@ -92,29 +87,6 @@ export class AmpIframeTransportMessageQueue { return Object.keys(this.creativeToPendingMessages_).length; } - /** - * Sets extra config data to be sent to a cross-domain iframe. - * @param {!string} creativeId Identifies which creative is sending the - * extra data - * @param {!string} extraData The extra config data - */ - setExtraData(creativeId, extraData) { - dev().assert(!this.creativeToExtraData_[creativeId], - 'Replacing existing extra data for ' + creativeId); - this.creativeToExtraData_[creativeId] = extraData; - this.flushQueue_(); - } - - /** - * Test method to get extra config data to be sent to a cross-domain iframe. - * @param {!string} creativeId Identifies which creative is sending the - * extra data - * @returns {string} The extra config data - */ - getExtraData(creativeId) { - return this.creativeToExtraData_[creativeId]; - } - /** * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain * iframe. @@ -138,12 +110,6 @@ export class AmpIframeTransportMessageQueue { */ flushQueue_() { if (this.isReady()) { - if (Object.keys(this.creativeToExtraData_).length) { - this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, - /** @type {!JsonObject} */ - ({data: this.creativeToExtraData_})); - this.creativeToExtraData_ = {}; - } if (this.queueSize()) { this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, /** @type {!JsonObject} */ diff --git a/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js index c134b50c16a2..4670d7ffbf95 100644 --- a/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js @@ -80,18 +80,5 @@ describe('amp-analytics.amp-iframe-transport-message-queue', () => { expect(letterCount).to.equal(3); expect(numberCount).to.equal(4); }); - - it('only allows extraData to be set once per sender ', () => { - queue.setExtraData('letter_sender', 'A'); - queue.setExtraData('number_sender', '1'); - - expect(() => { - queue.setExtraData('letter_sender', 'B'); - }).to.throw(/Replacing existing extra data/); - - expect(() => { - queue.setExtraData('number_sender', '2'); - }).to.throw(/Replacing existing extra data/); - }); }); diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index 7c7408e93b50..15c7a70de290 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -142,19 +142,6 @@ describe('amp-analytics.transport', () => { expect(transport.createCrossDomainIframe.calledOnce).to.be.true; }); - it('sends extra data to iframe', () => { - const url = 'https://example.com/test'; - const extraData = 'some extra data'; - const config = { - iframe: url, - extraData, - }; - transport.processCrossDomainIframe(window, config); - const queue = Transport.getFrameData(url).queue; - expect(queue.getExtraData(transport.getId())).to.equal(extraData); - expect(queue.queueSize()).to.equal(0); - }); - it('enqueues event messages correctly', () => { const url = 'https://example.com/test'; const config = {iframe: url}; diff --git a/extensions/amp-analytics/0.1/transport.js b/extensions/amp-analytics/0.1/transport.js index c5cb0c52afff..0fdea88da445 100644 --- a/extensions/amp-analytics/0.1/transport.js +++ b/extensions/amp-analytics/0.1/transport.js @@ -29,7 +29,7 @@ import {setStyle} from '../../../src/style'; const TAG_ = 'amp-analytics.Transport'; /** - * @param {!Window} win + * @param {!Window} win * @param {string} request * @param {!Object} transportOptions */ diff --git a/src/3p-analytics-common.js b/src/3p-analytics-common.js index 4a3550eb210f..895050b3e81f 100644 --- a/src/3p-analytics-common.js +++ b/src/3p-analytics-common.js @@ -16,19 +16,10 @@ /** @enum {string} */ export const AMP_ANALYTICS_3P_MESSAGE_TYPE = { - CREATIVE: 'C', EVENT: 'E', RESPONSE: 'R', }; -/** @typedef {!Object} */ -export let AmpAnalytics3pNewCreative; -// Example: -// { -// "2": "ThisIsExtraData", -// ... -// } - /** @typedef {!Object>} */ export let AmpAnalytics3pEvent; // Example: From 83fa6265fad3d335a15158233ce9d8bc483b34a1 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 22:40:52 -0400 Subject: [PATCH 19/85] Fixes transport unit tests --- .../amp-analytics/0.1/test/test-transport.js | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index 15c7a70de290..462ef63b95d4 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -129,23 +129,20 @@ describe('amp-analytics.transport', () => { }); it('reuses cross-domain iframe', () => { - const config = { - iframe: 'https://example.com/test', - }; + const url = 'https://example.com/test'; sandbox.spy(transport, 'createCrossDomainIframe'); - transport.processCrossDomainIframe(window, config); - + transport.processCrossDomainIframe(window, url); expect(transport.createCrossDomainIframe.calledOnce).to.be.true; - expect(Transport.hasCrossDomainIframe(config.iframe)).to.be.true; + expect(Transport.hasCrossDomainIframe(url)).to.be.true; - transport.processCrossDomainIframe(window, config); + transport.processCrossDomainIframe(window, url); expect(transport.createCrossDomainIframe.calledOnce).to.be.true; }); it('enqueues event messages correctly', () => { const url = 'https://example.com/test'; const config = {iframe: url}; - transport.processCrossDomainIframe(window, config); + transport.processCrossDomainIframe(window, url); transport.sendRequest(window, 'hello, world!', config); const queue = Transport.getFrameData(url).queue; expect(queue.messagesFor(transport.getId()).length).to.equal(1); @@ -160,10 +157,10 @@ describe('amp-analytics.transport', () => { const url4 = 'https://example.com/test4'; const transport2 = new Transport(); - transport.processCrossDomainIframe(window, {iframe: url1}); - transport.processCrossDomainIframe(window, {iframe: url2}); - transport2.processCrossDomainIframe(window, {iframe: url3}); - transport2.processCrossDomainIframe(window, {iframe: url4}); + transport.processCrossDomainIframe(window, url1); + transport.processCrossDomainIframe(window, url2); + transport2.processCrossDomainIframe(window, url3); + transport2.processCrossDomainIframe(window, url4); const frame1 = Transport.getFrameData(url1); const frame2 = Transport.getFrameData(url2); const frame3 = Transport.getFrameData(url3); @@ -177,8 +174,8 @@ describe('amp-analytics.transport', () => { // Add 2 iframes. const url1 = 'https://example.com/usageCountTest1'; const url2 = 'https://example.com/usageCountTest2'; - transport.processCrossDomainIframe(window, {iframe: url1}); - transport.processCrossDomainIframe(window, {iframe: url2}); + transport.processCrossDomainIframe(window, url1); + transport.processCrossDomainIframe(window, url2); const frame1 = Transport.getFrameData(url1); const frame2 = Transport.getFrameData(url2); expect(frame1.usageCount).to.equal(1); @@ -186,11 +183,11 @@ describe('amp-analytics.transport', () => { expect(window.document.getElementsByTagName('IFRAME').length).to.equal(2); // Mark the iframes as used multiple times each. - transport.processCrossDomainIframe(window, {iframe: url1}); - transport.processCrossDomainIframe(window, {iframe: url1}); - transport.processCrossDomainIframe(window, {iframe: url2}); - transport.processCrossDomainIframe(window, {iframe: url2}); - transport.processCrossDomainIframe(window, {iframe: url2}); + transport.processCrossDomainIframe(window, url1); + transport.processCrossDomainIframe(window, url1); + transport.processCrossDomainIframe(window, url2); + transport.processCrossDomainIframe(window, url2); + transport.processCrossDomainIframe(window, url2); expect(frame1.usageCount).to.equal(3); expect(frame2.usageCount).to.equal(4); From 8b5f53376b84d7c824c7c634e94576f5e8d2a130 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 23:20:49 -0400 Subject: [PATCH 20/85] Clarifies transport ID --- .../0.1/amp-iframe-transport-message-queue.js | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js index 63063ea3aeb4..cd85eda7ddc9 100644 --- a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js @@ -45,7 +45,7 @@ export class AmpIframeTransportMessageQueue { this.isReady_ = false; /** @private {!../../../src/3p-analytics-common.AmpAnalytics3pEvent} */ - this.creativeToPendingMessages_ = {}; + this.transportIdToPendingMessages_ = {}; /** @private {string} */ this.messageType_ = AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT; @@ -79,28 +79,29 @@ export class AmpIframeTransportMessageQueue { } /** - * Returns how many creativeId -> message(s) mappings there are + * Returns how many transportId -> message(s) mappings there are * @return {number} * @VisibleForTesting */ queueSize() { - return Object.keys(this.creativeToPendingMessages_).length; + return Object.keys(this.transportIdToPendingMessages_).length; } /** * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain * iframe. - * @param {!string} creativeId Identifies which creative is sending the message + * @param {!string} transportId Identifies which creative is sending the + * message * @param {!string} event The event to be enqueued and then sent to the iframe */ - enqueue(creativeId, event) { - this.creativeToPendingMessages_[creativeId] = - this.creativeToPendingMessages_[creativeId] || []; + enqueue(transportId, event) { + this.transportIdToPendingMessages_[transportId] = + this.transportIdToPendingMessages_[transportId] || []; if (this.queueSize() >= MAX_QUEUE_SIZE_) { - dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + creativeId); - this.creativeToPendingMessages_[creativeId].shift(); + dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + transportId); + this.transportIdToPendingMessages_[transportId].shift(); } - this.creativeToPendingMessages_[creativeId].push(event); + this.transportIdToPendingMessages_[transportId].push(event); this.flushQueue_(); } @@ -113,22 +114,23 @@ export class AmpIframeTransportMessageQueue { if (this.queueSize()) { this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, /** @type {!JsonObject} */ - ({data: this.creativeToPendingMessages_})); - this.creativeToPendingMessages_ = {}; + ({data: this.transportIdToPendingMessages_})); + this.transportIdToPendingMessages_ = {}; } } } /** * Test method to see which messages (if any) are associated with a given - * creativeId - * @param {!string} creativeId Identifies which creative is sending the message + * transportId + * @param {!string} transportId Identifies which creative is sending the + * message * @return {Array} * @VisibleForTesting */ - messagesFor(creativeId) { + messagesFor(transportId) { return /** @type {Array} */ ( - this.creativeToPendingMessages_[creativeId]); + this.transportIdToPendingMessages_[transportId]); } } From 88d118dad951071a79597fc44dbd98abf79c67d6 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 23:52:11 -0400 Subject: [PATCH 21/85] Fixes whitespace --- build-system/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build-system/app.js b/build-system/app.js index 7ad6e97f67ca..f5e063fde54b 100644 --- a/build-system/app.js +++ b/build-system/app.js @@ -937,9 +937,9 @@ app.get(['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], }); app.get('/dist/ampanalytics-lib.js', (req, res, next) => { - req.url = req.url.replace(/dist/, 'dist.3p/current'); - next(); - }); + req.url = req.url.replace(/dist/, 'dist.3p/current'); + next(); +}); app.get('/dist/ampanalytics-lib.js', (req, res, next) => { req.url = req.url.replace(/dist/, 'dist.3p/current'); From 5e4e3f7f5e52a022428ab0e31375b533e622faf2 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Sat, 15 Jul 2017 14:27:48 -0400 Subject: [PATCH 22/85] Improves example triggers. Fixes issue of delivery of creative response --- .../data/fake_amp_ad_with_3p_analytics.html | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html index dbb46b264af1..5eaf18f58359 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html +++ b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html @@ -2,44 +2,43 @@ - - + + - - + +

By Golden Trvs Gol twister (Own work) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons

- From 558339f75c95271ab35246d98ce4516c9d752065 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Sat, 15 Jul 2017 14:47:26 -0400 Subject: [PATCH 23/85] Makes indentation consistent in this section --- build-system/dep-check-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-system/dep-check-config.js b/build-system/dep-check-config.js index 3c33c9b0073f..82093e048d9b 100644 --- a/build-system/dep-check-config.js +++ b/build-system/dep-check-config.js @@ -183,7 +183,7 @@ exports.rules = [ 'extensions/amp-fx-parallax/0.1/amp-fx-parallax.js->' + 'src/service/parallax-impl.js', 'extensions/amp-analytics/0.1/iframe-transport.js->' + - 'src/service/extension-location.js', + 'src/service/extension-location.js', // TODO(@zhouyx, #9213) Remove this item. 'extensions/amp-ad/0.1/amp-ad-xorigin-iframe-handler.js->' + 'src/service/position-observer-impl.js', From 305a84bab97d7e1b7010e14aa95da207539d19e5 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 17 Jul 2017 13:02:24 -0400 Subject: [PATCH 24/85] Addresses review feedback --- build-system/dep-check-config.js | 2 +- .../0.1/amp-iframe-transport-message-queue.js | 22 +++++++++---------- .../amp-analytics/0.1/test/test-transport.js | 14 ++++++------ src/3p-analytics-common.js | 4 +++- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/build-system/dep-check-config.js b/build-system/dep-check-config.js index 82093e048d9b..3c33c9b0073f 100644 --- a/build-system/dep-check-config.js +++ b/build-system/dep-check-config.js @@ -183,7 +183,7 @@ exports.rules = [ 'extensions/amp-fx-parallax/0.1/amp-fx-parallax.js->' + 'src/service/parallax-impl.js', 'extensions/amp-analytics/0.1/iframe-transport.js->' + - 'src/service/extension-location.js', + 'src/service/extension-location.js', // TODO(@zhouyx, #9213) Remove this item. 'extensions/amp-ad/0.1/amp-ad-xorigin-iframe-handler.js->' + 'src/service/position-observer-impl.js', diff --git a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js index cd85eda7ddc9..bc0c9efefa0a 100644 --- a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js @@ -44,7 +44,9 @@ export class AmpIframeTransportMessageQueue { /** @private {boolean} */ this.isReady_ = false; - /** @private {!../../../src/3p-analytics-common.AmpAnalytics3pEvent} */ + /** @private + * {!Object= MAX_QUEUE_SIZE_) { dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + transportId); - this.transportIdToPendingMessages_[transportId].shift(); + this.messagesFor(transportId).shift(); } - this.transportIdToPendingMessages_[transportId].push(event); + this.messagesFor(transportId).push(event); this.flushQueue_(); } @@ -110,13 +112,11 @@ export class AmpIframeTransportMessageQueue { * @private */ flushQueue_() { - if (this.isReady()) { - if (this.queueSize()) { - this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, - /** @type {!JsonObject} */ - ({data: this.transportIdToPendingMessages_})); - this.transportIdToPendingMessages_ = {}; - } + if (this.isReady() && this.queueSize()) { + this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + /** @type {!JsonObject} */ + ({data: this.transportIdToPendingMessages_})); + this.transportIdToPendingMessages_ = {}; } } diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index 462ef63b95d4..d6eea2a8c97d 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -193,17 +193,17 @@ describe('amp-analytics.transport', () => { // Stop using the iframes, make sure usage counts go to zero and they are // removed from the DOM. - Transport.doneUsingCrossDomainIframe(window.document, {iframe: url1}); + Transport.markCrossDomainIframeAsDone(window.document, url1); expect(frame1.usageCount).to.equal(2); - Transport.doneUsingCrossDomainIframe(window.document, {iframe: url1}); - Transport.doneUsingCrossDomainIframe(window.document, {iframe: url1}); + Transport.markCrossDomainIframeAsDone(window.document, url1); + Transport.markCrossDomainIframeAsDone(window.document, url1); expect(frame1.usageCount).to.equal(0); expect(frame2.usageCount).to.equal(4); // (Still) expect(window.document.getElementsByTagName('IFRAME').length).to.equal(1); - Transport.doneUsingCrossDomainIframe(window.document, {iframe: url2}); - Transport.doneUsingCrossDomainIframe(window.document, {iframe: url2}); - Transport.doneUsingCrossDomainIframe(window.document, {iframe: url2}); - Transport.doneUsingCrossDomainIframe(window.document, {iframe: url2}); + Transport.markCrossDomainIframeAsDone(window.document, url2); + Transport.markCrossDomainIframeAsDone(window.document, url2); + Transport.markCrossDomainIframeAsDone(window.document, url2); + Transport.markCrossDomainIframeAsDone(window.document, url2); expect(frame2.usageCount).to.equal(0); expect(window.document.getElementsByTagName('IFRAME').length).to.equal(0); }); diff --git a/src/3p-analytics-common.js b/src/3p-analytics-common.js index 895050b3e81f..8d495e898f18 100644 --- a/src/3p-analytics-common.js +++ b/src/3p-analytics-common.js @@ -38,7 +38,9 @@ export let AmpAnalytics3pResponse; */ export class ResponseMap { /** - * Add a response + * Add a response. Note that only the most recent one for a given + * ampDoc+frameType+creativeUrl is stored. If add() is called again with + * the same first three parameters, the old value will be overwritten. * @param {!string} frameType The identifier for the third-party frame that * responded * @param {!string} creativeUrl The URL of the creative being responded to From 421b56eebf72ddc39a99c354d916d8aa20400a04 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 17 Jul 2017 14:16:21 -0400 Subject: [PATCH 25/85] Fixes a null check --- extensions/amp-analytics/0.1/amp-analytics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index b751be5686f9..c8ffb7c8581e 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -312,7 +312,7 @@ export class AmpAnalytics extends AMP.BaseElement { * * @param {!Object} params The params that need to be renamed. * @param {!Object} replaceMap A map of pattern and replacement - * value. + * value. * @private */ processExtraUrlParams_(params, replaceMap) { From c1b36b50d22356095e7f4e509be24f6ce72c217b Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 17 Jul 2017 16:46:18 -0400 Subject: [PATCH 26/85] Keys off of type rather than URL. Fixes issue of null origin, without relying on omitting sandbox allow-same-origin --- extensions/amp-analytics/0.1/amp-analytics.js | 2 +- .../amp-analytics/0.1/test/test-transport.js | 55 +++++++++---------- src/iframe-helper.js | 9 ++- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index c8ffb7c8581e..9136a912210a 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -79,7 +79,7 @@ export class AmpAnalytics extends AMP.BaseElement { * @private {?string} Predefined type associated with the tag. If specified, * the config from the predefined type is merged with the inline config */ - this.type_ = null; + this.type_ = this.element.getAttribute('type'); /** @private {!boolean} */ this.isSandbox_ = false; diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index d6eea2a8c97d..c13d9b549ee0 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -24,7 +24,7 @@ adopt(window); describe('amp-analytics.transport', () => { let sandbox; - const transport = new Transport(); + const transport = new Transport('foo'); beforeEach(() => { sandbox = sinon.sandbox.create(); }); @@ -133,7 +133,7 @@ describe('amp-analytics.transport', () => { sandbox.spy(transport, 'createCrossDomainIframe'); transport.processCrossDomainIframe(window, url); expect(transport.createCrossDomainIframe.calledOnce).to.be.true; - expect(Transport.hasCrossDomainIframe(url)).to.be.true; + expect(Transport.hasCrossDomainIframe(transport.getType())).to.be.true; transport.processCrossDomainIframe(window, url); expect(transport.createCrossDomainIframe.calledOnce).to.be.true; @@ -144,7 +144,7 @@ describe('amp-analytics.transport', () => { const config = {iframe: url}; transport.processCrossDomainIframe(window, url); transport.sendRequest(window, 'hello, world!', config); - const queue = Transport.getFrameData(url).queue; + const queue = Transport.getFrameData(transport.getType()).queue; expect(queue.messagesFor(transport.getId()).length).to.equal(1); transport.sendRequest(window, 'hello again, world!', config); expect(queue.messagesFor(transport.getId()).length).to.equal(2); @@ -153,31 +153,26 @@ describe('amp-analytics.transport', () => { it('does not cause sentinel collisions', () => { const url1 = 'https://example.com/test'; const url2 = 'https://example.com/test2'; - const url3 = 'https://example.com/test3'; - const url4 = 'https://example.com/test4'; - const transport2 = new Transport(); + const transport2 = new Transport('bar'); transport.processCrossDomainIframe(window, url1); - transport.processCrossDomainIframe(window, url2); - transport2.processCrossDomainIframe(window, url3); - transport2.processCrossDomainIframe(window, url4); - const frame1 = Transport.getFrameData(url1); - const frame2 = Transport.getFrameData(url2); - const frame3 = Transport.getFrameData(url3); - const frame4 = Transport.getFrameData(url4); + transport2.processCrossDomainIframe(window, url2); + const frame1 = Transport.getFrameData(transport.getType()); + const frame2 = Transport.getFrameData(transport2.getType()); expectAllUnique([transport.getId(), transport2.getId(), - frame1.frame.sentinel, frame2.frame.sentinel, - frame3.frame.sentinel, frame4.frame.sentinel]); + frame1.frame.sentinel, frame2.frame.sentinel]); }); it('correctly tracks usageCount and destroys iframes', () => { // Add 2 iframes. const url1 = 'https://example.com/usageCountTest1'; const url2 = 'https://example.com/usageCountTest2'; + const transport2 = new Transport('bar'); + transport.processCrossDomainIframe(window, url1); - transport.processCrossDomainIframe(window, url2); - const frame1 = Transport.getFrameData(url1); - const frame2 = Transport.getFrameData(url2); + transport2.processCrossDomainIframe(window, url2); + const frame1 = Transport.getFrameData(transport.getType()); + const frame2 = Transport.getFrameData(transport2.getType()); expect(frame1.usageCount).to.equal(1); expect(frame2.usageCount).to.equal(1); expect(window.document.getElementsByTagName('IFRAME').length).to.equal(2); @@ -185,25 +180,29 @@ describe('amp-analytics.transport', () => { // Mark the iframes as used multiple times each. transport.processCrossDomainIframe(window, url1); transport.processCrossDomainIframe(window, url1); - transport.processCrossDomainIframe(window, url2); - transport.processCrossDomainIframe(window, url2); - transport.processCrossDomainIframe(window, url2); + transport2.processCrossDomainIframe(window, url2); + transport2.processCrossDomainIframe(window, url2); + transport2.processCrossDomainIframe(window, url2); expect(frame1.usageCount).to.equal(3); expect(frame2.usageCount).to.equal(4); // Stop using the iframes, make sure usage counts go to zero and they are // removed from the DOM. - Transport.markCrossDomainIframeAsDone(window.document, url1); + Transport.markCrossDomainIframeAsDone(window.document, transport.getType()); expect(frame1.usageCount).to.equal(2); - Transport.markCrossDomainIframeAsDone(window.document, url1); - Transport.markCrossDomainIframeAsDone(window.document, url1); + Transport.markCrossDomainIframeAsDone(window.document, transport.getType()); + Transport.markCrossDomainIframeAsDone(window.document, transport.getType()); expect(frame1.usageCount).to.equal(0); expect(frame2.usageCount).to.equal(4); // (Still) expect(window.document.getElementsByTagName('IFRAME').length).to.equal(1); - Transport.markCrossDomainIframeAsDone(window.document, url2); - Transport.markCrossDomainIframeAsDone(window.document, url2); - Transport.markCrossDomainIframeAsDone(window.document, url2); - Transport.markCrossDomainIframeAsDone(window.document, url2); + Transport.markCrossDomainIframeAsDone(window.document, + transport2.getType()); + Transport.markCrossDomainIframeAsDone(window.document, + transport2.getType()); + Transport.markCrossDomainIframeAsDone(window.document, + transport2.getType()); + Transport.markCrossDomainIframeAsDone(window.document, + transport2.getType()); expect(frame2.usageCount).to.equal(0); expect(window.document.getElementsByTagName('IFRAME').length).to.equal(0); }); diff --git a/src/iframe-helper.js b/src/iframe-helper.js index 2c4f8cbcf51c..3c26be91b219 100644 --- a/src/iframe-helper.js +++ b/src/iframe-helper.js @@ -416,8 +416,12 @@ export class SubscriptionApi { * @param {boolean} is3p set to true if the iframe is 3p. * @param {function(!JsonObject, !Window, string)} requestCallback Callback * invoked whenever a new window subscribes. + * @param {boolean=} allowAnyOriginIfNull If sending a message but the + * subscribing frame has its origin set to 'null' (because it has 'sandbox + * allow-same-origin') then still send the message to that iframe by + * setting origin to '*' */ - constructor(iframe, type, is3p, requestCallback) { + constructor(iframe, type, is3p, requestCallback, allowAnyOriginIfNull) { /** @private @const {!Element} */ this.iframe_ = iframe; /** @private @const {boolean} */ @@ -429,6 +433,9 @@ export class SubscriptionApi { this.unlisten_ = listenFor(this.iframe_, type, (data, source, origin) => { // This message might be from any window within the iframe, we need // to keep track of which windows want to be sent updates. + if (allowAnyOriginIfNull && origin == 'null') { + origin = '*'; + } if (!this.clientWindows_.some(entry => entry.win == source)) { this.clientWindows_.push({win: source, origin}); } From e111811f32be39ef15d45370da252e5b7ffd95c4 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 17 Jul 2017 17:29:35 -0400 Subject: [PATCH 27/85] Minor type annotation corrections --- extensions/amp-analytics/0.1/transport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/transport.js b/extensions/amp-analytics/0.1/transport.js index 0fdea88da445..98e2faac6ef7 100644 --- a/extensions/amp-analytics/0.1/transport.js +++ b/extensions/amp-analytics/0.1/transport.js @@ -25,7 +25,7 @@ import {Services} from '../../../src/services'; import {removeElement} from '../../../src/dom'; import {setStyle} from '../../../src/style'; -/** @const {string} */ +/** @private @const {string} */ const TAG_ = 'amp-analytics.Transport'; /** From e590d9ec1c3643ee5e2df44c1afacf65a7fcbaf4 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 17 Jul 2017 17:50:49 -0400 Subject: [PATCH 28/85] Makes use of new param to SubscriptionAPI c'tor --- .../amp-analytics/0.1/amp-iframe-transport-message-queue.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js index bc0c9efefa0a..e023166dbeee 100644 --- a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js @@ -58,7 +58,8 @@ export class AmpIframeTransportMessageQueue { true, () => { this.setIsReady(); - }); + }, + true); } /** From 4c17cbefca57ac22c84519668167998594ed72ad Mon Sep 17 00:00:00 2001 From: jonkeller Date: Tue, 18 Jul 2017 09:47:05 -0400 Subject: [PATCH 29/85] Removes allow-same-origin --- extensions/amp-analytics/0.1/transport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/transport.js b/extensions/amp-analytics/0.1/transport.js index 98e2faac6ef7..0fdea88da445 100644 --- a/extensions/amp-analytics/0.1/transport.js +++ b/extensions/amp-analytics/0.1/transport.js @@ -25,7 +25,7 @@ import {Services} from '../../../src/services'; import {removeElement} from '../../../src/dom'; import {setStyle} from '../../../src/style'; -/** @private @const {string} */ +/** @const {string} */ const TAG_ = 'amp-analytics.Transport'; /** From 3ce7c3bba10529bd1fc77f18afe85fbe6d49e1dd Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 19 Jul 2017 17:50:12 -0400 Subject: [PATCH 30/85] Addresses review feedback, including removing response --- .../data/fake_amp_ad_with_3p_analytics.html | 2 +- extensions/amp-analytics/0.1/amp-analytics.js | 2 +- .../0.1/amp-iframe-transport-message-queue.js | 137 ------------------ .../0.1/iframe-transport-message-queue.js | 60 ++++---- ...test-amp-iframe-transport-message-queue.js | 84 ----------- .../amp-analytics/0.1/test/test-transport.js | 76 +++++----- src/3p-analytics-common.js | 54 ------- src/anchor-click-interceptor.js | 10 -- 8 files changed, 77 insertions(+), 348 deletions(-) delete mode 100644 extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js delete mode 100644 extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js diff --git a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html index 5eaf18f58359..8f9d2bc4f86c 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html +++ b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html @@ -8,7 +8,7 @@ - +

By Golden Trvs Gol twister (Own work) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index 9136a912210a..c8ffb7c8581e 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -79,7 +79,7 @@ export class AmpAnalytics extends AMP.BaseElement { * @private {?string} Predefined type associated with the tag. If specified, * the config from the predefined type is merged with the inline config */ - this.type_ = this.element.getAttribute('type'); + this.type_ = null; /** @private {!boolean} */ this.isSandbox_ = false; diff --git a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js deleted file mode 100644 index e023166dbeee..000000000000 --- a/extensions/amp-analytics/0.1/amp-iframe-transport-message-queue.js +++ /dev/null @@ -1,137 +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. - */ - -import {dev} from '../../../src/log'; -import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../../../src/3p-analytics-common'; -import {SubscriptionApi} from '../../../src/iframe-helper'; - -/** @private @const {string} */ -const TAG_ = 'amp-analytics.IframeTransportMessageQueue'; - -/** @private @const {number} */ -const MAX_QUEUE_SIZE_ = 100; - -/** - * @visibleForTesting - */ -export class AmpIframeTransportMessageQueue { - /** - * Constructor - * @param {!Window} win The window element - * @param {!HTMLIFrameElement} frame The cross-domain iframe to send - * messages to - */ - constructor(win, frame) { - /** @private {!Window} */ - this.win_ = win; - - /** @private {!HTMLIFrameElement} */ - this.frame_ = frame; - - /** @private {boolean} */ - this.isReady_ = false; - - /** @private - * {!Object { - this.setIsReady(); - }, - true); - } - - /** - * Returns whether the queue has been marked as ready yet - * @return {boolean} - * @VisibleForTesting - */ - isReady() { - return this.isReady_; - } - - /** - * Indicate that a cross-domain frame is ready to receive messages, and - * send all messages that were previously queued for it. - * @VisibleForTesting - */ - setIsReady() { - this.isReady_ = true; - this.flushQueue_(); - } - - /** - * Returns how many transportId -> message(s) mappings there are - * @return {number} - * @VisibleForTesting - */ - queueSize() { - return Object.keys(this.transportIdToPendingMessages_).length; - } - - /** - * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain - * iframe. - * @param {!string} transportId Identifies which creative is sending the - * message - * @param {!string} event The event to be enqueued and then sent to the iframe - */ - enqueue(transportId, event) { - this.transportIdToPendingMessages_[transportId] = - this.messagesFor(transportId) || []; - if (this.queueSize() >= MAX_QUEUE_SIZE_) { - dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + transportId); - this.messagesFor(transportId).shift(); - } - this.messagesFor(transportId).push(event); - this.flushQueue_(); - } - - /** - * Send queued data (if there is any) to a cross-domain iframe - * @private - */ - flushQueue_() { - if (this.isReady() && this.queueSize()) { - this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, - /** @type {!JsonObject} */ - ({data: this.transportIdToPendingMessages_})); - this.transportIdToPendingMessages_ = {}; - } - } - - /** - * Test method to see which messages (if any) are associated with a given - * transportId - * @param {!string} transportId Identifies which creative is sending the - * message - * @return {Array} - * @VisibleForTesting - */ - messagesFor(transportId) { - return /** @type {Array} */ ( - this.transportIdToPendingMessages_[transportId]); - } -} - diff --git a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js index e1d4723e7950..a9268bef41d9 100644 --- a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js @@ -15,9 +15,7 @@ */ import {dev} from '../../../src/log'; -import { - IFRAME_TRANSPORT_EVENTS_TYPE, -} from '../../../src/iframe-transport-common'; +import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../../../src/3p-analytics-common'; import {SubscriptionApi} from '../../../src/iframe-helper'; /** @private @const {string} */ @@ -46,14 +44,13 @@ export class IframeTransportMessageQueue { /** @private {boolean} */ this.isReady_ = false; - /** - * @private - * {!Array} + /** @private + * {!Object { this.setIsReady(); - }); + }, + true); } /** @@ -89,25 +87,24 @@ export class IframeTransportMessageQueue { * @VisibleForTesting */ queueSize() { - return this.pendingEvents_.length; + return Object.keys(this.transportIdToPendingMessages_).length; } /** - * Enqueues an event to be sent to a cross-domain iframe. - * @param {!../../../src/iframe-transport-common.IframeTransportEvent} event - * Identifies the event and which Transport instance (essentially which - * creative) is sending it. + * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain + * iframe. + * @param {!string} transportId Identifies which creative is sending the + * message + * @param {!string} event The event to be enqueued and then sent to the iframe */ - enqueue(event) { - dev().assert(TAG_, event && event.transportId && event.message, - 'Attempted to enqueue malformed message for: ' + - event.transportId); - this.pendingEvents_.push(event); + enqueue(transportId, event) { + this.transportIdToPendingMessages_[transportId] = + this.messagesFor(transportId) || []; if (this.queueSize() >= MAX_QUEUE_SIZE_) { - dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + - event.transportId); - this.pendingEvents_.shift(); + dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + transportId); + this.messagesFor(transportId).shift(); } + this.messagesFor(transportId).push(event); this.flushQueue_(); } @@ -117,11 +114,24 @@ export class IframeTransportMessageQueue { */ flushQueue_() { if (this.isReady() && this.queueSize()) { - this.postMessageApi_.send(IFRAME_TRANSPORT_EVENTS_TYPE, + this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, /** @type {!JsonObject} */ - ({events: this.pendingEvents_})); - this.pendingEvents_ = []; + ({data: this.transportIdToPendingMessages_})); + this.transportIdToPendingMessages_ = {}; } } + + /** + * Test method to see which messages (if any) are associated with a given + * transportId + * @param {!string} transportId Identifies which creative is sending the + * message + * @return {Array} + * @VisibleForTesting + */ + messagesFor(transportId) { + return /** @type {Array} */ ( + this.transportIdToPendingMessages_[transportId]); + } } diff --git a/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js deleted file mode 100644 index 4670d7ffbf95..000000000000 --- a/extensions/amp-analytics/0.1/test/test-amp-iframe-transport-message-queue.js +++ /dev/null @@ -1,84 +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. - */ - -import { - AmpIframeTransportMessageQueue, -} from '../amp-iframe-transport-message-queue'; -import {adopt} from '../../../../src/runtime'; -import * as sinon from 'sinon'; - -adopt(window); - -describe('amp-analytics.amp-iframe-transport-message-queue', () => { - let sandbox; - let frame; - let queue; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - frame = window.document.createElement('iframe'); - frame.setAttribute('sandbox', 'allow-scripts allow-same-origin'); - frame.setAttribute('name', 'some_name'); - frame.setAttribute('src', 'some_url'); - frame.sentinel = '42'; - queue = new AmpIframeTransportMessageQueue(window, frame); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('is empty when first created ', () => { - expect(queue.queueSize()).to.equal(0); - }); - - it('is not ready until setIsReady() is called ', () => { - expect(queue.isReady()).to.be.false; - queue.setIsReady(); - expect(queue.isReady()).to.be.true; - }); - - it('queues messages when not ready to send ', () => { - const beforeCount = queue.queueSize(); - queue.enqueue('some_senderId', 'some_data'); - queue.enqueue('another_senderId', 'some_data'); - const afterCount = queue.queueSize(); - expect(afterCount - beforeCount).to.equal(2); - }); - - it('flushes the queue when ready to send ', () => { - queue.enqueue('some_senderId', 'some_data'); - queue.setIsReady(); - const afterCount = queue.queueSize(); - expect(afterCount).to.equal(0); - }); - - it('groups messages from same sender ', () => { - queue.enqueue('letter_sender', 'A'); - queue.enqueue('letter_sender', 'B'); - queue.enqueue('letter_sender', 'C'); - queue.enqueue('number_sender', '1'); - queue.enqueue('number_sender', '2'); - queue.enqueue('number_sender', '3'); - queue.enqueue('number_sender', '4'); - const letterCount = queue.messagesFor('letter_sender').length; - const numberCount = queue.messagesFor('number_sender').length; - expect(queue.queueSize()).to.equal(2); - expect(letterCount).to.equal(3); - expect(numberCount).to.equal(4); - }); -}); - diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index c13d9b549ee0..44b071f18fb9 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -21,7 +21,7 @@ import * as sinon from 'sinon'; adopt(window); -describe('amp-analytics.transport', () => { +describes.realWin('amp-analytics.transport', {amp: true}, env => { let sandbox; const transport = new Transport('foo'); @@ -66,7 +66,7 @@ describe('amp-analytics.transport', () => { it('prefers cross-domain iframe over beacon, xhrpost, and image', () => { setupStubs(true, true, true, true); - transport.sendRequest(window, 'https://example.com/test', { + transport.sendRequest(env.win, 'https://example.com/test', { iframe: 'https://example.com/test', }); assertCallCounts(1, 0, 0, 0); @@ -74,7 +74,7 @@ describe('amp-analytics.transport', () => { it('prefers beacon over xhrpost and image', () => { setupStubs(true, true, true, true); - transport.sendRequest(window, 'https://example.com/test', { + transport.sendRequest(env.win, 'https://example.com/test', { beacon: true, xhrpost: true, image: true, }); assertCallCounts(0, 1, 0, 0); @@ -82,7 +82,7 @@ describe('amp-analytics.transport', () => { it('prefers xhrpost over image', () => { setupStubs(true, true, true, true); - transport.sendRequest(window, 'https://example.com/test', { + transport.sendRequest(env.win, 'https://example.com/test', { beacon: false, xhrpost: true, image: true, }); assertCallCounts(0, 0, 1, 0); @@ -90,7 +90,7 @@ describe('amp-analytics.transport', () => { it('reluctantly uses image if nothing else is enabled', () => { setupStubs(true, true, true, true); - transport.sendRequest(window, 'https://example.com/test', { + transport.sendRequest(env.win, 'https://example.com/test', { image: true, }); assertCallCounts(0, 0, 0, 1); @@ -98,7 +98,7 @@ describe('amp-analytics.transport', () => { it('falls back to xhrpost when enabled and beacon is not available', () => { setupStubs(false, false, false, true); - transport.sendRequest(window, 'https://example.com/test', { + transport.sendRequest(env.win, 'https://example.com/test', { beacon: true, xhrpost: true, image: true, }); assertCallCounts(0, 1, 1, 0); @@ -106,7 +106,7 @@ describe('amp-analytics.transport', () => { it('falls back to image when beacon not found and xhr disabled', () => { setupStubs(false, false, false, true); - transport.sendRequest(window, 'https://example.com/test', { + transport.sendRequest(env.win, 'https://example.com/test', { beacon: true, xhrpost: false, image: true, }); assertCallCounts(0, 1, 0, 1); @@ -114,7 +114,7 @@ describe('amp-analytics.transport', () => { it('falls back to image when beacon and xhr are not available', () => { setupStubs(false, false, false, false); - transport.sendRequest(window, 'https://example.com/test', { + transport.sendRequest(env.win, 'https://example.com/test', { beacon: true, xhrpost: true, image: true, }); assertCallCounts(0, 1, 1, 1); @@ -122,7 +122,7 @@ describe('amp-analytics.transport', () => { it('must create xframe before sending message to it', () => { expect(() => { - transport.sendRequest(window, 'https://example.com/test', { + transport.sendRequest(env.win, 'https://example.com/test', { iframe: 'https://example.com/test', }); }).to.throw(/send message to non-existent/); @@ -131,22 +131,22 @@ describe('amp-analytics.transport', () => { it('reuses cross-domain iframe', () => { const url = 'https://example.com/test'; sandbox.spy(transport, 'createCrossDomainIframe'); - transport.processCrossDomainIframe(window, url); + transport.processCrossDomainIframe(env.win, url); expect(transport.createCrossDomainIframe.calledOnce).to.be.true; expect(Transport.hasCrossDomainIframe(transport.getType())).to.be.true; - transport.processCrossDomainIframe(window, url); + transport.processCrossDomainIframe(env.win, url); expect(transport.createCrossDomainIframe.calledOnce).to.be.true; }); it('enqueues event messages correctly', () => { const url = 'https://example.com/test'; const config = {iframe: url}; - transport.processCrossDomainIframe(window, url); - transport.sendRequest(window, 'hello, world!', config); + transport.processCrossDomainIframe(env.win, url); + transport.sendRequest(env.win, 'hello, world!', config); const queue = Transport.getFrameData(transport.getType()).queue; expect(queue.messagesFor(transport.getId()).length).to.equal(1); - transport.sendRequest(window, 'hello again, world!', config); + transport.sendRequest(env.win, 'hello again, world!', config); expect(queue.messagesFor(transport.getId()).length).to.equal(2); }); @@ -155,8 +155,8 @@ describe('amp-analytics.transport', () => { const url2 = 'https://example.com/test2'; const transport2 = new Transport('bar'); - transport.processCrossDomainIframe(window, url1); - transport2.processCrossDomainIframe(window, url2); + transport.processCrossDomainIframe(env.win, url1); + transport2.processCrossDomainIframe(env.win, url2); const frame1 = Transport.getFrameData(transport.getType()); const frame2 = Transport.getFrameData(transport2.getType()); expectAllUnique([transport.getId(), transport2.getId(), @@ -169,59 +169,63 @@ describe('amp-analytics.transport', () => { const url2 = 'https://example.com/usageCountTest2'; const transport2 = new Transport('bar'); - transport.processCrossDomainIframe(window, url1); - transport2.processCrossDomainIframe(window, url2); + transport.processCrossDomainIframe(env.win, url1); + transport2.processCrossDomainIframe(env.win, url2); const frame1 = Transport.getFrameData(transport.getType()); const frame2 = Transport.getFrameData(transport2.getType()); expect(frame1.usageCount).to.equal(1); expect(frame2.usageCount).to.equal(1); - expect(window.document.getElementsByTagName('IFRAME').length).to.equal(2); + expect(env.win.document.getElementsByTagName('IFRAME').length).to.equal(2); // Mark the iframes as used multiple times each. - transport.processCrossDomainIframe(window, url1); - transport.processCrossDomainIframe(window, url1); - transport2.processCrossDomainIframe(window, url2); - transport2.processCrossDomainIframe(window, url2); - transport2.processCrossDomainIframe(window, url2); + transport.processCrossDomainIframe(env.win, url1); + transport.processCrossDomainIframe(env.win, url1); + transport2.processCrossDomainIframe(env.win, url2); + transport2.processCrossDomainIframe(env.win, url2); + transport2.processCrossDomainIframe(env.win, url2); expect(frame1.usageCount).to.equal(3); expect(frame2.usageCount).to.equal(4); // Stop using the iframes, make sure usage counts go to zero and they are // removed from the DOM. - Transport.markCrossDomainIframeAsDone(window.document, transport.getType()); + Transport.markCrossDomainIframeAsDone(env.win.document, + transport.getType()); expect(frame1.usageCount).to.equal(2); - Transport.markCrossDomainIframeAsDone(window.document, transport.getType()); - Transport.markCrossDomainIframeAsDone(window.document, transport.getType()); + Transport.markCrossDomainIframeAsDone(env.win.document, + transport.getType()); + Transport.markCrossDomainIframeAsDone(env.win.document, + transport.getType()); expect(frame1.usageCount).to.equal(0); expect(frame2.usageCount).to.equal(4); // (Still) - expect(window.document.getElementsByTagName('IFRAME').length).to.equal(1); - Transport.markCrossDomainIframeAsDone(window.document, + expect(env.win.document.getElementsByTagName('IFRAME').length).to.equal(1); + Transport.markCrossDomainIframeAsDone(env.win.document, transport2.getType()); - Transport.markCrossDomainIframeAsDone(window.document, + Transport.markCrossDomainIframeAsDone(env.win.document, transport2.getType()); - Transport.markCrossDomainIframeAsDone(window.document, + Transport.markCrossDomainIframeAsDone(env.win.document, transport2.getType()); - Transport.markCrossDomainIframeAsDone(window.document, + Transport.markCrossDomainIframeAsDone(env.win.document, transport2.getType()); expect(frame2.usageCount).to.equal(0); - expect(window.document.getElementsByTagName('IFRAME').length).to.equal(0); + expect(env.win.document.getElementsByTagName('IFRAME').length).to.equal(0); }); it('does not send a request when no transport methods are enabled', () => { setupStubs(true, true, true, true); - transport.sendRequest(window, 'https://example.com/test', {}); + transport.sendRequest(env.win, 'https://example.com/test', {}); assertCallCounts(0, 0, 0, 0); }); it('asserts that urls are https', () => { expect(() => { - transport.sendRequest(window, 'http://example.com/test'); + transport.sendRequest(env.win, 'http://example.com/test'); }).to.throw(/https/); }); it('should NOT allow __amp_source_origin', () => { expect(() => { - transport.sendRequest(window, 'https://twitter.com?__amp_source_origin=1'); + transport.sendRequest(env.win, + 'https://twitter.com?__amp_source_origin=1'); }).to.throw(/Source origin is not allowed in/); }); diff --git a/src/3p-analytics-common.js b/src/3p-analytics-common.js index 8d495e898f18..80d0d3f13456 100644 --- a/src/3p-analytics-common.js +++ b/src/3p-analytics-common.js @@ -17,7 +17,6 @@ /** @enum {string} */ export const AMP_ANALYTICS_3P_MESSAGE_TYPE = { EVENT: 'E', - RESPONSE: 'R', }; /** @typedef {!Object>} */ @@ -28,56 +27,3 @@ export let AmpAnalytics3pEvent; // ... // } -/** @typedef {JsonObject} */ -export let AmpAnalytics3pResponse; -// Example: -// {"status":"received","somethingElse":"42"} - -/** - * A class for holding AMP Analytics third-party vendors responses to frames. - */ -export class ResponseMap { - /** - * Add a response. Note that only the most recent one for a given - * ampDoc+frameType+creativeUrl is stored. If add() is called again with - * the same first three parameters, the old value will be overwritten. - * @param {!string} frameType The identifier for the third-party frame that - * responded - * @param {!string} creativeUrl The URL of the creative being responded to - * @param {Object} response What the response was - */ - static add(ampDoc, frameType, creativeUrl, response) { - const map = ampDoc.getAnchorClickListenerBinding(); - map[frameType] = map[frameType] || {}; - map[frameType][creativeUrl] = response; - } - - /** - * Gets the most recent response given by a certain frame to a certain - * creative - * @param {!string} frameType The identifier for the third-party frame - * whose response is sought - * @param {!string} creativeUrl The URL of the creative that the sought - * response was about - * @returns {?Object} - */ - static get(ampDoc, frameType, creativeUrl) { - const map = ampDoc.getAnchorClickListenerBinding(); - if (map[frameType] && map[frameType][creativeUrl]) { - return map[frameType][creativeUrl]; - } - return {}; - } - - /** - * Remove a response, for instance if a third-party frame is being destroyed - * @param {!string} frameType The identifier for the third-party frame - * whose responses are to be removed - */ - static remove(ampDoc, frameType) { - const map = ampDoc.getAnchorClickListenerBinding(); - if (map[frameType]) { - delete map[frameType]; - } - } -} diff --git a/src/anchor-click-interceptor.js b/src/anchor-click-interceptor.js index fecba905fc35..bfb1fe283153 100644 --- a/src/anchor-click-interceptor.js +++ b/src/anchor-click-interceptor.js @@ -19,7 +19,6 @@ import { } from './dom'; import {dev} from './log'; import {Services} from './services'; -import {ResponseMap} from './3p-analytics-common'; /** @private @const {string} */ const ORIG_HREF_ATTRIBUTE = 'data-a4a-orig-href'; @@ -60,14 +59,6 @@ function maybeExpandUrlParams(ampdoc, e) { 'CLICK_Y': () => { return e.pageY; }, - '3PANALYTICS': (frameType, key) => { - const responses = ResponseMap.get(ampdoc, frameType, - /** @type {!string} */ (target.baseURI)); - if (responses && responses[key]) { - return responses[key]; - } - return ''; - }, }; const newHref = Services.urlReplacementsForDoc(ampdoc).expandSync( hrefToExpand, vars, undefined, /* opt_whitelist */ { @@ -76,7 +67,6 @@ function maybeExpandUrlParams(ampdoc, e) { // NOTE: Addition to this whitelist requires additional review. 'CLICK_X': true, 'CLICK_Y': true, - '3PANALYTICS': true, }); if (newHref != hrefToExpand) { // Store original value so that later clicks can be processed with From fbdb4a84f7df4851580d9ee7a101214a3cb5db78 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 19 Jul 2017 17:52:08 -0400 Subject: [PATCH 31/85] Fixes unit test --- .../0.1/test/test-iframe-transport-message-queue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 52e1a5caa4bb..84782f5cdb03 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 @@ -21,7 +21,7 @@ describes.realWin('amp-analytics.iframe-transport-message-queue', {amp: true}, env => { let frame; let queue; - + beforeEach(() => { frame = createElementWithAttributes(env.win.document, 'iframe', { 'sandbox': 'allow-scripts', From f664d3125c37e7de7f5e10564e131ba563bcb991 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 19 Jul 2017 18:24:10 -0400 Subject: [PATCH 32/85] Adds a forgotten semicolon :( --- .../0.1/test/test-iframe-transport-message-queue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 84782f5cdb03..52e1a5caa4bb 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 @@ -21,7 +21,7 @@ describes.realWin('amp-analytics.iframe-transport-message-queue', {amp: true}, env => { let frame; let queue; - + beforeEach(() => { frame = createElementWithAttributes(env.win.document, 'iframe', { 'sandbox': 'allow-scripts', From fcad58afa8dd37e526f9df0cd509567c9c6e7b91 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 20 Jul 2017 11:11:32 -0400 Subject: [PATCH 33/85] Addresses review feedback --- .../0.1/iframe-transport-message-queue.js | 2 +- .../amp-analytics/0.1/test/test-transport.js | 236 +++++++++++------- src/3p-analytics-common.js | 9 +- src/anchor-click-interceptor.js | 1 - src/service/ampdoc-impl.js | 13 - 5 files changed, 157 insertions(+), 104 deletions(-) diff --git a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js index a9268bef41d9..e85f273ffacb 100644 --- a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js @@ -45,7 +45,7 @@ export class IframeTransportMessageQueue { this.isReady_ = false; /** @private - * {!Object { let sandbox; - const transport = new Transport('foo'); + let transport; + const frameUrl = 'https://www.google.com'; + beforeEach(() => { - sandbox = sinon.sandbox.create(); + sandbox = env.sandbox; + transport = new Transport(env.win, 'some_vendor_type', {iframe: frameUrl}); }); afterEach(() => { Transport.resetCrossDomainIframes(); - sandbox.restore(); }); - function setupStubs(crossDomainIframeRetval, imageRetval, - beaconRetval, xhrRetval) { + function setupStubs(returnValues) { sandbox.stub(transport, 'sendRequestUsingCrossDomainIframe').returns( - crossDomainIframeRetval); - sandbox.stub(Transport, 'sendRequestUsingImage').returns(imageRetval); - sandbox.stub(Transport, 'sendRequestUsingBeacon').returns(beaconRetval); - sandbox.stub(Transport, 'sendRequestUsingXhr').returns(xhrRetval); + returnValues['crossDomainIframe']); + sandbox.stub(Transport, 'sendRequestUsingImage').returns( + returnValues['image']); + sandbox.stub(Transport, 'sendRequestUsingBeacon').returns( + returnValues['beacon']); + sandbox.stub(Transport, 'sendRequestUsingXhr').returns( + returnValues['xhr']); } - function assertCallCounts( - expectedCrossDomainIframeCalls, - expectedBeaconCalls, expectedXhrCalls, expectedImageCalls) { + function assertCallCounts(counts) { expect(transport.sendRequestUsingCrossDomainIframe.callCount, 'sendRequestUsingCrossDomainIframe call count').to.equal( - expectedCrossDomainIframeCalls); + counts['crossDomainIframe']); + expect(Transport.sendRequestUsingImage.callCount, + 'sendRequestUsingImage call count').to.equal(counts['image']); expect(Transport.sendRequestUsingBeacon.callCount, - 'sendRequestUsingBeacon call count').to.equal(expectedBeaconCalls); + 'sendRequestUsingBeacon call count').to.equal(counts['beacon']); expect(Transport.sendRequestUsingXhr.callCount, - 'sendRequestUsingXhr call count').to.equal(expectedXhrCalls); - expect(Transport.sendRequestUsingImage.callCount, - 'sendRequestUsingImage call count').to.equal(expectedImageCalls); + 'sendRequestUsingXhr call count').to.equal(counts['xhr']); } function expectAllUnique(numArray) { if (!numArray) { return; } - expect(numArray.length).to.equal(new Set(numArray).size); + expect(numArray).to.have.lengthOf(new Set(numArray).size); } it('prefers cross-domain iframe over beacon, xhrpost, and image', () => { - setupStubs(true, true, true, true); - transport.sendRequest(env.win, 'https://example.com/test', { + setupStubs({ + 'crossDomainIframe': true, + 'image': true, + 'beacon': true, + 'xhr': true, + }); + transport.sendRequest('https://example.com/test', { iframe: 'https://example.com/test', }); - assertCallCounts(1, 0, 0, 0); + assertCallCounts({ + 'crossDomainIframe': 1, + 'image': 0, + 'beacon': 0, + 'xhr': 0, + }); }); it('prefers beacon over xhrpost and image', () => { - setupStubs(true, true, true, true); - transport.sendRequest(env.win, 'https://example.com/test', { + setupStubs({ + 'crossDomainIframe': true, + 'image': true, + 'beacon': true, + 'xhr': true, + }); + transport.sendRequest('https://example.com/test', { beacon: true, xhrpost: true, image: true, }); - assertCallCounts(0, 1, 0, 0); + assertCallCounts({ + 'crossDomainIframe': 0, + 'image': 0, + 'beacon': 1, + 'xhr': 0, + }); }); it('prefers xhrpost over image', () => { - setupStubs(true, true, true, true); - transport.sendRequest(env.win, 'https://example.com/test', { + setupStubs({ + 'crossDomainIframe': true, + 'image': true, + 'beacon': true, + 'xhr': true, + }); + transport.sendRequest('https://example.com/test', { beacon: false, xhrpost: true, image: true, }); - assertCallCounts(0, 0, 1, 0); + assertCallCounts({ + 'crossDomainIframe': 0, + 'image': 0, + 'beacon': 0, + 'xhr': 1, + }); }); it('reluctantly uses image if nothing else is enabled', () => { - setupStubs(true, true, true, true); - transport.sendRequest(env.win, 'https://example.com/test', { - image: true, + setupStubs({ + 'crossDomainIframe': true, + 'image': true, + 'beacon': true, + 'xhr': true, + }); + transport.sendRequest('https://example.com/test', {image: true}); + assertCallCounts({ + 'crossDomainIframe': 0, + 'image': 1, + 'beacon': 0, + 'xhr': 0, }); - assertCallCounts(0, 0, 0, 1); }); it('falls back to xhrpost when enabled and beacon is not available', () => { - setupStubs(false, false, false, true); - transport.sendRequest(env.win, 'https://example.com/test', { + setupStubs({ + 'crossDomainIframe': false, + 'image': false, + 'beacon': false, + 'xhr': true, + }); + transport.sendRequest('https://example.com/test', { beacon: true, xhrpost: true, image: true, }); - assertCallCounts(0, 1, 1, 0); + assertCallCounts({ + 'crossDomainIframe': 0, + 'image': 0, + 'beacon': 1, + 'xhr': 1, + }); }); it('falls back to image when beacon not found and xhr disabled', () => { - setupStubs(false, false, false, true); - transport.sendRequest(env.win, 'https://example.com/test', { + setupStubs({ + 'crossDomainIframe': false, + 'image': false, + 'beacon': false, + 'xhr': true, + }); + transport.sendRequest('https://example.com/test', { beacon: true, xhrpost: false, image: true, }); - assertCallCounts(0, 1, 0, 1); + assertCallCounts({ + 'crossDomainIframe': 0, + 'image': 1, + 'beacon': 1, + 'xhr': 0, + }); }); it('falls back to image when beacon and xhr are not available', () => { - setupStubs(false, false, false, false); - transport.sendRequest(env.win, 'https://example.com/test', { + setupStubs({ + 'crossDomainIframe': false, + 'image': false, + 'beacon': false, + 'xhr': false, + }); + transport.sendRequest('https://example.com/test', { beacon: true, xhrpost: true, image: true, }); - assertCallCounts(0, 1, 1, 1); - }); - - it('must create xframe before sending message to it', () => { - expect(() => { - transport.sendRequest(env.win, 'https://example.com/test', { - iframe: 'https://example.com/test', - }); - }).to.throw(/send message to non-existent/); + assertCallCounts({ + 'crossDomainIframe': 0, + 'image': 1, + 'beacon': 1, + 'xhr': 1, + }); }); - it('reuses cross-domain iframe', () => { - const url = 'https://example.com/test'; - sandbox.spy(transport, 'createCrossDomainIframe'); - transport.processCrossDomainIframe(env.win, url); - expect(transport.createCrossDomainIframe.calledOnce).to.be.true; + it('enforces one frame url per vendor type', () => { + const createCrossDomainIframeSpy = sandbox.spy(transport, + 'createCrossDomainIframe'); + transport.processCrossDomainIframe(); + expect(createCrossDomainIframeSpy).to.not.be.called; expect(Transport.hasCrossDomainIframe(transport.getType())).to.be.true; - transport.processCrossDomainIframe(env.win, url); - expect(transport.createCrossDomainIframe.calledOnce).to.be.true; + transport.processCrossDomainIframe(); + expect(createCrossDomainIframeSpy).to.not.be.called; }); it('enqueues event messages correctly', () => { const url = 'https://example.com/test'; const config = {iframe: url}; - transport.processCrossDomainIframe(env.win, url); - transport.sendRequest(env.win, 'hello, world!', config); + transport.processCrossDomainIframe(); + transport.sendRequest('hello, world!', config); const queue = Transport.getFrameData(transport.getType()).queue; - expect(queue.messagesFor(transport.getId()).length).to.equal(1); - transport.sendRequest(env.win, 'hello again, world!', config); - expect(queue.messagesFor(transport.getId()).length).to.equal(2); + expect(queue.messagesFor(transport.getId())).to.have.lengthOf(1); + transport.sendRequest('hello again, world!', config); + expect(queue.messagesFor(transport.getId())).to.have.lengthOf(2); }); it('does not cause sentinel collisions', () => { - const url1 = 'https://example.com/test'; - const url2 = 'https://example.com/test2'; - const transport2 = new Transport('bar'); + const transport2 = new Transport(env.win, 'some_other_vendor_type', + {iframe: 'https://example.com/test2'}); - transport.processCrossDomainIframe(env.win, url1); - transport2.processCrossDomainIframe(env.win, url2); const frame1 = Transport.getFrameData(transport.getType()); const frame2 = Transport.getFrameData(transport2.getType()); expectAllUnique([transport.getId(), transport2.getId(), @@ -164,25 +222,22 @@ describes.realWin('amp-analytics.transport', {amp: true}, env => { }); it('correctly tracks usageCount and destroys iframes', () => { - // Add 2 iframes. - const url1 = 'https://example.com/usageCountTest1'; - const url2 = 'https://example.com/usageCountTest2'; - const transport2 = new Transport('bar'); + const frameUrl2 = 'https://example.com/test2'; + const transport2 = new Transport(env.win, 'some_other_vendor_type', + {iframe: frameUrl2}); - transport.processCrossDomainIframe(env.win, url1); - transport2.processCrossDomainIframe(env.win, url2); const frame1 = Transport.getFrameData(transport.getType()); const frame2 = Transport.getFrameData(transport2.getType()); expect(frame1.usageCount).to.equal(1); expect(frame2.usageCount).to.equal(1); - expect(env.win.document.getElementsByTagName('IFRAME').length).to.equal(2); + expect(env.win.document.getElementsByTagName('IFRAME')).to.have.lengthOf(2); // Mark the iframes as used multiple times each. - transport.processCrossDomainIframe(env.win, url1); - transport.processCrossDomainIframe(env.win, url1); - transport2.processCrossDomainIframe(env.win, url2); - transport2.processCrossDomainIframe(env.win, url2); - transport2.processCrossDomainIframe(env.win, url2); + transport.processCrossDomainIframe(); + transport.processCrossDomainIframe(); + transport2.processCrossDomainIframe(); + transport2.processCrossDomainIframe(); + transport2.processCrossDomainIframe(); expect(frame1.usageCount).to.equal(3); expect(frame2.usageCount).to.equal(4); @@ -197,7 +252,7 @@ describes.realWin('amp-analytics.transport', {amp: true}, env => { transport.getType()); expect(frame1.usageCount).to.equal(0); expect(frame2.usageCount).to.equal(4); // (Still) - expect(env.win.document.getElementsByTagName('IFRAME').length).to.equal(1); + expect(env.win.document.getElementsByTagName('IFRAME')).to.have.lengthOf(1); Transport.markCrossDomainIframeAsDone(env.win.document, transport2.getType()); Transport.markCrossDomainIframeAsDone(env.win.document, @@ -207,25 +262,34 @@ describes.realWin('amp-analytics.transport', {amp: true}, env => { Transport.markCrossDomainIframeAsDone(env.win.document, transport2.getType()); expect(frame2.usageCount).to.equal(0); - expect(env.win.document.getElementsByTagName('IFRAME').length).to.equal(0); + expect(env.win.document.getElementsByTagName('IFRAME')).to.have.lengthOf(0); }); it('does not send a request when no transport methods are enabled', () => { - setupStubs(true, true, true, true); - transport.sendRequest(env.win, 'https://example.com/test', {}); - assertCallCounts(0, 0, 0, 0); + setupStubs({ + 'crossDomainIframe': true, + 'image': true, + 'beacon': true, + 'xhr': true, + }); + transport.sendRequest('https://example.com/test', {}); + assertCallCounts({ + 'crossDomainIframe': 0, + 'image': 0, + 'beacon': 0, + 'xhr': 0, + }); }); it('asserts that urls are https', () => { expect(() => { - transport.sendRequest(env.win, 'http://example.com/test'); + transport.sendRequest('http://example.com/test'); }).to.throw(/https/); }); it('should NOT allow __amp_source_origin', () => { expect(() => { - transport.sendRequest(env.win, - 'https://twitter.com?__amp_source_origin=1'); + transport.sendRequest('https://twitter.com?__amp_source_origin=1'); }).to.throw(/Source origin is not allowed in/); }); diff --git a/src/3p-analytics-common.js b/src/3p-analytics-common.js index 80d0d3f13456..4ac79190944c 100644 --- a/src/3p-analytics-common.js +++ b/src/3p-analytics-common.js @@ -20,10 +20,13 @@ export const AMP_ANALYTICS_3P_MESSAGE_TYPE = { }; /** @typedef {!Object>} */ -export let AmpAnalytics3pEvent; +export let AmpAnalytics3pEventMap; +// Maps transport IDs to events. For instance if the creative with transport +// ID 2 sends "hi" and "hello" and the creative with transport ID 3 sends +// "goodbye" then the map would look like: // Example: // { -// "2": ["viewed=true&...etc.", ... ], -// ... +// "2": ["hi", "hello" ], +// "3": ["goodbye" ] // } diff --git a/src/anchor-click-interceptor.js b/src/anchor-click-interceptor.js index bfb1fe283153..0ffa470ec2a5 100644 --- a/src/anchor-click-interceptor.js +++ b/src/anchor-click-interceptor.js @@ -81,4 +81,3 @@ function maybeExpandUrlParams(ampdoc, e) { export function maybeExpandUrlParamsForTesting(ampdoc, e) { maybeExpandUrlParams(ampdoc, e); } - diff --git a/src/service/ampdoc-impl.js b/src/service/ampdoc-impl.js index 96d597f43905..75e1634d8aea 100644 --- a/src/service/ampdoc-impl.js +++ b/src/service/ampdoc-impl.js @@ -187,9 +187,6 @@ export class AmpDoc { /** @private @const */ this.signals_ = new Signals(); - - /** @private {!Object>} */ - this.anchorClickListenerBinding_ = {}; } /** @@ -303,16 +300,6 @@ export class AmpDoc { contains(node) { return this.getRootNode().contains(node); } - - /** - * Binding of macro to function used as part of any installed anchor click - * listener. - * @return {!Object>} - * @see src/anchor-click-interceptor#installAnchorClickInterceptor - */ - getAnchorClickListenerBinding() { - return this.anchorClickListenerBinding_; - } } From 4c52b488ebf36411b454158d4ca4c3ff009f9773 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 20 Jul 2017 13:06:57 -0400 Subject: [PATCH 34/85] Splits crossDomainIframe functionality out from transport.js into iframe-transport.js (and likewise for unit tests) --- .../amp-analytics/0.1/test/test-transport.js | 257 +++--------------- 1 file changed, 39 insertions(+), 218 deletions(-) diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index 92c561ceaa1f..08333905a377 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -14,282 +14,103 @@ * limitations under the License. */ -import {sendRequestUsingIframe, Transport} from '../transport'; +import {sendRequest, sendRequestUsingIframe, Transport} from '../transport'; import {adopt} from '../../../../src/runtime'; import {loadPromise} from '../../../../src/event-helper'; +import * as sinon from 'sinon'; adopt(window); -describes.realWin('amp-analytics.transport', {amp: true}, env => { +describe('amp-analytics.transport', () => { let sandbox; - let transport; - const frameUrl = 'https://www.google.com'; - beforeEach(() => { - sandbox = env.sandbox; - transport = new Transport(env.win, 'some_vendor_type', {iframe: frameUrl}); + sandbox = sinon.sandbox.create(); }); afterEach(() => { - Transport.resetCrossDomainIframes(); + sandbox.restore(); }); - function setupStubs(returnValues) { - sandbox.stub(transport, 'sendRequestUsingCrossDomainIframe').returns( - returnValues['crossDomainIframe']); - sandbox.stub(Transport, 'sendRequestUsingImage').returns( - returnValues['image']); - sandbox.stub(Transport, 'sendRequestUsingBeacon').returns( - returnValues['beacon']); - sandbox.stub(Transport, 'sendRequestUsingXhr').returns( - returnValues['xhr']); + function setupStubs(beaconRetval, xhrRetval) { + sandbox.stub(Transport, 'sendRequestUsingImage'); + sandbox.stub(Transport, 'sendRequestUsingBeacon').returns(beaconRetval); + sandbox.stub(Transport, 'sendRequestUsingXhr').returns(xhrRetval); } - function assertCallCounts(counts) { - expect(transport.sendRequestUsingCrossDomainIframe.callCount, - 'sendRequestUsingCrossDomainIframe call count').to.equal( - counts['crossDomainIframe']); - expect(Transport.sendRequestUsingImage.callCount, - 'sendRequestUsingImage call count').to.equal(counts['image']); + function assertCallCounts( + expectedBeaconCalls, expectedXhrCalls, expectedImageCalls) { expect(Transport.sendRequestUsingBeacon.callCount, - 'sendRequestUsingBeacon call count').to.equal(counts['beacon']); + 'sendRequestUsingBeacon call count').to.equal(expectedBeaconCalls); expect(Transport.sendRequestUsingXhr.callCount, - 'sendRequestUsingXhr call count').to.equal(counts['xhr']); - } - - function expectAllUnique(numArray) { - if (!numArray) { - return; - } - expect(numArray).to.have.lengthOf(new Set(numArray).size); + 'sendRequestUsingXhr call count').to.equal(expectedXhrCalls); + expect(Transport.sendRequestUsingImage.callCount, + 'sendRequestUsingImage call count').to.equal(expectedImageCalls); } - it('prefers cross-domain iframe over beacon, xhrpost, and image', () => { - setupStubs({ - 'crossDomainIframe': true, - 'image': true, - 'beacon': true, - 'xhr': true, - }); - transport.sendRequest('https://example.com/test', { - iframe: 'https://example.com/test', - }); - assertCallCounts({ - 'crossDomainIframe': 1, - 'image': 0, - 'beacon': 0, - 'xhr': 0, - }); - }); - it('prefers beacon over xhrpost and image', () => { - setupStubs({ - 'crossDomainIframe': true, - 'image': true, - 'beacon': true, - 'xhr': true, - }); - transport.sendRequest('https://example.com/test', { + setupStubs(true, true); + sendRequest(window, 'https://example.com/test', { beacon: true, xhrpost: true, image: true, }); - assertCallCounts({ - 'crossDomainIframe': 0, - 'image': 0, - 'beacon': 1, - 'xhr': 0, - }); + assertCallCounts(1, 0, 0); }); it('prefers xhrpost over image', () => { - setupStubs({ - 'crossDomainIframe': true, - 'image': true, - 'beacon': true, - 'xhr': true, - }); - transport.sendRequest('https://example.com/test', { + setupStubs(true, true); + sendRequest(window, 'https://example.com/test', { beacon: false, xhrpost: true, image: true, }); - assertCallCounts({ - 'crossDomainIframe': 0, - 'image': 0, - 'beacon': 0, - 'xhr': 1, - }); + assertCallCounts(0, 1, 0); }); it('reluctantly uses image if nothing else is enabled', () => { - setupStubs({ - 'crossDomainIframe': true, - 'image': true, - 'beacon': true, - 'xhr': true, - }); - transport.sendRequest('https://example.com/test', {image: true}); - assertCallCounts({ - 'crossDomainIframe': 0, - 'image': 1, - 'beacon': 0, - 'xhr': 0, + setupStubs(true, true); + sendRequest(window, 'https://example.com/test', { + image: true, }); + assertCallCounts(0, 0, 1); }); it('falls back to xhrpost when enabled and beacon is not available', () => { - setupStubs({ - 'crossDomainIframe': false, - 'image': false, - 'beacon': false, - 'xhr': true, - }); - transport.sendRequest('https://example.com/test', { + setupStubs(false, true); + sendRequest(window, 'https://example.com/test', { beacon: true, xhrpost: true, image: true, }); - assertCallCounts({ - 'crossDomainIframe': 0, - 'image': 0, - 'beacon': 1, - 'xhr': 1, - }); + assertCallCounts(1, 1, 0); }); it('falls back to image when beacon not found and xhr disabled', () => { - setupStubs({ - 'crossDomainIframe': false, - 'image': false, - 'beacon': false, - 'xhr': true, - }); - transport.sendRequest('https://example.com/test', { + setupStubs(false, true); + sendRequest(window, 'https://example.com/test', { beacon: true, xhrpost: false, image: true, }); - assertCallCounts({ - 'crossDomainIframe': 0, - 'image': 1, - 'beacon': 1, - 'xhr': 0, - }); + assertCallCounts(1, 0, 1); }); it('falls back to image when beacon and xhr are not available', () => { - setupStubs({ - 'crossDomainIframe': false, - 'image': false, - 'beacon': false, - 'xhr': false, - }); - transport.sendRequest('https://example.com/test', { + setupStubs(false, false); + sendRequest(window, 'https://example.com/test', { beacon: true, xhrpost: true, image: true, }); - assertCallCounts({ - 'crossDomainIframe': 0, - 'image': 1, - 'beacon': 1, - 'xhr': 1, - }); - }); - - it('enforces one frame url per vendor type', () => { - const createCrossDomainIframeSpy = sandbox.spy(transport, - 'createCrossDomainIframe'); - transport.processCrossDomainIframe(); - expect(createCrossDomainIframeSpy).to.not.be.called; - expect(Transport.hasCrossDomainIframe(transport.getType())).to.be.true; - - transport.processCrossDomainIframe(); - expect(createCrossDomainIframeSpy).to.not.be.called; - }); - - it('enqueues event messages correctly', () => { - const url = 'https://example.com/test'; - const config = {iframe: url}; - transport.processCrossDomainIframe(); - transport.sendRequest('hello, world!', config); - const queue = Transport.getFrameData(transport.getType()).queue; - expect(queue.messagesFor(transport.getId())).to.have.lengthOf(1); - transport.sendRequest('hello again, world!', config); - expect(queue.messagesFor(transport.getId())).to.have.lengthOf(2); - }); - - it('does not cause sentinel collisions', () => { - const transport2 = new Transport(env.win, 'some_other_vendor_type', - {iframe: 'https://example.com/test2'}); - - const frame1 = Transport.getFrameData(transport.getType()); - const frame2 = Transport.getFrameData(transport2.getType()); - expectAllUnique([transport.getId(), transport2.getId(), - frame1.frame.sentinel, frame2.frame.sentinel]); - }); - - it('correctly tracks usageCount and destroys iframes', () => { - const frameUrl2 = 'https://example.com/test2'; - const transport2 = new Transport(env.win, 'some_other_vendor_type', - {iframe: frameUrl2}); - - const frame1 = Transport.getFrameData(transport.getType()); - const frame2 = Transport.getFrameData(transport2.getType()); - expect(frame1.usageCount).to.equal(1); - expect(frame2.usageCount).to.equal(1); - expect(env.win.document.getElementsByTagName('IFRAME')).to.have.lengthOf(2); - - // Mark the iframes as used multiple times each. - transport.processCrossDomainIframe(); - transport.processCrossDomainIframe(); - transport2.processCrossDomainIframe(); - transport2.processCrossDomainIframe(); - transport2.processCrossDomainIframe(); - expect(frame1.usageCount).to.equal(3); - expect(frame2.usageCount).to.equal(4); - - // Stop using the iframes, make sure usage counts go to zero and they are - // removed from the DOM. - Transport.markCrossDomainIframeAsDone(env.win.document, - transport.getType()); - expect(frame1.usageCount).to.equal(2); - Transport.markCrossDomainIframeAsDone(env.win.document, - transport.getType()); - Transport.markCrossDomainIframeAsDone(env.win.document, - transport.getType()); - expect(frame1.usageCount).to.equal(0); - expect(frame2.usageCount).to.equal(4); // (Still) - expect(env.win.document.getElementsByTagName('IFRAME')).to.have.lengthOf(1); - Transport.markCrossDomainIframeAsDone(env.win.document, - transport2.getType()); - Transport.markCrossDomainIframeAsDone(env.win.document, - transport2.getType()); - Transport.markCrossDomainIframeAsDone(env.win.document, - transport2.getType()); - Transport.markCrossDomainIframeAsDone(env.win.document, - transport2.getType()); - expect(frame2.usageCount).to.equal(0); - expect(env.win.document.getElementsByTagName('IFRAME')).to.have.lengthOf(0); + assertCallCounts(1, 1, 1); }); it('does not send a request when no transport methods are enabled', () => { - setupStubs({ - 'crossDomainIframe': true, - 'image': true, - 'beacon': true, - 'xhr': true, - }); - transport.sendRequest('https://example.com/test', {}); - assertCallCounts({ - 'crossDomainIframe': 0, - 'image': 0, - 'beacon': 0, - 'xhr': 0, - }); + setupStubs(true, true); + sendRequest(window, 'https://example.com/test', {}); + assertCallCounts(0, 0, 0); }); it('asserts that urls are https', () => { expect(() => { - transport.sendRequest('http://example.com/test'); + sendRequest(window, 'http://example.com/test'); }).to.throw(/https/); }); it('should NOT allow __amp_source_origin', () => { expect(() => { - transport.sendRequest('https://twitter.com?__amp_source_origin=1'); + sendRequest(window, 'https://twitter.com?__amp_source_origin=1'); }).to.throw(/Source origin is not allowed in/); }); From 29200689d4a1ecfc0240f35719b6bd1deb3a40da Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 20 Jul 2017 13:30:09 -0400 Subject: [PATCH 35/85] Removes a couple blank lines --- src/anchor-click-interceptor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/anchor-click-interceptor.js b/src/anchor-click-interceptor.js index 0ffa470ec2a5..544a6aedcb9b 100644 --- a/src/anchor-click-interceptor.js +++ b/src/anchor-click-interceptor.js @@ -18,7 +18,7 @@ import { closestByTag, } from './dom'; import {dev} from './log'; -import {Services} from './services'; +import {urlReplacementsForDoc} from './services'; /** @private @const {string} */ const ORIG_HREF_ATTRIBUTE = 'data-a4a-orig-href'; @@ -60,7 +60,7 @@ function maybeExpandUrlParams(ampdoc, e) { return e.pageY; }, }; - const newHref = Services.urlReplacementsForDoc(ampdoc).expandSync( + const newHref = urlReplacementsForDoc(ampdoc).expandSync( hrefToExpand, vars, undefined, /* opt_whitelist */ { // For now we only allow to replace the click location vars // and nothing else. From d5b2dae127137a1e00e92a61497212f12f80226e Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 20 Jul 2017 14:18:20 -0400 Subject: [PATCH 36/85] Fixes merge conflict --- src/anchor-click-interceptor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/anchor-click-interceptor.js b/src/anchor-click-interceptor.js index 544a6aedcb9b..0ffa470ec2a5 100644 --- a/src/anchor-click-interceptor.js +++ b/src/anchor-click-interceptor.js @@ -18,7 +18,7 @@ import { closestByTag, } from './dom'; import {dev} from './log'; -import {urlReplacementsForDoc} from './services'; +import {Services} from './services'; /** @private @const {string} */ const ORIG_HREF_ATTRIBUTE = 'data-a4a-orig-href'; @@ -60,7 +60,7 @@ function maybeExpandUrlParams(ampdoc, e) { return e.pageY; }, }; - const newHref = urlReplacementsForDoc(ampdoc).expandSync( + const newHref = Services.urlReplacementsForDoc(ampdoc).expandSync( hrefToExpand, vars, undefined, /* opt_whitelist */ { // For now we only allow to replace the click location vars // and nothing else. From 7b12404b592967b9190f510ac6ccc303254cf18f Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 20 Jul 2017 14:57:35 -0400 Subject: [PATCH 37/85] Mostly changes comments, removes a small amount of redundant unit test code --- extensions/amp-analytics/0.1/iframe-transport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/iframe-transport.js b/extensions/amp-analytics/0.1/iframe-transport.js index 217b85983211..cd81a82dd5f8 100644 --- a/extensions/amp-analytics/0.1/iframe-transport.js +++ b/extensions/amp-analytics/0.1/iframe-transport.js @@ -176,7 +176,7 @@ export class IframeTransport { * @param {!string} event A string describing the trigger event * @VisibleForTesting */ - sendRequest(event) { + sendRequest(event) { const frameData = IframeTransport.getFrameData(this.type_); dev().assert(frameData, 'Trying to send message to non-existent frame'); dev().assert(frameData.queue, 'Event queue is missing for ' + this.id_); From 28c7b7ab688d8feadbbcfdd1333a6b3602121e27 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 21 Jul 2017 11:13:47 -0400 Subject: [PATCH 38/85] Changes enum to const since all but one enum value no longer used. Removes changes to iframe-helper, makes xframe have allow-same-origin. Moves iframe-transport teardown from amp-analytics.unlayoutCallback to detachedCallback --- .../0.1/iframe-transport-message-queue.js | 11 ++++++----- src/3p-analytics-common.js | 6 ++---- src/iframe-helper.js | 9 +-------- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js index e85f273ffacb..4da94913f1c3 100644 --- a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js @@ -15,7 +15,9 @@ */ import {dev} from '../../../src/log'; -import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../../../src/3p-analytics-common'; +import { + AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, +} from '../../../src/3p-analytics-common'; import {SubscriptionApi} from '../../../src/iframe-helper'; /** @private @const {string} */ @@ -50,7 +52,7 @@ export class IframeTransportMessageQueue { this.transportIdToPendingMessages_ = {}; /** @private {string} */ - this.messageType_ = AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT; + this.messageType_ = AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE; /** @private {!../../../src/iframe-helper.SubscriptionApi} */ this.postMessageApi_ = new SubscriptionApi(this.frame_, @@ -58,8 +60,7 @@ export class IframeTransportMessageQueue { true, () => { this.setIsReady(); - }, - true); + }); } /** @@ -114,7 +115,7 @@ export class IframeTransportMessageQueue { */ flushQueue_() { if (this.isReady() && this.queueSize()) { - this.postMessageApi_.send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + this.postMessageApi_.send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({data: this.transportIdToPendingMessages_})); this.transportIdToPendingMessages_ = {}; diff --git a/src/3p-analytics-common.js b/src/3p-analytics-common.js index 4ac79190944c..7a6e482d1d4f 100644 --- a/src/3p-analytics-common.js +++ b/src/3p-analytics-common.js @@ -14,10 +14,8 @@ * limitations under the License. */ -/** @enum {string} */ -export const AMP_ANALYTICS_3P_MESSAGE_TYPE = { - EVENT: 'E', -}; +/** @const {string} */ +export const AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE = 'AA3pEvtMsgs'; /** @typedef {!Object>} */ export let AmpAnalytics3pEventMap; diff --git a/src/iframe-helper.js b/src/iframe-helper.js index 3c26be91b219..2c4f8cbcf51c 100644 --- a/src/iframe-helper.js +++ b/src/iframe-helper.js @@ -416,12 +416,8 @@ export class SubscriptionApi { * @param {boolean} is3p set to true if the iframe is 3p. * @param {function(!JsonObject, !Window, string)} requestCallback Callback * invoked whenever a new window subscribes. - * @param {boolean=} allowAnyOriginIfNull If sending a message but the - * subscribing frame has its origin set to 'null' (because it has 'sandbox - * allow-same-origin') then still send the message to that iframe by - * setting origin to '*' */ - constructor(iframe, type, is3p, requestCallback, allowAnyOriginIfNull) { + constructor(iframe, type, is3p, requestCallback) { /** @private @const {!Element} */ this.iframe_ = iframe; /** @private @const {boolean} */ @@ -433,9 +429,6 @@ export class SubscriptionApi { this.unlisten_ = listenFor(this.iframe_, type, (data, source, origin) => { // This message might be from any window within the iframe, we need // to keep track of which windows want to be sent updates. - if (allowAnyOriginIfNull && origin == 'null') { - origin = '*'; - } if (!this.clientWindows_.some(entry => entry.win == source)) { this.clientWindows_.push({win: source, origin}); } From 409ac7cd82958b909589f8813c63bc21cf9a6b49 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 21 Jul 2017 18:12:53 -0400 Subject: [PATCH 39/85] Renames things using 3P in name, changes wire format from map to array --- .../0.1/iframe-transport-message-queue.js | 54 ++++++++----------- .../amp-analytics/0.1/iframe-transport.js | 2 +- src/3p-analytics-common.js | 28 ++++++---- 3 files changed, 39 insertions(+), 45 deletions(-) diff --git a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js index 4da94913f1c3..c04cac2ec7cc 100644 --- a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js @@ -16,7 +16,7 @@ import {dev} from '../../../src/log'; import { - AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, + IFRAME_TRANSPORT_EVENTS_TYPE, } from '../../../src/3p-analytics-common'; import {SubscriptionApi} from '../../../src/iframe-helper'; @@ -46,13 +46,13 @@ export class IframeTransportMessageQueue { /** @private {boolean} */ this.isReady_ = false; - /** @private - * {!../../../src/3p-analytics-common.AmpAnalytics3pEventMap} + /** + * @private {!Array} */ - this.transportIdToPendingMessages_ = {}; + this.pendingEvents_ = []; /** @private {string} */ - this.messageType_ = AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE; + this.messageType_ = IFRAME_TRANSPORT_EVENTS_TYPE; /** @private {!../../../src/iframe-helper.SubscriptionApi} */ this.postMessageApi_ = new SubscriptionApi(this.frame_, @@ -88,24 +88,25 @@ export class IframeTransportMessageQueue { * @VisibleForTesting */ queueSize() { - return Object.keys(this.transportIdToPendingMessages_).length; + return this.pendingEvents_.length; } /** - * Enqueues an AmpAnalytics3pEvent message to be sent to a cross-domain - * iframe. - * @param {!string} transportId Identifies which creative is sending the - * message - * @param {!string} event The event to be enqueued and then sent to the iframe + * Enqueues an event to be sent to a cross-domain iframe. + * @param {!../../../src/3p-analytics-common.IframeTransportEvent} event + * Identifies the event and which Transport instance (essentially which + * creative) is sending it. */ - enqueue(transportId, event) { - this.transportIdToPendingMessages_[transportId] = - this.messagesFor(transportId) || []; + enqueue(event) { + dev().assert(TAG_, event && event.transportId && event.message, + 'Attempted to enqueue malformed message for: ' + + event.transportId); + this.pendingEvents_.push(event); if (this.queueSize() >= MAX_QUEUE_SIZE_) { - dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + transportId); - this.messagesFor(transportId).shift(); + dev().warn(TAG_, 'Exceeded maximum size of queue for: ' + + event.transportId); + this.pendingEvents_.shift(); } - this.messagesFor(transportId).push(event); this.flushQueue_(); } @@ -115,24 +116,11 @@ export class IframeTransportMessageQueue { */ flushQueue_() { if (this.isReady() && this.queueSize()) { - this.postMessageApi_.send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, + this.postMessageApi_.send(IFRAME_TRANSPORT_EVENTS_TYPE, /** @type {!JsonObject} */ - ({data: this.transportIdToPendingMessages_})); - this.transportIdToPendingMessages_ = {}; + ({events: this.pendingEvents_})); + this.pendingEvents_ = []; } } - - /** - * Test method to see which messages (if any) are associated with a given - * transportId - * @param {!string} transportId Identifies which creative is sending the - * message - * @return {Array} - * @VisibleForTesting - */ - messagesFor(transportId) { - return /** @type {Array} */ ( - this.transportIdToPendingMessages_[transportId]); - } } diff --git a/extensions/amp-analytics/0.1/iframe-transport.js b/extensions/amp-analytics/0.1/iframe-transport.js index cd81a82dd5f8..217b85983211 100644 --- a/extensions/amp-analytics/0.1/iframe-transport.js +++ b/extensions/amp-analytics/0.1/iframe-transport.js @@ -176,7 +176,7 @@ export class IframeTransport { * @param {!string} event A string describing the trigger event * @VisibleForTesting */ - sendRequest(event) { + sendRequest(event) { const frameData = IframeTransport.getFrameData(this.type_); dev().assert(frameData, 'Trying to send message to non-existent frame'); dev().assert(frameData.queue, 'Event queue is missing for ' + this.id_); diff --git a/src/3p-analytics-common.js b/src/3p-analytics-common.js index 7a6e482d1d4f..4170d6c98a34 100644 --- a/src/3p-analytics-common.js +++ b/src/3p-analytics-common.js @@ -14,17 +14,23 @@ * limitations under the License. */ -/** @const {string} */ -export const AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE = 'AA3pEvtMsgs'; +/** + * @const {string} + * This is the type of message that will be sent over the wire. + * The message will contain an array of the typedef declared below. + */ +export const IFRAME_TRANSPORT_EVENTS_TYPE = 'IframeTptEvts'; -/** @typedef {!Object>} */ -export let AmpAnalytics3pEventMap; -// Maps transport IDs to events. For instance if the creative with transport +/** @typedef {Object} */ +export let IframeTransportEvent; +// List of events, and the transport IDs of the amp-analytics tags that +// generated them. For instance if the creative with transport // ID 2 sends "hi" and "hello" and the creative with transport ID 3 sends -// "goodbye" then the map would look like: -// Example: -// { -// "2": ["hi", "hello" ], -// "3": ["goodbye" ] -// } +// "goodbye" then an array of 3 AmpAnalyticsIframeTransportEvent would be +// sent across the wire like so: +// [ +// { transportId: "2", message: "hi" }, // An AmpAnalyticsIframeTransportEvent +// { transportId: "2", message: "hello" }, // Another +// { transportId: "3", message: "goodbye" } // And another +// ] From ee822cf4599e2e97673613bceff8b8d31a1a3985 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 24 Jul 2017 10:42:31 -0400 Subject: [PATCH 40/85] Renamed files/classes to get rid of '3p' --- examples/analytics-3p.amp.html | 26 ----------- .../data/fake_amp_ad_with_3p_analytics.html | 46 ------------------- .../0.1/iframe-transport-message-queue.js | 7 +-- src/3p-analytics-common.js | 36 --------------- 4 files changed, 4 insertions(+), 111 deletions(-) delete mode 100644 examples/analytics-3p.amp.html delete mode 100644 extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html delete mode 100644 src/3p-analytics-common.js diff --git a/examples/analytics-3p.amp.html b/examples/analytics-3p.amp.html deleted file mode 100644 index c0a7298fc028..000000000000 --- a/examples/analytics-3p.amp.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - 3P AMP Analytics Example - - - - - - - - - - Here is some text above the ad.
- -

Loading...
-
Could not display the fake ad :(
-
-
- Here is some text below the ad.
- - diff --git a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html deleted file mode 100644 index 8f9d2bc4f86c..000000000000 --- a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_3p_analytics.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - -
- -

- By Golden Trvs Gol twister (Own work) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons -

-
- - - - - - diff --git a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js index c04cac2ec7cc..e1d4723e7950 100644 --- a/extensions/amp-analytics/0.1/iframe-transport-message-queue.js +++ b/extensions/amp-analytics/0.1/iframe-transport-message-queue.js @@ -17,7 +17,7 @@ import {dev} from '../../../src/log'; import { IFRAME_TRANSPORT_EVENTS_TYPE, -} from '../../../src/3p-analytics-common'; +} from '../../../src/iframe-transport-common'; import {SubscriptionApi} from '../../../src/iframe-helper'; /** @private @const {string} */ @@ -47,7 +47,8 @@ export class IframeTransportMessageQueue { this.isReady_ = false; /** - * @private {!Array} + * @private + * {!Array} */ this.pendingEvents_ = []; @@ -93,7 +94,7 @@ export class IframeTransportMessageQueue { /** * Enqueues an event to be sent to a cross-domain iframe. - * @param {!../../../src/3p-analytics-common.IframeTransportEvent} event + * @param {!../../../src/iframe-transport-common.IframeTransportEvent} event * Identifies the event and which Transport instance (essentially which * creative) is sending it. */ diff --git a/src/3p-analytics-common.js b/src/3p-analytics-common.js deleted file mode 100644 index 4170d6c98a34..000000000000 --- a/src/3p-analytics-common.js +++ /dev/null @@ -1,36 +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. - */ - -/** - * @const {string} - * This is the type of message that will be sent over the wire. - * The message will contain an array of the typedef declared below. - */ -export const IFRAME_TRANSPORT_EVENTS_TYPE = 'IframeTptEvts'; - -/** @typedef {Object} */ -export let IframeTransportEvent; -// List of events, and the transport IDs of the amp-analytics tags that -// generated them. For instance if the creative with transport -// ID 2 sends "hi" and "hello" and the creative with transport ID 3 sends -// "goodbye" then an array of 3 AmpAnalyticsIframeTransportEvent would be -// sent across the wire like so: -// [ -// { transportId: "2", message: "hi" }, // An AmpAnalyticsIframeTransportEvent -// { transportId: "2", message: "hello" }, // Another -// { transportId: "3", message: "goodbye" } // And another -// ] - From f4f5048ba421d077a4dac4b8ebb0db727143227f Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 13 Jul 2017 16:59:45 -0400 Subject: [PATCH 41/85] Adds 3p iframe to amp analytics. Uses Subscription API. Event message queue much simpler than previous PR. Response functionality is included, but extraData is currently commented out - will add again before PR --- extensions/amp-analytics/0.1/amp-analytics.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index c8ffb7c8581e..b767d87c004e 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -17,6 +17,7 @@ import {isJsonScriptTag} from '../../../src/dom'; import {assertHttpsUrl, appendEncodedParamStringToUrl} from '../../../src/url'; import {dev, rethrowAsync, user} from '../../../src/log'; +import {getMode} from '../../../src/mode'; import {expandTemplate} from '../../../src/string'; import {isArray, isObject} from '../../../src/types'; import {dict, hasOwn, map} from '../../../src/utils/object'; @@ -413,6 +414,22 @@ export class AmpAnalytics extends AMP.BaseElement { } const typeConfig = this.predefinedConfig_[type] || {}; + // transport.iframe is only allowed to be specified in typeConfig, not + // the others. Allowed when running locally for testing purposes. + [defaultConfig, inlineConfig, this.remoteConfig_].forEach(config => { + if (config && config.transport && config.transport.iframe) { + const TAG = this.getName_(); + if (getMode().localDev) { + user().warn(TAG, 'Only typeConfig may specify iframe transport,' + + ' but in local dev mode, so okay', config); + } else { + user().error(TAG, 'Only typeConfig may specify iframe transport', + config); + return; + } + } + }); + this.mergeObjects_(defaultConfig, config); this.mergeObjects_(typeConfig, config, /* predefined */ true); if (typeConfig) { From 02031222fdb83ba130e1af56953db846f5b19337 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 15:42:52 -0400 Subject: [PATCH 42/85] Implements review feedback --- extensions/amp-analytics/0.1/amp-analytics.js | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index b767d87c004e..d3308f229e56 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -17,7 +17,6 @@ import {isJsonScriptTag} from '../../../src/dom'; import {assertHttpsUrl, appendEncodedParamStringToUrl} from '../../../src/url'; import {dev, rethrowAsync, user} from '../../../src/log'; -import {getMode} from '../../../src/mode'; import {expandTemplate} from '../../../src/string'; import {isArray, isObject} from '../../../src/types'; import {dict, hasOwn, map} from '../../../src/utils/object'; From bff17d963e205bb538367448f58929ca526e399e Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 21:36:41 -0400 Subject: [PATCH 43/85] Removes extraData, new creative message --- build-system/app.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build-system/app.js b/build-system/app.js index f5e063fde54b..ca11b8adb2c1 100644 --- a/build-system/app.js +++ b/build-system/app.js @@ -936,6 +936,11 @@ app.get(['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], next(); }); +app.get('/dist/ampanalytics-lib.js', (req, res, next) => { + req.url = req.url.replace(/dist/, 'dist.3p/current'); + next(); + }); + app.get('/dist/ampanalytics-lib.js', (req, res, next) => { req.url = req.url.replace(/dist/, 'dist.3p/current'); next(); From 0d649164cf0a51b9f83736e5ed7cee7922888fae Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 13 Jul 2017 21:41:06 -0400 Subject: [PATCH 44/85] Amp 3p analytics using SubscriptionAPI, 3p side --- 3p/ampanalytics-lib.js | 310 ++++++++++++++++++++++++ build-system/config.js | 1 + build-system/tasks/presubmit-checks.js | 1 + examples/analytics-3p-remote-frame.html | 40 +++ gulpfile.js | 11 + 5 files changed, 363 insertions(+) create mode 100644 3p/ampanalytics-lib.js create mode 100644 examples/analytics-3p-remote-frame.html diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js new file mode 100644 index 000000000000..272bfde433b2 --- /dev/null +++ b/3p/ampanalytics-lib.js @@ -0,0 +1,310 @@ +/** + * 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. + */ + +import './polyfills'; +import {tryParseJson} from '../src/json'; +import {dev, initLogConstructor, setReportError} from '../src/log'; +import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../src/3p-analytics-common'; + +initLogConstructor(); +// TODO(alanorozco): Refactor src/error.reportError so it does not contain big +// transitive dependencies and can be included here. +setReportError(() => {}); + +/** @private @const {string} */ +const TAG_ = 'ampanalytics-lib'; + +/** + * Receives messages bound for this cross-domain iframe, from all creatives + */ +export class AmpAnalytics3pMessageRouter { + + /** @param {!Window} win */ + constructor(win) { + /** @private {!Window} */ + this.win_ = win; + + /** @const {string} */ + this.sentinel_ = dev().assertString( + tryParseJson(this.win_.name).sentinel, + 'Invalid/missing sentinel on iframe name attribute' + this.win_.name); + if (!this.sentinel_) { + return; + } + + /** + * Multiple creatives on a page may wish to use the same type of + * amp-analytics tag. This object provides a mapping between the + * IDs which identify which amp-analytics tag a message is to/from, + * with each ID's corresponding AmpAnalytics3pCreativeMessageRouter, + * which is an object that handles messages to/from a particular creative. + * @private {!Object} + */ + this.creativeMessageRouters_ = {}; + + window.addEventListener('message', event => { + const messageContainer = this.extractMessage(event); + dev().assert(messageContainer.type, + 'Received message with missing type in ' + this.win_.location.href); + dev().assert(messageContainer.data, + 'Received empty message in ' + this.win_.location.href); + let message; + dev().assert((message = Object.entries(messageContainer.data)).length, + 'Received empty events message in ' + this.win_.location.href); + switch (messageContainer.type) { + case AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE: + this.processNewCreativesMessage(message); + break; + case AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT: + this.processEventsMessage(message); + break; + default: + dev().assert(false, + 'Received unrecognized message type ' + messageContainer.type + + ' in ' + this.win_.location.href); + } + }, false); + + this.subscribeTo(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE); + this.subscribeTo(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT); + } + + /** + * Sends a message to the parent frame, requesting to subscribe to a + * particular message type + * @param messageType The type of message to subscribe to + */ + subscribeTo(messageType) { + window.parent./*OK*/postMessage({ + sentinel: this.sentinel_, + type: this.sentinel_ + messageType, + }, '*'); + } + + /** + * Handle receipt of a message indicating that there are new creative(s) + * that wish to use this frame + * @param {!JsonObject} message + */ + processNewCreativesMessage(message) { + dev().assert(window.onNewAmpAnalyticsInstance, + 'Must implement onNewAmpAnalyticsInstance in ' + + this.win_.location.href); + message.forEach(entry => { + const creativeId = entry[0]; + const extraData = entry[1]; + dev().assert(!this.creativeMessageRouters_[creativeId], + 'Duplicate new creative message for ' + creativeId); + this.creativeMessageRouters_[creativeId] = + new AmpAnalytics3pCreativeMessageRouter( + this.win_, this.sentinel_, creativeId, extraData); + try { + window.onNewAmpAnalyticsInstance( + this.creativeMessageRouters_[creativeId]); + } catch (e) { + dev().error(TAG_, 'Exception thrown by' + + ' onNewAmpAnalyticsInstance() in ' + this.win_.location.href + + ': ' + e.message); + } + }); + } + + /** + * Handle receipt of a message indicating that creative(s) have sent + * event(s) to this frame + * @param {!JsonObject} message + */ + processEventsMessage(message) { + message.forEach(entry => { + const creativeId = entry[0]; + const events = entry[1]; + try { + dev().assert(events && events.length, + 'Received empty events list for ' + creativeId); + dev().assert(this.creativeMessageRouters_[creativeId], + 'Discarding event message received prior to new creative' + + ' message for ' + creativeId); + this.creativeMessageRouters_[creativeId] + .sendMessagesToListener(events); + } catch (e) { + dev().error(TAG_, 'Failed to pass message to event listener: ' + + e.message); + } + }); + } + + /** + * Test method to ensure sentinel set correctly + * @returns {string} + * @VisibleForTesting + */ + getSentinel() { + return this.sentinel_; + } + + /** + * Gets the mapping of creative senderId to + * AmpAnalytics3pCreativeMessageRouter + * @returns {!Object.} + * @VisibleForTesting + */ + getCreativeMethodRouters() { + return this.creativeMessageRouters_; + } + + /** + * Gets rid of the mapping to AmpAnalytics3pMessageRouter + * @VisibleForTesting + */ + reset() { + this.creativeMessageRouters_ = {}; + } + + /** + * Takes the raw postMessage event, and extracts from it the actual data + * payload + * @param event + * @returns {!JsonObject} + */ + extractMessage(event) { + dev().assert(event && event.data, 'Received empty events message in ' + + this.win_.name); + let startIndex; + dev().assert((startIndex = event.data.indexOf('-') + 1) > -0, + 'Received truncated events message in ' + this.win_.name); + return tryParseJson(event.data.substr(startIndex)); + } +} + +if (!window.AMP_TEST) { + try { + new AmpAnalytics3pMessageRouter(window); + } catch (e) { + dev().error(TAG_, 'Failed to construct AmpAnalytics3pMessageRouter: ' + + e.message); + } +} + +/** + * Receives messages bound for this cross-domain iframe, from a particular + * creative. Also sends response messages from the iframe meant for this + * particular creative. + */ +export class AmpAnalytics3pCreativeMessageRouter { + /** + * @param {!Window} win The enclosing window object + * @param {!string} sentinel The communication sentinel of this iframe + * @param {!string} creativeId The ID of the creative to route messages + * to/from + * @param {string=} opt_extraData Extra data to be passed to the frame + */ + constructor(win, sentinel, creativeId, opt_extraData) { + /** @private {!Window} */ + this.win_ = win; + + /** @private {!string} */ + this.sentinel_ = sentinel; + + /** @private {!string} */ + this.creativeId_ = creativeId; + + /** @private {?string} */ + this.extraData_ = opt_extraData; + + /** @private {?function(!Array)} */ + this.eventListener_ = null; + } + + /** + * Registers a callback function to be called when AMP Analytics events occur. + * There may only be one listener. If another function has previously been + * registered as a listener, it will no longer receive events. + * @param {!function(!Array)} listener A function + * that takes an array of event strings, and does something with them. + */ + registerAmpAnalytics3pEventsListener(listener) { + if (this.eventListener_) { + dev().warn(TAG_, 'Replacing existing eventListener for ' + + this.creativeId_); + } + this.eventListener_ = listener; + } + + /** + * Receives message(s) from a creative for the cross-domain iframe + * and passes them to that iframe's listener, if a listener has been + * registered + * @param {!Array} messages The message that was received + */ + sendMessagesToListener(messages) { + if (!messages.length) { + dev().warn(TAG_, 'Attempted to send zero messages in ' + + this.creativeId_ + '. Ignoring.'); + return; + } + if (!this.eventListener_) { + dev().warn(TAG_, 'Attempted to send messages when no listener' + + ' configured in ' + this.creativeId_ + '. Be sure to' + + ' first call registerAmpAnalytics3pEventsListener()'); + } + try { + this.eventListener_(messages); + } catch (e) { + dev().error(TAG_, 'Caught exception executing listener for ' + + this.creativeId_ + ': ' + e.message); + } + } + + /** + * Gets any optional extra data that should be made available to the + * cross-domain frame, in the context of a particular creative. + * @returns {?string} + */ + getExtraData() { + return this.extraData_; + } + + /** + * Sends a message from the third-party vendor's metrics-collection page back + * to the creative. + * @param {!Object} response The response to send. + */ + sendMessageToCreative(response) { + const responseMessage = { + sentinel: this.sentinel_, + type: this.sentinel_ + AMP_ANALYTICS_3P_MESSAGE_TYPE.RESPONSE, + data: response, + }; + window.parent./*OK*/postMessage(responseMessage, '*'); + } + + /** + * @returns {!string} + * @VisibleForTesting + */ + getCreativeId() { + return this.creativeId_; + } + + /** + * @returns {?string} + * @VisibleForTesting + */ + getExtraData() { + return this.extraData_; + } +} + diff --git a/build-system/config.js b/build-system/config.js index 56798217ae13..f22fd9e76824 100644 --- a/build-system/config.js +++ b/build-system/config.js @@ -96,6 +96,7 @@ module.exports = { '!{node_modules,build,dist,dist.tools,' + 'dist.3p/[0-9]*,dist.3p/current-min}/**/*.*', '!dist.3p/current/**/ampcontext-lib.js', + '!dist.3p/current/**/ampanalytics-lib.js', '!validator/dist/**/*.*', '!validator/node_modules/**/*.*', '!validator/nodejs/node_modules/**/*.*', diff --git a/build-system/tasks/presubmit-checks.js b/build-system/tasks/presubmit-checks.js index 21f61cdec30f..0f17df259d7d 100644 --- a/build-system/tasks/presubmit-checks.js +++ b/build-system/tasks/presubmit-checks.js @@ -282,6 +282,7 @@ var forbiddenTerms = { whitelist: [ '3p/integration.js', '3p/ampcontext-lib.js', + '3p/ampanalytics-lib.js', 'ads/alp/install-alp.js', 'ads/inabox/inabox-host.js', 'dist.3p/current/integration.js', diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-3p-remote-frame.html new file mode 100644 index 000000000000..fef1e73baf76 --- /dev/null +++ b/examples/analytics-3p-remote-frame.html @@ -0,0 +1,40 @@ + + + + + Requests Frame + + + + diff --git a/gulpfile.js b/gulpfile.js index 7924c8624c1d..acd831ed7130 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -265,6 +265,17 @@ function compile(watch, shouldMinify, opt_preventRemoveAndMakeDir, include3pDirectories: true, includePolyfills: false, }), + compileJs('./3p/', 'ampanalytics-lib.js', + './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), { + minifiedName: 'ampanalytics-v0.js', + checkTypes: opt_checkTypes, + watch: watch, + minify: shouldMinify, + preventRemoveAndMakeDir: opt_preventRemoveAndMakeDir, + externs: ['ads/ads.extern.js',], + include3pDirectories: true, + includePolyfills: false, + }), // For compilation with babel we start with the amp-babel entry point, // but then rename to the amp.js which we've been using all along. compileJs('./src/', 'amp-babel.js', './dist', { From 0e54a38cc6c5ff91972242a57275ce13efac004c Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 11:53:25 -0400 Subject: [PATCH 45/85] Fixes ampanalytics-lib tests --- 3p/ampanalytics-lib.js | 36 ++-- examples/analytics-3p-remote-frame.html | 2 +- .../0.1/test/test-ampanalytics-lib.js | 192 ++++++++++++++++++ 3 files changed, 217 insertions(+), 13 deletions(-) create mode 100644 extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index 272bfde433b2..27955a6ef1aa 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -57,24 +57,28 @@ export class AmpAnalytics3pMessageRouter { window.addEventListener('message', event => { const messageContainer = this.extractMessage(event); + if (this.sentinel_ != messageContainer.sentinel) { + return; + } dev().assert(messageContainer.type, 'Received message with missing type in ' + this.win_.location.href); dev().assert(messageContainer.data, 'Received empty message in ' + this.win_.location.href); - let message; - dev().assert((message = Object.entries(messageContainer.data)).length, - 'Received empty events message in ' + this.win_.location.href); switch (messageContainer.type) { case AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE: - this.processNewCreativesMessage(message); + this.processNewCreativesMessage( + /* @type {!../src/3p-analytics-common.AmpAnalytics3pNewCreative}*/ + (messageContainer.data)); break; case AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT: - this.processEventsMessage(message); + this.processEventsMessage( + /* @type {!../src/3p-analytics-common.AmpAnalytics3pEvent} */ + (messageContainer.data)); break; default: dev().assert(false, 'Received unrecognized message type ' + messageContainer.type + - ' in ' + this.win_.location.href); + ' in ' + this.win_.location.href); } }, false); @@ -97,13 +101,16 @@ export class AmpAnalytics3pMessageRouter { /** * Handle receipt of a message indicating that there are new creative(s) * that wish to use this frame - * @param {!JsonObject} message + * @param {!../src/3p-analytics-common.AmpAnalytics3pNewCreative} message */ processNewCreativesMessage(message) { + let entries; + dev().assert((entries = Object.entries(message)).length, + 'Received empty events message in ' + this.win_.location.href); dev().assert(window.onNewAmpAnalyticsInstance, 'Must implement onNewAmpAnalyticsInstance in ' + this.win_.location.href); - message.forEach(entry => { + entries.forEach(entry => { const creativeId = entry[0]; const extraData = entry[1]; dev().assert(!this.creativeMessageRouters_[creativeId], @@ -125,10 +132,13 @@ export class AmpAnalytics3pMessageRouter { /** * Handle receipt of a message indicating that creative(s) have sent * event(s) to this frame - * @param {!JsonObject} message + * @param {!../src/3p-analytics-common.AmpAnalytics3pEvent} message */ processEventsMessage(message) { - message.forEach(entry => { + let entries; + dev().assert((entries = Object.entries(message)).length, + 'Received empty events message in ' + this.win_.location.href); + entries.forEach(entry => { const creativeId = entry[0]; const events = entry[1]; try { @@ -280,7 +290,8 @@ export class AmpAnalytics3pCreativeMessageRouter { /** * Sends a message from the third-party vendor's metrics-collection page back * to the creative. - * @param {!Object} response The response to send. + * @param {!../src/3p-analytics-common.AmpAnalytics3pResponse} response The + * response to send. */ sendMessageToCreative(response) { const responseMessage = { @@ -288,7 +299,8 @@ export class AmpAnalytics3pCreativeMessageRouter { type: this.sentinel_ + AMP_ANALYTICS_3P_MESSAGE_TYPE.RESPONSE, data: response, }; - window.parent./*OK*/postMessage(responseMessage, '*'); + window.parent./*OK*/postMessage( + /** @type {!JsonObject} */ (responseMessage), '*'); } /** diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-3p-remote-frame.html index fef1e73baf76..1f75a1f20394 100644 --- a/examples/analytics-3p-remote-frame.html +++ b/examples/analytics-3p-remote-frame.html @@ -22,7 +22,7 @@ ", and my extra data is: " + ampAnalytics.getExtraData()); ampAnalytics.sendMessageToCreative( - {'status': 'received', 'somethingElse': '42'}); + {'status': 'received', 'other': '42'}); }); }); }; diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js new file mode 100644 index 000000000000..4c247c140cb1 --- /dev/null +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -0,0 +1,192 @@ +/** + * 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. + */ + +import { + AMP_ANALYTICS_3P_MESSAGE_TYPE, +} from '../../../../src/3p-analytics-common'; +import { + AmpAnalytics3pMessageRouter, + AmpAnalytics3pCreativeMessageRouter, +} from '../../../../3p/ampanalytics-lib'; +import {dev} from '../../../../src/log'; +import {Timer} from '../../../../src/service/timer-impl'; +import {adopt} from '../../../../src/runtime'; +import * as sinon from 'sinon'; + +adopt(window); + +/** + * @const {number} + * Testing postMessage necessarily involves race conditions. Set this high + * enough to avoid flakiness. + */ +const POST_MESSAGE_DELAY = 100; + +let nextId = 5000; +function createUniqueId() { + return String(++(nextId)); +} + +describe('ampanalytics-lib', () => { + let sandbox; + const timer = new Timer(window); + let badAssertsCounterStub; + let router; + let sentinel; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + badAssertsCounterStub = sandbox.stub(); + sentinel = createUniqueId(); + window.name = '{"sentinel": "' + sentinel + '"}'; + sandbox.stub(AmpAnalytics3pMessageRouter.prototype, 'subscribeTo'); + router = new AmpAnalytics3pMessageRouter(window); + sandbox.stub(dev(), 'assert', (condition, msg) => { + if (!condition) { + badAssertsCounterStub(msg); + } + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + /** + * Sends a message from the current window to itself + * @param {string} type Type of the message. + * @param {!JsonObject} object Message payload. + */ + function send(type, data) { + let object = {}; + object['type'] = type; + object['sentinel'] = sentinel; + object['data'] = data; + payload = 'amp-' + JSON.stringify(object); + console.log('Sending: ' + payload) + window./*OK*/postMessage(payload, '*'); + } + + it('fails to create router if no window.name ', () => { + const oldWindowName = window.name; + expect(() => { + window.name = ''; + new AmpAnalytics3pMessageRouter(window); + }).to.throw(/Cannot read property 'sentinel' of undefined/); + window.name = oldWindowName; + }); + + it('sets sentinel from window.name.sentinel ', () => { + expect(router.getSentinel()).to.equal(sentinel); + }); + + it('initially has empty creativeMessageRouters mapping ', () => { + expect(Object.keys(router.getCreativeMethodRouters()).length).to.equal(0); + }); + + it('makes registration function available ', () => { + window.onNewAmpAnalyticsInstance = ampAnalytics => { + expect(ampAnalytics.registerAmpAnalytics3pEventsListener).to.exist; + ampAnalytics.registerAmpAnalytics3pEventsListener(() => {}); + }; + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, + /** @type {!JsonObject} */ ({'100': 'this is extra data'})); + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + /** @type {!JsonObject} */ ({'100': ['hello, world!']})); + }); + + it('receives an event message ', () => { + window.onNewAmpAnalyticsInstance = ampAnalytics => { + expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter).to.be.true; + expect(Object.keys(router.getCreativeMethodRouters()).length).to.equal(1); + ampAnalytics.registerAmpAnalytics3pEventsListener(events => { + expect(events.length).to.equal(1); + events.forEach(event => { + expect(ampAnalytics.getCreativeId()).to.equal('101'); + expect(event).to.equal('hello, world!'); + }); + }); + }; + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, + /** @type {!JsonObject} */ ({'101': 'this is extra data'})); + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + /** @type {!JsonObject} */ ({'101': ['hello, world!']})); + }); + + it('does not allow duplicate extraData ', () => { + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, + /** @type {!JsonObject} */ ({'102': 'this is extra data'})); + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, + /** @type {!JsonObject} */ ({'102': 'this is more extra data'})); + return timer.promise(POST_MESSAGE_DELAY).then(() => { + expect(badAssertsCounterStub.calledOnce).to.be.true; + expect(badAssertsCounterStub.alwaysCalledWith( + 'Duplicate new creative message for 102')).to.be.true; + }); + }); + + it('asserts when onNewAmpAnalyticsInstance is not implemented ', () => { + window.onNewAmpAnalyticsInstance = null; + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, + /** @type {!JsonObject} */ ({'103': 'this is extra data'})); + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + /** @type {!JsonObject} */ ({'103': ['hello, world!']})); + return timer.promise(POST_MESSAGE_DELAY).then(() => { + expect(badAssertsCounterStub.calledOnce).to.be.true; + expect(badAssertsCounterStub.alwaysCalledWith( + sinon.match(/Must implement onNewAmpAnalyticsInstance/))).to.be.true; + return Promise.resolve(); + }); + }); + + it('rejects an event message from an unknown creative ', () => { + window.onNewAmpAnalyticsInstance = null; + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + /** @type {!JsonObject} */ ({'104': ['hello, world!']})); + return timer.promise(POST_MESSAGE_DELAY).then(() => { + expect(badAssertsCounterStub.calledOnce).to.be.true; + const re = 'Discarding event message received prior to new creative' + + ' message for 104'; + expect(badAssertsCounterStub.alwaysCalledWith( + sinon.match(new RegExp(re)))).to.be.true; + return Promise.resolve(); + }); + }); + + it('receives multiple event messages ', () => { + window.onNewAmpAnalyticsInstance = ampAnalytics => { + expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter).to.be.true; + expect(Object.keys(router.getCreativeMethodRouters()).length).to.equal(1); + ampAnalytics.registerAmpAnalytics3pEventsListener(events => { + expect(events.length).to.equal(3); + events.forEach(event => { + expect(ampAnalytics.getCreativeId()).to.equal('105'); + }); + expect(events[0]).to.equal('something happened'); + expect(events[1]).to.equal('something else happened'); + expect(events[2]).to.equal('a third thing happened'); + }); + }; + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, + /** @type {!JsonObject} */ ({'105': 'this is extra data'})); + send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + /** @type {!JsonObject} */ ({'105': [ + 'something happened', + 'something else happened', + 'a third thing happened', + ]})); + }); +}); From 3d7dbd62766495a154713a18d1a8a2dd09c4f9a0 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 12:48:02 -0400 Subject: [PATCH 46/85] Fixes linter issues --- examples/analytics-3p-remote-frame.html | 3 +-- .../0.1/test/test-ampanalytics-lib.js | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-3p-remote-frame.html index 1f75a1f20394..adf295ee0f6a 100644 --- a/examples/analytics-3p-remote-frame.html +++ b/examples/analytics-3p-remote-frame.html @@ -29,8 +29,7 @@ // Load the script specified in the iframe’s name attribute: script = document.createElement('script'); - script.src = (document.location.hostname == 'iframe.localhost' - || document.location.hostname == 'iframe.localhost' ? '/' : + script.src = (document.location.hostname == 'localhost' ? '/' : 'https://3p.ampproject.net/') + JSON.parse(window.name).scriptSrc; document.head.appendChild(script); // The script will be loaded, and will call onNewAmpAnalyticsInstance() diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index 4c247c140cb1..cf5a5b9c10a2 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -71,12 +71,11 @@ describe('ampanalytics-lib', () => { * @param {!JsonObject} object Message payload. */ function send(type, data) { - let object = {}; + const object = {}; object['type'] = type; object['sentinel'] = sentinel; object['data'] = data; - payload = 'amp-' + JSON.stringify(object); - console.log('Sending: ' + payload) + const payload = 'amp-' + JSON.stringify(object); window./*OK*/postMessage(payload, '*'); } @@ -110,7 +109,8 @@ describe('ampanalytics-lib', () => { it('receives an event message ', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { - expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter).to.be.true; + expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter) + .to.be.true; expect(Object.keys(router.getCreativeMethodRouters()).length).to.equal(1); ampAnalytics.registerAmpAnalytics3pEventsListener(events => { expect(events.length).to.equal(1); @@ -134,7 +134,7 @@ describe('ampanalytics-lib', () => { return timer.promise(POST_MESSAGE_DELAY).then(() => { expect(badAssertsCounterStub.calledOnce).to.be.true; expect(badAssertsCounterStub.alwaysCalledWith( - 'Duplicate new creative message for 102')).to.be.true; + 'Duplicate new creative message for 102')).to.be.true; }); }); @@ -147,7 +147,7 @@ describe('ampanalytics-lib', () => { return timer.promise(POST_MESSAGE_DELAY).then(() => { expect(badAssertsCounterStub.calledOnce).to.be.true; expect(badAssertsCounterStub.alwaysCalledWith( - sinon.match(/Must implement onNewAmpAnalyticsInstance/))).to.be.true; + sinon.match(/Must implement onNewAmpAnalyticsInstance/))).to.be.true; return Promise.resolve(); }); }); @@ -161,18 +161,19 @@ describe('ampanalytics-lib', () => { const re = 'Discarding event message received prior to new creative' + ' message for 104'; expect(badAssertsCounterStub.alwaysCalledWith( - sinon.match(new RegExp(re)))).to.be.true; + sinon.match(new RegExp(re)))).to.be.true; return Promise.resolve(); }); }); it('receives multiple event messages ', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { - expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter).to.be.true; + expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter) + .to.be.true; expect(Object.keys(router.getCreativeMethodRouters()).length).to.equal(1); ampAnalytics.registerAmpAnalytics3pEventsListener(events => { expect(events.length).to.equal(3); - events.forEach(event => { + events.forEach(() => { expect(ampAnalytics.getCreativeId()).to.equal('105'); }); expect(events[0]).to.equal('something happened'); From 4889860c87ca2b32abe6670c55c50b0d38bb1c44 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 21:35:26 -0400 Subject: [PATCH 47/85] Removes extraData, new creative message --- 3p/ampanalytics-lib.js | 97 +++++-------------- examples/analytics-3p-remote-frame.html | 7 +- .../0.1/test/test-ampanalytics-lib.js | 34 ------- 3 files changed, 24 insertions(+), 114 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index 27955a6ef1aa..20fbe174b82b 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -64,25 +64,14 @@ export class AmpAnalytics3pMessageRouter { 'Received message with missing type in ' + this.win_.location.href); dev().assert(messageContainer.data, 'Received empty message in ' + this.win_.location.href); - switch (messageContainer.type) { - case AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE: - this.processNewCreativesMessage( - /* @type {!../src/3p-analytics-common.AmpAnalytics3pNewCreative}*/ - (messageContainer.data)); - break; - case AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT: - this.processEventsMessage( - /* @type {!../src/3p-analytics-common.AmpAnalytics3pEvent} */ - (messageContainer.data)); - break; - default: - dev().assert(false, - 'Received unrecognized message type ' + messageContainer.type + - ' in ' + this.win_.location.href); - } + dev().assert(messageContainer.type == AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + 'Received unrecognized message type ' + messageContainer.type + + ' in ' + this.win_.location.href); + this.processEventsMessage( + /* @type {!../src/3p-analytics-common.AmpAnalytics3pEvent} */ + (messageContainer.data)); }, false); - this.subscribeTo(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE); this.subscribeTo(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT); } @@ -94,41 +83,10 @@ export class AmpAnalytics3pMessageRouter { subscribeTo(messageType) { window.parent./*OK*/postMessage({ sentinel: this.sentinel_, - type: this.sentinel_ + messageType, + type: messageType, }, '*'); } - /** - * Handle receipt of a message indicating that there are new creative(s) - * that wish to use this frame - * @param {!../src/3p-analytics-common.AmpAnalytics3pNewCreative} message - */ - processNewCreativesMessage(message) { - let entries; - dev().assert((entries = Object.entries(message)).length, - 'Received empty events message in ' + this.win_.location.href); - dev().assert(window.onNewAmpAnalyticsInstance, - 'Must implement onNewAmpAnalyticsInstance in ' + - this.win_.location.href); - entries.forEach(entry => { - const creativeId = entry[0]; - const extraData = entry[1]; - dev().assert(!this.creativeMessageRouters_[creativeId], - 'Duplicate new creative message for ' + creativeId); - this.creativeMessageRouters_[creativeId] = - new AmpAnalytics3pCreativeMessageRouter( - this.win_, this.sentinel_, creativeId, extraData); - try { - window.onNewAmpAnalyticsInstance( - this.creativeMessageRouters_[creativeId]); - } catch (e) { - dev().error(TAG_, 'Exception thrown by' + - ' onNewAmpAnalyticsInstance() in ' + this.win_.location.href + - ': ' + e.message); - } - }); - } - /** * Handle receipt of a message indicating that creative(s) have sent * event(s) to this frame @@ -144,9 +102,19 @@ export class AmpAnalytics3pMessageRouter { try { dev().assert(events && events.length, 'Received empty events list for ' + creativeId); - dev().assert(this.creativeMessageRouters_[creativeId], - 'Discarding event message received prior to new creative' + - ' message for ' + creativeId); + if (!this.creativeMessageRouters_[creativeId]) { + this.creativeMessageRouters_[creativeId] = + new AmpAnalytics3pCreativeMessageRouter( + this.win_, this.sentinel_, creativeId); + try { + window.onNewAmpAnalyticsInstance( + this.creativeMessageRouters_[creativeId]); + } catch (e) { + dev().error(TAG_, 'Exception thrown by' + + ' onNewAmpAnalyticsInstance() in ' + this.win_.location.href + + ': ' + e.message); + } + } this.creativeMessageRouters_[creativeId] .sendMessagesToListener(events); } catch (e) { @@ -219,9 +187,8 @@ export class AmpAnalytics3pCreativeMessageRouter { * @param {!string} sentinel The communication sentinel of this iframe * @param {!string} creativeId The ID of the creative to route messages * to/from - * @param {string=} opt_extraData Extra data to be passed to the frame */ - constructor(win, sentinel, creativeId, opt_extraData) { + constructor(win, sentinel, creativeId) { /** @private {!Window} */ this.win_ = win; @@ -231,9 +198,6 @@ export class AmpAnalytics3pCreativeMessageRouter { /** @private {!string} */ this.creativeId_ = creativeId; - /** @private {?string} */ - this.extraData_ = opt_extraData; - /** @private {?function(!Array)} */ this.eventListener_ = null; } @@ -278,15 +242,6 @@ export class AmpAnalytics3pCreativeMessageRouter { } } - /** - * Gets any optional extra data that should be made available to the - * cross-domain frame, in the context of a particular creative. - * @returns {?string} - */ - getExtraData() { - return this.extraData_; - } - /** * Sends a message from the third-party vendor's metrics-collection page back * to the creative. @@ -296,7 +251,7 @@ export class AmpAnalytics3pCreativeMessageRouter { sendMessageToCreative(response) { const responseMessage = { sentinel: this.sentinel_, - type: this.sentinel_ + AMP_ANALYTICS_3P_MESSAGE_TYPE.RESPONSE, + type: AMP_ANALYTICS_3P_MESSAGE_TYPE.RESPONSE, data: response, }; window.parent./*OK*/postMessage( @@ -310,13 +265,5 @@ export class AmpAnalytics3pCreativeMessageRouter { getCreativeId() { return this.creativeId_; } - - /** - * @returns {?string} - * @VisibleForTesting - */ - getExtraData() { - return this.extraData_; - } } diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-3p-remote-frame.html index adf295ee0f6a..acd7497beef8 100644 --- a/examples/analytics-3p-remote-frame.html +++ b/examples/analytics-3p-remote-frame.html @@ -18,9 +18,7 @@ ampAnalytics.registerAmpAnalytics3pEventsListener(events => { events.forEach(event => { // Now, do something meaningful with the AMP Analytics event - console.log("Received an event: " + event + - ", and my extra data is: " + - ampAnalytics.getExtraData()); + console.log("Received an event: " + event); ampAnalytics.sendMessageToCreative( {'status': 'received', 'other': '42'}); }); @@ -29,8 +27,7 @@ // Load the script specified in the iframe’s name attribute: script = document.createElement('script'); - script.src = (document.location.hostname == 'localhost' ? '/' : - 'https://3p.ampproject.net/') + JSON.parse(window.name).scriptSrc; + script.src = JSON.parse(window.name).scriptSrc; document.head.appendChild(script); // The script will be loaded, and will call onNewAmpAnalyticsInstance() diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index cf5a5b9c10a2..d7e8bdf55f7e 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -101,8 +101,6 @@ describe('ampanalytics-lib', () => { expect(ampAnalytics.registerAmpAnalytics3pEventsListener).to.exist; ampAnalytics.registerAmpAnalytics3pEventsListener(() => {}); }; - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, - /** @type {!JsonObject} */ ({'100': 'this is extra data'})); send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, /** @type {!JsonObject} */ ({'100': ['hello, world!']})); }); @@ -120,28 +118,12 @@ describe('ampanalytics-lib', () => { }); }); }; - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, - /** @type {!JsonObject} */ ({'101': 'this is extra data'})); send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, /** @type {!JsonObject} */ ({'101': ['hello, world!']})); }); - it('does not allow duplicate extraData ', () => { - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, - /** @type {!JsonObject} */ ({'102': 'this is extra data'})); - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, - /** @type {!JsonObject} */ ({'102': 'this is more extra data'})); - return timer.promise(POST_MESSAGE_DELAY).then(() => { - expect(badAssertsCounterStub.calledOnce).to.be.true; - expect(badAssertsCounterStub.alwaysCalledWith( - 'Duplicate new creative message for 102')).to.be.true; - }); - }); - it('asserts when onNewAmpAnalyticsInstance is not implemented ', () => { window.onNewAmpAnalyticsInstance = null; - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, - /** @type {!JsonObject} */ ({'103': 'this is extra data'})); send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, /** @type {!JsonObject} */ ({'103': ['hello, world!']})); return timer.promise(POST_MESSAGE_DELAY).then(() => { @@ -152,20 +134,6 @@ describe('ampanalytics-lib', () => { }); }); - it('rejects an event message from an unknown creative ', () => { - window.onNewAmpAnalyticsInstance = null; - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, - /** @type {!JsonObject} */ ({'104': ['hello, world!']})); - return timer.promise(POST_MESSAGE_DELAY).then(() => { - expect(badAssertsCounterStub.calledOnce).to.be.true; - const re = 'Discarding event message received prior to new creative' + - ' message for 104'; - expect(badAssertsCounterStub.alwaysCalledWith( - sinon.match(new RegExp(re)))).to.be.true; - return Promise.resolve(); - }); - }); - it('receives multiple event messages ', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter) @@ -181,8 +149,6 @@ describe('ampanalytics-lib', () => { expect(events[2]).to.equal('a third thing happened'); }); }; - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, - /** @type {!JsonObject} */ ({'105': 'this is extra data'})); send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, /** @type {!JsonObject} */ ({'105': [ 'something happened', From 21d272f13f84f482feb5d61d2f355316accee826 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 23:11:12 -0400 Subject: [PATCH 48/85] Stash --- 3p/ampanalytics-lib.js | 3 +++ extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index 20fbe174b82b..a73341ed489b 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -96,6 +96,9 @@ export class AmpAnalytics3pMessageRouter { let entries; dev().assert((entries = Object.entries(message)).length, 'Received empty events message in ' + this.win_.location.href); + dev().assert(window.onNewAmpAnalyticsInstance, + 'Must implement onNewAmpAnalyticsInstance in ' + + this.win_.location.href); entries.forEach(entry => { const creativeId = entry[0]; const events = entry[1]; diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index d7e8bdf55f7e..bacab98ed445 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -56,6 +56,7 @@ describe('ampanalytics-lib', () => { router = new AmpAnalytics3pMessageRouter(window); sandbox.stub(dev(), 'assert', (condition, msg) => { if (!condition) { + console.log('Stubbed failed assert: ' + msg); badAssertsCounterStub(msg); } }); @@ -127,7 +128,7 @@ describe('ampanalytics-lib', () => { send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, /** @type {!JsonObject} */ ({'103': ['hello, world!']})); return timer.promise(POST_MESSAGE_DELAY).then(() => { - expect(badAssertsCounterStub.calledOnce).to.be.true; + expect(badAssertsCounterStub.callCount > 0).to.be.true; expect(badAssertsCounterStub.alwaysCalledWith( sinon.match(/Must implement onNewAmpAnalyticsInstance/))).to.be.true; return Promise.resolve(); From f363bf1c122b2da3c367537cdaedb896f73c81dc Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 14 Jul 2017 23:35:48 -0400 Subject: [PATCH 49/85] Changes creative ID to transport ID --- 3p/ampanalytics-lib.js | 32 +++++++++---------- .../0.1/test/test-ampanalytics-lib.js | 5 ++- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index a73341ed489b..06c4432a7daf 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -100,25 +100,25 @@ export class AmpAnalytics3pMessageRouter { 'Must implement onNewAmpAnalyticsInstance in ' + this.win_.location.href); entries.forEach(entry => { - const creativeId = entry[0]; + const transportId = entry[0]; const events = entry[1]; try { dev().assert(events && events.length, - 'Received empty events list for ' + creativeId); - if (!this.creativeMessageRouters_[creativeId]) { - this.creativeMessageRouters_[creativeId] = + 'Received empty events list for ' + transportId); + if (!this.creativeMessageRouters_[transportId]) { + this.creativeMessageRouters_[transportId] = new AmpAnalytics3pCreativeMessageRouter( - this.win_, this.sentinel_, creativeId); + this.win_, this.sentinel_, transportId); try { window.onNewAmpAnalyticsInstance( - this.creativeMessageRouters_[creativeId]); + this.creativeMessageRouters_[transportId]); } catch (e) { dev().error(TAG_, 'Exception thrown by' + ' onNewAmpAnalyticsInstance() in ' + this.win_.location.href + ': ' + e.message); } } - this.creativeMessageRouters_[creativeId] + this.creativeMessageRouters_[transportId] .sendMessagesToListener(events); } catch (e) { dev().error(TAG_, 'Failed to pass message to event listener: ' + @@ -188,10 +188,10 @@ export class AmpAnalytics3pCreativeMessageRouter { /** * @param {!Window} win The enclosing window object * @param {!string} sentinel The communication sentinel of this iframe - * @param {!string} creativeId The ID of the creative to route messages + * @param {!string} transportId The ID of the creative to route messages * to/from */ - constructor(win, sentinel, creativeId) { + constructor(win, sentinel, transportId) { /** @private {!Window} */ this.win_ = win; @@ -199,7 +199,7 @@ export class AmpAnalytics3pCreativeMessageRouter { this.sentinel_ = sentinel; /** @private {!string} */ - this.creativeId_ = creativeId; + this.transportId_ = transportId; /** @private {?function(!Array)} */ this.eventListener_ = null; @@ -215,7 +215,7 @@ export class AmpAnalytics3pCreativeMessageRouter { registerAmpAnalytics3pEventsListener(listener) { if (this.eventListener_) { dev().warn(TAG_, 'Replacing existing eventListener for ' + - this.creativeId_); + this.transportId_); } this.eventListener_ = listener; } @@ -229,19 +229,19 @@ export class AmpAnalytics3pCreativeMessageRouter { sendMessagesToListener(messages) { if (!messages.length) { dev().warn(TAG_, 'Attempted to send zero messages in ' + - this.creativeId_ + '. Ignoring.'); + this.transportId_ + '. Ignoring.'); return; } if (!this.eventListener_) { dev().warn(TAG_, 'Attempted to send messages when no listener' + - ' configured in ' + this.creativeId_ + '. Be sure to' + + ' configured in ' + this.transportId_ + '. Be sure to' + ' first call registerAmpAnalytics3pEventsListener()'); } try { this.eventListener_(messages); } catch (e) { dev().error(TAG_, 'Caught exception executing listener for ' + - this.creativeId_ + ': ' + e.message); + this.transportId_ + ': ' + e.message); } } @@ -265,8 +265,8 @@ export class AmpAnalytics3pCreativeMessageRouter { * @returns {!string} * @VisibleForTesting */ - getCreativeId() { - return this.creativeId_; + getTransportId() { + return this.transportId_; } } diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index bacab98ed445..fc0517728c31 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -56,7 +56,6 @@ describe('ampanalytics-lib', () => { router = new AmpAnalytics3pMessageRouter(window); sandbox.stub(dev(), 'assert', (condition, msg) => { if (!condition) { - console.log('Stubbed failed assert: ' + msg); badAssertsCounterStub(msg); } }); @@ -114,7 +113,7 @@ describe('ampanalytics-lib', () => { ampAnalytics.registerAmpAnalytics3pEventsListener(events => { expect(events.length).to.equal(1); events.forEach(event => { - expect(ampAnalytics.getCreativeId()).to.equal('101'); + expect(ampAnalytics.getTransportId()).to.equal('101'); expect(event).to.equal('hello, world!'); }); }); @@ -143,7 +142,7 @@ describe('ampanalytics-lib', () => { ampAnalytics.registerAmpAnalytics3pEventsListener(events => { expect(events.length).to.equal(3); events.forEach(() => { - expect(ampAnalytics.getCreativeId()).to.equal('105'); + expect(ampAnalytics.getTransportId()).to.equal('105'); }); expect(events[0]).to.equal('something happened'); expect(events[1]).to.equal('something else happened'); From 284d363bb05b5ea4b05b79cf17621bfd51211d36 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Sat, 15 Jul 2017 14:24:34 -0400 Subject: [PATCH 50/85] Includes transport ID in example logging, response --- 3p/ampanalytics-lib.js | 1 + examples/analytics-3p-remote-frame.html | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index 06c4432a7daf..ab094db5e4cc 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -254,6 +254,7 @@ export class AmpAnalytics3pCreativeMessageRouter { sendMessageToCreative(response) { const responseMessage = { sentinel: this.sentinel_, + transport: this.transportId_, type: AMP_ANALYTICS_3P_MESSAGE_TYPE.RESPONSE, data: response, }; diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-3p-remote-frame.html index acd7497beef8..d613c8841cb7 100644 --- a/examples/analytics-3p-remote-frame.html +++ b/examples/analytics-3p-remote-frame.html @@ -18,9 +18,14 @@ ampAnalytics.registerAmpAnalytics3pEventsListener(events => { events.forEach(event => { // Now, do something meaningful with the AMP Analytics event - console.log("Received an event: " + event); - ampAnalytics.sendMessageToCreative( - {'status': 'received', 'other': '42'}); + console.log("The page at: " + window.location.href + + " has received an event: " + event + + " from the creative with transport ID: " + + ampAnalytics.getTransportId()); + ampAnalytics.sendMessageToCreative({ + 'status': 'received', + 'other': ampAnalytics.getTransportId() + }); }); }); }; From c7898d7ddde56a9c512bb9f23ce184e7465ce371 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Sat, 15 Jul 2017 14:42:57 -0400 Subject: [PATCH 51/85] Makes example ad click URL match parent branch --- .../0.1/data/fake_amp_ad_with_iframe_transport.html | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html index ed6ab89f91fa..d137fbf9acdb 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html +++ b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html @@ -43,4 +43,3 @@ - From 352f1f860d778e1635016ca9fd4da7cfef17cc94 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 17 Jul 2017 13:00:19 -0400 Subject: [PATCH 52/85] Addresses review feedback --- 3p/ampanalytics-lib.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index ab094db5e4cc..a290ea12850e 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -55,7 +55,7 @@ export class AmpAnalytics3pMessageRouter { */ this.creativeMessageRouters_ = {}; - window.addEventListener('message', event => { + this.win_.addEventListener('message', event => { const messageContainer = this.extractMessage(event); if (this.sentinel_ != messageContainer.sentinel) { return; @@ -81,7 +81,7 @@ export class AmpAnalytics3pMessageRouter { * @param messageType The type of message to subscribe to */ subscribeTo(messageType) { - window.parent./*OK*/postMessage({ + this.win_.parent./*OK*/postMessage({ sentinel: this.sentinel_, type: messageType, }, '*'); @@ -96,7 +96,7 @@ export class AmpAnalytics3pMessageRouter { let entries; dev().assert((entries = Object.entries(message)).length, 'Received empty events message in ' + this.win_.location.href); - dev().assert(window.onNewAmpAnalyticsInstance, + dev().assert(this.win_.onNewAmpAnalyticsInstance, 'Must implement onNewAmpAnalyticsInstance in ' + this.win_.location.href); entries.forEach(entry => { @@ -107,16 +107,10 @@ export class AmpAnalytics3pMessageRouter { 'Received empty events list for ' + transportId); if (!this.creativeMessageRouters_[transportId]) { this.creativeMessageRouters_[transportId] = - new AmpAnalytics3pCreativeMessageRouter( - this.win_, this.sentinel_, transportId); - try { - window.onNewAmpAnalyticsInstance( - this.creativeMessageRouters_[transportId]); - } catch (e) { - dev().error(TAG_, 'Exception thrown by' + - ' onNewAmpAnalyticsInstance() in ' + this.win_.location.href + - ': ' + e.message); - } + new AmpAnalytics3pCreativeMessageRouter( + this.win_, this.sentinel_, transportId); + this.win_.onNewAmpAnalyticsInstance( + this.creativeMessageRouters_[transportId]); } this.creativeMessageRouters_[transportId] .sendMessagesToListener(events); @@ -164,7 +158,7 @@ export class AmpAnalytics3pMessageRouter { dev().assert(event && event.data, 'Received empty events message in ' + this.win_.name); let startIndex; - dev().assert((startIndex = event.data.indexOf('-') + 1) > -0, + dev().assert((startIndex = event.data.indexOf('-') + 1) > 0, 'Received truncated events message in ' + this.win_.name); return tryParseJson(event.data.substr(startIndex)); } @@ -258,7 +252,7 @@ export class AmpAnalytics3pCreativeMessageRouter { type: AMP_ANALYTICS_3P_MESSAGE_TYPE.RESPONSE, data: response, }; - window.parent./*OK*/postMessage( + this.win_.parent./*OK*/postMessage( /** @type {!JsonObject} */ (responseMessage), '*'); } From 904148d0da92a40d769c959014a45240a2e6ca52 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 17 Jul 2017 14:15:41 -0400 Subject: [PATCH 53/85] Addresses further review feedback --- 3p/ampanalytics-lib.js | 26 ++++++++++--------- .../0.1/test/test-ampanalytics-lib.js | 7 ++++- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index a290ea12850e..ce4fb8faf4ad 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -16,7 +16,7 @@ import './polyfills'; import {tryParseJson} from '../src/json'; -import {dev, initLogConstructor, setReportError} from '../src/log'; +import {dev, user, initLogConstructor, setReportError} from '../src/log'; import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../src/3p-analytics-common'; initLogConstructor(); @@ -38,7 +38,7 @@ export class AmpAnalytics3pMessageRouter { this.win_ = win; /** @const {string} */ - this.sentinel_ = dev().assertString( + this.sentinel_ = user().assertString( tryParseJson(this.win_.name).sentinel, 'Invalid/missing sentinel on iframe name attribute' + this.win_.name); if (!this.sentinel_) { @@ -60,11 +60,12 @@ export class AmpAnalytics3pMessageRouter { if (this.sentinel_ != messageContainer.sentinel) { return; } - dev().assert(messageContainer.type, + user().assert(messageContainer.type, 'Received message with missing type in ' + this.win_.location.href); - dev().assert(messageContainer.data, + user().assert(messageContainer.data, 'Received empty message in ' + this.win_.location.href); - dev().assert(messageContainer.type == AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + user().assert( + messageContainer.type == AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, 'Received unrecognized message type ' + messageContainer.type + ' in ' + this.win_.location.href); this.processEventsMessage( @@ -94,9 +95,9 @@ export class AmpAnalytics3pMessageRouter { */ processEventsMessage(message) { let entries; - dev().assert((entries = Object.entries(message)).length, + user().assert((entries = Object.entries(message)).length, 'Received empty events message in ' + this.win_.location.href); - dev().assert(this.win_.onNewAmpAnalyticsInstance, + user().assert(this.win_.onNewAmpAnalyticsInstance, 'Must implement onNewAmpAnalyticsInstance in ' + this.win_.location.href); entries.forEach(entry => { @@ -115,7 +116,7 @@ export class AmpAnalytics3pMessageRouter { this.creativeMessageRouters_[transportId] .sendMessagesToListener(events); } catch (e) { - dev().error(TAG_, 'Failed to pass message to event listener: ' + + user().error(TAG_, 'Failed to pass message to event listener: ' + e.message); } }); @@ -155,10 +156,10 @@ export class AmpAnalytics3pMessageRouter { * @returns {!JsonObject} */ extractMessage(event) { - dev().assert(event && event.data, 'Received empty events message in ' + + user().assert(event && event.data, 'Received empty events message in ' + this.win_.name); let startIndex; - dev().assert((startIndex = event.data.indexOf('-') + 1) > 0, + user().assert((startIndex = event.data.indexOf('-') + 1) > 0, 'Received truncated events message in ' + this.win_.name); return tryParseJson(event.data.substr(startIndex)); } @@ -168,7 +169,7 @@ if (!window.AMP_TEST) { try { new AmpAnalytics3pMessageRouter(window); } catch (e) { - dev().error(TAG_, 'Failed to construct AmpAnalytics3pMessageRouter: ' + + user().error(TAG_, 'Failed to construct AmpAnalytics3pMessageRouter: ' + e.message); } } @@ -230,11 +231,12 @@ export class AmpAnalytics3pCreativeMessageRouter { dev().warn(TAG_, 'Attempted to send messages when no listener' + ' configured in ' + this.transportId_ + '. Be sure to' + ' first call registerAmpAnalytics3pEventsListener()'); + return; } try { this.eventListener_(messages); } catch (e) { - dev().error(TAG_, 'Caught exception executing listener for ' + + user().error(TAG_, 'Caught exception executing listener for ' + this.transportId_ + ': ' + e.message); } } diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index fc0517728c31..1a3735b87fb0 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -21,7 +21,7 @@ import { AmpAnalytics3pMessageRouter, AmpAnalytics3pCreativeMessageRouter, } from '../../../../3p/ampanalytics-lib'; -import {dev} from '../../../../src/log'; +import {dev, user} from '../../../../src/log'; import {Timer} from '../../../../src/service/timer-impl'; import {adopt} from '../../../../src/runtime'; import * as sinon from 'sinon'; @@ -59,6 +59,11 @@ describe('ampanalytics-lib', () => { badAssertsCounterStub(msg); } }); + sandbox.stub(user(), 'assert', (condition, msg) => { + if (!condition) { + badAssertsCounterStub(msg); + } + }); }); afterEach(() => { From 56cc6a688ac83438ae890f9ede14ddf4e79aeb85 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 17 Jul 2017 17:35:43 -0400 Subject: [PATCH 54/85] extensions/amp-analytics/0.1/amp-analytics.js --- extensions/amp-analytics/0.1/amp-analytics.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index d3308f229e56..c8ffb7c8581e 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -413,22 +413,6 @@ export class AmpAnalytics extends AMP.BaseElement { } const typeConfig = this.predefinedConfig_[type] || {}; - // transport.iframe is only allowed to be specified in typeConfig, not - // the others. Allowed when running locally for testing purposes. - [defaultConfig, inlineConfig, this.remoteConfig_].forEach(config => { - if (config && config.transport && config.transport.iframe) { - const TAG = this.getName_(); - if (getMode().localDev) { - user().warn(TAG, 'Only typeConfig may specify iframe transport,' + - ' but in local dev mode, so okay', config); - } else { - user().error(TAG, 'Only typeConfig may specify iframe transport', - config); - return; - } - } - }); - this.mergeObjects_(defaultConfig, config); this.mergeObjects_(typeConfig, config, /* predefined */ true); if (typeConfig) { From 2cccfc36401abe013fc70252a57efffaa683b28d Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 17 Jul 2017 17:39:45 -0400 Subject: [PATCH 55/85] Corrects rebase issue --- extensions/amp-analytics/0.1/transport.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/amp-analytics/0.1/transport.js b/extensions/amp-analytics/0.1/transport.js index 0fdea88da445..18159397df9f 100644 --- a/extensions/amp-analytics/0.1/transport.js +++ b/extensions/amp-analytics/0.1/transport.js @@ -28,6 +28,7 @@ import {setStyle} from '../../../src/style'; /** @const {string} */ const TAG_ = 'amp-analytics.Transport'; + /** * @param {!Window} win * @param {string} request From cfa8c33ad1f209d204d30ea1e2768c578ae16115 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Tue, 18 Jul 2017 08:58:55 -0400 Subject: [PATCH 56/85] Add XSS check to example HTML --- examples/analytics-3p-remote-frame.html | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-3p-remote-frame.html index d613c8841cb7..bc4f73dee645 100644 --- a/examples/analytics-3p-remote-frame.html +++ b/examples/analytics-3p-remote-frame.html @@ -31,10 +31,15 @@ }; // Load the script specified in the iframe’s name attribute: - script = document.createElement('script'); - script.src = JSON.parse(window.name).scriptSrc; - document.head.appendChild(script); - // The script will be loaded, and will call onNewAmpAnalyticsInstance() + const url = JSON.parse(window.name).scriptSrc; + if (url && url.startsWith('https://3p.ampproject.net/')) { + script = document.createElement('script'); + script.src = url; + document.head.appendChild(script); + // The script will be loaded, and will call onNewAmpAnalyticsInstance() + } else { + console.log('Received invalid URL - risk of XSS! ' + url); + } From d8dde0ff14dab08e3acdc5a4e83bcf5b1fedd34c Mon Sep 17 00:00:00 2001 From: jonkeller Date: Tue, 18 Jul 2017 09:41:47 -0400 Subject: [PATCH 57/85] Changes XSS message from log to warning --- examples/analytics-3p-remote-frame.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-3p-remote-frame.html index bc4f73dee645..c54e54225646 100644 --- a/examples/analytics-3p-remote-frame.html +++ b/examples/analytics-3p-remote-frame.html @@ -38,7 +38,7 @@ document.head.appendChild(script); // The script will be loaded, and will call onNewAmpAnalyticsInstance() } else { - console.log('Received invalid URL - risk of XSS! ' + url); + console.warn('Received invalid URL - risk of XSS! ' + url); } From 98249eb6f20891dd9b43a8849faa13f874931614 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Tue, 18 Jul 2017 10:59:06 -0400 Subject: [PATCH 58/85] Addresses review nits --- 3p/ampanalytics-lib.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index ce4fb8faf4ad..0e68acc2f4ae 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -56,7 +56,7 @@ export class AmpAnalytics3pMessageRouter { this.creativeMessageRouters_ = {}; this.win_.addEventListener('message', event => { - const messageContainer = this.extractMessage(event); + const messageContainer = this.extractMessage_(event); if (this.sentinel_ != messageContainer.sentinel) { return; } @@ -68,7 +68,7 @@ export class AmpAnalytics3pMessageRouter { messageContainer.type == AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, 'Received unrecognized message type ' + messageContainer.type + ' in ' + this.win_.location.href); - this.processEventsMessage( + this.processEventsMessage_( /* @type {!../src/3p-analytics-common.AmpAnalytics3pEvent} */ (messageContainer.data)); }, false); @@ -80,6 +80,7 @@ export class AmpAnalytics3pMessageRouter { * Sends a message to the parent frame, requesting to subscribe to a * particular message type * @param messageType The type of message to subscribe to + * @VisibleForTesting */ subscribeTo(messageType) { this.win_.parent./*OK*/postMessage({ @@ -92,8 +93,9 @@ export class AmpAnalytics3pMessageRouter { * Handle receipt of a message indicating that creative(s) have sent * event(s) to this frame * @param {!../src/3p-analytics-common.AmpAnalytics3pEvent} message + * @private */ - processEventsMessage(message) { + processEventsMessage_(message) { let entries; user().assert((entries = Object.entries(message)).length, 'Received empty events message in ' + this.win_.location.href); @@ -110,8 +112,14 @@ export class AmpAnalytics3pMessageRouter { this.creativeMessageRouters_[transportId] = new AmpAnalytics3pCreativeMessageRouter( this.win_, this.sentinel_, transportId); - this.win_.onNewAmpAnalyticsInstance( - this.creativeMessageRouters_[transportId]); + try { + this.win_.onNewAmpAnalyticsInstance( + this.creativeMessageRouters_[transportId]); + } catch (e) { + user().error(TAG_, 'Caught exception in' + + ' onNewAmpAnalyticsInstance: ' + e.message); + throw e; + } } this.creativeMessageRouters_[transportId] .sendMessagesToListener(events); @@ -153,9 +161,10 @@ export class AmpAnalytics3pMessageRouter { * Takes the raw postMessage event, and extracts from it the actual data * payload * @param event - * @returns {!JsonObject} + * @returns {JsonObject} + * @private */ - extractMessage(event) { + extractMessage_(event) { user().assert(event && event.data, 'Received empty events message in ' + this.win_.name); let startIndex; @@ -230,7 +239,8 @@ export class AmpAnalytics3pCreativeMessageRouter { if (!this.eventListener_) { dev().warn(TAG_, 'Attempted to send messages when no listener' + ' configured in ' + this.transportId_ + '. Be sure to' + - ' first call registerAmpAnalytics3pEventsListener()'); + ' call registerAmpAnalytics3pEventsListener() within' + + ' onNewAmpAnalyticsInstance()!'); return; } try { From 78098c546e9e3a77b2a94d03e7d4b1e4a82def68 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 19 Jul 2017 17:28:52 -0400 Subject: [PATCH 59/85] Addresses review feedback including removing response temporarily --- 3p/ampanalytics-lib.js | 73 +++++++++++-------------- examples/analytics-3p-remote-frame.html | 4 -- gulpfile.js | 7 +++ 3 files changed, 38 insertions(+), 46 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index 0e68acc2f4ae..b47905089bb6 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -18,6 +18,7 @@ import './polyfills'; import {tryParseJson} from '../src/json'; import {dev, user, initLogConstructor, setReportError} from '../src/log'; import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../src/3p-analytics-common'; +import {getData} from '../src/event-helper'; initLogConstructor(); // TODO(alanorozco): Refactor src/error.reportError so it does not contain big @@ -39,7 +40,7 @@ export class AmpAnalytics3pMessageRouter { /** @const {string} */ this.sentinel_ = user().assertString( - tryParseJson(this.win_.name).sentinel, + tryParseJson(this.win_.name)['sentinel'], 'Invalid/missing sentinel on iframe name attribute' + this.win_.name); if (!this.sentinel_) { return; @@ -57,20 +58,20 @@ export class AmpAnalytics3pMessageRouter { this.win_.addEventListener('message', event => { const messageContainer = this.extractMessage_(event); - if (this.sentinel_ != messageContainer.sentinel) { + if (this.sentinel_ != messageContainer['sentinel']) { return; } - user().assert(messageContainer.type, + user().assert(messageContainer['type'], 'Received message with missing type in ' + this.win_.location.href); - user().assert(messageContainer.data, + user().assert(messageContainer['data'], 'Received empty message in ' + this.win_.location.href); user().assert( - messageContainer.type == AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, - 'Received unrecognized message type ' + messageContainer.type + + messageContainer['type'] == AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + 'Received unrecognized message type ' + messageContainer['type'] + ' in ' + this.win_.location.href); this.processEventsMessage_( - /* @type {!../src/3p-analytics-common.AmpAnalytics3pEvent} */ - (messageContainer.data)); + /** @type {!../src/3p-analytics-common.AmpAnalytics3pEvent} */ + (messageContainer['data'])); }, false); this.subscribeTo(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT); @@ -83,10 +84,10 @@ export class AmpAnalytics3pMessageRouter { * @VisibleForTesting */ subscribeTo(messageType) { - this.win_.parent./*OK*/postMessage({ + this.win_.parent./*OK*/postMessage(/** @type {JsonObject} */ ({ sentinel: this.sentinel_, type: messageType, - }, '*'); + }), '*'); } /** @@ -96,15 +97,17 @@ export class AmpAnalytics3pMessageRouter { * @private */ processEventsMessage_(message) { - let entries; - user().assert((entries = Object.entries(message)).length, + let keys; + user().assert((keys = Object.keys(message)).length, 'Received empty events message in ' + this.win_.location.href); + this.win_.onNewAmpAnalyticsInstance = + this.win_.onNewAmpAnalyticsInstance || null; user().assert(this.win_.onNewAmpAnalyticsInstance, 'Must implement onNewAmpAnalyticsInstance in ' + this.win_.location.href); - entries.forEach(entry => { - const transportId = entry[0]; - const events = entry[1]; + keys.forEach(key => { + const transportId = key ; + const events = message[key]; try { dev().assert(events && events.length, 'Received empty events list for ' + transportId); @@ -165,12 +168,13 @@ export class AmpAnalytics3pMessageRouter { * @private */ extractMessage_(event) { - user().assert(event && event.data, 'Received empty events message in ' + - this.win_.name); + user().assert(event, 'Received null event in ' + this.win_.name); + const data = String(getData(event)); + user().assert(data, 'Received empty event in ' + this.win_.name); let startIndex; - user().assert((startIndex = event.data.indexOf('-') + 1) > 0, + user().assert((startIndex = data.indexOf('-') + 1) > 0, 'Received truncated events message in ' + this.win_.name); - return tryParseJson(event.data.substr(startIndex)); + return tryParseJson(data.substr(startIndex)) || null; } } @@ -185,8 +189,7 @@ if (!window.AMP_TEST) { /** * Receives messages bound for this cross-domain iframe, from a particular - * creative. Also sends response messages from the iframe meant for this - * particular creative. + * creative. */ export class AmpAnalytics3pCreativeMessageRouter { /** @@ -205,7 +208,8 @@ export class AmpAnalytics3pCreativeMessageRouter { /** @private {!string} */ this.transportId_ = transportId; - /** @private {?function(!Array)} */ + /** @private + * {?function(!Array)} */ this.eventListener_ = null; } @@ -213,8 +217,9 @@ export class AmpAnalytics3pCreativeMessageRouter { * Registers a callback function to be called when AMP Analytics events occur. * There may only be one listener. If another function has previously been * registered as a listener, it will no longer receive events. - * @param {!function(!Array)} listener A function - * that takes an array of event strings, and does something with them. + * @param {!function(!Array)} + * listener A function that takes an array of event strings, and does + * something with them. */ registerAmpAnalytics3pEventsListener(listener) { if (this.eventListener_) { @@ -228,7 +233,8 @@ export class AmpAnalytics3pCreativeMessageRouter { * Receives message(s) from a creative for the cross-domain iframe * and passes them to that iframe's listener, if a listener has been * registered - * @param {!Array} messages The message that was received + * @param {!Array} messages + * The message that was received */ sendMessagesToListener(messages) { if (!messages.length) { @@ -251,23 +257,6 @@ export class AmpAnalytics3pCreativeMessageRouter { } } - /** - * Sends a message from the third-party vendor's metrics-collection page back - * to the creative. - * @param {!../src/3p-analytics-common.AmpAnalytics3pResponse} response The - * response to send. - */ - sendMessageToCreative(response) { - const responseMessage = { - sentinel: this.sentinel_, - transport: this.transportId_, - type: AMP_ANALYTICS_3P_MESSAGE_TYPE.RESPONSE, - data: response, - }; - this.win_.parent./*OK*/postMessage( - /** @type {!JsonObject} */ (responseMessage), '*'); - } - /** * @returns {!string} * @VisibleForTesting diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-3p-remote-frame.html index c54e54225646..84faa64e6473 100644 --- a/examples/analytics-3p-remote-frame.html +++ b/examples/analytics-3p-remote-frame.html @@ -22,10 +22,6 @@ " has received an event: " + event + " from the creative with transport ID: " + ampAnalytics.getTransportId()); - ampAnalytics.sendMessageToCreative({ - 'status': 'received', - 'other': ampAnalytics.getTransportId() - }); }); }); }; diff --git a/gulpfile.js b/gulpfile.js index acd831ed7130..642c9a6f747f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -679,6 +679,13 @@ function checkTypes() { includePolyfills: true, checkTypes: true, }), + closureCompile(['./3p/ampanalytics-lib.js'], './dist', + 'ampanalytics-check-types.js', { + externs: ['ads/ads.extern.js'], + include3pDirectories: true, + includePolyfills: true, + checkTypes: true, + }), ]); }); } From fa9c3f35171fd74c09340a5c7b9bc6b9e8a92eae Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 20 Jul 2017 10:31:59 -0400 Subject: [PATCH 60/85] Clean up unit tests, rename event datatype --- 3p/ampanalytics-lib.js | 4 ++-- .../amp-analytics/0.1/test/test-ampanalytics-lib.js | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index b47905089bb6..8751a5f3b752 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -70,7 +70,7 @@ export class AmpAnalytics3pMessageRouter { 'Received unrecognized message type ' + messageContainer['type'] + ' in ' + this.win_.location.href); this.processEventsMessage_( - /** @type {!../src/3p-analytics-common.AmpAnalytics3pEvent} */ + /** @type {!../src/3p-analytics-common.AmpAnalytics3pEventMap} */ (messageContainer['data'])); }, false); @@ -93,7 +93,7 @@ export class AmpAnalytics3pMessageRouter { /** * Handle receipt of a message indicating that creative(s) have sent * event(s) to this frame - * @param {!../src/3p-analytics-common.AmpAnalytics3pEvent} message + * @param {!../src/3p-analytics-common.AmpAnalytics3pEventMap} message * @private */ processEventsMessage_(message) { diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index 1a3735b87fb0..0211f282414a 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -98,7 +98,7 @@ describe('ampanalytics-lib', () => { }); it('initially has empty creativeMessageRouters mapping ', () => { - expect(Object.keys(router.getCreativeMethodRouters()).length).to.equal(0); + expect(Object.keys(router.getCreativeMethodRouters())).to.have.lengthOf(0); }); it('makes registration function available ', () => { @@ -114,9 +114,10 @@ describe('ampanalytics-lib', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter) .to.be.true; - expect(Object.keys(router.getCreativeMethodRouters()).length).to.equal(1); + expect(Object.keys(router.getCreativeMethodRouters())) + .to.have.lengthOf(1); ampAnalytics.registerAmpAnalytics3pEventsListener(events => { - expect(events.length).to.equal(1); + expect(events).to.have.lengthOf(1); events.forEach(event => { expect(ampAnalytics.getTransportId()).to.equal('101'); expect(event).to.equal('hello, world!'); @@ -143,9 +144,10 @@ describe('ampanalytics-lib', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter) .to.be.true; - expect(Object.keys(router.getCreativeMethodRouters()).length).to.equal(1); + expect(Object.keys(router.getCreativeMethodRouters())) + .to.have.lengthOf(1); ampAnalytics.registerAmpAnalytics3pEventsListener(events => { - expect(events.length).to.equal(3); + expect(events).to.have.lengthOf(3); events.forEach(() => { expect(ampAnalytics.getTransportId()).to.equal('105'); }); From ac5bdca1f3da575fc03b9c41b3b6b7201c19a9e0 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 20 Jul 2017 13:14:16 -0400 Subject: [PATCH 61/85] Resolves rebase issue --- extensions/amp-analytics/0.1/transport.js | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/transport.js b/extensions/amp-analytics/0.1/transport.js index 18159397df9f..0fdea88da445 100644 --- a/extensions/amp-analytics/0.1/transport.js +++ b/extensions/amp-analytics/0.1/transport.js @@ -28,7 +28,6 @@ import {setStyle} from '../../../src/style'; /** @const {string} */ const TAG_ = 'amp-analytics.Transport'; - /** * @param {!Window} win * @param {string} request From d96b64441acee4c2b099e76c3fcfadbab2646712 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 20 Jul 2017 14:31:27 -0400 Subject: [PATCH 62/85] Intentionally adding trivial change, will remove in a minute --- 3p/ampanalytics-lib.js | 1 + 1 file changed, 1 insertion(+) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index 8751a5f3b752..4c3c128db8af 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -28,6 +28,7 @@ setReportError(() => {}); /** @private @const {string} */ const TAG_ = 'ampanalytics-lib'; + /** * Receives messages bound for this cross-domain iframe, from all creatives */ From 0f03774c0b2a4b32d2160cd0a07f008add1112aa Mon Sep 17 00:00:00 2001 From: jonkeller Date: Thu, 20 Jul 2017 14:32:18 -0400 Subject: [PATCH 63/85] Removing trival change added a minute ago. This is because Git UI is being strange. --- 3p/ampanalytics-lib.js | 1 - 1 file changed, 1 deletion(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index 4c3c128db8af..8751a5f3b752 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -28,7 +28,6 @@ setReportError(() => {}); /** @private @const {string} */ const TAG_ = 'ampanalytics-lib'; - /** * Receives messages bound for this cross-domain iframe, from all creatives */ From 3ff0ae475842541689a6e2a213f8ee5a68e7e31b Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 21 Jul 2017 11:09:02 -0400 Subject: [PATCH 64/85] Changes an enum to a const, since all but one enum value have been removed --- 3p/ampanalytics-lib.js | 6 +++--- .../amp-analytics/0.1/test/test-ampanalytics-lib.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index 8751a5f3b752..ce354c58cdd0 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -17,7 +17,7 @@ import './polyfills'; import {tryParseJson} from '../src/json'; import {dev, user, initLogConstructor, setReportError} from '../src/log'; -import {AMP_ANALYTICS_3P_MESSAGE_TYPE} from '../src/3p-analytics-common'; +import {AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE} from '../src/3p-analytics-common'; import {getData} from '../src/event-helper'; initLogConstructor(); @@ -66,7 +66,7 @@ export class AmpAnalytics3pMessageRouter { user().assert(messageContainer['data'], 'Received empty message in ' + this.win_.location.href); user().assert( - messageContainer['type'] == AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + messageContainer['type'] == AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, 'Received unrecognized message type ' + messageContainer['type'] + ' in ' + this.win_.location.href); this.processEventsMessage_( @@ -74,7 +74,7 @@ export class AmpAnalytics3pMessageRouter { (messageContainer['data'])); }, false); - this.subscribeTo(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT); + this.subscribeTo(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE); } /** diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index 0211f282414a..fface5356353 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -15,7 +15,7 @@ */ import { - AMP_ANALYTICS_3P_MESSAGE_TYPE, + AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, } from '../../../../src/3p-analytics-common'; import { AmpAnalytics3pMessageRouter, @@ -106,7 +106,7 @@ describe('ampanalytics-lib', () => { expect(ampAnalytics.registerAmpAnalytics3pEventsListener).to.exist; ampAnalytics.registerAmpAnalytics3pEventsListener(() => {}); }; - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({'100': ['hello, world!']})); }); @@ -124,13 +124,13 @@ describe('ampanalytics-lib', () => { }); }); }; - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({'101': ['hello, world!']})); }); it('asserts when onNewAmpAnalyticsInstance is not implemented ', () => { window.onNewAmpAnalyticsInstance = null; - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({'103': ['hello, world!']})); return timer.promise(POST_MESSAGE_DELAY).then(() => { expect(badAssertsCounterStub.callCount > 0).to.be.true; @@ -156,7 +156,7 @@ describe('ampanalytics-lib', () => { expect(events[2]).to.equal('a third thing happened'); }); }; - send(AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, + send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({'105': [ 'something happened', 'something else happened', From 30927700cec9b332ced8812ae362a79cd42028b1 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 21 Jul 2017 17:52:49 -0400 Subject: [PATCH 65/85] Renames things using 3P in name, changes wire format from map to array --- 3p/ampanalytics-lib.js | 53 ++++++++----------- examples/analytics-3p-remote-frame.html | 14 +++-- .../0.1/test/test-ampanalytics-lib.js | 34 +++++++----- 3 files changed, 49 insertions(+), 52 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index ce354c58cdd0..69c761f5d9ea 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -17,7 +17,7 @@ import './polyfills'; import {tryParseJson} from '../src/json'; import {dev, user, initLogConstructor, setReportError} from '../src/log'; -import {AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE} from '../src/3p-analytics-common'; +import {IFRAME_TRANSPORT_EVENTS_TYPE} from '../src/3p-analytics-common'; import {getData} from '../src/event-helper'; initLogConstructor(); @@ -63,18 +63,19 @@ export class AmpAnalytics3pMessageRouter { } user().assert(messageContainer['type'], 'Received message with missing type in ' + this.win_.location.href); - user().assert(messageContainer['data'], + user().assert(messageContainer['data'] && + messageContainer['data']['events'], 'Received empty message in ' + this.win_.location.href); user().assert( - messageContainer['type'] == AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, + messageContainer['type'] == IFRAME_TRANSPORT_EVENTS_TYPE, 'Received unrecognized message type ' + messageContainer['type'] + ' in ' + this.win_.location.href); this.processEventsMessage_( - /** @type {!../src/3p-analytics-common.AmpAnalytics3pEventMap} */ - (messageContainer['data'])); + /** @type {!Array<../src/3p-analytics-common.IframeTransportEvent>} */ + (messageContainer['data']['events'])); }, false); - this.subscribeTo(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE); + this.subscribeTo(IFRAME_TRANSPORT_EVENTS_TYPE); } /** @@ -93,24 +94,22 @@ export class AmpAnalytics3pMessageRouter { /** * Handle receipt of a message indicating that creative(s) have sent * event(s) to this frame - * @param {!../src/3p-analytics-common.AmpAnalytics3pEventMap} message + * @param {!Array} + * events An array of events * @private */ - processEventsMessage_(message) { - let keys; - user().assert((keys = Object.keys(message)).length, - 'Received empty events message in ' + this.win_.location.href); + processEventsMessage_(events) { + user().assert(events && events.length, + 'Received empty events list in ' + this.win_.location.href); this.win_.onNewAmpAnalyticsInstance = this.win_.onNewAmpAnalyticsInstance || null; user().assert(this.win_.onNewAmpAnalyticsInstance, 'Must implement onNewAmpAnalyticsInstance in ' + this.win_.location.href); - keys.forEach(key => { - const transportId = key ; - const events = message[key]; + events.forEach(event => { + const transportId = event['transportId']; + const message = event['message']; try { - dev().assert(events && events.length, - 'Received empty events list for ' + transportId); if (!this.creativeMessageRouters_[transportId]) { this.creativeMessageRouters_[transportId] = new AmpAnalytics3pCreativeMessageRouter( @@ -125,7 +124,7 @@ export class AmpAnalytics3pMessageRouter { } } this.creativeMessageRouters_[transportId] - .sendMessagesToListener(events); + .sendMessageToListener(message); } catch (e) { user().error(TAG_, 'Failed to pass message to event listener: ' + e.message); @@ -217,9 +216,9 @@ export class AmpAnalytics3pCreativeMessageRouter { * Registers a callback function to be called when AMP Analytics events occur. * There may only be one listener. If another function has previously been * registered as a listener, it will no longer receive events. - * @param {!function(!Array)} - * listener A function that takes an array of event strings, and does - * something with them. + * @param {!function(!string)} + * listener A function that takes an event string, and does something with + * it. */ registerAmpAnalytics3pEventsListener(listener) { if (this.eventListener_) { @@ -233,24 +232,18 @@ export class AmpAnalytics3pCreativeMessageRouter { * Receives message(s) from a creative for the cross-domain iframe * and passes them to that iframe's listener, if a listener has been * registered - * @param {!Array} messages - * The message that was received + * @param {!string} message The event message that was received */ - sendMessagesToListener(messages) { - if (!messages.length) { - dev().warn(TAG_, 'Attempted to send zero messages in ' + - this.transportId_ + '. Ignoring.'); - return; - } + sendMessageToListener(message) { if (!this.eventListener_) { - dev().warn(TAG_, 'Attempted to send messages when no listener' + + dev().warn(TAG_, 'Attempted to send message when no listener' + ' configured in ' + this.transportId_ + '. Be sure to' + ' call registerAmpAnalytics3pEventsListener() within' + ' onNewAmpAnalyticsInstance()!'); return; } try { - this.eventListener_(messages); + this.eventListener_(message); } catch (e) { user().error(TAG_, 'Caught exception executing listener for ' + this.transportId_ + ': ' + e.message); diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-3p-remote-frame.html index 84faa64e6473..98b4db59ce18 100644 --- a/examples/analytics-3p-remote-frame.html +++ b/examples/analytics-3p-remote-frame.html @@ -15,14 +15,12 @@ * passing your function which will receive the analytics events. */ window.onNewAmpAnalyticsInstance = ampAnalytics => { - ampAnalytics.registerAmpAnalytics3pEventsListener(events => { - events.forEach(event => { - // Now, do something meaningful with the AMP Analytics event - console.log("The page at: " + window.location.href + - " has received an event: " + event + - " from the creative with transport ID: " + - ampAnalytics.getTransportId()); - }); + ampAnalytics.registerAmpAnalytics3pEventsListener(event => { + // Now, do something meaningful with the AMP Analytics event + console.log("The page at: " + window.location.href + + " has received an event: " + event + + " from the creative with transport ID: " + + ampAnalytics.getTransportId()); }); }; diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index fface5356353..b58106b3eac2 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -15,7 +15,7 @@ */ import { - AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, + IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, } from '../../../../src/3p-analytics-common'; import { AmpAnalytics3pMessageRouter, @@ -106,8 +106,10 @@ describe('ampanalytics-lib', () => { expect(ampAnalytics.registerAmpAnalytics3pEventsListener).to.exist; ampAnalytics.registerAmpAnalytics3pEventsListener(() => {}); }; - send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, - /** @type {!JsonObject} */ ({'100': ['hello, world!']})); + send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ + events: [ + {transportId: '100', message: 'hello, world!'}, + ]})); }); it('receives an event message ', () => { @@ -124,17 +126,21 @@ describe('ampanalytics-lib', () => { }); }); }; - send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, - /** @type {!JsonObject} */ ({'101': ['hello, world!']})); + send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ + events: [ + {transportId: '101', message: 'hello, world!'}, + ]})); }); it('asserts when onNewAmpAnalyticsInstance is not implemented ', () => { window.onNewAmpAnalyticsInstance = null; - send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, - /** @type {!JsonObject} */ ({'103': ['hello, world!']})); + send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ + events: [ + {transportId: '102', message: 'hello, world!'}, + ]})); return timer.promise(POST_MESSAGE_DELAY).then(() => { expect(badAssertsCounterStub.callCount > 0).to.be.true; - expect(badAssertsCounterStub.alwaysCalledWith( + expect(badAssertsCounterStub.calledWith( sinon.match(/Must implement onNewAmpAnalyticsInstance/))).to.be.true; return Promise.resolve(); }); @@ -149,18 +155,18 @@ describe('ampanalytics-lib', () => { ampAnalytics.registerAmpAnalytics3pEventsListener(events => { expect(events).to.have.lengthOf(3); events.forEach(() => { - expect(ampAnalytics.getTransportId()).to.equal('105'); + expect(ampAnalytics.getTransportId()).to.equal('103'); }); expect(events[0]).to.equal('something happened'); expect(events[1]).to.equal('something else happened'); expect(events[2]).to.equal('a third thing happened'); }); }; - send(AMP_ANALYTICS_3P_EVENT_MESSAGES_TYPE, - /** @type {!JsonObject} */ ({'105': [ - 'something happened', - 'something else happened', - 'a third thing happened', + send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ + events: [ + {transportId: '103', message: 'something happened'}, + {transportId: '103', message: 'something else happened'}, + {transportId: '103', message: 'a third thing happened'}, ]})); }); }); From ac08ff3cac4fb3de9adaf021ee133b5eb0007e92 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Fri, 21 Jul 2017 18:08:19 -0400 Subject: [PATCH 66/85] Fixes unit test --- 3p/ampanalytics-lib.js | 5 ++--- extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index 69c761f5d9ea..b4e515805650 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -63,8 +63,7 @@ export class AmpAnalytics3pMessageRouter { } user().assert(messageContainer['type'], 'Received message with missing type in ' + this.win_.location.href); - user().assert(messageContainer['data'] && - messageContainer['data']['events'], + user().assert(messageContainer['events'], 'Received empty message in ' + this.win_.location.href); user().assert( messageContainer['type'] == IFRAME_TRANSPORT_EVENTS_TYPE, @@ -72,7 +71,7 @@ export class AmpAnalytics3pMessageRouter { ' in ' + this.win_.location.href); this.processEventsMessage_( /** @type {!Array<../src/3p-analytics-common.IframeTransportEvent>} */ - (messageContainer['data']['events'])); + (messageContainer['events'])); }, false); this.subscribeTo(IFRAME_TRANSPORT_EVENTS_TYPE); diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index b58106b3eac2..e30e5c2850d2 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -79,7 +79,11 @@ describe('ampanalytics-lib', () => { const object = {}; object['type'] = type; object['sentinel'] = sentinel; - object['data'] = data; + if (data['events']) { + object['events'] = data['events']; + } else { + object['data'] = data; + } const payload = 'amp-' + JSON.stringify(object); window./*OK*/postMessage(payload, '*'); } From 8e4a0a37f5c49157802d1a9ae9a7bf2438ab3259 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 24 Jul 2017 10:40:35 -0400 Subject: [PATCH 67/85] Renamed files/classes to get rid of '3p' --- 3p/ampanalytics-lib.js | 35 ++++++++++--------- ...lytics-iframe-transport-remote-frame.html} | 2 +- .../0.1/test/test-ampanalytics-lib.js | 24 ++++++------- 3 files changed, 32 insertions(+), 29 deletions(-) rename examples/{analytics-3p-remote-frame.html => analytics-iframe-transport-remote-frame.html} (95%) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index b4e515805650..f3b12068c4ef 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -17,7 +17,7 @@ import './polyfills'; import {tryParseJson} from '../src/json'; import {dev, user, initLogConstructor, setReportError} from '../src/log'; -import {IFRAME_TRANSPORT_EVENTS_TYPE} from '../src/3p-analytics-common'; +import {IFRAME_TRANSPORT_EVENTS_TYPE} from '../src/iframe-transport-common'; import {getData} from '../src/event-helper'; initLogConstructor(); @@ -29,9 +29,10 @@ setReportError(() => {}); const TAG_ = 'ampanalytics-lib'; /** - * Receives messages bound for this cross-domain iframe, from all creatives + * Receives event messages bound for this cross-domain iframe, from all + * creatives */ -export class AmpAnalytics3pMessageRouter { +export class EventRouter { /** @param {!Window} win */ constructor(win) { @@ -50,9 +51,9 @@ export class AmpAnalytics3pMessageRouter { * Multiple creatives on a page may wish to use the same type of * amp-analytics tag. This object provides a mapping between the * IDs which identify which amp-analytics tag a message is to/from, - * with each ID's corresponding AmpAnalytics3pCreativeMessageRouter, + * with each ID's corresponding CreativeEventRouter, * which is an object that handles messages to/from a particular creative. - * @private {!Object} + * @private {!Object} */ this.creativeMessageRouters_ = {}; @@ -70,7 +71,9 @@ export class AmpAnalytics3pMessageRouter { 'Received unrecognized message type ' + messageContainer['type'] + ' in ' + this.win_.location.href); this.processEventsMessage_( - /** @type {!Array<../src/3p-analytics-common.IframeTransportEvent>} */ + /** + * @type {!Array<../src/iframe-transport-common.IframeTransportEvent>} + */ (messageContainer['events'])); }, false); @@ -93,7 +96,7 @@ export class AmpAnalytics3pMessageRouter { /** * Handle receipt of a message indicating that creative(s) have sent * event(s) to this frame - * @param {!Array} + * @param {!Array} * events An array of events * @private */ @@ -111,7 +114,7 @@ export class AmpAnalytics3pMessageRouter { try { if (!this.creativeMessageRouters_[transportId]) { this.creativeMessageRouters_[transportId] = - new AmpAnalytics3pCreativeMessageRouter( + new CreativeEventRouter( this.win_, this.sentinel_, transportId); try { this.win_.onNewAmpAnalyticsInstance( @@ -142,8 +145,8 @@ export class AmpAnalytics3pMessageRouter { /** * Gets the mapping of creative senderId to - * AmpAnalytics3pCreativeMessageRouter - * @returns {!Object.} + * CreativeEventRouter + * @returns {!Object.} * @VisibleForTesting */ getCreativeMethodRouters() { @@ -151,7 +154,7 @@ export class AmpAnalytics3pMessageRouter { } /** - * Gets rid of the mapping to AmpAnalytics3pMessageRouter + * Gets rid of the mapping to EventRouter * @VisibleForTesting */ reset() { @@ -178,9 +181,9 @@ export class AmpAnalytics3pMessageRouter { if (!window.AMP_TEST) { try { - new AmpAnalytics3pMessageRouter(window); + new EventRouter(window); } catch (e) { - user().error(TAG_, 'Failed to construct AmpAnalytics3pMessageRouter: ' + + user().error(TAG_, 'Failed to construct EventRouter: ' + e.message); } } @@ -189,7 +192,7 @@ if (!window.AMP_TEST) { * Receives messages bound for this cross-domain iframe, from a particular * creative. */ -export class AmpAnalytics3pCreativeMessageRouter { +export class CreativeEventRouter { /** * @param {!Window} win The enclosing window object * @param {!string} sentinel The communication sentinel of this iframe @@ -219,7 +222,7 @@ export class AmpAnalytics3pCreativeMessageRouter { * listener A function that takes an event string, and does something with * it. */ - registerAmpAnalytics3pEventsListener(listener) { + registerCreativeEventListener(listener) { if (this.eventListener_) { dev().warn(TAG_, 'Replacing existing eventListener for ' + this.transportId_); @@ -237,7 +240,7 @@ export class AmpAnalytics3pCreativeMessageRouter { if (!this.eventListener_) { dev().warn(TAG_, 'Attempted to send message when no listener' + ' configured in ' + this.transportId_ + '. Be sure to' + - ' call registerAmpAnalytics3pEventsListener() within' + + ' call registerCreativeEventListener() within' + ' onNewAmpAnalyticsInstance()!'); return; } diff --git a/examples/analytics-3p-remote-frame.html b/examples/analytics-iframe-transport-remote-frame.html similarity index 95% rename from examples/analytics-3p-remote-frame.html rename to examples/analytics-iframe-transport-remote-frame.html index 98b4db59ce18..06d216236a96 100644 --- a/examples/analytics-3p-remote-frame.html +++ b/examples/analytics-iframe-transport-remote-frame.html @@ -15,7 +15,7 @@ * passing your function which will receive the analytics events. */ window.onNewAmpAnalyticsInstance = ampAnalytics => { - ampAnalytics.registerAmpAnalytics3pEventsListener(event => { + ampAnalytics.registerCreativeEventListener(event => { // Now, do something meaningful with the AMP Analytics event console.log("The page at: " + window.location.href + " has received an event: " + event + diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index e30e5c2850d2..136066dff9cc 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -16,10 +16,10 @@ import { IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, -} from '../../../../src/3p-analytics-common'; +} from '../../../../src/iframe-transport-common'; import { - AmpAnalytics3pMessageRouter, - AmpAnalytics3pCreativeMessageRouter, + EventRouter, + CreativeEventRouter, } from '../../../../3p/ampanalytics-lib'; import {dev, user} from '../../../../src/log'; import {Timer} from '../../../../src/service/timer-impl'; @@ -52,8 +52,8 @@ describe('ampanalytics-lib', () => { badAssertsCounterStub = sandbox.stub(); sentinel = createUniqueId(); window.name = '{"sentinel": "' + sentinel + '"}'; - sandbox.stub(AmpAnalytics3pMessageRouter.prototype, 'subscribeTo'); - router = new AmpAnalytics3pMessageRouter(window); + sandbox.stub(EventRouter.prototype, 'subscribeTo'); + router = new EventRouter(window); sandbox.stub(dev(), 'assert', (condition, msg) => { if (!condition) { badAssertsCounterStub(msg); @@ -92,7 +92,7 @@ describe('ampanalytics-lib', () => { const oldWindowName = window.name; expect(() => { window.name = ''; - new AmpAnalytics3pMessageRouter(window); + new EventRouter(window); }).to.throw(/Cannot read property 'sentinel' of undefined/); window.name = oldWindowName; }); @@ -107,8 +107,8 @@ describe('ampanalytics-lib', () => { it('makes registration function available ', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { - expect(ampAnalytics.registerAmpAnalytics3pEventsListener).to.exist; - ampAnalytics.registerAmpAnalytics3pEventsListener(() => {}); + expect(ampAnalytics.registerCreativeEventListener).to.exist; + ampAnalytics.registerCreativeEventListener(() => {}); }; send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ events: [ @@ -118,11 +118,11 @@ describe('ampanalytics-lib', () => { it('receives an event message ', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { - expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter) + expect(ampAnalytics instanceof CreativeEventRouter) .to.be.true; expect(Object.keys(router.getCreativeMethodRouters())) .to.have.lengthOf(1); - ampAnalytics.registerAmpAnalytics3pEventsListener(events => { + ampAnalytics.registerCreativeEventListener(events => { expect(events).to.have.lengthOf(1); events.forEach(event => { expect(ampAnalytics.getTransportId()).to.equal('101'); @@ -152,11 +152,11 @@ describe('ampanalytics-lib', () => { it('receives multiple event messages ', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { - expect(ampAnalytics instanceof AmpAnalytics3pCreativeMessageRouter) + expect(ampAnalytics instanceof CreativeEventRouter) .to.be.true; expect(Object.keys(router.getCreativeMethodRouters())) .to.have.lengthOf(1); - ampAnalytics.registerAmpAnalytics3pEventsListener(events => { + ampAnalytics.registerCreativeEventListener(events => { expect(events).to.have.lengthOf(3); events.forEach(() => { expect(ampAnalytics.getTransportId()).to.equal('103'); From 29e94e50d6e2dabc6cee1ad7e1f84c974d8a9b26 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 24 Jul 2017 12:58:13 -0400 Subject: [PATCH 68/85] Fixes some merge issues --- build-system/app.js | 10 ---------- .../0.1/data/fake_amp_ad_with_iframe_transport.html | 1 + extensions/amp-analytics/0.1/amp-analytics.js | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/build-system/app.js b/build-system/app.js index ca11b8adb2c1..ca50bb95c1d0 100644 --- a/build-system/app.js +++ b/build-system/app.js @@ -936,16 +936,6 @@ app.get(['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], next(); }); -app.get('/dist/ampanalytics-lib.js', (req, res, next) => { - req.url = req.url.replace(/dist/, 'dist.3p/current'); - next(); - }); - -app.get('/dist/ampanalytics-lib.js', (req, res, next) => { - req.url = req.url.replace(/dist/, 'dist.3p/current'); - next(); -}); - app.get('/dist/ampanalytics-lib.js', (req, res, next) => { req.url = req.url.replace(/dist/, 'dist.3p/current'); next(); diff --git a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html index d137fbf9acdb..ed6ab89f91fa 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html +++ b/extensions/amp-ad-network-fake-impl/0.1/data/fake_amp_ad_with_iframe_transport.html @@ -43,3 +43,4 @@ + diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index c8ffb7c8581e..b751be5686f9 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -312,7 +312,7 @@ export class AmpAnalytics extends AMP.BaseElement { * * @param {!Object} params The params that need to be renamed. * @param {!Object} replaceMap A map of pattern and replacement - * value. + * value. * @private */ processExtraUrlParams_(params, replaceMap) { From ab3db5f7014746d9b6162c894432708bf7541037 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 24 Jul 2017 13:49:51 -0400 Subject: [PATCH 69/85] Found typo in method name, which led to renaming that and a few other things --- 3p/ampanalytics-lib.js | 21 ++++++++++--------- .../0.1/test/test-ampanalytics-lib.js | 6 +++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/3p/ampanalytics-lib.js b/3p/ampanalytics-lib.js index f3b12068c4ef..8574975eee1d 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/ampanalytics-lib.js @@ -55,7 +55,7 @@ export class EventRouter { * which is an object that handles messages to/from a particular creative. * @private {!Object} */ - this.creativeMessageRouters_ = {}; + this.creativeEventRouters_ = {}; this.win_.addEventListener('message', event => { const messageContainer = this.extractMessage_(event); @@ -112,20 +112,20 @@ export class EventRouter { const transportId = event['transportId']; const message = event['message']; try { - if (!this.creativeMessageRouters_[transportId]) { - this.creativeMessageRouters_[transportId] = + if (!this.creativeEventRouters_[transportId]) { + this.creativeEventRouters_[transportId] = new CreativeEventRouter( this.win_, this.sentinel_, transportId); try { this.win_.onNewAmpAnalyticsInstance( - this.creativeMessageRouters_[transportId]); + this.creativeEventRouters_[transportId]); } catch (e) { user().error(TAG_, 'Caught exception in' + ' onNewAmpAnalyticsInstance: ' + e.message); throw e; } } - this.creativeMessageRouters_[transportId] + this.creativeEventRouters_[transportId] .sendMessageToListener(message); } catch (e) { user().error(TAG_, 'Failed to pass message to event listener: ' + @@ -149,16 +149,16 @@ export class EventRouter { * @returns {!Object.} * @VisibleForTesting */ - getCreativeMethodRouters() { - return this.creativeMessageRouters_; + getCreativeEventRouters() { + return this.creativeEventRouters_; } /** - * Gets rid of the mapping to EventRouter + * Gets rid of the mapping to CreativeEventRouter * @VisibleForTesting */ reset() { - this.creativeMessageRouters_ = {}; + this.creativeEventRouters_ = {}; } /** @@ -239,7 +239,8 @@ export class CreativeEventRouter { sendMessageToListener(message) { if (!this.eventListener_) { dev().warn(TAG_, 'Attempted to send message when no listener' + - ' configured in ' + this.transportId_ + '. Be sure to' + + ' configured. Sentinel=' + this.sentinel_ + ', TransportID=' + + this.transportId_ + '. Be sure to' + ' call registerCreativeEventListener() within' + ' onNewAmpAnalyticsInstance()!'); return; diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js index 136066dff9cc..732ef04054ba 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js @@ -102,7 +102,7 @@ describe('ampanalytics-lib', () => { }); it('initially has empty creativeMessageRouters mapping ', () => { - expect(Object.keys(router.getCreativeMethodRouters())).to.have.lengthOf(0); + expect(Object.keys(router.getCreativeEventRouters())).to.have.lengthOf(0); }); it('makes registration function available ', () => { @@ -120,7 +120,7 @@ describe('ampanalytics-lib', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { expect(ampAnalytics instanceof CreativeEventRouter) .to.be.true; - expect(Object.keys(router.getCreativeMethodRouters())) + expect(Object.keys(router.getCreativeEventRouters())) .to.have.lengthOf(1); ampAnalytics.registerCreativeEventListener(events => { expect(events).to.have.lengthOf(1); @@ -154,7 +154,7 @@ describe('ampanalytics-lib', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { expect(ampAnalytics instanceof CreativeEventRouter) .to.be.true; - expect(Object.keys(router.getCreativeMethodRouters())) + expect(Object.keys(router.getCreativeEventRouters())) .to.have.lengthOf(1); ampAnalytics.registerCreativeEventListener(events => { expect(events).to.have.lengthOf(3); From a7f05ea2fbf90fded8f2770dbd81d80202df9759 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Mon, 24 Jul 2017 13:53:35 -0400 Subject: [PATCH 70/85] Adds missing space in error msg --- extensions/amp-analytics/0.1/amp-analytics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amp-analytics/0.1/amp-analytics.js b/extensions/amp-analytics/0.1/amp-analytics.js index b751be5686f9..783012a52d4c 100644 --- a/extensions/amp-analytics/0.1/amp-analytics.js +++ b/extensions/amp-analytics/0.1/amp-analytics.js @@ -419,7 +419,7 @@ export class AmpAnalytics extends AMP.BaseElement { // TODO(zhouyx, #7096) Track overwrite percentage. Prevent transport overwriting if (inlineConfig['transport'] || this.remoteConfig_['transport']) { const TAG = this.getName_(); - user().error(TAG, 'Inline or remote config should not' + + user().error(TAG, 'Inline or remote config should not ' + 'overwrite vendor transport settings'); } } From 2f9bec5a223b71d902c2e181438dde6f7b15bd4a Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 26 Jul 2017 11:01:12 -0400 Subject: [PATCH 71/85] Addresses review feedback re: renaming files/class, and object field access. Have not yet addressed the comments about IframeMessagingClient nor simplifying to eliminate CreativeEventRouter. --- ...tics-lib.js => iframe-transport-client.js} | 22 +++++++++---------- build-system/app.js | 1 + build-system/config.js | 2 +- build-system/tasks/presubmit-checks.js | 2 +- ...lib.js => test-iframe-transport-client.js} | 12 +++++----- gulpfile.js | 8 +++---- src/iframe-transport-common.js | 2 +- 7 files changed, 24 insertions(+), 25 deletions(-) rename 3p/{ampanalytics-lib.js => iframe-transport-client.js} (93%) rename extensions/amp-analytics/0.1/test/{test-ampanalytics-lib.js => test-iframe-transport-client.js} (95%) diff --git a/3p/ampanalytics-lib.js b/3p/iframe-transport-client.js similarity index 93% rename from 3p/ampanalytics-lib.js rename to 3p/iframe-transport-client.js index 8574975eee1d..3b26c4258e89 100644 --- a/3p/ampanalytics-lib.js +++ b/3p/iframe-transport-client.js @@ -26,13 +26,13 @@ initLogConstructor(); setReportError(() => {}); /** @private @const {string} */ -const TAG_ = 'ampanalytics-lib'; +const TAG_ = 'iframe-transport-client'; /** * Receives event messages bound for this cross-domain iframe, from all * creatives */ -export class EventRouter { +export class IframeTransportClient { /** @param {!Window} win */ constructor(win) { @@ -109,24 +109,22 @@ export class EventRouter { 'Must implement onNewAmpAnalyticsInstance in ' + this.win_.location.href); events.forEach(event => { - const transportId = event['transportId']; - const message = event['message']; try { - if (!this.creativeEventRouters_[transportId]) { - this.creativeEventRouters_[transportId] = + if (!this.creativeEventRouters_[event.transportId]) { + this.creativeEventRouters_[event.transportId] = new CreativeEventRouter( - this.win_, this.sentinel_, transportId); + this.win_, this.sentinel_, event.transportId); try { this.win_.onNewAmpAnalyticsInstance( - this.creativeEventRouters_[transportId]); + this.creativeEventRouters_[event.transportId]); } catch (e) { user().error(TAG_, 'Caught exception in' + ' onNewAmpAnalyticsInstance: ' + e.message); throw e; } } - this.creativeEventRouters_[transportId] - .sendMessageToListener(message); + this.creativeEventRouters_[event.transportId] + .sendMessageToListener(event.message); } catch (e) { user().error(TAG_, 'Failed to pass message to event listener: ' + e.message); @@ -181,9 +179,9 @@ export class EventRouter { if (!window.AMP_TEST) { try { - new EventRouter(window); + new IframeTransportClient(window); } catch (e) { - user().error(TAG_, 'Failed to construct EventRouter: ' + + user().error(TAG_, 'Failed to construct IframeTransportClient: ' + e.message); } } diff --git a/build-system/app.js b/build-system/app.js index ca50bb95c1d0..0450975a96e6 100644 --- a/build-system/app.js +++ b/build-system/app.js @@ -938,6 +938,7 @@ app.get(['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], app.get('/dist/ampanalytics-lib.js', (req, res, next) => { req.url = req.url.replace(/dist/, 'dist.3p/current'); + req.url = req.url.replace(/ampanalytics-lib/, 'iframe-transport-client'); next(); }); diff --git a/build-system/config.js b/build-system/config.js index f22fd9e76824..4b31d336959f 100644 --- a/build-system/config.js +++ b/build-system/config.js @@ -96,7 +96,7 @@ module.exports = { '!{node_modules,build,dist,dist.tools,' + 'dist.3p/[0-9]*,dist.3p/current-min}/**/*.*', '!dist.3p/current/**/ampcontext-lib.js', - '!dist.3p/current/**/ampanalytics-lib.js', + '!dist.3p/current/**/iframe-transport-client.js', '!validator/dist/**/*.*', '!validator/node_modules/**/*.*', '!validator/nodejs/node_modules/**/*.*', diff --git a/build-system/tasks/presubmit-checks.js b/build-system/tasks/presubmit-checks.js index 0f17df259d7d..1b24e89c9737 100644 --- a/build-system/tasks/presubmit-checks.js +++ b/build-system/tasks/presubmit-checks.js @@ -282,7 +282,7 @@ var forbiddenTerms = { whitelist: [ '3p/integration.js', '3p/ampcontext-lib.js', - '3p/ampanalytics-lib.js', + '3p/iframe-transport-client.js', 'ads/alp/install-alp.js', 'ads/inabox/inabox-host.js', 'dist.3p/current/integration.js', diff --git a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js similarity index 95% rename from extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js rename to extensions/amp-analytics/0.1/test/test-iframe-transport-client.js index 732ef04054ba..8a478065b66a 100644 --- a/extensions/amp-analytics/0.1/test/test-ampanalytics-lib.js +++ b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js @@ -18,9 +18,9 @@ import { IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, } from '../../../../src/iframe-transport-common'; import { - EventRouter, + IframeTransportClient, CreativeEventRouter, -} from '../../../../3p/ampanalytics-lib'; +} from '../../../../3p/iframe-transport-client'; import {dev, user} from '../../../../src/log'; import {Timer} from '../../../../src/service/timer-impl'; import {adopt} from '../../../../src/runtime'; @@ -40,7 +40,7 @@ function createUniqueId() { return String(++(nextId)); } -describe('ampanalytics-lib', () => { +describe('iframe-transport-client', () => { let sandbox; const timer = new Timer(window); let badAssertsCounterStub; @@ -52,8 +52,8 @@ describe('ampanalytics-lib', () => { badAssertsCounterStub = sandbox.stub(); sentinel = createUniqueId(); window.name = '{"sentinel": "' + sentinel + '"}'; - sandbox.stub(EventRouter.prototype, 'subscribeTo'); - router = new EventRouter(window); + sandbox.stub(IframeTransportClient.prototype, 'subscribeTo'); + router = new IframeTransportClient(window); sandbox.stub(dev(), 'assert', (condition, msg) => { if (!condition) { badAssertsCounterStub(msg); @@ -92,7 +92,7 @@ describe('ampanalytics-lib', () => { const oldWindowName = window.name; expect(() => { window.name = ''; - new EventRouter(window); + new IframeTransportClient(window); }).to.throw(/Cannot read property 'sentinel' of undefined/); window.name = oldWindowName; }); diff --git a/gulpfile.js b/gulpfile.js index 642c9a6f747f..b8491c5e5c80 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -265,9 +265,9 @@ function compile(watch, shouldMinify, opt_preventRemoveAndMakeDir, include3pDirectories: true, includePolyfills: false, }), - compileJs('./3p/', 'ampanalytics-lib.js', + compileJs('./3p/', 'iframe-transport-client.js', './dist.3p/' + (shouldMinify ? internalRuntimeVersion : 'current'), { - minifiedName: 'ampanalytics-v0.js', + minifiedName: 'iframe-transport-client-v0.js', checkTypes: opt_checkTypes, watch: watch, minify: shouldMinify, @@ -679,8 +679,8 @@ function checkTypes() { includePolyfills: true, checkTypes: true, }), - closureCompile(['./3p/ampanalytics-lib.js'], './dist', - 'ampanalytics-check-types.js', { + closureCompile(['./3p/iframe-transport-client.js'], './dist', + 'iframe-transport-client-check-types.js', { externs: ['ads/ads.extern.js'], include3pDirectories: true, includePolyfills: true, diff --git a/src/iframe-transport-common.js b/src/iframe-transport-common.js index 83209bd32f23..de9f3be732b7 100644 --- a/src/iframe-transport-common.js +++ b/src/iframe-transport-common.js @@ -21,7 +21,7 @@ */ export const IFRAME_TRANSPORT_EVENTS_TYPE = 'IframeTransportEvents'; -/** @typedef {Object} */ +/** @typedef {{transportId: string, message: string}} */ export let IframeTransportEvent; // An event, and the transport ID of the amp-analytics tags that // generated it. For instance if the creative with transport From 66d0cc473d9dc83d00dbe0e5643a1d4cdc3337a3 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 26 Jul 2017 11:18:23 -0400 Subject: [PATCH 72/85] Switch to IframeMessagingClient, add clarifying comment --- 3p/iframe-transport-client.js | 11 +++++++---- .../0.1/test/test-iframe-transport-client.js | 6 +++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/3p/iframe-transport-client.js b/3p/iframe-transport-client.js index 3b26c4258e89..3164e2a796ff 100644 --- a/3p/iframe-transport-client.js +++ b/3p/iframe-transport-client.js @@ -19,6 +19,7 @@ import {tryParseJson} from '../src/json'; import {dev, user, initLogConstructor, setReportError} from '../src/log'; import {IFRAME_TRANSPORT_EVENTS_TYPE} from '../src/iframe-transport-common'; import {getData} from '../src/event-helper'; +import {IframeMessagingClient} from './iframe-messaging-client'; initLogConstructor(); // TODO(alanorozco): Refactor src/error.reportError so it does not contain big @@ -47,6 +48,11 @@ export class IframeTransportClient { return; } + /** @protected {!IframeMessagingClient} */ + this.client_ = new IframeMessagingClient(win); + this.client_.setHostWindow(this.win_); + this.client_.setSentinel(this.sentinel_); + /** * Multiple creatives on a page may wish to use the same type of * amp-analytics tag. This object provides a mapping between the @@ -87,10 +93,7 @@ export class IframeTransportClient { * @VisibleForTesting */ subscribeTo(messageType) { - this.win_.parent./*OK*/postMessage(/** @type {JsonObject} */ ({ - sentinel: this.sentinel_, - type: messageType, - }), '*'); + this.client_./*OK*/sendMessage(messageType); } /** diff --git a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js index 8a478065b66a..64cf92c11d89 100644 --- a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js +++ b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js @@ -52,7 +52,11 @@ describe('iframe-transport-client', () => { badAssertsCounterStub = sandbox.stub(); sentinel = createUniqueId(); window.name = '{"sentinel": "' + sentinel + '"}'; - sandbox.stub(IframeTransportClient.prototype, 'subscribeTo'); + // This is OK because we're not stubbing something that lives in an + // iframe at all in this test, so certainly not in a cross-domain one. + // It's just something that has "Iframe" in its name, which triggered + // the presubmit rule. + sandbox./*OK*/stub(IframeTransportClient.prototype, 'subscribeTo'); router = new IframeTransportClient(window); sandbox.stub(dev(), 'assert', (condition, msg) => { if (!condition) { From 545ac7bf5bc928e4f5c4d8554e01cfac6f5bc075 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 26 Jul 2017 13:59:03 -0400 Subject: [PATCH 73/85] Now uses IframeMessagingClient --- 3p/iframe-transport-client.js | 171 ++++++------------ .../0.1/test/test-iframe-transport-client.js | 21 +-- 2 files changed, 60 insertions(+), 132 deletions(-) diff --git a/3p/iframe-transport-client.js b/3p/iframe-transport-client.js index 3164e2a796ff..a1af5cc73d90 100644 --- a/3p/iframe-transport-client.js +++ b/3p/iframe-transport-client.js @@ -18,7 +18,6 @@ import './polyfills'; import {tryParseJson} from '../src/json'; import {dev, user, initLogConstructor, setReportError} from '../src/log'; import {IFRAME_TRANSPORT_EVENTS_TYPE} from '../src/iframe-transport-common'; -import {getData} from '../src/event-helper'; import {IframeMessagingClient} from './iframe-messaging-client'; initLogConstructor(); @@ -40,19 +39,6 @@ export class IframeTransportClient { /** @private {!Window} */ this.win_ = win; - /** @const {string} */ - this.sentinel_ = user().assertString( - tryParseJson(this.win_.name)['sentinel'], - 'Invalid/missing sentinel on iframe name attribute' + this.win_.name); - if (!this.sentinel_) { - return; - } - - /** @protected {!IframeMessagingClient} */ - this.client_ = new IframeMessagingClient(win); - this.client_.setHostWindow(this.win_); - this.client_.setSentinel(this.sentinel_); - /** * Multiple creatives on a page may wish to use the same type of * amp-analytics tag. This object provides a mapping between the @@ -63,85 +49,58 @@ export class IframeTransportClient { */ this.creativeEventRouters_ = {}; - this.win_.addEventListener('message', event => { - const messageContainer = this.extractMessage_(event); - if (this.sentinel_ != messageContainer['sentinel']) { - return; - } - user().assert(messageContainer['type'], - 'Received message with missing type in ' + this.win_.location.href); - user().assert(messageContainer['events'], - 'Received empty message in ' + this.win_.location.href); - user().assert( - messageContainer['type'] == IFRAME_TRANSPORT_EVENTS_TYPE, - 'Received unrecognized message type ' + messageContainer['type'] + - ' in ' + this.win_.location.href); - this.processEventsMessage_( - /** - * @type {!Array<../src/iframe-transport-common.IframeTransportEvent>} - */ - (messageContainer['events'])); - }, false); - - this.subscribeTo(IFRAME_TRANSPORT_EVENTS_TYPE); - } - - /** - * Sends a message to the parent frame, requesting to subscribe to a - * particular message type - * @param messageType The type of message to subscribe to - * @VisibleForTesting - */ - subscribeTo(messageType) { - this.client_./*OK*/sendMessage(messageType); - } - - /** - * Handle receipt of a message indicating that creative(s) have sent - * event(s) to this frame - * @param {!Array} - * events An array of events - * @private - */ - processEventsMessage_(events) { - user().assert(events && events.length, - 'Received empty events list in ' + this.win_.location.href); - this.win_.onNewAmpAnalyticsInstance = - this.win_.onNewAmpAnalyticsInstance || null; - user().assert(this.win_.onNewAmpAnalyticsInstance, - 'Must implement onNewAmpAnalyticsInstance in ' + - this.win_.location.href); - events.forEach(event => { - try { - if (!this.creativeEventRouters_[event.transportId]) { - this.creativeEventRouters_[event.transportId] = - new CreativeEventRouter( - this.win_, this.sentinel_, event.transportId); - try { - this.win_.onNewAmpAnalyticsInstance( - this.creativeEventRouters_[event.transportId]); - } catch (e) { - user().error(TAG_, 'Caught exception in' + - ' onNewAmpAnalyticsInstance: ' + e.message); - throw e; - } - } - this.creativeEventRouters_[event.transportId] - .sendMessageToListener(event.message); - } catch (e) { - user().error(TAG_, 'Failed to pass message to event listener: ' + - e.message); - } - }); - } - - /** - * Test method to ensure sentinel set correctly - * @returns {string} - * @VisibleForTesting - */ - getSentinel() { - return this.sentinel_; + /** @protected {!IframeMessagingClient} */ + this.client_ = new IframeMessagingClient(win); + this.client_.setHostWindow(this.win_.parent); + this.client_.setSentinel(dev().assertString( + tryParseJson(this.win_.name)['sentinel'], + 'Invalid/missing sentinel on iframe name attribute' + this.win_.name)); + this.client_.makeRequest( + IFRAME_TRANSPORT_EVENTS_TYPE, + IFRAME_TRANSPORT_EVENTS_TYPE, + eventData => { + user().assert(eventData['type'], + 'Received message with missing type in ' + + this.win_.location.href); + user().assert(eventData['type'] == IFRAME_TRANSPORT_EVENTS_TYPE, + 'Received unrecognized message type ' + eventData['type'] + + ' in ' + this.win_.location.href); + const events = + /** + * @type {!Array<../src/iframe-transport-common.IframeTransportEvent>} + */ + (eventData['events']); + user().assert(events, + 'Received malformed events list in ' + this.win_.location.href); + dev().assert(events.length, + 'Received empty events list in ' + this.win_.location.href); + this.win_.onNewAmpAnalyticsInstance = + this.win_.onNewAmpAnalyticsInstance || null; + user().assert(this.win_.onNewAmpAnalyticsInstance, + 'Must implement onNewAmpAnalyticsInstance in ' + + this.win_.location.href); + events.forEach(event => { + try { + if (!this.creativeEventRouters_[event.transportId]) { + this.creativeEventRouters_[event.transportId] = + new CreativeEventRouter(this.win_, event.transportId); + try { + this.win_.onNewAmpAnalyticsInstance( + this.creativeEventRouters_[event.transportId]); + } catch (e) { + user().error(TAG_, + 'Exception in onNewAmpAnalyticsInstance: ' + e.message); + throw e; + } + } + this.creativeEventRouters_[event.transportId] + .sendMessageToListener(event.message); + } catch (e) { + user().error(TAG_, 'Failed to pass message to event listener: ' + + e.message); + } + }); + }); } /** @@ -163,20 +122,12 @@ export class IframeTransportClient { } /** - * Takes the raw postMessage event, and extracts from it the actual data - * payload - * @param event - * @returns {JsonObject} - * @private + * Gets the IframeMessagingClient + * @returns {!IframeMessagingClient} + * @VisibleForTesting */ - extractMessage_(event) { - user().assert(event, 'Received null event in ' + this.win_.name); - const data = String(getData(event)); - user().assert(data, 'Received empty event in ' + this.win_.name); - let startIndex; - user().assert((startIndex = data.indexOf('-') + 1) > 0, - 'Received truncated events message in ' + this.win_.name); - return tryParseJson(data.substr(startIndex)) || null; + getClient() { + return this.client_; } } @@ -196,17 +147,13 @@ if (!window.AMP_TEST) { export class CreativeEventRouter { /** * @param {!Window} win The enclosing window object - * @param {!string} sentinel The communication sentinel of this iframe * @param {!string} transportId The ID of the creative to route messages * to/from */ - constructor(win, sentinel, transportId) { + constructor(win, transportId) { /** @private {!Window} */ this.win_ = win; - /** @private {!string} */ - this.sentinel_ = sentinel; - /** @private {!string} */ this.transportId_ = transportId; @@ -240,7 +187,7 @@ export class CreativeEventRouter { sendMessageToListener(message) { if (!this.eventListener_) { dev().warn(TAG_, 'Attempted to send message when no listener' + - ' configured. Sentinel=' + this.sentinel_ + ', TransportID=' + + ' configured. TransportID=' + this.transportId_ + '. Be sure to' + ' call registerCreativeEventListener() within' + ' onNewAmpAnalyticsInstance()!'); diff --git a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js index 64cf92c11d89..5b1133129ad6 100644 --- a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js +++ b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js @@ -52,11 +52,6 @@ describe('iframe-transport-client', () => { badAssertsCounterStub = sandbox.stub(); sentinel = createUniqueId(); window.name = '{"sentinel": "' + sentinel + '"}'; - // This is OK because we're not stubbing something that lives in an - // iframe at all in this test, so certainly not in a cross-domain one. - // It's just something that has "Iframe" in its name, which triggered - // the presubmit rule. - sandbox./*OK*/stub(IframeTransportClient.prototype, 'subscribeTo'); router = new IframeTransportClient(window); sandbox.stub(dev(), 'assert', (condition, msg) => { if (!condition) { @@ -102,7 +97,7 @@ describe('iframe-transport-client', () => { }); it('sets sentinel from window.name.sentinel ', () => { - expect(router.getSentinel()).to.equal(sentinel); + expect(router.getClient().sentinel_).to.equal(sentinel); }); it('initially has empty creativeMessageRouters mapping ', () => { @@ -140,20 +135,6 @@ describe('iframe-transport-client', () => { ]})); }); - it('asserts when onNewAmpAnalyticsInstance is not implemented ', () => { - window.onNewAmpAnalyticsInstance = null; - send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ - events: [ - {transportId: '102', message: 'hello, world!'}, - ]})); - return timer.promise(POST_MESSAGE_DELAY).then(() => { - expect(badAssertsCounterStub.callCount > 0).to.be.true; - expect(badAssertsCounterStub.calledWith( - sinon.match(/Must implement onNewAmpAnalyticsInstance/))).to.be.true; - return Promise.resolve(); - }); - }); - it('receives multiple event messages ', () => { window.onNewAmpAnalyticsInstance = ampAnalytics => { expect(ampAnalytics instanceof CreativeEventRouter) From c7b6c8833a9e011b3e6a0cb7e01a1ed38bcb04ce Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 26 Jul 2017 14:34:46 -0400 Subject: [PATCH 74/85] Greatly simplified iframe-transport-client --- 3p/iframe-transport-client.js | 136 ++---------------- ...alytics-iframe-transport-remote-frame.html | 14 +- .../0.1/test/test-iframe-transport-client.js | 9 -- 3 files changed, 17 insertions(+), 142 deletions(-) diff --git a/3p/iframe-transport-client.js b/3p/iframe-transport-client.js index a1af5cc73d90..9595e36ee766 100644 --- a/3p/iframe-transport-client.js +++ b/3p/iframe-transport-client.js @@ -39,15 +39,10 @@ export class IframeTransportClient { /** @private {!Window} */ this.win_ = win; - /** - * Multiple creatives on a page may wish to use the same type of - * amp-analytics tag. This object provides a mapping between the - * IDs which identify which amp-analytics tag a message is to/from, - * with each ID's corresponding CreativeEventRouter, - * which is an object that handles messages to/from a particular creative. - * @private {!Object} - */ - this.creativeEventRouters_ = {}; + // Necessary, or else check-types will complain "Property + // processAmpAnalyticsEvent never defined on Window" + this.win_.processAmpAnalyticsEvent = + this.win_.processAmpAnalyticsEvent || null; /** @protected {!IframeMessagingClient} */ this.client_ = new IframeMessagingClient(win); @@ -59,68 +54,30 @@ export class IframeTransportClient { IFRAME_TRANSPORT_EVENTS_TYPE, IFRAME_TRANSPORT_EVENTS_TYPE, eventData => { - user().assert(eventData['type'], - 'Received message with missing type in ' + - this.win_.location.href); - user().assert(eventData['type'] == IFRAME_TRANSPORT_EVENTS_TYPE, - 'Received unrecognized message type ' + eventData['type'] + - ' in ' + this.win_.location.href); const events = /** - * @type {!Array<../src/iframe-transport-common.IframeTransportEvent>} + * @type + * {!Array<../src/iframe-transport-common.IframeTransportEvent>} */ (eventData['events']); user().assert(events, 'Received malformed events list in ' + this.win_.location.href); dev().assert(events.length, 'Received empty events list in ' + this.win_.location.href); - this.win_.onNewAmpAnalyticsInstance = - this.win_.onNewAmpAnalyticsInstance || null; - user().assert(this.win_.onNewAmpAnalyticsInstance, - 'Must implement onNewAmpAnalyticsInstance in ' + + user().assert(this.win_.processAmpAnalyticsEvent, + 'Must implement processAmpAnalyticsEvent in ' + this.win_.location.href); events.forEach(event => { try { - if (!this.creativeEventRouters_[event.transportId]) { - this.creativeEventRouters_[event.transportId] = - new CreativeEventRouter(this.win_, event.transportId); - try { - this.win_.onNewAmpAnalyticsInstance( - this.creativeEventRouters_[event.transportId]); - } catch (e) { - user().error(TAG_, - 'Exception in onNewAmpAnalyticsInstance: ' + e.message); - throw e; - } - } - this.creativeEventRouters_[event.transportId] - .sendMessageToListener(event.message); + this.win_.processAmpAnalyticsEvent(event.message, event.transportId); } catch (e) { - user().error(TAG_, 'Failed to pass message to event listener: ' + - e.message); + user().error(TAG_, + 'Exception in processAmpAnalyticsEvent: ' + e.message); } }); }); } - /** - * Gets the mapping of creative senderId to - * CreativeEventRouter - * @returns {!Object.} - * @VisibleForTesting - */ - getCreativeEventRouters() { - return this.creativeEventRouters_; - } - - /** - * Gets rid of the mapping to CreativeEventRouter - * @VisibleForTesting - */ - reset() { - this.creativeEventRouters_ = {}; - } - /** * Gets the IframeMessagingClient * @returns {!IframeMessagingClient} @@ -139,74 +96,3 @@ if (!window.AMP_TEST) { e.message); } } - -/** - * Receives messages bound for this cross-domain iframe, from a particular - * creative. - */ -export class CreativeEventRouter { - /** - * @param {!Window} win The enclosing window object - * @param {!string} transportId The ID of the creative to route messages - * to/from - */ - constructor(win, transportId) { - /** @private {!Window} */ - this.win_ = win; - - /** @private {!string} */ - this.transportId_ = transportId; - - /** @private - * {?function(!Array)} */ - this.eventListener_ = null; - } - - /** - * Registers a callback function to be called when AMP Analytics events occur. - * There may only be one listener. If another function has previously been - * registered as a listener, it will no longer receive events. - * @param {!function(!string)} - * listener A function that takes an event string, and does something with - * it. - */ - registerCreativeEventListener(listener) { - if (this.eventListener_) { - dev().warn(TAG_, 'Replacing existing eventListener for ' + - this.transportId_); - } - this.eventListener_ = listener; - } - - /** - * Receives message(s) from a creative for the cross-domain iframe - * and passes them to that iframe's listener, if a listener has been - * registered - * @param {!string} message The event message that was received - */ - sendMessageToListener(message) { - if (!this.eventListener_) { - dev().warn(TAG_, 'Attempted to send message when no listener' + - ' configured. TransportID=' + - this.transportId_ + '. Be sure to' + - ' call registerCreativeEventListener() within' + - ' onNewAmpAnalyticsInstance()!'); - return; - } - try { - this.eventListener_(message); - } catch (e) { - user().error(TAG_, 'Caught exception executing listener for ' + - this.transportId_ + ': ' + e.message); - } - } - - /** - * @returns {!string} - * @VisibleForTesting - */ - getTransportId() { - return this.transportId_; - } -} - diff --git a/examples/analytics-iframe-transport-remote-frame.html b/examples/analytics-iframe-transport-remote-frame.html index 06d216236a96..19a0e4bc7585 100644 --- a/examples/analytics-iframe-transport-remote-frame.html +++ b/examples/analytics-iframe-transport-remote-frame.html @@ -14,14 +14,12 @@ * @param ampAnalytics Call registerAmpAnalyticsEventListener() on this, * passing your function which will receive the analytics events. */ - window.onNewAmpAnalyticsInstance = ampAnalytics => { - ampAnalytics.registerCreativeEventListener(event => { - // Now, do something meaningful with the AMP Analytics event - console.log("The page at: " + window.location.href + - " has received an event: " + event + - " from the creative with transport ID: " + - ampAnalytics.getTransportId()); - }); + window.processAmpAnalyticsEvent = (event, transportId) => { + // Now, do something meaningful with the AMP Analytics event + console.log("The page at: " + window.location.href + + " has received an event: " + event + + " from the creative with transport ID: " + + transportId); }; // Load the script specified in the iframe’s name attribute: diff --git a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js index 5b1133129ad6..1353946dc65a 100644 --- a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js +++ b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js @@ -22,19 +22,11 @@ import { CreativeEventRouter, } from '../../../../3p/iframe-transport-client'; import {dev, user} from '../../../../src/log'; -import {Timer} from '../../../../src/service/timer-impl'; import {adopt} from '../../../../src/runtime'; import * as sinon from 'sinon'; adopt(window); -/** - * @const {number} - * Testing postMessage necessarily involves race conditions. Set this high - * enough to avoid flakiness. - */ -const POST_MESSAGE_DELAY = 100; - let nextId = 5000; function createUniqueId() { return String(++(nextId)); @@ -42,7 +34,6 @@ function createUniqueId() { describe('iframe-transport-client', () => { let sandbox; - const timer = new Timer(window); let badAssertsCounterStub; let router; let sentinel; From 88b6c4c0df48605d77f71710081866fde22d2e21 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 26 Jul 2017 14:39:17 -0400 Subject: [PATCH 75/85] Updates unit tests --- .../0.1/test/test-iframe-transport-client.js | 54 ++----------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js index 1353946dc65a..311342dd73cf 100644 --- a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js +++ b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js @@ -91,62 +91,14 @@ describe('iframe-transport-client', () => { expect(router.getClient().sentinel_).to.equal(sentinel); }); - it('initially has empty creativeMessageRouters mapping ', () => { - expect(Object.keys(router.getCreativeEventRouters())).to.have.lengthOf(0); - }); - - it('makes registration function available ', () => { - window.onNewAmpAnalyticsInstance = ampAnalytics => { - expect(ampAnalytics.registerCreativeEventListener).to.exist; - ampAnalytics.registerCreativeEventListener(() => {}); - }; - send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ - events: [ - {transportId: '100', message: 'hello, world!'}, - ]})); - }); - it('receives an event message ', () => { - window.onNewAmpAnalyticsInstance = ampAnalytics => { - expect(ampAnalytics instanceof CreativeEventRouter) - .to.be.true; - expect(Object.keys(router.getCreativeEventRouters())) - .to.have.lengthOf(1); - ampAnalytics.registerCreativeEventListener(events => { - expect(events).to.have.lengthOf(1); - events.forEach(event => { - expect(ampAnalytics.getTransportId()).to.equal('101'); - expect(event).to.equal('hello, world!'); - }); - }); + window.processAmpAnalyticsEvent = (event, transportId) => { + expect(ampAnalytics.getTransportId()).to.equal('101'); + expect(event).to.equal('hello, world!'); }; send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ events: [ {transportId: '101', message: 'hello, world!'}, ]})); }); - - it('receives multiple event messages ', () => { - window.onNewAmpAnalyticsInstance = ampAnalytics => { - expect(ampAnalytics instanceof CreativeEventRouter) - .to.be.true; - expect(Object.keys(router.getCreativeEventRouters())) - .to.have.lengthOf(1); - ampAnalytics.registerCreativeEventListener(events => { - expect(events).to.have.lengthOf(3); - events.forEach(() => { - expect(ampAnalytics.getTransportId()).to.equal('103'); - }); - expect(events[0]).to.equal('something happened'); - expect(events[1]).to.equal('something else happened'); - expect(events[2]).to.equal('a third thing happened'); - }); - }; - send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ - events: [ - {transportId: '103', message: 'something happened'}, - {transportId: '103', message: 'something else happened'}, - {transportId: '103', message: 'a third thing happened'}, - ]})); - }); }); From 015c48ae71071ae950fc80262b92705b606d8789 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 26 Jul 2017 14:40:10 -0400 Subject: [PATCH 76/85] Changes whitespace only --- 3p/iframe-transport-client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/3p/iframe-transport-client.js b/3p/iframe-transport-client.js index 9595e36ee766..4c32b456d058 100644 --- a/3p/iframe-transport-client.js +++ b/3p/iframe-transport-client.js @@ -69,7 +69,8 @@ export class IframeTransportClient { this.win_.location.href); events.forEach(event => { try { - this.win_.processAmpAnalyticsEvent(event.message, event.transportId); + this.win_.processAmpAnalyticsEvent(event.message, + event.transportId); } catch (e) { user().error(TAG_, 'Exception in processAmpAnalyticsEvent: ' + e.message); From ef8fb8d70cc8e14f03e5023b7437115f57dd2ecd Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 26 Jul 2017 14:50:14 -0400 Subject: [PATCH 77/85] Fixes a broken unit test --- .../amp-analytics/0.1/test/test-iframe-transport-client.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js index 311342dd73cf..8d615510d285 100644 --- a/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js +++ b/extensions/amp-analytics/0.1/test/test-iframe-transport-client.js @@ -19,7 +19,6 @@ import { } from '../../../../src/iframe-transport-common'; import { IframeTransportClient, - CreativeEventRouter, } from '../../../../3p/iframe-transport-client'; import {dev, user} from '../../../../src/log'; import {adopt} from '../../../../src/runtime'; @@ -93,7 +92,7 @@ describe('iframe-transport-client', () => { it('receives an event message ', () => { window.processAmpAnalyticsEvent = (event, transportId) => { - expect(ampAnalytics.getTransportId()).to.equal('101'); + expect(transportId).to.equal('101'); expect(event).to.equal('hello, world!'); }; send(IFRAME_TRANSPORT_EVENT_MESSAGES_TYPE, /** @type {!JsonObject} */ ({ From e7c031195196ddc89cb61f12d5c09cd450550fd2 Mon Sep 17 00:00:00 2001 From: jonkeller Date: Wed, 26 Jul 2017 15:06:59 -0400 Subject: [PATCH 78/85] Changes comment only --- .../analytics-iframe-transport-remote-frame.html | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/analytics-iframe-transport-remote-frame.html b/examples/analytics-iframe-transport-remote-frame.html index 19a0e4bc7585..ebaab8f1358e 100644 --- a/examples/analytics-iframe-transport-remote-frame.html +++ b/examples/analytics-iframe-transport-remote-frame.html @@ -5,14 +5,13 @@ Requests Frame