mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
222 lines
6.8 KiB
JavaScript
222 lines
6.8 KiB
JavaScript
/**
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
// This is a built-in polyfill for requestIdleCallback. It works by scheduling
|
|
// a requestAnimationFrame, storing the time for the start of the frame, then
|
|
// scheduling a postMessage which gets scheduled after paint. Within the
|
|
// postMessage handler do as much work as possible until 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.
|
|
|
|
import type {Deadline} from 'react-reconciler';
|
|
|
|
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
|
import warning from 'fbjs/lib/warning';
|
|
|
|
if (__DEV__) {
|
|
if (
|
|
ExecutionEnvironment.canUseDOM &&
|
|
typeof requestAnimationFrame !== 'function'
|
|
) {
|
|
warning(
|
|
false,
|
|
'React depends on requestAnimationFrame. Make sure that you load a ' +
|
|
'polyfill in older browsers. https://fb.me/react-polyfills',
|
|
);
|
|
}
|
|
}
|
|
|
|
const hasNativePerformanceNow =
|
|
typeof performance === 'object' && typeof performance.now === 'function';
|
|
|
|
let now;
|
|
if (hasNativePerformanceNow) {
|
|
now = function() {
|
|
return performance.now();
|
|
};
|
|
} else {
|
|
now = function() {
|
|
return Date.now();
|
|
};
|
|
}
|
|
|
|
// TODO: There's no way to cancel, because Fiber doesn't atm.
|
|
let rIC: (
|
|
callback: (deadline: Deadline, options?: {timeout: number}) => void,
|
|
) => number;
|
|
let cIC: (callbackID: number) => void;
|
|
|
|
if (!ExecutionEnvironment.canUseDOM) {
|
|
rIC = function(
|
|
frameCallback: (deadline: Deadline, options?: {timeout: number}) => void,
|
|
): number {
|
|
return setTimeout(() => {
|
|
frameCallback({
|
|
timeRemaining() {
|
|
return Infinity;
|
|
},
|
|
});
|
|
});
|
|
};
|
|
cIC = function(timeoutID: number) {
|
|
clearTimeout(timeoutID);
|
|
};
|
|
} else if (
|
|
typeof requestIdleCallback !== 'function' ||
|
|
typeof cancelIdleCallback !== 'function'
|
|
) {
|
|
// Polyfill requestIdleCallback and cancelIdleCallback
|
|
|
|
let scheduledRICCallback = null;
|
|
let isIdleScheduled = false;
|
|
let timeoutTime = -1;
|
|
|
|
let isAnimationFrameScheduled = false;
|
|
|
|
let frameDeadline = 0;
|
|
// We start out assuming that we run at 30fps but then the heuristic tracking
|
|
// will adjust this value to a faster fps if we get more frequent animation
|
|
// frames.
|
|
let previousFrameTime = 33;
|
|
let activeFrameTime = 33;
|
|
|
|
let frameDeadlineObject;
|
|
if (hasNativePerformanceNow) {
|
|
frameDeadlineObject = {
|
|
didTimeout: false,
|
|
timeRemaining() {
|
|
// We assume that if we have a performance timer that the rAF callback
|
|
// gets a performance timer value. Not sure if this is always true.
|
|
const remaining = frameDeadline - performance.now();
|
|
return remaining > 0 ? remaining : 0;
|
|
},
|
|
};
|
|
} else {
|
|
frameDeadlineObject = {
|
|
didTimeout: false,
|
|
timeRemaining() {
|
|
// Fallback to Date.now()
|
|
const remaining = frameDeadline - Date.now();
|
|
return remaining > 0 ? remaining : 0;
|
|
},
|
|
};
|
|
}
|
|
|
|
// We use the postMessage trick to defer idle work until after the repaint.
|
|
const messageKey =
|
|
'__reactIdleCallback$' +
|
|
Math.random()
|
|
.toString(36)
|
|
.slice(2);
|
|
const idleTick = function(event) {
|
|
if (event.source !== window || event.data !== messageKey) {
|
|
return;
|
|
}
|
|
|
|
isIdleScheduled = false;
|
|
|
|
const currentTime = now();
|
|
if (frameDeadline - currentTime <= 0) {
|
|
// There's no time left in this idle period. Check if the callback has
|
|
// a timeout and whether it's been exceeded.
|
|
if (timeoutTime !== -1 && timeoutTime <= currentTime) {
|
|
// Exceeded the timeout. Invoke the callback even though there's no
|
|
// time left.
|
|
frameDeadlineObject.didTimeout = true;
|
|
} else {
|
|
// No timeout.
|
|
if (!isAnimationFrameScheduled) {
|
|
// Schedule another animation callback so we retry later.
|
|
isAnimationFrameScheduled = true;
|
|
requestAnimationFrame(animationTick);
|
|
}
|
|
// Exit without invoking the callback.
|
|
return;
|
|
}
|
|
} else {
|
|
// There's still time left in this idle period.
|
|
frameDeadlineObject.didTimeout = false;
|
|
}
|
|
|
|
timeoutTime = -1;
|
|
const callback = scheduledRICCallback;
|
|
scheduledRICCallback = null;
|
|
if (callback !== null) {
|
|
callback(frameDeadlineObject);
|
|
}
|
|
};
|
|
// Assumes that we have addEventListener in this environment. Might need
|
|
// something better for old IE.
|
|
window.addEventListener('message', idleTick, false);
|
|
|
|
const animationTick = function(rafTime) {
|
|
isAnimationFrameScheduled = false;
|
|
let nextFrameTime = rafTime - frameDeadline + activeFrameTime;
|
|
if (
|
|
nextFrameTime < activeFrameTime &&
|
|
previousFrameTime < activeFrameTime
|
|
) {
|
|
if (nextFrameTime < 8) {
|
|
// Defensive coding. We don't support higher frame rates than 120hz.
|
|
// If we get lower than that, it is probably a bug.
|
|
nextFrameTime = 8;
|
|
}
|
|
// If one frame goes long, then the next one can be short to catch up.
|
|
// If two frames are short in a row, then that's an indication that we
|
|
// actually have a higher frame rate than what we're currently optimizing.
|
|
// We adjust our heuristic dynamically accordingly. For example, if we're
|
|
// running on 120hz display or 90hz VR display.
|
|
// Take the max of the two in case one of them was an anomaly due to
|
|
// missed frame deadlines.
|
|
activeFrameTime =
|
|
nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime;
|
|
} else {
|
|
previousFrameTime = nextFrameTime;
|
|
}
|
|
frameDeadline = rafTime + activeFrameTime;
|
|
if (!isIdleScheduled) {
|
|
isIdleScheduled = true;
|
|
window.postMessage(messageKey, '*');
|
|
}
|
|
};
|
|
|
|
rIC = function(
|
|
callback: (deadline: Deadline) => void,
|
|
options?: {timeout: number},
|
|
): number {
|
|
// This assumes that we only schedule one callback at a time because that's
|
|
// how Fiber uses it.
|
|
scheduledRICCallback = callback;
|
|
if (options != null && typeof options.timeout === 'number') {
|
|
timeoutTime = now() + options.timeout;
|
|
}
|
|
if (!isAnimationFrameScheduled) {
|
|
// If rAF didn't already schedule one, we need to schedule a frame.
|
|
// TODO: If this rAF doesn't materialize because the browser throttles, we
|
|
// might want to still have setTimeout trigger rIC as a backup to ensure
|
|
// that we keep performing work.
|
|
isAnimationFrameScheduled = true;
|
|
requestAnimationFrame(animationTick);
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
cIC = function() {
|
|
scheduledRICCallback = null;
|
|
isIdleScheduled = false;
|
|
timeoutTime = -1;
|
|
};
|
|
} else {
|
|
rIC = window.requestIdleCallback;
|
|
cIC = window.cancelIdleCallback;
|
|
}
|
|
|
|
export {now, rIC, cIC};
|