-
Notifications
You must be signed in to change notification settings - Fork 136
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add core requestIdleCallback functionality.
Builds on ReactScheduler to account for the browsers framerate: https://github.com/facebook/react/blob/43a137d9c13064b530d95ba51138ec1607de2c99/packages/react-scheduler/src/ReactScheduler.js is defined Tests passing: - is defined - should return a callback identifier number of "1" - should increment the callback identifier - schedules a callback with one IdleDeadline argument passed to the callback - schedules multiple callbacks - scheduled callbacks are called in order - schedules callbacks with an expired timeout first regardless of order - schedules callbacks with an expired timeout in order of their timeout - schedules nested callbacks to run in different idle periods - schedules a callback for the next idle period when the event loop is busy - schedules a callback for the same idle period when the event loop is busy but the callbacks's timeout has expired - sets the callback's deadline "didTimeout" property to true when the callback's timeout is exceeded - sets the callback's deadline "didTimeout" property to false when the callback's timeout is not exceeded or set
- Loading branch information
Showing
5 changed files
with
510 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
"aliases": [], | ||
"browsers": { | ||
"android": "*", | ||
"bb": "*", | ||
"chrome": "<47", | ||
"edge": "*", | ||
"edge_mob": "*", | ||
"firefox": "<55", | ||
"ios_chr": "*", | ||
"ios_saf": "*", | ||
"ie": "*", | ||
"ie_mob": "*", | ||
"opera": "<34", | ||
"op_mini": "*", | ||
"op_mob": "*", | ||
"safari": "*", | ||
"firefox_mob": "<55", | ||
"samsung_mob": "*" | ||
}, | ||
"dependencies": [ | ||
"Date.now", | ||
"Array.prototype.indexOf", | ||
"requestAnimationFrame", | ||
"Event" | ||
], | ||
"license": "MIT", | ||
"docs": "https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'requestIdleCallback' in this |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
(function (global) { | ||
|
||
// MIT License | ||
// | ||
// Copyright (c) 2013-present, Facebook, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all | ||
// copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
|
||
// The requestIdleCallback polyfill builds on ReactScheduler: | ||
// "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." | ||
|
||
var idleCallbackIdentifier = 0; | ||
var scheduledCallbacks = []; | ||
var nestedCallbacks = []; | ||
|
||
var isIdleScheduled = false; | ||
var isCallbackRunning = false; | ||
var isAnimationFrameScheduled = false; | ||
|
||
var frameDeadline = 0; | ||
|
||
var messageKey = 'polyfillIdleCallback' + Math.random().toString(36).slice(2); | ||
|
||
// 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; | ||
|
||
function getNow() { | ||
return performance && typeof performance.now === 'function' ? | ||
performance.now() : | ||
Date.now(); | ||
} | ||
|
||
function timeRemaining() { | ||
return frameDeadline - getNow(); | ||
}; | ||
|
||
function getDeadline(callbackObject) { | ||
var timeout = callbackObject.options.timeout; | ||
var added = callbackObject.added; | ||
return { | ||
timeRemaining: timeRemaining, | ||
didTimeout: timeout ? added + timeout < getNow() : false | ||
}; | ||
}; | ||
|
||
function runCallback(callbackObject) { | ||
var deadline = getDeadline(callbackObject); | ||
var callback = callbackObject.callback; | ||
callback(deadline); | ||
}; | ||
|
||
function scheduleAnimationFrame() { | ||
if (!isAnimationFrameScheduled) { | ||
isAnimationFrameScheduled = true; | ||
// 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. | ||
requestAnimationFrame(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 = Math.max(previousFrameTime, nextFrameTime); | ||
} else { | ||
previousFrameTime = nextFrameTime; | ||
} | ||
frameDeadline = rafTime + activeFrameTime; | ||
if (!isIdleScheduled) { | ||
isIdleScheduled = true; | ||
window.postMessage(messageKey, '*'); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
// We use the postMessage trick to defer idle work until after the repaint. | ||
window.addEventListener('message', function (event) { | ||
if (event.source !== window || event.data !== messageKey) { | ||
return; | ||
} | ||
|
||
isIdleScheduled = false; | ||
isCallbackRunning = true; | ||
|
||
// Find timed-out callbacks from the scheduled callbacks array. | ||
var timedOutCallbacks = []; | ||
for (var index = 0; index < scheduledCallbacks.length; index++) { | ||
var callbackObject = scheduledCallbacks[index]; | ||
var deadline = getDeadline(callbackObject); | ||
if (deadline.didTimeout) { | ||
timedOutCallbacks.push(callbackObject); | ||
} | ||
} | ||
|
||
// Of the timed-out callbacks, order by those with the lowest timeout. | ||
timedOutCallbacks.sort(function (a, b) { | ||
return a.options.timeout - b.options.timeout; | ||
}); | ||
|
||
// Remove timed-out callbacks from the scheduled callbacks array. | ||
for (var index = 0; index < timedOutCallbacks.length; index++) { | ||
var callbackObject = timedOutCallbacks[index]; | ||
var scheduledCallbackIndex = scheduledCallbacks.indexOf(callbackObject); | ||
if (scheduledCallbackIndex > -1) { | ||
scheduledCallbacks.splice(scheduledCallbackIndex, 1); | ||
} | ||
} | ||
|
||
// Run all timed-out callbacks, regardless of the deadline time | ||
// remaining. | ||
while (timedOutCallbacks.length > 0) { | ||
var callbackObject = timedOutCallbacks.shift(); | ||
runCallback(callbackObject); | ||
} | ||
|
||
// While there is deadline time remaining, run remaining scheduled | ||
// callbacks. | ||
while (scheduledCallbacks.length > 0 && timeRemaining() > 0) { | ||
var callbackObject = scheduledCallbacks.shift(); | ||
runCallback(callbackObject); | ||
} | ||
|
||
// Schedule callbacks added during this idle period to run in the next | ||
// idle period (nested callbacks). | ||
if (nestedCallbacks.length > 0) { | ||
scheduledCallbacks = scheduledCallbacks.concat(nestedCallbacks); | ||
nestedCallbacks = []; | ||
} | ||
|
||
// Schedule any remaining callbacks for a future idle period. | ||
if (scheduledCallbacks.length > 0) { | ||
scheduleAnimationFrame(); | ||
} | ||
|
||
isCallbackRunning = false; | ||
}, false); | ||
|
||
|
||
/** | ||
* @param {function} callback | ||
* @return {number} | ||
*/ | ||
global.requestIdleCallback = function requestIdleCallback(callback, options) { | ||
// Create an object to store the callback, its options, and the time it | ||
// was added. | ||
var callbackObject = { | ||
callback: callback, | ||
options: options || {}, | ||
added: getNow() | ||
}; | ||
|
||
// If an idle callback is running already this is a nested idle callback | ||
// and should be scheduled for a different period. If no idle callback | ||
// is running schedule immediately. | ||
if (isCallbackRunning) { | ||
nestedCallbacks.push(callbackObject); | ||
} else { | ||
scheduledCallbacks.push(callbackObject); | ||
} | ||
|
||
// Run scheduled idle callbacks after the next animation frame. | ||
scheduleAnimationFrame(); | ||
|
||
// Return the callbacks identifier. | ||
return ++idleCallbackIdentifier; | ||
}; | ||
|
||
}(this)); |
Oops, something went wrong.