Pass context down for a4a ad rendering to allow window.context to function#7132
Pass context down for a4a ad rendering to allow window.context to function#7132lannka merged 34 commits intoampproject:masterfrom google:frizz-a4a-pass-context
Conversation
| @@ -0,0 +1,25 @@ | |||
| <!doctype html> | |||
There was a problem hiding this comment.
Is this file supposed to be part of this PR?
There was a problem hiding this comment.
no this shouldn't be. removed
extensions/amp-a4a/0.1/amp-a4a.js
Outdated
| }, SHARED_IFRAME_PROPERTIES)); | ||
| const sentinel = generateSentinel(window); | ||
| const metadata = getContextMetadata(window, iframe, sentinel); | ||
| iframe.setAttribute('name', encodeURIComponent(JSON.stringify(metadata))); |
There was a problem hiding this comment.
because this is getting written to the name attribute in the html, this was to protect against if anything in the metadata has a closing quote
There was a problem hiding this comment.
Why would it matter if something had a closing quote?
There was a problem hiding this comment.
My concern is an XSS injection, because we're writing this data into the page that could contain in the JSON a bunch of stuff that we're not controlling. So I meant that they could close the quote on the name attribute, and then like open a script tag or something. Like if the json object was:
{"foo":"<script>alert("foo");</script>}
that would be written right in the page.
There was a problem hiding this comment.
DOM manipulation doesn't work that way. iframe.setAttribute('name', someString) will never change anything other than the value of iframe.name no matter what's in someString.
There was a problem hiding this comment.
ok. if there's no risk then I guess we don't need encodeURIComponent
extensions/amp-a4a/0.1/amp-a4a.js
Outdated
| 'src': srcPath, | ||
| 'name': nameData, | ||
| }, SHARED_IFRAME_PROPERTIES)); | ||
| if (method == XORIGIN_MODE.NAMEFRAME){ |
There was a problem hiding this comment.
Add a space before the brace.
extensions/amp-a4a/0.1/amp-a4a.js
Outdated
| if (method == XORIGIN_MODE.NAMEFRAME){ | ||
| const sentinel = generateSentinel(window); | ||
| const metadata = getContextMetadata(window, iframe, sentinel); | ||
| let name = JSON.stringify({creative, _context: metadata._context}); |
There was a problem hiding this comment.
const, since it doesn't change. Also, I'd prefer for these string keys to be quoted; even if the actual Closure rules (which I don't fully understand) don't allow renaming of fields under these circumstances, it's better for no one reading the code to have to wonder about that.
There was a problem hiding this comment.
done, changed it so both creative and _context are the name of the local variable, so they variables are the keys and we don't need the strings anyways
src/attributes.js
Outdated
| * @param {!Window} parentWindow | ||
| * @param {!Element} element | ||
| * @return {!Object} Contains | ||
| * precedence over the data- attributes. |
There was a problem hiding this comment.
Did something get incorrectly copy-pasted here?
There was a problem hiding this comment.
yes that's screwed up, but this file actually also should not be in this PR (although it is needed for this pr). #7129 is where I am creating it, and this PR should block on that one's merging
|
Upon talking to keith, we are going to ignore the safeframe case for now and just focus on getting this working for nameframe and direct frame |
|
blocked by #7129 |
| .then(() => this.noContent_()); | ||
| } | ||
|
|
||
| this.element_.appendChild(this.iframe); |
There was a problem hiding this comment.
Appending this before setting all the styles could affect painting.
There was a problem hiding this comment.
would moving the setStyle line above the append be sufficient?
| } | ||
|
|
||
| this.element_.appendChild(this.iframe); | ||
| if (opt_isA4A) { |
There was a problem hiding this comment.
Why was this moved down?
There was a problem hiding this comment.
This was moved down because we want to listen for the no content message in the a4a case.
extensions/amp-a4a/0.1/amp-a4a.js
Outdated
| }, SHARED_IFRAME_PROPERTIES)); | ||
| if (method == XORIGIN_MODE.NAMEFRAME) { | ||
| const sentinel = generateSentinel(window); | ||
| const _context = getContextMetadata(window, iframe, sentinel)._context; |
There was a problem hiding this comment.
Can we expose this as a method?
There was a problem hiding this comment.
sure. I put it in amp-a4a, but maybe this should go in iframe-attributes.js?
lannka
left a comment
There was a problem hiding this comment.
I'm wondering what the status of those various ways of delayed rendering: nameframe, safeframe, cachedcontent.
Do we plan to eventually eliminate them and keep one? Maintaining them is a bit costly.
extensions/amp-a4a/0.1/amp-a4a.js
Outdated
| */ | ||
| generateSentinelAndContext(iframe) { | ||
| const sentinel = generateSentinel(window); | ||
| const context = getContextMetadata(window, iframe, sentinel)._context; |
extensions/amp-a4a/0.1/amp-a4a.js
Outdated
| */ | ||
| generateSentinelAndContext(iframe) { | ||
| const sentinel = generateSentinel(window); | ||
| const context = getContextMetadata(window, iframe, sentinel)._context; |
There was a problem hiding this comment.
looking at the impl of getContextMetadata, it seems weird that we're building an attributes object but end up using on one field of it. can't we just extract that part out as a separate method?
There was a problem hiding this comment.
this is actually a mistake, shouldn't be returning ._context, just the main object
| if (opt_isA4A) { | ||
| this.element_.appendChild(this.iframe); | ||
| // A4A writes creative frame directly to page therefore does not expect | ||
| // post message to unset visibility hidden |
There was a problem hiding this comment.
not sure what the "post message" is here.
There was a problem hiding this comment.
I'm also not sure, this was a holdover comment. I don't think that this postmessage ever happens. I'm going to delete this comment
There was a problem hiding this comment.
ah, i think it is referring to render-start and no-content events. Please update the doc.
| setStyle(this.iframe, 'visibility', ''); | ||
| if (this.baseInstance_.emitLifecycleEvent) { | ||
| this.baseInstance_.emitLifecycleEvent('adSlotUnhidden'); | ||
| if (opt_isA4A) { |
There was a problem hiding this comment.
any reason to move this code? A4A creative in friendly iframe will not send 'render-start' event, so there's no need to listen to it.
There was a problem hiding this comment.
because we want to register listening for 'no-content'
|
Regarding xdomain write methods (cached, safeframe, nameframe): once nameframe shows same metrics as safeframe, we will no longer use sameframe methodology. We may keep the cached way as currently it shows better metrics than safeframe (this is expected given it requires less browser resources) however my SRA design will necessitate the use of nameframe. If we find that majority of requests use nameframe, we can also remove cached method. |
3p/ampcontext.js
Outdated
| * @private | ||
| */ | ||
| getWindowSentinel_() { | ||
| this.sentinel = this.win_.AMP_SENTINEL || null; |
There was a problem hiding this comment.
I'm wondering who will be setting window.AMP_SENTINEL
There was a problem hiding this comment.
this is to allow a bypass with safeframe if needed, in which case we don't want to write on the sentinel or hash. instead we can write a script into the ad itself that sets this variable
There was a problem hiding this comment.
Can we make that more explicit by changing AMP_SENTINEL to another name?
There was a problem hiding this comment.
changed to SAFEFRAME_SENTINEL
3p/ampcontext.js
Outdated
| this.setupMetadata_(); | ||
| this.win_ = win; | ||
| if (!this.getWindowSentinel_()) { | ||
| this.setupMetadata_(); |
There was a problem hiding this comment.
can you add some doc to explain in what scenario we don't need to parse the metadata?
3p/ampcontext.js
Outdated
| * @private | ||
| */ | ||
| getWindowSentinel_() { | ||
| this.sentinel = this.win_.AMP_SENTINEL || null; |
There was a problem hiding this comment.
Can we make that more explicit by changing AMP_SENTINEL to another name?
| if (opt_isA4A) { | ||
| this.element_.appendChild(this.iframe); | ||
| return Promise.resolve(); | ||
| } else { |
| 'name': nameData, | ||
| }, SHARED_IFRAME_PROPERTIES)); | ||
| if (method == XORIGIN_MODE.NAMEFRAME) { | ||
| const attributes = this.generateSentinelAndContext(iframe); |
There was a problem hiding this comment.
Does this return a context or attributes?
There was a problem hiding this comment.
I've been using it somewhat interchangeably, but sentinel and context together are the attributes assigned on the frame. 'context' is referring to the old context that did not include sentinel
There was a problem hiding this comment.
what about renaming attributes to metadata, generateSentinelAndContext to generateMetadata?
There was a problem hiding this comment.
I could do that, but there are a lot of other places that would have to change. could I do a cleanup refactoring PR for that? really need to finish this asap
| const contentWindow = we.frame.contentWindow; | ||
| if (!contentWindow) { | ||
| setTimeout(dropListenSentinel, 0, listenSentinel); | ||
| } else if (sentinel === 'amp') { |
There was a problem hiding this comment.
so, we end up removing this whole block for "sentinel==='amp''.
why was it affecting the A4A iframes? I don't think they have sentinel of 'amp', do they?
will this break our non-3p iframes which has sentinel = 'amp'?
|
In addition to the gulp tests, I also did manual testing on live pages and my example page to verify that direct frame and nameframe both set up properly and can send/receive postMessages to/from the runtime. |
| if (typeof this.win_.AMP_CONTEXT_DATA == 'string') { | ||
| this.sentinel = this.win_.AMP_CONTEXT_DATA; | ||
| } else { | ||
| this.setupMetadata_(this.win_.AMP_CONTEXT_DATA); |
There was a problem hiding this comment.
now this method mutates states, so we should rename it.
And probably you will just want to move line 33 into this method as well.
3p/ampcontext.js
Outdated
| /** | ||
| * Parse the metadata attributes from the name and add them to | ||
| * the class instance. | ||
| * @param {!Object|!String} contextData |
3p/ampcontext.js
Outdated
| this.referrer = context.referrer; | ||
| } catch (err) { | ||
| data = JSON.parse(decodeURI(data)); | ||
| } catch (err) {} |
There was a problem hiding this comment.
you can use tryParseJson() in json.js
3p/ampcontext.js
Outdated
| } catch (err) {} | ||
|
|
||
| if (!data || typeof data != 'object') { | ||
| user().error('AMPCONTEXT', '- Could not parse metadata.'); |
There was a problem hiding this comment.
this is more likely a dev error? we actually can just skip it, since we are throwing in the next line.
3p/ampcontext.js
Outdated
| data = JSON.parse(decodeURI(data)); | ||
| } catch (err) {} | ||
|
|
||
| if (!data || typeof data != 'object') { |
There was a problem hiding this comment.
no need to check typeof data
| <br> | ||
| <br> | ||
| <br> | ||
| <br> |
There was a problem hiding this comment.
why so many br? can you use a blank div instead?
tdrl
left a comment
There was a problem hiding this comment.
Mostly minor stuff and nits. The only one I'm really concerned about is the comment about overwriting name that's just been set. Otherwise, LGTM.
3p/ampcontext.js
Outdated
| this.setupMetadata_(); | ||
| this.win_ = win; | ||
|
|
||
| // if the window sentinel is set directly, that means we are running |
There was a problem hiding this comment.
Nit: Start with capital letter and end with a period.
| /** | ||
| * Checks to see if there is a window variable assigned with the | ||
| * sentinel value, sets it, and returns true if so. | ||
| * @private |
There was a problem hiding this comment.
removed return value
3p/ampcontext.js
Outdated
|
|
||
| // if the window sentinel is set directly, that means we are running | ||
| // in a situation where we couldn't attach the name attribute before | ||
| // creating the iframe, so don't try to parse the metadata |
There was a problem hiding this comment.
Woo -- I'm not sure I can follow all the negatives here. Can you restate this in terms of positives -- when do you set the metadata?
I'm trying to decide if you're setting the sentinel in hasSafeframeSentinel and then overriding in setupMetadata. I don't think so, but I'm too foggy-headed this morning to work through the negatives and cases. :-)
examples/ampcontext-creative.js
Outdated
| @@ -0,0 +1,88 @@ | |||
| if (!window.context){ | |||
| // window.context doesn't exist yet, must perform steps to create it | |||
examples/ampcontext-creative.js
Outdated
| // before using it | ||
| console.log("window.context NOT READY"); | ||
|
|
||
| // must add listener for the creation of window.context |
There was a problem hiding this comment.
Nit: Start with capital letter. (Here and below.)
| expect(attributes._context).to.be.ok; | ||
| if (!attributes._context.amp3pSentinel) { | ||
| expect(attributes._context.sentinel).to.be.ok; | ||
| } |
There was a problem hiding this comment.
So here you're saying that at least one of these should be present, right? Do you want to ensure that exactly one of them is present?
Perhaps a better way to structure this is to add an argument to verifyNameFrameRender that specifies which you should expect, given the case being tested. Then you can do something like:
function verifyNameFrameRender(element, expectSentinel) {
// stuff
expect(attributes._context).not.to.contain.all.keys('sentinel', 'amp3pSentinel');
if (expectSentinel) {
expect(attributes._context).to.contain.keys('sentinel');
expect(attributes._context.sentinel).to.match(/some_regex/);
} else {
expect(attributes._context).to.contain.keys('amp3pSentinel');
expect(attributes._context.amp3pSentinel).to.match(/some_regex/);
}There was a problem hiding this comment.
The two cases for sentinel naming will collapse down very soon, so I didn't want to modify the other tests too much. I can add additional logic in here as suggested for making sure it doesn't have both keys and that it matches the regex
| const attributes = JSON.parse(nameData); | ||
| expect(attributes).to.be.ok; | ||
| expect(attributes._context).to.be.ok; | ||
| if (!attributes._context.amp3pSentinel) { |
There was a problem hiding this comment.
Ah. You're doing essentially the same test here as above. I suggest factoring the nameData validation out to a common function that you call from both places.
|
|
||
| it('should be able to create AmpContext', () => { | ||
| return a4a.layoutCallback().then(() => { | ||
| const window_ = a4aElement.childNodes[0].contentWindow; |
There was a problem hiding this comment.
Probably safer to use a querySelector to get reference to the iframe, so that this test doesn't break if we add stuff to or rearrange the DOM structure under the a4a element.
|
|
||
| it('should be able to create AmpContext', () => { | ||
| return a4a.layoutCallback().then(() => { | ||
| const window_ = a4aElement.childNodes[0].contentWindow; |
| .then(() => this.noContent_()); | ||
| } | ||
|
|
||
| if (opt_isA4A) { |
There was a problem hiding this comment.
Why did you move this down here? Does it depend on the listeners having already been set up? If so, should probably add a note about that dependency. Also, seems worth keeping the original comment -- I don't think it's deprecated. (?)
There was a problem hiding this comment.
because we want to listen for nocontent message for a4a. I'll add comment back in
3p/ampcontext.js
Outdated
| } else { | ||
| this.setupMetadata_(this.win_.AMP_CONTEXT_DATA); | ||
| } | ||
| return !!this.sentinel; |
| src="http://localhost:8000/examples/ampcontext-creative.html"> | ||
| </amp-ad> | ||
| <div> | ||
|
|
There was a problem hiding this comment.
if all you want is space, you can do it via CSS. not sure if empty lines here would work
examples/ampcontext-creative.html
Outdated
|
|
||
| function intersectionCallback(payload){ | ||
| changes = payload.changes; | ||
| // Step 4: Do something with the intersection updates! |
There was a problem hiding this comment.
Nit: Still need to do something with "Step 4", b/c there's no 1..3 in this file.
…ction (ampproject#7132) * Created attributes.js * Added context to direct frame rendering * Added context to rendering via nameframe * Removed unneeded files * Fixed lint and naming * Don't need to uriencode * Modified tests * Created generateSentinelAndContext and moved setStyle * Fixed test breakages, handled sentinel name change experiment * Addressed comments; ampContext now checks for window var; added/modified tests * Added docstring * Addressed comments * Added test * Attach data-amp-3p-sentinel attribute to frame * Fix a4a frames not having messages processed correctly * Added examples for manual testing * Addressed comments' * Revert "Addressed comments'" This reverts commit a61ddf8. * Changed name of function to hasSafeframeSentinel: * Added todo * Updated docstring and added todo * Fixed getListenForEvents * Collapse if block cases * Undid change to getListenForEvents * Fixed nameframe to keep context data on name * Broke out isAmpMessage function to use for parsing * Handle different ways of setting up AmpContext * Make test page take up multiple viewports * Fixed lint mistakes * Inlined ampcontext-creative.js into html * Addressed comments * Addressed comments * Add comments to explain setting of name on nameframe * removed comment
…ction (ampproject#7132) * Created attributes.js * Added context to direct frame rendering * Added context to rendering via nameframe * Removed unneeded files * Fixed lint and naming * Don't need to uriencode * Modified tests * Created generateSentinelAndContext and moved setStyle * Fixed test breakages, handled sentinel name change experiment * Addressed comments; ampContext now checks for window var; added/modified tests * Added docstring * Addressed comments * Added test * Attach data-amp-3p-sentinel attribute to frame * Fix a4a frames not having messages processed correctly * Added examples for manual testing * Addressed comments' * Revert "Addressed comments'" This reverts commit a61ddf8. * Changed name of function to hasSafeframeSentinel: * Added todo * Updated docstring and added todo * Fixed getListenForEvents * Collapse if block cases * Undid change to getListenForEvents * Fixed nameframe to keep context data on name * Broke out isAmpMessage function to use for parsing * Handle different ways of setting up AmpContext * Make test page take up multiple viewports * Fixed lint mistakes * Inlined ampcontext-creative.js into html * Addressed comments * Addressed comments * Add comments to explain setting of name on nameframe * removed comment
|
Hi all. Can creatives now use this window.AMP_CONTEXT_DATA to get the top level url? An issue I keep having, from a creative standpoint, is trying to get the top level page url when inside a x-domain iframe. I've noticed if the x-domain iframe has domain *.ampproject.net I can get it by parsing the window.name JSON and eventually getting the sourceUrl value. Now, I've come across an occasion where I'm inside a safeframe and have an empty window.name property, but this AMP_CONTEXT_DATA is populated. Is there a consistent way for creatives to get the top page url from AMP? I raised this issue before in #12342. |
|
hi @bruscantini So as you've seen, in a safeframe setting, the window name is wiped clean before you have access to it, so GPT safeframe saves the amp context data onto that window var to allow for use later. To save yourself some hassle, you can just use the AmpContext library directly https://github.com/ampproject/amphtml/blob/master/3p/ampcontext.js which handles all the parsing for you. The binary is served as https://3p.ampproject.net/1518441587106/ampcontext-v0.js |
No description provided.