w3c / requestidlecallback

Cooperative Scheduling of Background Tasks
https://w3c.github.io/requestidlecallback/
Other
50 stars 19 forks source link

idle callbacks should not be scheduled, if layout is dirty #37

Closed aFarkas closed 8 years ago

aFarkas commented 8 years ago

I'm playing around with requestIdleCallback in Chrome implemenation and I have realized, that rIC is also called if layout is dirty.

Because of the general behavior of rIC it is a great way to schedule layout neutral or layout read operations. But if layout was invalidated before, calling idle callbacks destroys the possibility to schedule layout read operations, because the browser actually has a "hidden/future/async" task, that becomes immediately blocking and exceeds the deadline by many times as soon as just a simple and unsplittable layout read operation occurs.

I think even if the timeout option is used, the browser should try to avoid scheduling idle callbacks, while layout is dirty by any cost.

rmcilroy commented 8 years ago

This is interesting. I think it is the case that the implementation in Chrome only calls rIC callbacks after the layout operations of the previous rAF have been performed. @skyostil do we wait for all layout operations from the previous rAF to be complete before running rIC callbacks?

@aFarkas, do you do all the layout write operations in rAF callbacks or other timers / callbacks? I guess a timer or another callback could occur between the rAF and the rIC and dirty the layout which may result in the situation that you mention above. I don't think we want to avoid scheduling idle callbacks in these situations since it isn't clear if the rIC needs to read the layout or is doing something else in the background which wouldn't be impacted by dirty layout. Maybe a better alternative would be to have an API which exposes whether layout is dirty, and then rICs which care about this could check before doing any work (reposting themselves for later if the layout is dirty).

aFarkas commented 8 years ago

I guess a timer or another callback could occur between the rAF and the rIC and dirty the layout which may result in the situation that you mention above.

Yes, this is causing the issue. Unfortunately, it is caused by a script that I don't own. And there is no clean solution to avoid layout thrashing at this point for me.

Maybe a better alternative would be to have an API which exposes whether layout is dirty...

Yes, If this API could be implemented in a synchronous way in all browsers, it would be a great feature. And can be used standalone as also in combination with rIC. If this is only possible async, it wouldn't be that bad to merge this into rIC either:

//call as soon as layout isn't dirty
requestIdleCallback(()=>{}, {timeout: 0, read: true});

//or
requestIdleCallback((idle)=>{console.log(idle.isDirty);}, {timeout: 6});

At the end: No matter how, I want this API badly.

igrigorik commented 8 years ago

I guess a timer or another callback could occur between the rAF and the rIC and dirty the layout which may result in the situation that you mention above.

Yes, this is causing the issue. Unfortunately, it is caused by a script that I don't own. And there is no clean solution to avoid layout thrashing at this point for me.

Aside: does this result in double layout as well, then? As in, the browser doing the layout work twice. Because if so, then ugh.. If you don't mind me asking, what script / who is the provider?

Yes, If this API could be implemented in a synchronous way in all browsers, it would be a great feature. And can be used standalone as also in combination with rIC.

I agree with Ross that this smells like an orthogonal feature to rIC. My suggestion would be open a discussion on https://discourse.wicg.io/ and see where that goes.

aFarkas commented 8 years ago

does this result in double layout as well, then?

Yes, the third party script ends with layout invalidation. Then I force layout in a rIC and in a rAF I write layout. So the forced layout calculation is never committed and needs a relayout, due to my rAF.

If you don't mind me asking, what script / who is the provider?

Not really important. I work on a tv site and the player script is a custom, but third party solution (also hosted on a different server), by the tv station. But I'm pretty sure it will get messy as soon as I have to add the tracking scripts.

I agree with Ross that this smells like an orthogonal feature to rIC.

My suggestion would be open a discussion on https://discourse.wicg.io/ and see where that goes.

Hope you can help here: https://discourse.wicg.io/t/api-for-save-layout-read-operation-efficiently-preventing-layout-thrashing/1407

igrigorik commented 8 years ago

Not really important. I work on a tv site and the player script is a custom, but third party solution (also hosted on a different server), by the tv station. But I'm pretty sure it will get messy as soon as I have to add the tracking scripts.

I see. Any chance you can ping them and get them to fix this behavior? Yes, it's one of many, but we have to start somewhere.. I'm not convinced that even if we had the API you're asking for, it would actually solve your problem, since all it takes is one bad actor that continually schedules a task outside of a rAF to break your logic -- i.e. layout is always dirty and browser is doing twice the work.

aFarkas commented 8 years ago

I'm not convinced that even if we had the API you're asking for, it would actually solve your problem.

