mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
165 lines
5.2 KiB
JavaScript
165 lines
5.2 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. http://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) => void) => number;
|
|
|
|
if (!ExecutionEnvironment.canUseDOM) {
|
|
rIC = function(frameCallback: (deadline: Deadline) => void): number {
|
|
setTimeout(() => {
|
|
frameCallback({
|
|
timeRemaining() {
|
|
return Infinity;
|
|
},
|
|
});
|
|
});
|
|
return 0;
|
|
};
|
|
} else if (typeof requestIdleCallback !== 'function') {
|
|
// Polyfill requestIdleCallback.
|
|
|
|
var scheduledRICCallback = null;
|
|
|
|
var isIdleScheduled = false;
|
|
var isAnimationFrameScheduled = false;
|
|
|
|
var 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.
|
|
var previousFrameTime = 33;
|
|
var activeFrameTime = 33;
|
|
|
|
var frameDeadlineObject;
|
|
if (hasNativePerformanceNow) {
|
|
frameDeadlineObject = {
|
|
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.
|
|
return frameDeadline - performance.now();
|
|
},
|
|
};
|
|
} else {
|
|
frameDeadlineObject = {
|
|
timeRemaining() {
|
|
// Fallback to Date.now()
|
|
return frameDeadline - Date.now();
|
|
},
|
|
};
|
|
}
|
|
|
|
// We use the postMessage trick to defer idle work until after the repaint.
|
|
var messageKey = '__reactIdleCallback$' + Math.random().toString(36).slice(2);
|
|
var idleTick = function(event) {
|
|
if (event.source !== window || event.data !== messageKey) {
|
|
return;
|
|
}
|
|
isIdleScheduled = false;
|
|
var 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);
|
|
|
|
var animationTick = function(rafTime) {
|
|
isAnimationFrameScheduled = false;
|
|
var 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): number {
|
|
// This assumes that we only schedule one callback at a time because that's
|
|
// how Fiber uses it.
|
|
scheduledRICCallback = callback;
|
|
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;
|
|
};
|
|
} else {
|
|
rIC = requestIdleCallback;
|
|
}
|
|
|
|
export {now, rIC};
|