mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[Scheduler] requestPaint (#15960)
* [Scheduler] requestPaint Signals to Scheduler that the browser needs to paint the screen. React will call it in the commit phase. Scheduler will yield at the end of the current frame, even if there is no pending input. When `isInputPending` is not available, this has no effect, because we yield at the end of every frame regardless. React will call `requestPaint` in the commit phase as long as there's at least one effect. We could choose not to call it if none of the effects are DOM mutations, but this is so rare that it doesn't seem worthwhile to bother checking. * Fall back gracefully if requestPaint is missing
This commit is contained in:
parent
8d4ddd33ac
commit
6568a79931
12
packages/react-art/src/ReactARTHostConfig.js
vendored
12
packages/react-art/src/ReactARTHostConfig.js
vendored
|
|
@ -7,23 +7,11 @@
|
|||
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
import * as Scheduler from 'scheduler';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
|
||||
import type {ReactEventComponentInstance} from 'shared/ReactTypes';
|
||||
|
||||
// Intentionally not named imports because Rollup would
|
||||
// use dynamic dispatch for CommonJS interop named imports.
|
||||
const {
|
||||
unstable_now: now,
|
||||
unstable_scheduleCallback: scheduleDeferredCallback,
|
||||
unstable_shouldYield: shouldYield,
|
||||
unstable_cancelCallback: cancelDeferredCallback,
|
||||
} = Scheduler;
|
||||
|
||||
export {now, scheduleDeferredCallback, shouldYield, cancelDeferredCallback};
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
const NO_CONTEXT = {};
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import * as Scheduler from 'scheduler';
|
||||
|
||||
import {precacheFiberNode, updateFiberProps} from './ReactDOMComponentTree';
|
||||
import {
|
||||
createElement,
|
||||
|
|
@ -113,17 +111,6 @@ import warning from 'shared/warning';
|
|||
|
||||
const {html: HTML_NAMESPACE} = Namespaces;
|
||||
|
||||
// Intentionally not named imports because Rollup would
|
||||
// use dynamic dispatch for CommonJS interop named imports.
|
||||
const {
|
||||
unstable_now: now,
|
||||
unstable_scheduleCallback: scheduleDeferredCallback,
|
||||
unstable_shouldYield: shouldYield,
|
||||
unstable_cancelCallback: cancelDeferredCallback,
|
||||
} = Scheduler;
|
||||
|
||||
export {now, scheduleDeferredCallback, shouldYield, cancelDeferredCallback};
|
||||
|
||||
let SUPPRESS_HYDRATION_WARNING;
|
||||
if (__DEV__) {
|
||||
SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {
|
|||
getCurrentPriorityLevel,
|
||||
runWithPriority,
|
||||
shouldYield,
|
||||
requestPaint,
|
||||
now,
|
||||
ImmediatePriority,
|
||||
UserBlockingPriority,
|
||||
|
|
@ -1666,6 +1667,10 @@ function commitRootImpl(root) {
|
|||
|
||||
nextEffect = null;
|
||||
|
||||
// Tell Scheduler to yield at the end of the frame, so the browser has an
|
||||
// opportunity to paint.
|
||||
requestPaint();
|
||||
|
||||
if (enableSchedulerTracing) {
|
||||
__interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const {
|
|||
unstable_scheduleCallback: Scheduler_scheduleCallback,
|
||||
unstable_cancelCallback: Scheduler_cancelCallback,
|
||||
unstable_shouldYield: Scheduler_shouldYield,
|
||||
unstable_requestPaint: Scheduler_requestPaint,
|
||||
unstable_now: Scheduler_now,
|
||||
unstable_getCurrentPriorityLevel: Scheduler_getCurrentPriorityLevel,
|
||||
unstable_ImmediatePriority: Scheduler_ImmediatePriority,
|
||||
|
|
@ -63,6 +64,9 @@ export const IdlePriority: ReactPriorityLevel = 95;
|
|||
export const NoPriority: ReactPriorityLevel = 90;
|
||||
|
||||
export const shouldYield = Scheduler_shouldYield;
|
||||
export const requestPaint =
|
||||
// Fall back gracefully if we're running an older verison of Scheduler.
|
||||
Scheduler_requestPaint !== undefined ? Scheduler_requestPaint : () => {};
|
||||
|
||||
let syncQueue: Array<SchedulerCallback> | null = null;
|
||||
let immediateQueueCallbackNode: mixed | null = null;
|
||||
|
|
|
|||
|
|
@ -166,6 +166,32 @@ describe('ReactSchedulerIntegration', () => {
|
|||
expect(Scheduler).toFlushAndYield(['A [UserBlocking]', 'B [Normal]']);
|
||||
});
|
||||
|
||||
it('requests a paint after committing', () => {
|
||||
const scheduleCallback = Scheduler.unstable_scheduleCallback;
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
root.render('Initial');
|
||||
Scheduler.flushAll();
|
||||
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('A'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('B'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('C'));
|
||||
|
||||
// Schedule a React render. React will request a paint after committing it.
|
||||
root.render('Update');
|
||||
|
||||
// Advance time just to be sure the next tasks have lower priority
|
||||
Scheduler.advanceTime(2000);
|
||||
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('D'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.yieldValue('E'));
|
||||
|
||||
// Flush everything up to the next paint. Should yield after the
|
||||
// React commit.
|
||||
Scheduler.unstable_flushUntilNextPaint();
|
||||
expect(Scheduler).toHaveYielded(['A', 'B', 'C']);
|
||||
});
|
||||
|
||||
// TODO
|
||||
it.skip('passive effects have render priority even if they are flushed early', () => {});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,6 +47,13 @@
|
|||
);
|
||||
}
|
||||
|
||||
function unstable_requestPaint() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_requestPaint.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_runWithPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
|
||||
this,
|
||||
|
|
@ -108,6 +115,7 @@
|
|||
unstable_scheduleCallback: unstable_scheduleCallback,
|
||||
unstable_cancelCallback: unstable_cancelCallback,
|
||||
unstable_shouldYield: unstable_shouldYield,
|
||||
unstable_requestPaint: unstable_requestPaint,
|
||||
unstable_runWithPriority: unstable_runWithPriority,
|
||||
unstable_next: unstable_next,
|
||||
unstable_wrapCallback: unstable_wrapCallback,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,13 @@
|
|||
);
|
||||
}
|
||||
|
||||
function unstable_requestPaint() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_requestPaint.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_runWithPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
|
||||
this,
|
||||
|
|
@ -102,6 +109,7 @@
|
|||
unstable_scheduleCallback: unstable_scheduleCallback,
|
||||
unstable_cancelCallback: unstable_cancelCallback,
|
||||
unstable_shouldYield: unstable_shouldYield,
|
||||
unstable_requestPaint: unstable_requestPaint,
|
||||
unstable_runWithPriority: unstable_runWithPriority,
|
||||
unstable_next: unstable_next,
|
||||
unstable_wrapCallback: unstable_wrapCallback,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,13 @@
|
|||
);
|
||||
}
|
||||
|
||||
function unstable_requestPaint() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_requestPaint.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_runWithPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
|
||||
this,
|
||||
|
|
@ -102,6 +109,7 @@
|
|||
unstable_scheduleCallback: unstable_scheduleCallback,
|
||||
unstable_cancelCallback: unstable_cancelCallback,
|
||||
unstable_shouldYield: unstable_shouldYield,
|
||||
unstable_requestPaint: unstable_requestPaint,
|
||||
unstable_runWithPriority: unstable_runWithPriority,
|
||||
unstable_next: unstable_next,
|
||||
unstable_wrapCallback: unstable_wrapCallback,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
shouldYieldToHost,
|
||||
getCurrentTime,
|
||||
forceFrameRate,
|
||||
requestPaint,
|
||||
} from './SchedulerHostConfig';
|
||||
|
||||
// TODO: Use symbols?
|
||||
|
|
@ -506,6 +507,8 @@ function unstable_shouldYield() {
|
|||
);
|
||||
}
|
||||
|
||||
const unstable_requestPaint = requestPaint;
|
||||
|
||||
export {
|
||||
ImmediatePriority as unstable_ImmediatePriority,
|
||||
UserBlockingPriority as unstable_UserBlockingPriority,
|
||||
|
|
@ -519,6 +522,7 @@ export {
|
|||
unstable_wrapCallback,
|
||||
unstable_getCurrentPriorityLevel,
|
||||
unstable_shouldYield,
|
||||
unstable_requestPaint,
|
||||
unstable_continueExecution,
|
||||
unstable_pauseExecution,
|
||||
unstable_getFirstCallbackNode,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export let cancelHostCallback;
|
|||
export let requestHostTimeout;
|
||||
export let cancelHostTimeout;
|
||||
export let shouldYieldToHost;
|
||||
export let requestPaint;
|
||||
export let getCurrentTime;
|
||||
export let forceFrameRate;
|
||||
|
||||
|
|
@ -125,7 +126,7 @@ if (
|
|||
shouldYieldToHost = function() {
|
||||
return false;
|
||||
};
|
||||
forceFrameRate = function() {};
|
||||
requestPaint = forceFrameRate = function() {};
|
||||
} else {
|
||||
if (typeof console !== 'undefined') {
|
||||
// TODO: Remove fb.me link
|
||||
|
|
@ -162,7 +163,8 @@ if (
|
|||
|
||||
// TODO: Make this configurable
|
||||
// TODO: Adjust this based on priority?
|
||||
let maxFrameLength = 300;
|
||||
let maxFrameLength = 150;
|
||||
let needsPaint = false;
|
||||
|
||||
const isInputPending =
|
||||
navigator !== undefined &&
|
||||
|
|
@ -181,11 +183,12 @@ if (
|
|||
// main thread, so the browser can perform high priority tasks. The main
|
||||
// ones are painting and user input. If we're certain there's no user
|
||||
// input, then we can yield less often without making the app less
|
||||
// responsive. We'll eventually yield regardless, since there could be
|
||||
// other main thread tasks that we don't know about.
|
||||
if (isInputPending !== null && !isInputPending()) {
|
||||
// There's no pending input. Only yield if we've reached the max
|
||||
// frame length.
|
||||
// responsive. We'll also check if a paint was requested. We'll eventually
|
||||
// yield regardless, since there could be other main thread tasks that we
|
||||
// don't know about.
|
||||
if (!needsPaint && isInputPending !== null && !isInputPending()) {
|
||||
// There's no pending input, and no task requested a paint. Only yield
|
||||
// if we've reached the max frame length.
|
||||
return currentTime >= frameDeadline + maxFrameLength;
|
||||
}
|
||||
// Either there is pending input, or there's no way for us to be sure
|
||||
|
|
@ -242,6 +245,9 @@ if (
|
|||
port.postMessage(undefined);
|
||||
throw error;
|
||||
}
|
||||
// Yielding to the browser will give it a chance to paint, so we can
|
||||
// reset this.
|
||||
needsPaint = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -321,4 +327,8 @@ if (
|
|||
localClearTimeout(timeoutID);
|
||||
timeoutID = -1;
|
||||
};
|
||||
|
||||
requestPaint = function() {
|
||||
needsPaint = true;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ let yieldedValues: Array<mixed> | null = null;
|
|||
let expectedNumberOfYields: number = -1;
|
||||
let didStop: boolean = false;
|
||||
let isFlushing: boolean = false;
|
||||
let needsPaint: boolean = false;
|
||||
let shouldYieldForPaint: boolean = false;
|
||||
|
||||
export function requestHostCallback(callback: boolean => void) {
|
||||
scheduledCallback = callback;
|
||||
|
|
@ -36,9 +38,10 @@ export function cancelHostTimeout(): void {
|
|||
|
||||
export function shouldYieldToHost(): boolean {
|
||||
if (
|
||||
expectedNumberOfYields !== -1 &&
|
||||
(expectedNumberOfYields !== -1 &&
|
||||
yieldedValues !== null &&
|
||||
yieldedValues.length >= expectedNumberOfYields
|
||||
yieldedValues.length >= expectedNumberOfYields) ||
|
||||
(shouldYieldForPaint && needsPaint)
|
||||
) {
|
||||
// We yielded at least as many values as expected. Stop flushing.
|
||||
didStop = true;
|
||||
|
|
@ -67,6 +70,7 @@ export function reset() {
|
|||
expectedNumberOfYields = -1;
|
||||
didStop = false;
|
||||
isFlushing = false;
|
||||
needsPaint = false;
|
||||
}
|
||||
|
||||
// Should only be used via an assertion helper that inspects the yielded values.
|
||||
|
|
@ -94,6 +98,31 @@ export function unstable_flushNumberOfYields(count: number): void {
|
|||
}
|
||||
}
|
||||
|
||||
export function unstable_flushUntilNextPaint(): void {
|
||||
if (isFlushing) {
|
||||
throw new Error('Already flushing work.');
|
||||
}
|
||||
if (scheduledCallback !== null) {
|
||||
const cb = scheduledCallback;
|
||||
shouldYieldForPaint = true;
|
||||
needsPaint = false;
|
||||
isFlushing = true;
|
||||
try {
|
||||
let hasMoreWork = true;
|
||||
do {
|
||||
hasMoreWork = cb(true, currentTime);
|
||||
} while (hasMoreWork && !didStop);
|
||||
if (!hasMoreWork) {
|
||||
scheduledCallback = null;
|
||||
}
|
||||
} finally {
|
||||
shouldYieldForPaint = false;
|
||||
didStop = false;
|
||||
isFlushing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function unstable_flushExpired() {
|
||||
if (isFlushing) {
|
||||
throw new Error('Already flushing work.');
|
||||
|
|
@ -181,3 +210,7 @@ export function advanceTime(ms: number) {
|
|||
unstable_flushExpired();
|
||||
}
|
||||
}
|
||||
|
||||
export function requestPaint() {
|
||||
needsPaint = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export {
|
|||
unstable_flushNumberOfYields,
|
||||
unstable_flushExpired,
|
||||
unstable_clearYields,
|
||||
unstable_flushUntilNextPaint,
|
||||
flushAll,
|
||||
yieldValue,
|
||||
advanceTime,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const {
|
|||
unstable_now,
|
||||
unstable_scheduleCallback,
|
||||
unstable_shouldYield,
|
||||
unstable_requestPaint,
|
||||
unstable_getFirstCallbackNode,
|
||||
unstable_runWithPriority,
|
||||
unstable_next,
|
||||
|
|
@ -39,6 +40,7 @@ export {
|
|||
unstable_now,
|
||||
unstable_scheduleCallback,
|
||||
unstable_shouldYield,
|
||||
unstable_requestPaint,
|
||||
unstable_getFirstCallbackNode,
|
||||
unstable_runWithPriority,
|
||||
unstable_next,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user