Bug 164193 - Implement requestIdleCallback
Summary: Implement requestIdleCallback
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: DOM (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Nobody
URL: https://w3c.github.io/requestidlecall...
Keywords: BrowserCompat, InRadar
Depends on: 268152 202653 202716 202824 202946 203023 203137 203667 203708 203840 204042 206676 219761 259850 259866 259903 259917 259955 259996 260115 260131 260183 260369 260667
Blocks:
  Show dependency treegraph
 
Reported: 2016-10-29 15:54 PDT by Simon Fraser (smfr)
Modified: 2024-01-30 14:28 PST (History)
28 users (show)

See Also:


Attachments
WIP (bindings only) (17.57 KB, patch)
2017-09-06 04:19 PDT, Ms2ger (he/him; ⌚ UTC+1/+2)
no flags Details | Formatted Diff | Diff
Video showing how requestIdleCallback fires after a minute on an empty page, should fire directly (99.49 MB, video/mp4)
2024-01-26 04:54 PST, Daniel
no flags Details
A real world example that shows how image loading is delayed too far, due to requestIdleCallback being buggy (70.37 MB, video/mp4)
2024-01-26 04:56 PST, Daniel
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Simon Fraser (smfr) 2016-10-29 15:54:46 PDT
We should implement requestIdleCallback. IntersectionObserver makes use of it.
Comment 1 Ms2ger (he/him; ⌚ UTC+1/+2) 2017-09-06 04:19:51 PDT
Created attachment 320005 [details]
WIP (bindings only)
Comment 2 Jon Lee 2017-09-06 10:38:45 PDT
rdar://problem/22900866
Comment 3 tom 2021-05-11 09:03:50 PDT
Can we get this properly implemented? More and more sites are starting to rely on this.
Currently enabling it as an experimental feature breaks a bunch of stuff (like the ability to log into your gmail account).

Thanks.
Comment 4 Simon Fraser (smfr) 2021-05-11 11:08:01 PDT
Can you list some sites that use it?
Comment 5 tom 2021-05-11 13:49:25 PDT
Sure: gmail.com, netflix.com, microsoft.com, amazon.com, tripadvisor.com, urbandictionary.com, nypost.com, groupon.com, primevideo.com, …

Certain web based software has started to require it (as in won't work without), such as Tiny Tiny RSS.
Comment 6 Alexandre Dieulot 2021-12-11 15:14:52 PST
This has been available in both Chrome and Firefox for 4.5 years. This was listed in WebKit’s 2020 goals under the “Catchup” session. It’s weird for the browser most aggressively marketing its speed not to have that API.

I don’t have any numbers on how useful it is in the wild, but a Google engineer that went all-in with rAF got a two-third reduction in first input delay for the worst 1% of cases and 20% reduction for the worst 5% on a JS-light site in late 2018: https://philipwalton.com/articles/idle-until-urgent/#how-much-does-this-actually-improve-performance%3F
Comment 7 Tyler Gross 2022-03-24 06:55:12 PDT
It's worth noting that React heavily relies on requestIdleCallback for scheduling. If requestIdleCallback is missing, then React falls back to setTimeout, which can lead to running much more code than is necessary.

Sources:
https://github.com/facebook/react/blob/c7ce0091dcb2036e19df980247c9a4388ad75741/packages/shared/ReactDOMFrameScheduling.js#L71
https://twitter.com/dan_abramov/status/962810069510492167?lang=en
Comment 8 Sam Sneddon [:gsnedders] 2022-06-13 16:30:03 PDT
(In reply to Tyler Gross from comment #7)
> It's worth noting that React heavily relies on requestIdleCallback for
> scheduling. If requestIdleCallback is missing, then React falls back to
> setTimeout, which can lead to running much more code than is necessary.
> 
> Sources:
> https://github.com/facebook/react/blob/
> c7ce0091dcb2036e19df980247c9a4388ad75741/packages/shared/
> ReactDOMFrameScheduling.js#L71
> https://twitter.com/dan_abramov/status/962810069510492167?lang=en

This is apparently no longer true: https://github.com/facebook/react/issues/13206#issuecomment-418417892
Comment 9 Alexandre Dieulot 2022-10-12 10:27:12 PDT
May we know what’s blocking WebKit from shipping requestIdleCallback, if anything? Has it just been utterly deprioritized? Nearly all the dependencies of this ticket haven’t moved in the last three years. It’s bewildering.
Comment 10 Michael Catanzaro 2023-03-14 08:30:02 PDT
This is now required by github.com to set labels in pull requests. WebKit development moved to github.com and landing changes depends on setting a label, so for us to continue to develop WebKit we now need to use Firefox or another non-WebKit browser to land pull requests.

Exception: it seems to work in Safari even without requestIdleCallback, but we don't know why. I initially assumed that GitHub was doing user agent header shenanigans to avoid this API when the browser is Safari, but that does not seem to be it.
Comment 11 Michael Catanzaro 2023-03-14 08:49:08 PDT
So Ms2ger's patch from five years ago provides a starting point.

We will need four different implementations for four different platform event loops:

 * Windows
 * Cocoa (uses CFRunLoop) 
 * GLib (uses GMainContext/GMainLoop)
 * Generic

The generic RunLoop currently has no concept of priority, but we could add one.

For GLib it's easy, because the concept of idle dispatch maps directly to G_PRIORITY_DEFAULT_IDLE.

For Cocoa, CFRunLoop supports CFRunLoopMode. I guess we could use a special mode and dispatch it only when no events for other modes are pending? I'm not sure.

For Windows, I did not investigate because the implementation looks gnarly. It looks to be based on a "message window" concept that is quite different from GMainContext/GMainLoop or CFRunLoop.
Comment 12 Michael Catanzaro 2023-03-14 09:05:15 PDT
So assuming we add idle-priority execution to RunLoop, then the cross-platform implementation would be:

 * If no timeout is specified, attach a RunLoop task at idle priority
 * If a timeout is specified, then attach two RunLoop tasks: one at idle priority and one using Timer.startOneShot(). Whichever task executes first should cancel the other.

That would be needed in DOMWindow, where Ms2ger's patch provides us with empty stubs.

All this assumes that "run task at idle priority" is actually a valid way to decide when the browser is idle, but I think it probably is. The browser might be doing more stuff on secondary threads, but the main thread is definitely idle. Only problem could be if a secondary thread is doing something that will imminently cause a task to post to the main thread. WebKit developers might know that something important might be about to happen, but the RunLoop isn't smart enough to avoid this. So perhaps we might theoretically want to have "blackout" times when idle callbacks are not dispatched. But that adds extra complexity and I don't know when those times would be, so this is probably not part of the minimal viable solution.

(Honestly, the minimal viable solution would be to implement it equivalent to setTimeout(0), but it's not hard to do better.)
Comment 13 Michael Catanzaro 2023-03-14 14:13:53 PDT
> Only problem could be if a secondary thread is doing something that will imminently cause a task to post to the main thread.

Actually, it's also possible that higher-priority timeouts are already scheduled on the main thread that might be about to post. That could be detected automatically by implementing a rule that idles do not dispatch if a timeout is scheduled within the next n milliseconds. But if we set n too high then we could accidentally denial of service every idle callback, so not sure it's a good idea, probably again not part of the minimal viable solution. I'm inclined to ignore all of this and schedule the idle callbacks whenever there is no other non-idle priority source to be dispatched on the current run loop iteration. Simple is often best; if it doesn't work well in practice, then we can deal with added complexity later on.
Comment 14 Michael Catanzaro 2023-03-14 15:02:30 PDT
(In reply to Alexandre Dieulot from comment #9)
> May we know what’s blocking WebKit from shipping requestIdleCallback, if
> anything? Has it just been utterly deprioritized? Nearly all the
> dependencies of this ticket haven’t moved in the last three years. It’s
> bewildering.

Well nobody has submitted any pull requests. It won't fix itself; help welcome.

Anyway, good thing you pointed me to the dependent issues, because now I see there is the DOM-level EventLoop.cpp, WindowEventLoop.cpp, WorkerEventLoop.cpp, which I didn't know about. I had only been thinking in terms of RunLoop.cpp. Since it's a wrapper over Timer and RunLoop, I had hoped that maybe it's just one more layer to deal with, but nope there's a complicated specification at https://html.spec.whatwg.org/multipage/webappapis.html#event-loops which I haven't read and which should ideally be understood before proceeding. But it looks seriously complicated, and I don't have enough time or expertise to understand it, so I won't try. (Then there's also a request idle callback specification https://w3c.github.io/requestidlecallback/ but fortunately that one is simple and more or less exactly what I expected.)

Regarding the dependent issues: maybe these are required to implement that event loop spec as expected, but frankly I only have a couple afternoons to spend on this and I only care about being able to create pull requests on github.com and nothing else, so I'm going to ignore those. ;) None look required for a minimum viable implementation. Bug #203137 is about timers and not idles, bug #203667 is about media loading, bug #203840 is basically what I wrote in comment #13 above, and bug #206676 is about a test that fails because this API doesn't exist yet.
Comment 15 Michael Catanzaro 2023-03-14 15:16:59 PDT
Oh, maybe I should look at the *closed* dependent bugs. Finally I see that Ryosuke has already landed most of the work, e.g. in https://trac.webkit.org/changeset/251050/webkit, including everything that I had planned to look into. But the feature is disabled (presumably due to the warning in comment #3). OK.

So I think next step is to expose this only for github.com for starters, to avoid breaking other websites before Ryosuke thinks it is ready. I don't think we actually have capability to enable DOM APIs via quirks currently. Will report a separate bug for this.

Hopefully this concludes my Bugzilla conversation with myself.
Comment 16 Sam Sneddon [:gsnedders] 2023-03-15 03:42:25 PDT
(In reply to Michael Catanzaro from comment #15)
> Oh, maybe I should look at the *closed* dependent bugs. Finally I see that
> Ryosuke has already landed most of the work, e.g. in
> https://trac.webkit.org/changeset/251050/webkit, including everything that I
> had planned to look into. But the feature is disabled (presumably due to the
> warning in comment #3). OK.

Yes, a bunch has landed, but there's also various things that per spec should go via the event loop but don't in WebKit (see the open dependent bugs), which means we will quite often (with the current off-by-default implementation) fire the callback prior to actually being idle. We've been pretty conservative here, not wanting to expose it while it still has obviously different timing characteristics to other engines; that said, it probably is no worse than setTimeout(…, 0) today?

> So I think next step is to expose this only for github.com for starters, to
> avoid breaking other websites before Ryosuke thinks it is ready. I don't
> think we actually have capability to enable DOM APIs via quirks currently.
> Will report a separate bug for this.

See EnabledByQuirk/DisabledByQuirk IDL extended attributes; you probably just want to define a quirk for GitHub and use that? That said, given GitHub _does_ work on Safari this should probably only enable it on the platforms where it is currently broken.
Comment 17 Michael Catanzaro 2023-03-15 07:56:04 PDT
(In reply to Sam Sneddon [:gsnedders] from comment #16)
> Yes, a bunch has landed, but there's also various things that per spec
> should go via the event loop but don't in WebKit (see the open dependent
> bugs), which means we will quite often (with the current off-by-default
> implementation) fire the callback prior to actually being idle. We've been
> pretty conservative here, not wanting to expose it while it still has
> obviously different timing characteristics to other engines; that said, it
> probably is no worse than setTimeout(…, 0) today?

Right, so I see now that the challenge here is not requestIdleCallback itself but  rather the DOM EventLoop portion of the HTML spec, which WebKit previously had no concept of. Looks like that all is fairly new and not quite right yet. I would have hoped that getting things not exactly right would be no worse than setTimeout(…, 0) but there is a warning in comment #3 indicating otherwise, so I guess we need to be careful here.

> See EnabledByQuirk/DisabledByQuirk IDL extended attributes; you probably
> just want to define a quirk for GitHub and use that? That said, given GitHub
> _does_ work on Safari this should probably only enable it on the platforms
> where it is currently broken.

Looks like exactly what I want. Thanks. Whether to limit the quirk to specific ports or not, I'm not sure. (There are risks either way, but at least they'll only affect github.com.)
Comment 18 Michael Catanzaro 2023-03-16 08:33:28 PDT
My understanding is still limited, but here are some more considerations I've been thinking about:

 * There are multiple WindowEventLoops/WorkerEventLoops per underlying RunLoop, one per security origin, and they can know when they are themselves in idle periods, but they cannot know about the idle periods of other EventLoops running on the same RunLoop. Therefore they do not know when the underlying RunLoop is truly idle and idle work will be scheduled during non-idle times.
 * They schedule work on the underlying RunLoop always using default priority callbacks, not idle priority callbacks, because RunLoop doesn't expose the concept of idle priority callbacks. Everything scheduled using the cross-platform RunLoop interface is currently default priority.
   * For most ports this just means the callbacks will be scheduled during non-idle times, not ideal but probably not harmful.
   * But for WPE/GTK it additionally means that idle callbacks of the EventLoop will actually delay execution of all work scheduled by application-level idle callbacks until the next run loop iteration. I think a website can perform a denial of service against application-level idle callbacks in the web process by simply always ensuring that at least one requestIdleCallback is scheduled. (The lower-priority callbacks never dispatch when a higher-priority callback is scheduled.)
 * So we should consider implementing idle priority for the underlying RunLoop (comment #11) so we can schedule the requestIdleCallback when the underlying RunLoop is actually idle

I'll also note that the EventLoop spec is seriously complex and I'm not sure how realistic it is to fully implement it. This affects basically everything and is alien to how WebKit works today. So if we're aiming for full spec compliance before we ship requestIdleCallback, I suspect it's going to be a very, very, very long wait. We might want to ship requestIdleCallback much sooner as long as it's not breaking websites in practice (which it currently does, comment #3).

(In reply to Michael Catanzaro from comment #10)
> This is now required by github.com to set labels in pull requests.

Um, it seems this is no longer required after fixing bug #211979, so this is no longer an emergency. I'll remove the dependent bug.
Comment 19 Michael Catanzaro 2023-03-16 08:40:36 PDT
(In reply to Michael Catanzaro from comment #18)
> I think a website can perform a denial of service against application-level idle
> callbacks in the web process by simply always ensuring that at least one
> requestIdleCallback is scheduled. (The lower-priority callbacks never
> dispatch when a higher-priority callback is scheduled.)

Hm, this is probably already possible by continuously using setTimeout/setInterval(..., 0).
Comment 20 tom 2023-04-02 12:35:16 PDT
Seems that Safari 16.4 even removed this from the experimental features, effectively leaving Safari broken with no recourse.

Going on 6+ years now, absolutely mind-boggling…
Comment 21 Michael Catanzaro 2023-04-03 05:59:44 PDT
Please only comment here if you are a developer with helpful information to add; otherwise, this bug will grow to the point where it becomes difficult to keep track of the previous comments.
Comment 22 Anne van Kesteren 2023-08-24 03:58:48 PDT
This was finished for Apple ports in bug 260369. Linux ports are tracked in bug 260478.
Comment 23 Daniel 2024-01-26 04:54:39 PST
Created attachment 469554 [details]
Video showing how requestIdleCallback fires after a minute on an empty page, should fire directly
Comment 24 Daniel 2024-01-26 04:56:40 PST
Created attachment 469555 [details]
A real world example that shows how image loading is delayed too far, due to requestIdleCallback being buggy
Comment 25 Daniel 2024-01-26 05:00:14 PST
Hello, first of all: AMAZING that Safari is finally getting requestIdleCallback (referring to https://bugs.webkit.org/show_bug.cgi?id=260369).

However, it unfortunately doesn't seem to work as expected. It's quite glitchy and sometimes never fires, even when the browser is obviously completely idle. I got it to fire in Safari Version 17.2.1 (19617.1.17.11.12) after enabling the feature flag and going to page that uses it, then to another one, then back again.

In Safari Technology Preview (Release 187 (Safari 17.4, WebKit 19619.0.1.2)) where it's enabled by default, it doesn't seem to work at all (or is *very* spotty). To illustrate this example, creating a totally blank index.html file and then running requestIdleCallback inside it would be expected to fire immediately, like it does in Firefox and Chrome. In Safari however it seems to sometimes fire after a minute, even after disabling all browser extensions (see attached video).

As for a real world example:
If you go to https://singular-society.com/search?q= all but the first 8 images are loaded using requestIdleCallback. This increases performance when scrolling down, as the product cards are fairly complex and contain many images.

If you visit the page in chrome and scroll down ~20 images, you will notice that the loading of the images is delayed a bit to keep the browser smooth but they still load really quickly. If you visit it in safari technology preview, the images don't load at all for minutes (seems to only sometimes happen. Try: after a fresh browser start, first go to singular-society.com and then click on the search icon and then press enter, then scroll down a bit). (See also attached video)
Comment 26 Michael Catanzaro 2024-01-26 05:16:07 PST
Please create a new bug report. You can link to this one using the See Also field.
Comment 27 Daniel 2024-01-26 05:28:06 PST
Ok, opened https://bugs.webkit.org/show_bug.cgi?id=268152