Of course this API wouldn't solve every layout thrashing problem that exist. If we look into famous scripts, we find enough layout thrashing (1, 2 and many more. (Note that all those scripts not only cause layout thrashing on their own, but also end with a DOM write outside of a rAF, building a read trap for any following script execution.)

But it would absolutely solve the described problem (Let's call it read traps):

  1. script one (that you don't own) is invalidating layout outside of rAF.
  2. script two (that you own) is reading afterwards.

Even if there are unlimited number of modules invalidating layout outside of rAF. With this API you can make sure, that your own reads never force layout.

And as demonstrated above read traps are pretty common (jQuery, modernizr...).

I'm also the generator of this "third party" lazyloader script. And from this viewpoint an API that gives me this information about the layout would be a huge win and would make sure, that my script never causes layout thrashing. No matter in which unknown web environment it was included.

@paulirish asked me in a perf issue there, what I think I need to do a more performant layzloader.

And my answer was quite interesting from a rIC standpoint (rIC was not invented at this time):

Another thing that would help is something like a requestAfterLayoutFrame/requestIdleFrame or something like this. While the after rAF pattern helped, it does not guarantee that I don't force layout and I think this can only accomplished interoperable, if there is a standard.

Till a week ago I thought rIC would (also) give me exactly this. (In fact I thought this had some influence on this API here.)

From debugging a lot third party sites using lazysizes. I can say this. Normally checking positions of 200+ elements takes between 1ms and 5ms depending on the device. But as soon as layout is dirty it takes 10ms - 500ms depending on the complexity of the website with only one element (= one unsplittable invocation of getBoundingClientBox and you are ruined) .

This is why knowing whether layout is actually dirty is more important for read heavy scripts than knowing whether the browser has some idle time for a read heavy script.

And there is currently no way to accomplish the task.

Sure something like the planed IntersectionObserver would help with lazyloaders, but it is to high level to be helpful with other read heavy tasks. And I'm pretty sure the IntersectionObserver would need to do this layout dirty check (not only the idle check) internally to provide us a high performance API. So if you need this as a building block internally to do things fast, give us web developers the same tools to do so too.

To recap on this: Such an API can make sure that read heavy scripts like lazy loaders can be developed with little effort and work highly efficient even in a bad environment (many read traps). And this is huge.

skyostil commented 8 years ago

@rmcilroy in Chrome idle tasks run only after all other tasks, but if one of those tasks mutates the DOM and doesn't subsequently read from it (causing a synchronous layout), we'll enter the idle task with a dirty layout.

At first blush restricting idle tasks to run only with a clean layout might be an easy way to accidentally stop them running at all. Because of that I also agree that a separate API for querying layout state -- ideally per element -- might be more useful.

aFarkas commented 8 years ago

in Chrome idle tasks run only after all other tasks

This makes totally sense for idle detection and makes clear, why my wish shouldn't be part of the rIC API.

Basically a simple requestReadCallback API should be called right after frame commit but before any other tasks to make sure no other scripts/tasks have invalidated layout.

I'm closing this, because it should be handled somewhere else, but I hope we can still discuss this here: https://discourse.wicg.io/t/api-for-save-layout-read-operation-efficiently-preventing-layout-thrashing/1407

I really believe it would be huge and let me try to convince you.

aFarkas commented 8 years ago

@skyostil and @rmcilroy

I have two questions: I re-thought on this. As I understand rIC, rIC is in general scheduled between frame commit and upcoming v-sync. At the time of frame commit the layout shouldn't be invalidated and it is already very unlikely that layout is dirty at the time a idle callback is called.

Only in the rare situation, that a timeout sneaks in to this exact time, it will be processed before and it can invalidate layout. 1. Question: Is my understanding here correct?

If we can't say, that if a timeout sneaks in and invalidates layout rIC shouldn't be called. Can't we define premature, that in this situation timeouts and such tasks are deferred after v-sync or after rIC has been called? This is basically also the thing what the browser does with certain events like the scroll, mouseover event, right?

Additionally the time between frame commit and v sync should be so short, that it doesn't hurt accuracy.

skyostil commented 8 years ago

That's correct, we currently don't coordinate rIC with timeouts, network loads or other activity that might invalidate the document before rIC runs. I think I'm leaning toward left-aligning those tasks in the frame to avoid the redundant computation and the possibility of janking you mentioned. One issue is that this will somewhat reduce the precision of setTimeout(), although that hasn't been very good to begin with.

I've opened https://bugs.chromium.org/p/chromium/issues/detail?id=603457 to track this.