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
RFC: Separate the load phase of AMP into multiple chunks. #5536
Conversation
22ef6b0
to
62ea424
Compare
throw errors[0]; | ||
} | ||
|
||
class Chunk { |
There was a 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 nice to have a more aggregate name. Chunker
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Called it Chunks
/** | ||
* Run fn as a "chunk". It'll run in a micro task when the doc is visible | ||
* and otherwise run it after having yielded to the event queue once. | ||
* @param {function()} fn |
There was a 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
There was a 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
/** | ||
* Run a task. | ||
* Schedule the next round if there are more tasks. | ||
* @return {boolean} Whether anything was executed. |
There was a 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
There was a 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
// Ask the viewer or try to infer whether we are visible. | ||
return this.viewer_ | ||
? this.viewer_.isVisible() | ||
: !(/visibilityState=hidden/.test(this.win_.location.hash)); |
There was a 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 also include visibilityState=prerender
.
There was a 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 icky.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
visibilityState=prerender
is the only one!
I added support for document.hidden
*/ | ||
run_(fn) { | ||
this.tasks_.push(fn); | ||
this.schedule_(); |
There was a 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 "schedule next" be done from the starting point of run_
here, or from the point when previous task was executed in 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.
We always need to schedule running tasks when we add one (There might not be one) and I want microtasks to be scheduled immediately instead of nesting them. In the end this schedules too many, but that should be fine.
// new ticks in place to batch the ticks properly. | ||
perf.flush(); | ||
} | ||
maybeValidate(self); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like maybeValidate
should definitely just be out of a critical path in its own microtask.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its a noop in production, though and otherwise doesn't have an immediate side effect.
@@ -244,9 +245,14 @@ function adoptShared(global, opts, callback) { | |||
*/ | |||
function installExtension(fnOrStruct) { | |||
if (typeof fnOrStruct == 'function') { | |||
fnOrStruct(global.AMP); | |||
const fn = fnOrStruct; | |||
chunk(global.document, () => fn(global.AMP)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Important! This will immediately break shadow multi-doc mode since this is run before an ampdoc is available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Went back and forth a lot here, and decided to just add an option to turn it off in this case.
installExtension(fnOrStruct); | ||
}); | ||
const register = function() { | ||
waitForBody(global.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.
It might be better to move waitForBody
outside. It's a very rare thing that body is not available by this 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.
Isn't that an argument to do it on the inside like it is?
* entry. | ||
* @param {string} href | ||
*/ | ||
setForTesting(href) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything here is for testing :) Let's call it something like setQuiet
. Generally, the intention here is if you need an initially set location, you declare it in describe
. That might not be applicable in all cases, so I'm fine with the change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Called it resetHref
while (true) { | ||
try { | ||
if (!service.execute_()) { | ||
if (errors.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.
Why not check for this after the try catch block?
There was a 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
errors.push(e); | ||
} | ||
} | ||
throw errors[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.
Is the intention to run all regardless of errors but only throw the 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.
This was convenient for testing purposes so far. Will document.
return; | ||
} | ||
// The message doesn't actually matter. | ||
this.win_.postMessage/*OK*/('amp-macro-task', '*'); |
There was a 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 we post to only this origin?
There was a 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 really doesn't matter. We also don't care about the actual message on the receiving side. Tasks are executed on every message received by the current window.
// Ask the viewer or try to infer whether we are visible. | ||
return this.viewer_ | ||
? this.viewer_.isVisible() | ||
: !(/visibilityState=hidden/.test(this.win_.location.hash)); |
There was a 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 icky.
f899c56
to
e657a5e
Compare
return false; | ||
} | ||
// Viewers send a URL param if we are not visible. | ||
return !(/visibilityState=prerender/.test(this.win_.location.hash)); |
There was a 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 the viewer sends visibilityState=hidden
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, changed back.
c4f9c43
to
01f82b2
Compare
Friendly ping |
01f82b2
to
eab9b3a
Compare
return false; | ||
} | ||
// Viewers send a URL param if we are not visible. | ||
return !(/visibilityState=hidden/.test(this.win_.location.hash)); |
There was a 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 if I already commented on this, but there are many other values that might be of important here: prerender
, paused
, inactive
. Probably the most important is prerender
since this is the state most of docs start in. Are you counting on the fact that it will be rare that viewer_
is not available yet to respond?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rare is not really the right word. This happens for the first 1-2 calls to chunk
on every page load. The viewer only sends this very string and nothing else, so it doesn't seem important to care about any other value. After that viewer.isVisible
is what is being 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.
Hmm. (/cc @jridgewell). Viewer should be sending visibilityState=prerender
in these cases. I know that we were migrating viewer to this system some time ago, not sure what's the status is. It might be safer to support both hidden
and prerender
here for now to avoid migration pains.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We never completed it. I think testing for either prerender
(since that's what they should be using) or hidden
is the right choice. But we definitely need to test for hidden
.
There was a 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 viewer is definitely sending hidden. Added support for both.
There was a 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.
9a7421e
to
61fdbee
Compare
LGTM |
@jridgewell Probably a good time to follow up with the viewer team? |
b/32222292. |
The general idea is that AMP can be instructed to not block the UI thread during load phase for long if the document itself is not visible. The change may be quite impactful. It does split up work into neat chunks (about 10-20ms on desktop, longer on mobile) instead of one 400ms+ block. I validated that the number of style recalc doesn't change when the experiment is off. - Current doesn't change `amp-shadow`. That will come in a follow up CL, but might not make as much sense. - With the experiment not active the only thing this should change is that the initialization now goes through a bunch of micro tasks. This should behave as before in browsers with native promises.
61fdbee
to
5f049b8
Compare
The style installer doesn't work for unknown reasons at the stage of the iframe's lifecycle. Just adding the style works fine.
…mp_reddit_extension * 'master' of https://github.com/ampproject/amphtml: (63 commits) Position:relative on body (ampproject#5665) Fix for ampproject#5663 (ampproject#5664) Nokta Ad Server is added as an ad type (ampproject#5550) RFC: Separate the load phase of AMP into multiple chunks. (ampproject#5536) Add OWNERS files for A4A. (ampproject#5651) Update DEVELOPING.md Call original event add/remove via interceptor (ampproject#5650) Fix wording on confusing steps to protect against CSRF. (ampproject#5646) Install runtime CSS for all amp tests (ampproject#5642) Implement outgoing link URL replacements. (ampproject#5628) Wait for window to load before installing ServiceWorker. (ampproject#5638) iOS wrapper viewport implementation (ampproject#5629) Do not use AMP Version as RTV Versions (ampproject#5474) Purch Ad Support for Amp-Ads (ampproject#5464) A11Y fix for sticky ad close button. (ampproject#5640) Propagate ARIA attributes to real-elements (ampproject#5590) Ibillboard integration (ampproject#5392) Add some keywords to the NPM description of the validator. (ampproject#5633) PWA: messaging and broadcast (ampproject#5588) Carousel swipe not working well on Android Firefox (ampproject#5626) ...
…#5536) The general idea is that AMP can be instructed to not block the UI thread during load phase for long if the document itself is not visible. The change may be quite impactful. It does split up work into neat chunks (about 10-20ms on desktop, longer on mobile) instead of one 400ms+ block. I validated that the number of style recalc doesn't change when the experiment is off. - Current doesn't change `amp-shadow`. That will come in a follow up CL, but might not make as much sense. - With the experiment not active the only thing this should change is that the initialization now goes through a bunch of micro tasks. This should behave as before in browsers with native promises.
…#5536) The general idea is that AMP can be instructed to not block the UI thread during load phase for long if the document itself is not visible. The change may be quite impactful. It does split up work into neat chunks (about 10-20ms on desktop, longer on mobile) instead of one 400ms+ block. I validated that the number of style recalc doesn't change when the experiment is off. - Current doesn't change `amp-shadow`. That will come in a follow up CL, but might not make as much sense. - With the experiment not active the only thing this should change is that the initialization now goes through a bunch of micro tasks. This should behave as before in browsers with native promises.
The general idea is that AMP can be instructed to not block the UI thread during load phase for long if the document itself is not visible.
The change may be quite impactful. It does split up work into neat chunks (about 10-20ms on desktop, longer on mobile) instead of one 400ms+ block. I validated that the number of style recalc doesn't change when the experiment is off.
amp-shadow
. That will come in a follow up CL, but might not make as much sense.