-
Notifications
You must be signed in to change notification settings - Fork 47.5k
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
Polyfill requestIdleCallback when native is not available #8833
Conversation
I introduce the ReactDOMFrameScheduling module which just exposes rIC and rAF. The polyfill works by scheduling a requestAnimationFrame, store the time for the start of the frame, then schedule a postMessage which gets scheduled after paint. The deadline is set to time + frame rate. By separating the idle call into a separate event tick we ensure that layout, paint and other browser work is counted against the available time. The frame rate is dynamically adjusted by tracking the minimum time between two rAF callbacks. This is not perfect because browsers can schedule multiple callbacks to catch up after a long frame. To compensate for this we only adjust if two consecutive periods are faster than normal. This seems to guarantee that we will hit frame deadlines and we don't end up dropping frames. However, there is still some lost time so we risk starving by not having enough idle time. Especially Firefox seems to have issues keeping up on the triangle demo However, that is also true for native rIC in Firefox. It seems like more render work is scheduled on the main thread pipeline and also that JS execution just generally has more overhead.
95ff2aa
to
f1f9de8
Compare
I originally used |
Perhaps we should be more aggressive about detecting native rIC? There are some bad polyfills out there which would work very poorly with React if they're used on the page. |
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.
Very clever! Left a thought or two but overall I think this looks great.
return frameDeadline - performance.now(); | ||
} : function() { | ||
// As a fallback we use Date.now. | ||
return frameDeadline - Date.now(); |
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.
Thinking out loud: performance.now()
and Date.now()
return very different values but they're being used interchangeably, driven by the param passed to requestAnimationFrame
. Wouldn't this cause problems if we ever end up mixing the 2 due to polyfills? (requestAnimationFrame
is supposed to be passed a time from performance.now()
but some polyfills are Date
based.)
Fortunately I don't think this is likely to occur. It seems both are supported by everything we target except for:
- IE9, which supports neither; presumably this means we'd use
Date.now
for both. - iOS 8.4 which supports
requestAnimationFrame
but notperformance.now
. This version is 2.5 years old though and iOS has pretty strong upgrade adoption, so maybe we can just ignore this one point release.
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.
If iOS 8.4 supports requestAnimationFrame
but not performance.now
my hypothesis is that the value passed to requestAnimationFrame
is the old Date.now
version of requestAnimationFrame
.
Yes, it is very possible that there is a browser that implements the new requestAnimationFrame
without implementing performance.now
. Seems unlikely though.
However, even more likely is that there are browsers out there that implement the old requestAnimationFrame
while already implementing performance.now
. That's very possible.
We could probably do a guess based on the value passed to requestAnimationFrame
which one it is (if it is a very low number) and then switch dynamically between the two. For the case where the new rAF is implemented but no performance.now
we could use a Date.now
time stamp instead. I'd rather not complicate it unless we know of at least one browser where either of this happens though. Perhaps we can call on the community to help out identifying issues here?
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.
Hm. Actually I wonder if performance.now
might be implemented behind performance.webkitNow
in some cases which would trigger the bad case.
} | ||
}; | ||
// Assumes that we have addEventListener in this environment. Might need | ||
// something better for old IE. |
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.
IE9+ supports addEventListener
; as we no longer support IE8 this should be fine. 👍
Oh, btw, it's possible that other input events gets scheduled after our |
I introduce the ReactDOMFrameScheduling module which just exposes rIC and rAF.
The polyfill works by scheduling a requestAnimationFrame, store the
frame start time
for the start of the frame, then schedule a postMessage which gets scheduled after paint. The deadline is set toframe start time
+frame rate
. By separating the idle call into a separate event tick we ensure that layout, paint and other browser work is counted against the available time.The frame rate is dynamically adjusted by tracking the minimum time between two rAF callbacks. This is not perfect because browsers can schedule multiple callbacks to catch up after a long frame. To compensate for this we only adjust if two consecutive periods are faster than normal.
This seems to guarantee that we will hit frame deadlines and we don't end up dropping frames. However, there is still some lost time so we risk starving by not having enough idle time.
Especially Firefox seems to have issues keeping up on the triangle demo. However, that is also true for native rIC in Firefox. It seems like more render work is scheduled on the main thread pipeline and also that JS execution just generally has more overhead. Safari has little trouble keeping up with this polyfill. I have not tested IE yet.