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

Fall back to 'setTimeout' when 'requestAnimationFrame' is not called #13091

Merged
merged 3 commits into from
Jun 22, 2018

Conversation

flarnie
Copy link
Contributor

@flarnie flarnie commented Jun 21, 2018

Will you be the hero to review this PR?
Would be great to get the fix landed in Monday's sync. 😁

what is the change?:
If 'requestAnimationFrame' is not called for 100ms we fall back to
'setTimeout' to schedule the postmessage.

why make this change?:
When you start loading a page, and then switch tabs,
'requestAnimationFrame' is throttled or not called until you come back
to that tab. That means React's rendering, any any other scheduled work,
are paused.

Users expect the page to continue loading, and rendering is part of the
page load in a React app. So we need to continue calling callbacks.

test plan:
Manually tested using the new fixture test, observed that the callbacks
were called while switched to another tab. They were called more
slowly, but that seems like a reasonable thing.

issue:
Internal task T30754186

flarnie added 2 commits June 21, 2018 13:44
**what is the change?:**
Just adding a test to the fixture, where we can easily see whether
scheduled callbacks are called after switching away from the fixture
tab.

**why make this change?:**
We are about to fix the schedule module so that it still runs even when
the tab is in the backround.

**test plan:**
Manually tested the fixture, verified that it works as expected and
right now callbacks are not called when the tab is in the background.

**issue:**
Internal task T30754186
**what is the change?:**
If 'requestAnimationFrame' is not called for 100ms we fall back to
'setTimeout' to schedule the postmessage.

**why make this change?:**
When you start loading a page, and then switch tabs,
'requestAnimationFrame' is throttled or not called until you come back
to that tab. That means React's rendering, any any other scheduled work,
are paused.

Users expect the page to continue loading, and rendering is part of the
page load in a React app. So we need to continue calling callbacks.

**test plan:**
Manually tested using the new fixture test, observed that the callbacks
were called while switched to another tab. They were called more
slowly, but that seems like a reasonable thing.

**issue:**
Internal task T30754186
@pull-bot
Copy link

pull-bot commented Jun 21, 2018

ReactDOM: size: 🔺+0.1%, gzip: 🔺+0.1%

Details of bundled changes.

Comparing: da5c87b...d9f6f42

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js +0.1% +0.1% 626.62 KB 627.48 KB 146.5 KB 146.72 KB UMD_DEV
react-dom.production.min.js 🔺+0.1% 🔺+0.1% 95.27 KB 95.4 KB 30.79 KB 30.82 KB UMD_PROD
react-dom.development.js +0.1% +0.1% 622.74 KB 623.61 KB 145.32 KB 145.54 KB NODE_DEV
react-dom.production.min.js 🔺+0.1% 🔺+0.2% 95.26 KB 95.39 KB 30.32 KB 30.37 KB NODE_PROD
react-dom.profiling.min.js +0.1% +0.2% 96.23 KB 96.36 KB 30.65 KB 30.71 KB NODE_PROFILING

react-art

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-art.development.js +0.2% +0.2% 410.67 KB 411.53 KB 92.07 KB 92.29 KB UMD_DEV
react-art.production.min.js 🔺+0.2% 🔺+0.2% 82.46 KB 82.58 KB 25.37 KB 25.42 KB UMD_PROD
react-art.development.js +0.3% +0.3% 342.65 KB 343.51 KB 74.93 KB 75.15 KB NODE_DEV
react-art.production.min.js 🔺+0.3% 🔺+0.5% 47.44 KB 47.57 KB 14.75 KB 14.81 KB NODE_PROD

react-scheduler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-scheduler.development.js +5.8% +4.6% 15.1 KB 15.98 KB 4.73 KB 4.95 KB UMD_DEV
react-scheduler.production.min.js 🔺+4.9% 🔺+4.0% 2.43 KB 2.55 KB 1.18 KB 1.23 KB UMD_PROD
react-scheduler.development.js +5.8% +4.7% 14.91 KB 15.78 KB 4.69 KB 4.91 KB NODE_DEV
react-scheduler.production.min.js 🔺+5.3% 🔺+4.4% 2.34 KB 2.47 KB 1.13 KB 1.17 KB NODE_PROD
ReactScheduler-dev.js +6.4% +5.2% 13.67 KB 14.54 KB 4.21 KB 4.43 KB FB_WWW_DEV
ReactScheduler-prod.js 🔺+6.8% 🔺+4.8% 6.91 KB 7.38 KB 1.8 KB 1.89 KB FB_WWW_PROD

Generated by 🚫 dangerJS

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

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

Feels wasteful to always do this but I can't figure out what a better solution would be if we want to stay responsive.

Maybe we could use visibilitychange event to detect backgrounding and toggle the "aggressive" mode, but avoid extra timeouts when we're visible?

Accept to unblock but would be nice to investigate the above solution

let timeoutID;
const scheduleAnimationFrameWithFallbackSupport = function(callback) {
// schedule rAF and also a setTimeout
rafID = localRequestAnimationFrame(function(...args) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: We expect a single argument, let's keep that explicit? Even if we changed to many, the other if branch already assumes it's just one, so might as well do here for clarity

@gaearon
Copy link
Collaborator

gaearon commented Jun 21, 2018

Here's what velocity (a super popular animation library) does:

https://github.com/julianshapiro/velocity/blob/440d986c18a715da94f218001a7b2ddb1501783d/velocity.js#L2145-L2253

and

https://github.com/julianshapiro/velocity/blob/440d986c18a715da94f218001a7b2ddb1501783d/velocity.js#L2439-L2458

Kind of fun but likely overkill for us.

@flarnie
Copy link
Contributor Author

flarnie commented Jun 22, 2018

That is a fascinating solution - great find! I think it's worth exploring, but will land this first in order to unblock moving forward with further async rendering experiments.

My only concerns would be

  • bundle size; can make a separate PR and compare sizes before and after switching approaches.
  • looking out for any gotchas with using webworkers, although the example from 'velocity' seems like they have covered the issues I can think of.

const counterNode = document.getElementById('test-7');
counter++;
counterNode.innerHTML = counter;
waitForTimeToPass(100);
Copy link
Contributor

@nhunzaker nhunzaker Jun 22, 2018

Choose a reason for hiding this comment

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

Why 100? Sorry. I'll keep reading :)

Copy link
Contributor Author

@flarnie flarnie Jun 22, 2018

Choose a reason for hiding this comment

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

Good question~! It's arbitrary - seemed like enough time to have lots of numbers increment, but not too fast to see.

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool. I didn't know about the scheduling fixtures! This is really neat!

Copy link
Contributor

@nhunzaker nhunzaker left a comment

Choose a reason for hiding this comment

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

I'm really curious about using the Page Visibility API too, but I really like how simple this solution is.

Is waiting 100ms just a decent compromise between delaying too much and avoiding this behavior on an active tab?

@flarnie
Copy link
Contributor Author

flarnie commented Jun 22, 2018

Is waiting 100ms just a decent compromise between delaying too much and avoiding this behavior on an active tab?

Yes, and it matches what some comparable code in Facebook internals was doing.

@flarnie
Copy link
Contributor Author

flarnie commented Jun 22, 2018

Also I'm observing that, in Firefox at least, even setTimeout is throttled and fires about once per second, so the ms timeout number probably doesn't matter much, so long as it is long enough to not pre-empt the rAF call.

The solution @gaearon found could work better, would be nice to continue firing at 60fps when backgrounded if possible.

@flarnie flarnie merged commit 076bbea into facebook:master Jun 22, 2018
@acdlite
Copy link
Contributor

acdlite commented Jun 22, 2018

This fix seems wrong to me. What are the circumstances under which requestAnimationFrame would fail to fire, other than main thread starvation? Isn't this why we have a timeout option?

@acdlite
Copy link
Contributor

acdlite commented Jun 22, 2018

would be nice to continue firing at 60fps when backgrounded if possible.

"schedule" is not an animation library. 60fps would be overkill.

My proposed alternative fix would be to setTimeout using the user-provided timeout, perhaps with a default value. Not every 100ms.

@flarnie
Copy link
Contributor Author

flarnie commented Jun 22, 2018

This fix seems wrong to me. What are the circumstances under which requestAnimationFrame would fail to fire, other than main thread starvation? Isn't this why we have a timeout option?

This is purely to fix the problem of when the tab is backgrounded.

"schedule" is not an animation library. 60fps would be overkill.
Would like to make it something which could be used by an animation library, but for now that's true.

My proposed alternative fix would be to setTimeout using the user-provided timeout, perhaps with a default value. Not every 100ms.

Is the concern that it's too heavy to schedule a timeout so often? Wouldn't we still do that even if the timeout was 100ms?

@gaearon
Copy link
Collaborator

gaearon commented Jun 22, 2018

Btw browsers throttle setTimeout too. So don't expect it to fire more often than ~1s. Might as well set that as the timeout.

@acdlite
Copy link
Contributor

acdlite commented Jun 22, 2018

Totally skimmed over the part about tab switching, my bad.

Shared my other concerns in person but I'm fine with this as a temporary solution. Using the visibilitychange event sounds promising.

@acdlite
Copy link
Contributor

acdlite commented Jun 22, 2018

My biggest concern is that you switch tabs, the callback times out, and all the async work of the background tab is synchronously flushed without yielding, which is what I suspect might be happening in the current incarnation. Need more tests. Let's follow up before open sourcing this.

@sag1v
Copy link

sag1v commented Jul 2, 2018

Hi, Sorry for burgling in on this conversation.
If i'm not mistaken you can mistakenly run the setTimeout fallback at some situations that the rAF is called at the end of the render steps.

These situations are likely to happen in safari & edge as stated by @jakearchibald in this issue .
Or am i way off 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants