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

RFC: Separate the load phase of AMP into multiple chunks. #5536

Merged
merged 11 commits into from Oct 18, 2016

Conversation

cramforce
Copy link
Member

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.

throw errors[0];
}

class Chunk {
Copy link
Contributor

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?

Copy link
Member Author

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

Choose a reason for hiding this comment

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

@private

Copy link
Member Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

@private

Copy link
Member Author

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is icky.

Copy link
Member Author

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

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_?

Copy link
Member Author

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

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.

Copy link
Member Author

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

@dvoytenko dvoytenko Oct 12, 2016

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.

Copy link
Member Author

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

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.

Copy link
Member Author

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

@dvoytenko dvoytenko Oct 12, 2016

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.

Copy link
Member Author

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

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?

Copy link
Member Author

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

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?

Copy link
Member Author

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

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?

Copy link
Member Author

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

Choose a reason for hiding this comment

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

This is icky.

return false;
}
// Viewers send a URL param if we are not visible.
return !(/visibilityState=prerender/.test(this.win_.location.hash));
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, changed back.

@cramforce cramforce mentioned this pull request Oct 13, 2016
3 tasks
@cramforce cramforce force-pushed the chunked-amp branch 2 times, most recently from c4f9c43 to 01f82b2 Compare October 17, 2016 02:01
@cramforce
Copy link
Member Author

Friendly ping

return false;
}
// Viewers send a URL param if we are not visible.
return !(/visibilityState=hidden/.test(this.win_.location.hash));
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

@jridgewell jridgewell left a comment

Choose a reason for hiding this comment

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

LGTM.

@dvoytenko
Copy link
Contributor

LGTM

@dvoytenko
Copy link
Contributor

@jridgewell Probably a good time to follow up with the viewer team?

@jridgewell
Copy link
Contributor

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.
The style installer doesn't work for unknown reasons at the stage of the iframe's lifecycle.
Just adding the style works fine.
@cramforce cramforce merged commit ffc3de9 into ampproject:master Oct 18, 2016
@cramforce cramforce deleted the chunked-amp branch October 18, 2016 16:28
samiamorwas pushed a commit to samiamorwas/amphtml that referenced this pull request Oct 18, 2016
…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)
  ...
Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
…#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.
Lith pushed a commit to Lith/amphtml that referenced this pull request Dec 22, 2016
…#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants