Skip to content
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

Merged
merged 14 commits into from Nov 30, 2016
Merged

Move all creative rendering for A4A to layoutcallback #6249

merged 14 commits into from Nov 30, 2016

Conversation

keithwrightbos
Copy link
Contributor

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.

Copy link
Contributor

@lannka lannka left a 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': '',
Copy link
Contributor

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

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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 => {
Copy link
Contributor

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?

Copy link
Contributor Author

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) {
Copy link
Contributor

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().

Copy link
Contributor Author

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();
Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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? :-)

Copy link
Contributor Author

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 => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename promiseResult => creativeMetaData ?

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link

@tdrl tdrl left a 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',
Copy link

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();
Copy link

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();
Copy link

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);
Copy link

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));
Copy link

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);
});

Copy link
Contributor Author

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(
Copy link

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

Copy link
Contributor Author

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));
Copy link

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.

Copy link
Contributor Author

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 */
Copy link

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.

Copy link
Contributor Author

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);
Copy link

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 &&
Copy link

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.

Copy link
Contributor Author

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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

false?

Copy link
Contributor Author

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 => {
Copy link

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.

Copy link
Contributor Author

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', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true => 3?

Copy link
Contributor Author

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.

Copy link

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();
Copy link

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.

Copy link
Contributor Author

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;
Copy link

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@keithwrightbos
Copy link
Contributor Author

@lannka PTAL

Copy link
Contributor

@lannka lannka left a 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' ?
Copy link
Contributor

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?

Copy link
Contributor Author

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...

@lannka lannka added LGTM and removed NEEDS REVIEW labels Nov 29, 2016
@keithwrightbos
Copy link
Contributor Author

@tdrl PTAL

@taymonbeal
Copy link
Member

LGTM

@tdrl
Copy link

tdrl commented Nov 29, 2016

I'm mostly good, but there are still a couple of open questions / issues:

Other than those, LGTM.

@keithwrightbos
Copy link
Contributor Author

@tdrl PTAL

@tdrl
Copy link

tdrl commented Nov 30, 2016

LGTM. Thanks! @lannka I'm happy, and it looks like everyone else is, so please merge when you get a chance.

@lannka
Copy link
Contributor

lannka commented Nov 30, 2016

Will merge once Travis is happy too

@keithwrightbos
Copy link
Contributor Author

@lannka travis is happy, please merge

@lannka lannka merged commit 627cdff into ampproject:master Nov 30, 2016
@taymonbeal taymonbeal deleted the a4a_render_layout branch November 30, 2016 21:52
Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
* 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
Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants