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
Move all creative rendering for A4A to layoutcallback #6249
Conversation
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.
Good refactoring. Much easier to read now.
const iframe = /** @type {!HTMLIFrameElement} */( | ||
createElementWithAttributes( | ||
/** @type {!Document} */(this.element.ownerDocument), 'iframe', { | ||
'frameborder': '0', 'allowfullscreen': '', |
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 the "'" around the 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
this.emitLifecycleEvent('renderFriendlyStart', creativeMetaData); | ||
try { | ||
// Create and setup friendly iframe. | ||
dev().assert(!!this.element.ownerDocument); |
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 check necessary? or do you want to add a 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.
Removed
this.applyFillContent(iframe); | ||
const fontsArray = []; | ||
if (creativeMetaData.customStylesheets) { | ||
creativeMetaData.customStylesheets.forEach(s => { |
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.
according to CreativeMetaDataDef
, CreativeMedaData.customStylesheets is Array<string>
. Where is the href attribute 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.
Done
this.onAmpCreativeRender(); | ||
return true; | ||
}); | ||
} 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 will not catch any error thrown inside the promise then blocks.
Yuu'll want to use the().catch()
.
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
return super.renderOutsideViewport(); | ||
const elementCheck = getAmpAdRenderOutsideViewport(this.element); | ||
return typeof elementCheck == 'number' ? | ||
elementCheck : super.renderOutsideViewport(); |
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.
pls indent 2 more spaces for line break.
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 {!Window} win | ||
*/ | ||
export function decrementLoadingAds(timerId, win) { | ||
timerFor(win).cancel(timerId); |
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 suppose you did research and it's safe to let it go? :-)
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.
Yes, previously we incremented at start of layoutCallback and decrement once we know if creative is AMP. Now, we only increment after knowing if that creative is not AMP.
' any ad'); | ||
} | ||
// Promise chain will have determined if creative is valid AMP. | ||
return this.adPromise_.then(promiseResult => { |
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.
rename promiseResult
=> creativeMetaData
?
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 (promiseResult) { | ||
dev().assert(promiseResult.minifiedCreative); | ||
// Must be an AMP creative. | ||
const metaData = /** @type {CreativeMetaDataDef} */(promiseResult); |
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 you try annotate this.adPromise_
instead?
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.
D'oh! I had started this review, but not sent it to you yet. Sorry! Sending what I have now and will finish reading and send more.
@@ -115,6 +106,7 @@ export const LIFECYCLE_STAGES = { | |||
renderCrossDomainEnd: '9', | |||
preAdThrottle: '10', | |||
renderSafeFrameStart: '11', | |||
throttled3p: '12', |
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 different than preAdThrottle
, I assume?
/** @private {?Promise<!boolean>} */ | ||
/** @const @private{string} tag to be used for dev/user logging */ | ||
this.logTag_ = | ||
('AMP-A4A-' + this.element.getAttribute('type')).toUpperCase(); |
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 this will be 'AMP-A4A-NULL' in the nasty case that this is constructed before its attributes are populated? That's probably okay for our purposes, I guess.
return super.renderOutsideViewport(); | ||
const elementCheck = getAmpAdRenderOutsideViewport(this.element); | ||
return typeof elementCheck == 'number' ? | ||
elementCheck : super.renderOutsideViewport(); |
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 don't think this fall-through logic is quite the same as before, is it? If elementCheck == false
, then you'll return super.renderOutsideViewport()
, while before you would have returned false
. I don't think it makes a practical difference at the moment (I'm sure that elementCheck
can ever be false
), but worth verifying that this will be ok.
} | ||
// Otherwise the ad is good to go. | ||
return super.renderOutsideViewport(); | ||
const elementCheck = getAmpAdRenderOutsideViewport(this.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.
elementCheck
seems vague. Maybe renderOutsideViewportCheck
?
// variable. | ||
this.isVerifiedAmpCreative_ = true; | ||
return utf8Decode(creative).then( | ||
creative => this.getAmpAdMetadata_(creative)); |
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 you can chain this to the main promise chain, so that we don't have then-inside-then:
.then(creative => {
this.isVerifiedAmpCreative_ = true;
return utf8Decode(creative);
})
.then(creativeDecoded => {
return this.getAmpAdMetadata_(creativeDecoded);
});
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 though I have to check that creative and creativeDecoded exist as part of the return chain
// viewport but cannot wait on promise. Sadly, need a state a | ||
// variable. | ||
this.isVerifiedAmpCreative_ = true; | ||
return utf8Decode(creative).then( |
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.
Heads-up that this is about to get changed out from under you. They're migrating utf8Decode
to synchronous. Maybe we can get this change in first. fingers crossed
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.
ack
// on precisely the same creative that was validated | ||
// via #validateAdResponse_. See GitHub issue | ||
// https://github.com/ampproject/amphtml/issues/4187 | ||
|
||
// TODO(levitzky) If creative comes back null, we should consider re- | ||
// fetching the signing server public keys and try the verification | ||
// step again. | ||
return creative && this.maybeRenderAmpAd_(creative); | ||
}) | ||
.catch(error => this.promiseErrorHandler_(error)); |
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.
After going over promise catch semantics w/ Taymon the other day, I think I was wrong to recommend killing this catch. I think we need it for the cases where layoutCallback
is deferred by the AMP runtime. In that case, the error will fall off the end here and not be preserved. I think it will be picked up by the outer AMP catch, but we'd rather explicitly catch it here and format via promiseErrorHandler
.
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
@@ -504,37 +509,26 @@ export class AmpA4A extends AMP.BaseElement { | |||
/** @override */ |
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 won't let me attach comments to stuff that was folded (WTF GitHub?), but now I question what happens at line 493 (in your version). We're throwing from a Promise.catch
there and I think that's converted into another rejection, which, again, will fall off the end of the promise chain if layoutCallback
isn't pending itself.
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 sure where this lines up but hopefully fixed when I explicitly added catch to promise chain initialization in onLayoutMeasure
// Promise chain will have determined if creative is valid AMP. | ||
return this.adPromise_.then(promiseResult => { | ||
if (promiseResult) { | ||
dev().assert(promiseResult.minifiedCreative); |
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 we don't eliminate the .catch
at the end of the onLayoutMeasure
promise chain, then I think that promiseResult
can be an Error
here, so you'll need a case for that, rather than just an assert
.
@@ -879,7 +878,10 @@ export class AmpA4A extends AMP.BaseElement { | |||
creative.slice(metadataStart + METADATA_STRING.length, metadataEnd)); | |||
const ampRuntimeUtf16CharOffsets = | |||
metaDataObj['ampRuntimeUtf16CharOffsets']; | |||
if (!isValidOffsetArray(ampRuntimeUtf16CharOffsets)) { | |||
if (!isArray(ampRuntimeUtf16CharOffsets) || | |||
ampRuntimeUtf16CharOffsets.length != 2 && |
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 inline this and get rid of the function? Just b/c it's only used here these days? I still think it makes the code more readable, but if you'd rather kill it, no problem. But I think you want to convert all of the &&
to ||
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
return executeLayoutCallbackTest(true); | ||
}); | ||
it('#layoutCallback not valid AMP', () => { | ||
return executeLayoutCallbackTest(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.
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.
Done
a4a.onLayoutMeasure(); | ||
expect(a4a.adPromise_).to.be.instanceof(Promise); | ||
return a4a.adPromise_.then(() => { | ||
return a4a.adPromise_.then(promiseResult => { |
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.
Perhaps not in this test, but somewhere, we should have a test for the different throw/catch cases. I'm not sure we (I, anyway) fully understand those interactions.
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.
ack
incrementLoadingAds(fixture.win); | ||
expect(a4a.renderOutsideViewport()).to.be.false; | ||
}); | ||
it('should return true if throttled, but AMP creative', () => { |
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
=> 3
?
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 means take default which is 3.
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.
But you test for 3.
return super.renderOutsideViewport(); | ||
const elementCheck = getAmpAdRenderOutsideViewport(this.element); | ||
return typeof elementCheck == 'number' ? | ||
elementCheck : super.renderOutsideViewport(); |
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 thought I had commented on this before, but I don't see the comment now, so apologies if this is redundant.
In the case that getAmpAdRenderOutsideViewport
returns a non-numeric value, you can lose that value here. Previously, if allowRender
was false
, it would be returned. Now if elementCheck
is false
, you'll return super.renderOutsideViewport()
, which is 3
.
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... right now getAmpAdRenderOutsideViewport will never return false (either true or 1.25) so this is not currently an issue but I do agree that how this is setup is somewhat fragile. I reworked so that getAmpAdRenderOutsideViewport now returns ?number where null indicates use default. Let me know what you think.
@@ -28,7 +28,7 @@ import {timerFor} from '../../../src/timer'; | |||
import {setStyle} from '../../../src/style'; | |||
import {AdDisplayState} from './amp-ad-ui'; | |||
|
|||
const TIMEOUT_VALUE = 10000; | |||
const TIMEOUT_VALUE = 1000000; |
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.
16 min? That's a long timeout.
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.
Same question :-)
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
@lannka PTAL |
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.
LGTM with one comment
} | ||
// Otherwise the ad is good to go. | ||
return super.renderOutsideViewport(); | ||
const elementCheck = getAmpAdRenderOutsideViewport(this.element); | ||
return typeof elementCheck == 'number' ? |
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 avoid this weird typeof
check, shall we just make the function always return a number?
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 don't have reference to the default value of 3 and didn't want to hardcode it just in case its is later changed...
@tdrl PTAL |
LGTM |
I'm mostly good, but there are still a couple of open questions / issues:
Other than those, LGTM. |
@tdrl PTAL |
LGTM. Thanks! @lannka I'm happy, and it looks like everyone else is, so please merge when you get a chance. |
Will merge once Travis is happy too |
@lannka travis is happy, please merge |
* Initial modifications to move all creative rendering into layoutCallback; tests TBD * Fix merge issues * Test fixes * Fix tests * revert a4a examples * PR review * PR feedback * PR review * fix presubmit errors * fix test failure
* Initial modifications to move all creative rendering into layoutCallback; tests TBD * Fix merge issues * Test fixes * Fix tests * revert a4a examples * PR review * PR feedback * PR review * fix presubmit errors * fix test failure
Previously AMP Creatives were rendered as part of promise chain initiated via onLayoutMeasure. Move to use layoutCallback which will allow for dynamically changing priority if AMP creative (to be done in later PR) as well as enforce data-loading-strategy = prefer-viewability-over-views data attribute.