-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Amp analytics 3p vendors #9661
Amp analytics 3p vendors #9661
Conversation
@@ -148,6 +148,10 @@ exports.rules = [ | |||
{ | |||
filesMatching: '{src,extensions}/**/*.js', | |||
mustNotDependOn: '3p/**/*.js', | |||
whitelist: [ | |||
'extensions/amp-analytics/0.1/transport.js->' + | |||
'3p/iframe-messaging-client.js', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This exception is added because the amp-analytics tag needs to send messages to the third-party iframe. It covers the import of the file that contains sendMessage().
@@ -317,6 +317,7 @@ var forbiddenTerms = { | |||
'3p/ampcontext.js', | |||
'3p/ampcontext-integration.js', | |||
'dist.3p/current/integration.js', // includes previous | |||
'extensions/amp-analytics/0.1/transport.js', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This exception is added because the amp-analytics tag needs to send messages to the third-party iframe. It covers the actual calls to the sendMessage() method.
@@ -0,0 +1,188 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upon approval of its content, this file will be placed at a public/static URL. In so doing, we'll include a small set of other AMP files, which will resolve (or enable to be resolved) the TODOs on lines 17, 44 and 139.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, pending separate code review
const frame = createElementWithAttributes(ampDoc, 'iframe', { | ||
sandbox: 'allow-scripts', | ||
name: JSON.stringify({ | ||
'scriptSrc': '/examples/analytics-3p-remote-frame-helper.js', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in the comments of /examples/analytics-3p-remote-frame-helper.js, upon approval that frame will be placed at a public/static URL. Once that is done, the value on line 249 must change to that URL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be an absolute path
*/ | ||
processCrossDomainIframeResponse_(msg) { | ||
// Used in ../../../src/anchor-click-interceptor.js | ||
this.win.document.mostRecentCrossDomainIframeResponse = msg; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^^ Reviewers: extra attention requested on this line. Is there a better place to store this? The logic is that, when there are multiple ads with amp-analytics tags on the page, then if the user clicks on ad A, we want to use the most recent response from ad A. But if they click on ad B, we want to use the most recent response from ad B. So I've stored the response as an attribute on the ad's document (not the containg AMP publisher page). Then anchorClickInterceptor pulls it from the event target's document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to do this with an enclosed object as a field which maps the creative to its last message, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but enclosed in what? AnchorClickInterceptor knows what "A" tag has been clicked, and can get from there to the enclosing document in O(1) time. But if I store it in the amp-analytics tag then we'd have to go walk the DOM tree to find it at the time of the click.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Note that it's enclosed in the anchor click listener not amp-analytics, to avoid anchor click listener having a dependency on amp-analytics which lives in extensions.
* @param {string} request | ||
* @param {!Object<string, string>} transportOptions | ||
*/ | ||
export function sendRequest(win, request, transportOptions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not deleted, just moved inside the Transport class below, because needs to maintain extraData as non-static.
if (!frameData.isReady) { | ||
timerFor(window).delay(() => { | ||
this.sendExtraData(frameUrl, extraData); | ||
}, 10); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue here is that the sequence could go:
- Ad A amp-analytics tag calls processCrossDomainIframe() (line 164).
- That calls createCrossDomainIframe_(), which sets up the frame load promise (line 254)
- Ad B amp-analytics tag calls processCrossDomainIframe() too, before the load promise from Remove usages of for-of #2 has fired. So there is no point in doing sendMessage() (line 232) yet since the recipient isn't listening yet.
I'd like to explore chaining the loadPromise somehow but personally I see this as an optimization that is not necessary for phase 1. Please let me know if there is disagreement here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of things:
- After consideration, I do not want to use load as an indicator that the slot is ready. Instead assume library within the frame will send a message indicating it is ready to which you will response
- Given Add commit hooks #1, you should have sendExtraData (or send any post messages) store pending messages in a queue and send them upon receiving the ready message (you should consider grouping into a single message to ensure no potential race conditions though I assume post message receiver would always get them in the order they were sent)
- The ready signal should trigger the queue to be flushed for all creatives waiting on the frame
Waiting for an arbitrary 10 ms seems odd...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, cool. Am already doing the queue idea for event messages, but not for extraData. Will not be difficult to switch to using the queue for all. Will also not be difficult to switch from load to a proactive send.
* limitations under the License. | ||
*/ | ||
|
||
const RTV_VERSION = '1234'; // TODO Replace with import from AMP, or just remove |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you can just put
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (removed entire line)
class AmpAnalyticsRemoteFrameManager { | ||
constructor() { | ||
/** | ||
* @type {Function<!String>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type def should be: {!function(string)} but I'm curious why you bother with an empty function? Would seem better to allow these to be null by default and then only bother calling them when they are not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, can change.
(I did it that way because "if (listener_) { listener_(); }" or "listener_ && listener_()" seemed uglier than what's here IMO)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* @param {!String} message The message that was received. | ||
*/ | ||
receiveMessageFromCreative(message) { | ||
if (message && message.data) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer returns in order to remove indents. Something like:
if (!message || !message.data) {
return;
}
if (!deserialized) {
return;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, here and elsewhere
*/ | ||
setSentinel_(sentinel) { | ||
if (this.sentinel_ && sentinel != this.sentinel_) { | ||
console.warn("Attempting to set sentinel to " + sentinel + " when it" + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dev().warn and use single quotes for strings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix (in concert with bringing in the AMP imports - as-is, dev() is not defined yet)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done but temporarily added function dev() { return console; } at the bottom of the page, which can stay until I do the AMP imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done completely now
console.warn("Attempting to set sentinel to " + sentinel + " when it" + | ||
" was already set to " + this.sentinel_); | ||
} | ||
this.sentinel_ = sentinel; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this safe? It implies that you'll honor the first sentinel you receive? Would be preferable to set the sentinel at the time you create the analytics instance as presumably you're passing it to the 3p frame
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sentinel is created in transport.js, in createCrossDomainIframe():
iframeMessagingClient.setSentinel(Transport.createUniqueId_());
So each frame (since we may have multiple vendors) gets a unique sentinel, which associated to the frame URL in a map that is static to the Transport class. And then this is indeed passed to the 3p frame (this file is running in the 3p frame).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed - now set sentinel on frame's name attr at creation time instead.
Transport.incrementFrameUsageCount_(frameUrl); | ||
this.sendExtraData(frameUrl, transportOptions['extraData']); | ||
} else { | ||
const frame = this.createCrossDomainIframe_(ampDoc, frameUrl, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider creating getOrCreateCrossDomainFrame which combines this logic in an easily testable location?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Called it beginUsingCrossDomainIframe_() since get...() implies a return value and none was needed. Name pairs with existing doneUsing...()
// Regardless of whether we just created it, or are re-using an existing | ||
// one, wire up the response callback | ||
if (processResponse) { | ||
const imc = Transport.crossDomainIframes_[frameUrl].iframeMessagingClient; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
give this variable a better name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I also dislike the current name. The line is precisely 80 chars, hence choosing something with length=3. Will switch to better name and just bite the bullet and carriage-return on "."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* Sends extra data (from the amp-analytics config object) to the | ||
* cross-domain iframe. | ||
* @param {!string} frameUrl The URL of the frame to send the data to | ||
* @param {?string=} extraData The data to send to the frame |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is ? needed (would you expect null?) and variable name should start with opt_
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, here and elsewhere.
if (!frameData.isReady) { | ||
timerFor(window).delay(() => { | ||
this.sendExtraData(frameUrl, extraData); | ||
}, 10); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of things:
- After consideration, I do not want to use load as an indicator that the slot is ready. Instead assume library within the frame will send a message indicating it is ready to which you will response
- Given Add commit hooks #1, you should have sendExtraData (or send any post messages) store pending messages in a queue and send them upon receiving the ready message (you should consider grouping into a single message to ensure no potential race conditions though I assume post message receiver would always get them in the order they were sent)
- The ready signal should trigger the queue to be flushed for all creatives waiting on the frame
Waiting for an arbitrary 10 ms seems odd...
const frame = createElementWithAttributes(ampDoc, 'iframe', { | ||
sandbox: 'allow-scripts', | ||
name: JSON.stringify({ | ||
'scriptSrc': '/examples/analytics-3p-remote-frame-helper.js', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be an absolute path
gulpfile.js
Outdated
@@ -228,6 +228,18 @@ function compile(watch, shouldMinify, opt_preventRemoveAndMakeDir, | |||
includePolyfills: false, | |||
}), | |||
|
|||
compileJs('./3p/', 'ampanalytics-lib.js', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: This file was changed by another PR which will require resolving
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't complete the review but some initial proposed changes to the API and flow
class AmpAnalyticsRemoteFrameManager { | ||
constructor() { | ||
/** | ||
* @type {function<!string>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{?function}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...in fact, this file was renamed to /3p/ampanalytics-lib.js so you're definitely looking at old stuff.
this.listener_ = null; | ||
|
||
/** | ||
* @type {Function<!String>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{?function}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outdated
|
||
/** | ||
* Holds a mapping between sender ID and extra data | ||
* @type {Map} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...and actually I just changed all my instances of Map back to Object, because now that my "gulp check-types" is working correctly again, it complains about those:
ERROR - Cannot do '[]' access on a struct
|
||
/** | ||
* Registers a callback function to be called when AMP Analytics events occur | ||
* @param {!Function} listener A function that takes an array of event |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
types should match field types. Any reason this couldn't be part of the constructor? If a frame is loading the library, I think we can assume it wants to listen to any inbound messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be happy to make this change. But, you previously preferred the frame to contain this:
window.onNewAmpAnalyticsInstance = ampAnalytics => {
ampAnalytics.registerAmpAnalytics3pEventsListener(events => {
...which means that ampAnalytics must exist in order to be passed to the frame code. Is this no longer preferable?
* passing your function which will receive the analytics events. | ||
*/ | ||
window.onNewAmpAnalyticsInstance = ampAnalytics => { | ||
ampAnalytics.registerAmpAnalyticsEventListener( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking a better name would be onAmpAnalyticsEvents
const frameData = Transport.crossDomainIframes_[frameUrl]; | ||
const iframeMessagingClient = frameData.iframeMessagingClient; | ||
iframeMessagingClient.registerCallback( | ||
'ampAnalytics3pReady', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally the enum declaring these message types would be in a common location shared between AMP analytics and the library to ensure they remain consistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree.
} | ||
iframeMessagingClient.registerCallback( | ||
'ampAnalytics3pResponse', response => { | ||
if (!response || !response.ampAnalytics3pResponse) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be dev().assert(response && response.ampAnalytics3pResponse) as I would assume you expect them to be present?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
if (!response || !response.ampAnalytics3pResponse) { | ||
return; | ||
} | ||
opt_processResponse(this.type_, response.ampAnalytics3pResponse); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider removing opt_processResponse existence check and instead put it here such that if you receive a message you did not expect, you do dev().warn('unexpected response...') so that developers can get feedback as to why integration is not working
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
3p/ampanalytics-lib.js
Outdated
if (window.onNewAmpAnalyticsInstance) { | ||
window.onNewAmpAnalyticsInstance(remoteFrameHelper_); | ||
} else { | ||
dev().error(TAG_, 'Vendor page must implement onNewAmpAnalyticsInstance.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add ' prior to loading library script.' I would also advocate that we wait to create the remoteFrameHelper instance until we get a message indicating a new amp analytics instance has been created as we don't need it until then. The flow is:
- create xdomain frame for provider passing sentinel and lib source path in name
- xdomain frame executes by loading script which send out 'ready' post message
- AMP analytics responds to ready post message by sending 'new instance' message (with any additional state)
- xdomain frame receives 'new instance' message (which I believe you're referring to as "extraData"?) and calls window.onNewAmpAnalyticsInstance
For any additional ad slots that use the same vendor, window.onNewAmpAnalyticsInstance would be called again and should use the latest value (e.g. a vendor could re-assign onNewAmpAnalyticsInstance between slot instances which is ugly but not impossible).
We need to move away from using an extraData listener and instead you should call onNewAmpAnalyticsInstance whenever you receive a "new slot" message. This will ensure the vendor can properly enclose a slot's state in a single call to onNewAmpAnalyticsInstance. Does that make sense?
}); | ||
}); | ||
// The following is optional: | ||
ampAnalytics.registerAmpAnalytics3pExtraDataListener(extraDataMsgs => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, we should remove the registerAmpAnalytics3pExtraDataListener and instead you should either pass the initial state in onNewAmpAnalyticsInstance (e.g. (ampAnalytics, startUpState)) or make it a field of the ampAnalytics instance such that it has 1 field (startUpState) and 2 functions (onAmpAnalytics3pEvents & sendMessageToCreative)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about I just pass it in the frame's name attribute?
externs: ['ads/ads.extern.js',], | ||
include3pDirectories: true, | ||
includePolyfills: false, | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type checking issues I was having yesterday on unchanged files are fixed. I had duplicated the "closureCompile(['./3p/ampcontext-lib.js'], './dist'," block beginning on line 653 and changed "ampcontext" to "ampanalytics". It also made "gulp check-types" not report any typing errors in my actual code. Removing that fixed the compile issue, and allowed me to see my own typing errors again. I am currently investigating whether that code is necessary, and if so, whether its re-inclusion now that my typing errors are resolved will be better behaved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, putting it back in hoses things again. The block of code is:
closureCompile(['./3p/ampanalytics-lib.js'], './dist', 'ampanalytics-check-types.js', { externs: ['ads/ads.extern.js'], include3pDirectories: true, includePolyfills: true, checkTypes: true, }),
The errors are stuff like this:
src/services.js:225: ERROR - Bad type annotation. Unknown type module$src$service$template_impl.Templates return /** @type {!./service/template-impl.Templates} */ ( ^
(the caret points to the dot when not using a variable-width font...)
3p/ampanalytics-lib.js
Outdated
/** @private {string} */ | ||
this.sentinel_ = null; | ||
|
||
/** @private {Object<string, AmpAnalytics3pCreativeMessageRouter>} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!Object<string, !AmpAnalytics3pCreativeMessageRouter>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
this.creativeMessageRouters_ = []; | ||
|
||
/** | ||
* Simple requestIdleCallback polyfill |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
native requestIdleCallback when available, setTimeout based polyfill otherwise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
let newId; | ||
do { | ||
newId = String(Math.random()).substr(2); | ||
} while (Transport.usedIds_[newId]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let newId;
while (Transport.usedIds[(newId = String(Math.random()).substr(2))]);
Transport.usedIds_[newId] = true;
return newId;
Why do you substring? Possibly unending while loops make me nervous as they could cause the browser to hang. Just to be uber safe, let's bound this to a max # of iterations (say 20).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Substring: because without it the string will be like "0.xxxxxxxx" instead of "xxxxxxxx" and that looked ugly.
I agree we don't want it to hang. And likely the universe will end before it chooses and already-chosen number 20x in a row. But there is still the tiny chance that it will therefore return a number that is not unique. Are we okay with that miniscule risk?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm hoping we move to incrementing number but assuming we don't, I would argue the probability of an inadvertent, neverending loop is higher than 20x random not creating unique ID and as mentioned, you could append the type (e.g. moat) to ensure its truly unique.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. Moved to incrementing number.
3p/ampanalytics-lib.js
Outdated
/** @private {!Window} */ | ||
this.win_ = win; | ||
|
||
/** @private {string} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment that the sentinel is required by iframe messaging client and you use it to uniquely identify the frame as part of message routing? FWIW - you could have used the type given it must be unique, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment done.
Not sure what you mean about the type...a frame can send multiple responses. If the order goes:
- creative 1 creates xframe
- creative 2 wants to use xframe
- xframe renders
...then xframe will send ready messages to both creatives in order to release their queues
3p/ampanalytics-lib.js
Outdated
*/ | ||
this.requestIdleCallback = | ||
this.win_.requestIdleCallback.bind(this.win_) || | ||
(cb => setTimeout(cb, 1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not use timeout delay of 0? I believe most browsers enforce a minimum of higher anyway (5 by my recollection)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
iframeMessagingClient.registerCallback( | ||
MessageTypes.ampAnalytics3pReady, () => { | ||
iframeMessagingClient.setHostWindow(frameData.frame.contentWindow); | ||
Transport.setIsReady_(frameUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have reference to frameData so might as well pass to isReady_ directly instead of imposing a lookup. In general, you should only ever need to lookup frameData once here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* @param {string=} opt_extraData The data to send to the frame | ||
* @private | ||
*/ | ||
beginUsingCrossDomainIframe_(ampDoc, frameUrl, opt_extraData) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have this return frameData so that you do not need to look it up again within processCrossDomainIframe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if (frameData.sendTimer) { | ||
return; // Timer is already about to fire, no need to set a new one | ||
} | ||
frameData.sendTimer = Transport.timer_.delay(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The negative with this approach is that a new message will ALWAYS need to wait the throttle time even if there has been no messaging traffic. The intent here is to not send them to often so I suggest moving to an interval based solution which you clear when destroying the frameData (note that you currently do not actually cancel the timer, at least not that I could find nor do you attempt to flush any existing messages)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you mean, send the first one immediately, and then set an invariant "I will not send again for at least 100ms", and if subsequent ones come in during that 100ms queue them until that time has elapsed? I like this.
(Currently implementation destroys the timer about 4 lines down from here, upon it firing.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point regarding destruction is that its possible you set the delay, unlayoutCallback executes clearing a bunch on state, and then the timeout runs leading to errors. To be safe, you should ensure that unlayoutCallback cancels the timer.
removeElement(iframe); | ||
}, 5000); | ||
}; | ||
user().assert( | ||
dev().assert( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you downgrade to dev?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't touch the sendRequestUsingIframe() method at all. Note that it is comparing line 141 to line 426, so I suspect that's just the diff tool trying to gain an efficiency when it shouldn't. Want me to change it to dev(), though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could it be an merge issue? I believe it should match whatever is in master.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh. It is user() in master...guess it was a merge issue after all. Fixed.
src/3p-analytics-common.js
Outdated
*/ | ||
|
||
/** @private @const {Object<string,string>} */ | ||
export const MessageTypes = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not be plural and consider a name more specific to your usage case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
*/ | ||
this.iframeMessagingClient_ = new IframeMessagingClient(win); | ||
try { | ||
const frameNameData = JSON.parse(this.win_.name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you move this parse up in the constructor, you can make sentinel a const and pass it directly to the frame. I'm curious why we don't allow passing sentinel in the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
3p/ampanalytics-lib.js
Outdated
this.iframeMessagingClient_ = new IframeMessagingClient(win); | ||
try { | ||
const frameNameData = JSON.parse(this.win_.name); | ||
if (frameNameData.sentinel) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currently if there is valid JSON in the name but no sentinel, the catch block does not execute
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
3p/ampanalytics-lib.js
Outdated
*/ | ||
receiveMessage(message) { | ||
if (message[AMP_ANALYTICS_3P_MESSAGE_TYPE.NEW]) { | ||
this.extraData_ = message[AMP_ANALYTICS_3P_MESSAGE_TYPE.NEW]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally onNewAmpAnalyticsInstance is called here but I believe you're working on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. Done but not committed/pushed yet.
3p/ampanalytics-lib.js
Outdated
* in the queue. | ||
*/ | ||
sendQueuedMessagesToListeners() { | ||
if (this.receivedMessagesQueue_.length == 0 || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, You don't need the == 0, can instead do !this.receivedMessagesQueue_
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have switched to (!this.receivedMessagesQueue.length) but what you said is not correct:
var foo = [];
if (foo) { console.log('truthy'); } else { console.log('falsey'); }
truthy
3p/ampanalytics-lib.js
Outdated
this.eventListeners_.length == 0) { | ||
return; | ||
} | ||
this.eventListeners_.forEach(listener => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.eventListeners_.forEach(listener => listener(this. receivedMessagesQueue_));
We should consider wrapping each listener call with try/catch as otherwise the first throwing would not only cause the rest not to execute, it would cause the queue not to be cleared.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moot in this particular location due to refactor, but done in new location.
3p/iframe-messaging-client.js
Outdated
@@ -33,7 +33,7 @@ export class IframeMessagingClient { | |||
this.win_ = win; | |||
/** @private {?string} */ | |||
this.rtvVersion_ = getMode().rtvVersion || null; | |||
/** @private {!Window} */ | |||
/** @private {!(Window|Element)} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a type specific to frames?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried this originally, but unfortunately there is not.
`
3p/iframe-messaging-client.js:131: ERROR - Bad type annotation. Unknown type Frame
- @param {!(Window|Frame)} win The window to communicate with. This may
`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try HTMLIFrameElement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. (Where did you find that, so I don't have to bug you next time I need to type something as a checkbox or whatever?)
3p/ampanalytics-lib.js
Outdated
'Received empty events envelope'); | ||
// Iterate through the array of events, dispatching each to the | ||
// listener for the appropriate creative | ||
envelope[AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENTS].forEach(event => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to loop through them here, because they may have come from different creatives
3p/ampanalytics-lib.js
Outdated
initLogConstructor(); | ||
// TODO(alanorozco): Refactor src/error.reportError so it does not contain big | ||
// transitive dependencies and can be included here. | ||
setReportError(() => {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re: your suggestion over lunch, lines 23-25 are from ampcontext, and fix the issue of assert() complaining that ampdoc does not exist.
/** @type {Array<( | ||
* ../../../src/3p-analytics-common.ampAnalytics3pEvent| | ||
* ../../../src/3p-analytics-common.ampAnalytics3pNewCreative | ||
* )>} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this preferable to a linter exception to the 80-char line, but if you feel otherwise I am happy to change it.
@@ -0,0 +1,164 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored the message queueing/sending stuff from transport.js to here.
const typedMessageObject = | ||
/** @type {../../../src/3p-analytics-common.ampAnalytics3pEvent} */ | ||
(messageObject); | ||
return typedMessageObject; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did it this way because if you do:
return
/** @type {../../../src/3p-analytics-common.ampAnalytics3pEvent} */
(messageObject);
...then it interprets the "return" as a complete statement with no semicolon, and the subsequent two lines are unreachable code.
includePolyfills: true, | ||
checkTypes: true, | ||
}), | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uncomment, or delete?
src/3p-analytics-common.js
Outdated
* limitations under the License. | ||
*/ | ||
|
||
// TODO(jonkeller): Extern this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's already externed, just a vestigial comment. Removed comment already...will commit/push with next set of changes, if any.
3p/ampanalytics-lib.js
Outdated
* Receives messages bound for this cross-domain iframe, from all creatives | ||
*/ | ||
class AmpAnalytics3pMessageRouter { | ||
/** @param {!Window} win */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: add line here and remove first, empty line in constructor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
// The sentinel is required by iframe-messaging-client, and is used to | ||
// uniquely identify the frame as part of message routing | ||
/** @const {string} */ | ||
this.sentinel_ = frameNameData.sentinel; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move this outside of the try/catch and change to this.sentinel_ = frameNameData && frameNameData.sentinel;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
const frameNameData = JSON.parse(this.win_.name); | ||
if (!frameNameData.sentinel) { | ||
const ex = {message: 'JSON parsed, but did not include sentinel.'}; | ||
throw ex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be throw new Error('JSON parsed...');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
return; | ||
} | ||
|
||
/** @private {!Object<string, !AmpAnalytics3pCreativeMessageRouter>} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add some documentation explaining the purpose of this object and the value of its keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
this.iframeMessagingClient_.setSentinel(this.sentinel_); | ||
this.iframeMessagingClient_.registerCallback( | ||
AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVES, | ||
envelope => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
envelope is an odd variable name IMO. What exactly is the purpose of this object? What are its values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant it in a similar sense to its use in https://docs.oracle.com/cd/E19509-01/820-4408/gguuw/index.html or https://www.tutorialspoint.com/soap/soap_message_structure.htm but somewhat less formal - it's just what I always use to name something whose sole purpose is to contain other thing(s) during transit (since that's the purpose of a real-world envelope). Happy to change if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed
@@ -278,6 +298,17 @@ export class AmpAnalytics extends AMP.BaseElement { | |||
} | |||
|
|||
/** | |||
* Receives any response that may be sent from the cross-domain iframe | |||
* @param {!string} type The type parameter of the cross-domain iframe | |||
* @param {Object} response The response message from the iframe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the response be typed to a known def?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, but to be honest I don't like it. The iframe creates an arbitrary object, which is passed to sendMessageToCreative(), which then wraps that in a AmpAnalytics3pResponse. I liked the symmetry of the amp-analytics tag receiving exactly what the iframe created, with Transport doing the unwrapping. Now that AmpAnalytics.processCrossDomainIframeResponse_() is receiving an AmpAnalytics3pResponse, that symmetry is broken. (We could restore it by requiring the iframe to create the AmpAnalytics3pResponse directly, but I am less in favor of that.)
@@ -748,8 +796,8 @@ export class AmpAnalytics extends AMP.BaseElement { | |||
* Merges two objects. If the value is array or plain object, the values are | |||
* merged otherwise the value is overwritten. | |||
* | |||
* @param {Object|Array} from Object or array to merge from | |||
* @param {Object|Array} to Object or Array to merge into | |||
* @param {(Object|Array)} from Object or array to merge from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why add parens here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
(The "why" is that at Taymon's suggestion, I tried this when I was experiencing all those type-checking errors last week in files that I didn't touch. They didn't fix it, and I later found something else which did, but did not remove these parens.)
@@ -791,8 +839,8 @@ export class AmpAnalytics extends AMP.BaseElement { | |||
} | |||
|
|||
/** | |||
* @param {!Object<string, Object<string, string|Array<string>>>} source1 | |||
* @param {!Object<string, Object<string, string|Array<string>>>} source2 | |||
* @param {!Object<string, Object<string, (string|Array<string>)>>} source1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
* contain the queued messages (see 3p-analytics-common) | ||
*/ | ||
constructor(iframeMessagingClient, envelopeType) { | ||
/** @type {Array<( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!Array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if (this.timerId_) { | ||
return; // Timer is already about to fire, no need to set a new one | ||
} | ||
this.timerId_ = CrossDomainIframeMessageQueue.timer_.delay(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still suffers from the same problem where message after long inactivity will result in it being unnecessarily delayed. I recommend moving to a setInterval approach which does nothing if the queue is empty. Seems simpler to me and gives the correct behavior of throttling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that my method unnecessarily delays the first message after a long delay.
But I think that your proposed method is also (albeit less) wasteful, in that you're going to have an interval go off frequently when I bet most of the time there will be no work for it to do.
What about an approach where the first message gets sent immediately, and then we say that no more messages will be sent for the next x milliseconds (we'll queue them and send them once that expires). Something like this pseudocode:
if (door is unlocked) {
send the message NOW
lock the door
setTimeout({ unlock the door; flush the queue; }, 100ms);
} else {
enqueue the message
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, needs tests and I tend to agree that the abstract queue arrangement seems a bit over engineered.
3p/ampanalytics-lib.js
Outdated
if (!frameNameData.sentinel) { | ||
throw new Error('JSON parsed, but did not include sentinel.'); | ||
} | ||
// The sentinel is required by iframe-messaging-client, and is used to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be moved to just above the this.sentinel_ line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops. Done.
3p/ampanalytics-lib.js
Outdated
let frameNameData = null; | ||
try { | ||
frameNameData = JSON.parse(this.win_.name); | ||
if (!frameNameData.sentinel) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would structure this slightly differently:
let frameNameData = null;
try { frameNameData = JSON.parse(this.win_.name); } catch (e) {}
this.sentinel_ = user().assertString(frameNameData && frameNameData.sentinel, 'Invalid/missing sentinel on iframe name attribute', this.win_.name);
if (!this.sentinel_) { return; }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. (Note that assertString() only takes 2 args. So changed your last comma to a + )
3p/ampanalytics-lib.js
Outdated
this.iframeMessagingClient_.registerCallback( | ||
AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, | ||
messageContainer => { | ||
dev().assert(messageContainer.data, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider checking empty array case: dev().assert(messageContainer.data && messageContainer.data.length)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (Object.entries(messageContainer.data).length since it's an object not an array)
3p/ampanalytics-lib.js
Outdated
Object.entries(messageContainer.data).forEach(entry => { | ||
const creativeId = entry[0]; | ||
const extraData = entry[1]; | ||
if (this.creativeMessageRouters_.hasOwnProperty(creativeId)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can collapse to: dev().assert(!this.creativeMessageRouters_[creativeId])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
dev().warn(TAG_, 'Duplicate new creative message for ' + creativeId); | ||
} | ||
this.creativeMessageRouters_[creativeId] = | ||
this.creativeMessageRouters_[creativeId] || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to do || case as this.creativeMessageRouters_[creativeId] should not exist
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
this.throttledFlushQueue_ = throttle(this.win_, () => { | ||
this.flushQueue_(); | ||
}, MESSAGE_THROTTLE_TIME_); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: remove extra line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* @private | ||
*/ | ||
flushQueue_() { | ||
if (this.isReady_ && Object.keys(this.store_).length > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
greater than 0 is not necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* iframe | ||
*/ | ||
enqueue(senderId, opt_data) { | ||
if (this.store_.hasOwnProperty(senderId)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should never happen, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. Will change to assert() instead of warn().
dev().warn(TAG_, 'Replacing existing extra data for: ' + senderId); | ||
} | ||
this.store_[senderId] = opt_data || ''; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: remove extra line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* @param {string=} opt_data The data to be enqueued and then sent to the | ||
* iframe | ||
*/ | ||
enqueue(senderId, opt_data) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like both implement unqueue so this should be part of your abstract class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Intentional. They implement it differently, since for new creatives each entry in .creativeToPendingMessages_ is <string, string> since you can only have one extraData per creative, whereas for events it's <string, Array < string >> since one creative can have any number of events. I originally had just the parent class with no subclasses, and this is actually what made me split it into the current class structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good. Just needs unit test coverage and try to remove ResponseMap.
3p/ampanalytics-lib.js
Outdated
// The sentinel is required by iframe-messaging-client, and is used to | ||
// uniquely identify the frame as part of message routing | ||
/** @const {string} */ | ||
this.sentinel_ = dev().assertString( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry you can further reduce code here by using src/json#tryParseJson:
this.sentinel_ = dev().assertString(tryParseJson(this.win_.name, {}).sentinel, 'Invalid/missing sentinel on iframe name attribute' + this.win_.name);
if (!this.sentinel_) {
return;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool find! Done.
3p/ampanalytics-lib.js
Outdated
AMP_ANALYTICS_3P_MESSAGE_TYPE.CREATIVE, | ||
messageContainer => { | ||
dev().assert( | ||
messageContainer.data && Object.entries(messageContainer.data).length, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For all I know the JS compiler optimizes this but currently you're converting into an array twice so instead do:
let entries;
dev().assert(messageContainer.data && (entries = Object.entries(messageContainer.data)).length, ...);
entries.forEach(entry =>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
this.iframeMessagingClient_ = iframeMessagingClient; | ||
|
||
/** | ||
* @private {!string} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: make all of these one line to match format of iframeMessageClient_ doc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
this.extraData_ = opt_extraData; | ||
|
||
/** | ||
* private {?function(!Array<!AmpAnalytics3pEvent>)=} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing @
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that it didn't like the = at the end, so did not include that
/** @type {!Object<string,!string|!Array<string>>} */ | ||
this.creativeToPendingMessages_ = {}; | ||
|
||
this.throttledFlushQueue_ = throttle(this.win_, () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs doc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
opt_processResponse( | ||
this.type_, | ||
/** @type {!../../../src/3p-analytics-common.AmpAnalytics3pResponse} */ (response)); // eslint-disable-line max-len |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just put (response)) on next line so you can remove the disable line length?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because the /** */ itself exceeds the limit
static doneUsingCrossDomainIframe(ampDoc, transportOptions) { | ||
const frameUrl = transportOptions['iframe']; | ||
const frameData = Transport.getFrameData_(frameUrl); | ||
if (!frameData) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this ever occur?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose not. Removed.
setStyles(frame, | ||
{width: 0, height: 0, display: 'none', | ||
position: 'absolute', top: 0, left: 0}); | ||
/** @const {FrameData} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you want this to be: const frameData = /** @type {!FrameData} */ ({....});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
/** @private @const {!Object<string,boolean>} */ | ||
Transport.usedIds_ = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed. I wonder if we could make the linter catch this...
build-system/amp.extern.js
Outdated
@@ -60,6 +60,20 @@ window.AMP_CONFIG.errorReportingUrl; | |||
|
|||
window.AMP_CONTEXT_DATA; | |||
|
|||
// AMP-Analytics Cross-domain iframes | |||
/** @enum {string} */ | |||
const AMP_ANALYTICS_3P_MESSAGE_TYPE = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you already include this in a common util class, I don't believe you need it in externs as the compiler will have enforced type check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial thoughts. Need to keep going through it. Looks like we're still missing unit test coverage and removing ResponseMap in favor of a closure based solution
3p/ampanalytics-lib.js
Outdated
dev().assert(!this.creativeMessageRouters_[creativeId], | ||
'Duplicate new creative message for ' + creativeId); | ||
this.creativeMessageRouters_[creativeId] = | ||
new AmpAnalytics3pCreativeMessageRouter( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this indentation correct? I would have expected "new" to be indented further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
this.iframeMessagingClient_.registerCallback( | ||
AMP_ANALYTICS_3P_MESSAGE_TYPE.EVENT, | ||
messageContainer => { | ||
Object.entries(messageContainer.data).forEach(entry => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add dev().assert(isArray(messageContainer.data) && messageContainer.data.length); // isArray comes from src/types.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, in the style of lines 70-74 since messageContainer.data is an object, not an array.
3p/ampanalytics-lib.js
Outdated
const messages = entry[1]; | ||
dev().assert(messages && messages.length, | ||
'Received empty events list for' + creativeId); | ||
dev().assert(this.creativeMessageRouters_[creativeId], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that asserts throw so should we consider wrapping the contents in a try/catch to ensure one invalid data item doesn't cause all to be lost?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
3p/ampanalytics-lib.js
Outdated
this.eventListener_ = null; | ||
|
||
if (this.win_.onNewAmpAnalyticsInstance) { | ||
this.win_.onNewAmpAnalyticsInstance(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we handle if this throws?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a dev().error().
3p/iframe-messaging-client.js
Outdated
@@ -100,6 +100,17 @@ export class IframeMessagingClient { | |||
*/ | |||
setupEventListener_() { | |||
listen(this.win_, 'message', event => { | |||
try { | |||
if (!this.hostWindow_.postMessage && this.hostWindow_.contentWindow) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can assume all AMP browsers will have postMessage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not what I'm trying to do here. I'm trying to determine whether this.hostWindow_ is a window or a frame. If it's a frame, it won't have .postMessage(), it'll have .contentWindow.postMessage(). But I don't want to go straight for the .contentWindow.postMessage() check, because if it is a cross-domain window then that may throw.
I've split it into a helper method, and added comments, to make this clearer.
3p/iframe-messaging-client.js
Outdated
// contentWindow is now non-null. | ||
this.hostWindow_ = this.hostWindow_.contentWindow; | ||
} | ||
} catch (e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would occur if window is xdomained? Do we want to warn in that case? When would it happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't happen, given that I'm being careful in the "if" condition above (which is now extracted to another method, but works the same). Removed.
*/ | ||
constructor(win, iframeMessagingClient, messageType) { | ||
/** @private {boolean} */ | ||
this.isReady_ = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I like to put parameter based fields first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* ../../../src/3p-analytics-common.AmpAnalytics3pEvent} | ||
* @abstract | ||
*/ | ||
buildMessage_() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usual make the default content an error: throw new Error('Not yet implemented?!');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, I had originally thought of doing this, but https://github.com/google/closure-compiler/wiki/%40abstract-classes-and-methods says it's not allowed (second bullet point under "Rules" section)
Although, it only generates a warning, not an error:
WARNING - Misplaced @abstract annotation. function with a non-empty body cannot be abstract
Ideally we should change the type checking to allow an abstract method to contain a throw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PS Tried with @Suppress {misplacedTypeAnnotation} but that doesn't squelch the warning. There does not seem to be a reference to misplacedAbstractAnnotation in https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/DiagnosticGroups.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surprisingly, Travis will fail if it gets a warning. So, taking this throw back out :(
https://travis-ci.org/ampproject/amphtml/jobs/245496442
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have filed a feature request w/ the Closure compiler team for this.
type: this.messageType_, | ||
data: this.creativeToPendingMessages_, | ||
}); | ||
return message; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: you can put the object constructor right after return, save message variable creation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general yes, but then you're left with two bad choices. Choice 1:
return
/** @type {../../../src/3p-analytics-common.AmpAnalytics3pNewCreative} */
({
type: this.messageType_,
data: this.creativeToPendingMessages_,
});
In this case, the JS interpreter in its infinite wisdom thinks that "return" is a complete statement with no semicolon, so you're returning undefined, and what comes after that is unreachable code >:(
Choice 2:
return /** @type {../../../src/3p-analytics-common.AmpAnalytics3pNewCreative} */ ({
type: this.messageType_,
data: this.creativeToPendingMessages_,
});
Putting the opening paren (and therefore necessarily the casting comment) on a single line will do what we want semantically, but now we'd have to add a comment to tell the linter to ignore the line length violation.
I did it the way I did it for these reasons, though I agree it's stupid that I had to do so.
The Percy failure was due to #10022, which has now been fixed. |
… typedefs. Fixes error in which asserts cause error due to no ampdoc
…c flaw when sentinel is missing
…sage format, but new message format resulted in this being over-engineered
… warning makes Travis fail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see test coverage for transport but not any of the new classes you created. Are those in progress?
this.creativeMessageRouters_[creativeId] | ||
.sendMessagesToListener(messages); | ||
} catch (e) { | ||
dev().error(TAG_, 'Failed to send message to listener: ' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the term "send" is a bit misleading here as one may expect it to mean you postMessaged. Perhaps "Failed to execute listener"...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re: your above comment which GitHub UI won't let me comment on, I forgot to "git add" the file to test the new Queue classes. Will add that together with addressing your comments here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And re: the comment I'm actually responding to here, done. Changed to "Failed to pass message to event listener"
}); | ||
}); | ||
this.iframeMessagingClient_.sendMessage( | ||
AMP_ANALYTICS_3P_MESSAGE_TYPE.READY); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this fit on one line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
81 characters, unless I rename stuff.
try { | ||
this.win_.onNewAmpAnalyticsInstance(this); | ||
} catch (e) { | ||
dev().error(TAG_, 'Exception thrown by onNewAmpAnalyticsInstance in' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider moving onNewAmpAnalyticsInstance check prior to creating AmpAnalytics3pCreativeMessageRouter as AmpAnalytics3pMessageRouter essentially expects it to work properly. In other words, it would call router's sendMessagesToListener function even though client is not actually listening
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* @param {!Array<AmpAnalytics3pEvent>} messages The message that was received | ||
*/ | ||
sendMessagesToListener(messages) { | ||
if (!messages.length || !this.eventListener_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a dev().warn indicating messages are being lost and registerAmpAnalytics3pEventsListener should be called.
We could also try and recover from previous invalid/missing onNewAmpAnalyticsInstance here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the fix to your previous comment, it is no longer possible to reach here without implementing onNewAmpAnalyticsInstance, as the enclosing AmpAnalytics3pMessageRouter will now not have been created.
However, this could still happen if they have done the first line here but not the second:
window.onNewAmpAnalyticsInstance = ampAnalytics => {
ampAnalytics.registerAmpAnalytics3pEventsListener(events => {
Unfortunately we can't go and create that for them, so I just made the dev().warn message explicitly say "Attempted to send messages when no listener configured. Be sure to first call registerAmpAnalytics3pEventsListener()"
|
||
// Load the script specified in the iframe’s name attribute: | ||
script = document.createElement('script'); | ||
script.src = JSON.parse(window.name).scriptSrc; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fear this opens an XSS exploit for 3p analytic vendors. They shouldn't just trust the script src is to a valid AMP url. Instead we should either provide only the path to the file and hard code the domain or the vendor should have code verifying the domain. See how remote HTML loads the context script: https://github.com/ampproject/amphtml/blob/master/3p/remote.html#L9
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds like a good idea.
In Transport.createCrossDomainIframe, it sets the URL like this:
const scriptSrc = getMode().localDev
? '/dist.3p/current/ampanalytics-lib.js'
: `${urls.thirdParty}/$internalRuntimeVersion$/ampanalytics-v0.js`;
I just ran it and forced the non-local path, and that gave me:
https://3p.ampproject.net/$internalRuntimeVersion$/ampanalytics-v0.js
So, sure, I could definitely just send everything after "https://3p.ampproject.net", and hard-code that part on the client side.
Though this makes me also wonder if I'm missing something necessary for the substitution of the internal runtime version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm...though we'll also have to know on that side whether we're local, since I won't want to prepend the "https://3p.ampproject.net" in that case.
sandbox.stub(Transport, 'sendRequestUsingImage'); | ||
function setupStubs(crossDomainIframeRetval, imageRetval, | ||
beaconRetval, xhrRetval) { | ||
sandbox.stub(transport, 'sendRequestUsingCrossDomainIframe_').returns( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similarly consider making the function public but with @VisibleForTesting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, for all uses of private methods/members
@@ -48,69 +57,193 @@ describe('amp-analytics.transport', () => { | |||
'sendRequestUsingImage call count').to.equal(expectedImageCalls); | |||
} | |||
|
|||
function expectAllUnique(numArray) { | |||
if (!numArray) { | |||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why return true instead of just return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
|
||
it('must create xframe before sending message to it', () => { | ||
expect(() => { | ||
transport.sendRequest(window, 'https://example.com/test', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test referencing window makes me nervous and implies your test may not be hermetic...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to use a window object, to hang iframes on. Also the existing tests need one, to do stuff like win.navigator.sendBeacon(request, '');
But I agree it would be best to somehow reset it, or use some type of "test window" object that lives in the sinon sandbox. Do you know a way to do this?
sandbox.spy(transport, 'createCrossDomainIframe_'); | ||
transport.processCrossDomainIframe(window, config); | ||
|
||
assert(transport.useExistingOrCreateCrossDomainIframe_.calledOnce); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why aren't these expect (e.g. expect(transport.useExistingOrCreateCrossDomainIframe_).to.be.calledOnce; )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I thought they were equivalent, but just tested with assert(false) and expect(false).to.be.true and I see now that the latter has a more informative message.
Converted these to look like:
expect(transport.createCrossDomainIframe.calledOnce).to.be.true;
@@ -59,6 +60,14 @@ function maybeExpandUrlParams(ampdoc, e) { | |||
'CLICK_Y': () => { | |||
return e.pageY; | |||
}, | |||
'3PANALYTICS': (frameType, key) => { | |||
const responses = ResponseMap.get(ampdoc, frameType, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need responseMap? Idea was to use closure to keep track of data where message listener would update ampDoc.getAnchorClickListenerBinding() value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that, but it didn't seem like it was going to work. Though maybe I am missing something - I would be happy to pair on it if you'd like.
As it stands now, ResponseMap is now just a convenience wrapper around ampdoc's binding. I didn't want to spread the implementation details across both anchor-click-interceptor and amp-analytics, especially if they should ever change.
@@ -101,6 +101,15 @@ export class IframeMessagingClient { | |||
*/ | |||
setupEventListener_() { | |||
listen(this.win_, 'message', event => { | |||
if (this.hostWindowIsActuallyAnIframe_()) { | |||
// this.hostWindow_ can now be set to an iframe, after it has been |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why hostWindow can be an iframe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a chicken-and-egg problem. I've created this iframe, and I want to know when it is ready. So I want to say "I am willing to receive messages from this iframe's contentWindow". But the iframe's content window is not defined until it has finished rendering. So basically, if I can only use the window object, then I can't know it's ready unless I can receive messages from it, but I can't receive messages from it until I know it's ready.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should you wait for a handshake initiated from the iframe window?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, wait, you wanted to make the iframe client a "host". which should not be a case after you switched to SubscriptionApi.
'Cross-domain frame parameters missing ${this.type_}'); | ||
const frameData = this.useExistingOrCreateCrossDomainIframe_( | ||
win, frameUrl, transportOptions['extraData']); | ||
const iframeMessagingClient = frameData.iframeMessagingClient; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should you use SubscriptionApi
at host side?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keith, what do you think? It does seem like this would be better on the AMP side. But AFAICT the 3p side would have to revert to window.addEventListener("message", ...) which loses a lot of what we gain from iframeMessagingClient.
/** | ||
* Receives messages bound for this cross-domain iframe, from all creatives | ||
*/ | ||
class AmpAnalytics3pMessageRouter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to accelerate this PR, I suggest you split off the code into a 2nd PR. I can see clear boundary between the 2:
PR1: runtime side code
PR2: adding a library for 3p to use.
As long as there're clear API defined, you can easily separate the 2
@@ -152,6 +152,10 @@ exports.rules = [ | |||
{ | |||
filesMatching: '{src,extensions}/**/*.js', | |||
mustNotDependOn: '3p/**/*.js', | |||
whitelist: [ | |||
'extensions/amp-analytics/0.1/transport.js->' + | |||
'3p/iframe-messaging-client.js', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't do this
Enables amp-analytics tag to use third-party vendors. These vendors' code will be run in a non-AMP iframe.
One-pager: https://docs.google.com/document/d/1mCW7HbQBWQONTWA2q_rKNJyTEdqGOyhX-x-ces4JHWc/edit#