@samitouri / QOS-React-2 / commits / f305d2a489

[scheduler] Priority levels, continuations, and wrapped callbacks (#13720)

All of these features are based on features of React's internal scheduler. The eventual goal is to lift as much as possible out of the React internals into the Scheduler package. Includes some renaming of existing methods. - `scheduleWork` is now `scheduleCallback` - `cancelScheduledWork` is now `cancelCallback` Priority levels --------------- Adds the ability to schedule callbacks at different priority levels. The current levels are (final names TBD): - Immediate priority. Fires at the end of the outermost currently executing (similar to a microtask). - Interactive priority. Fires within a few hundred milliseconds. This should only be used to provide quick feedback to the user as a result of an interaction. - Normal priority. This is the default. Fires within several seconds. - "Maybe" priority. Only fires if there's nothing else to do. Used for prerendering or warming a cache. The priority is changed using `runWithPriority`: ```js runWithPriority(InteractivePriority, () => { scheduleCallback(callback); }); ``` Continuations ------------- Adds the ability for a callback to yield without losing its place in the queue, by returning a continuation. The continuation will have the same expiration as the callback that yielded. Wrapped callbacks ----------------- Adds the ability to wrap a callback so that, when it is called, it receives the priority of the current execution context.

Andrew Clark committed Sep 25, 2018 at 15:11 UTC f305d2a489e75a27f843fcca917dfd13e994268d
15 files changed +913 -301
fixtures/scheduler/index.html
+78 -76
@@ -1,8 +1,9 @@
1 <!DOCTYPE html>
2 <html style="width: 100%; height: 100%;">
3 - <head>
4 - <meta charset="utf-8">
5 - <title>Scheduler Test Page</title>
3 +
4 +<head>
5 + <meta charset="utf-8">
6 + <title>Scheduler Test Page</title>
7 <style>
8 .correct {
9 border: solid green 2px;
@@ -11,91 +12,92 @@
12 border: dashed red 2px;
13 }
14 </style>
14 - </head>
15 - <body>
16 - <h1>Scheduler Fixture</h1>
17 - <p>
18 - This fixture is for manual testing purposes, and the patterns used in
19 - implementing it should not be used as a model. This is mainly for anyone
20 - working on making changes to the `schedule` module.
21 - </p>
22 - <h2>Tests:</h2>
23 - <ol>
24 - <li>
25 - <button onClick="runTestOne()">Run Test 1</button>
26 - <p>Calls the callback within the frame when not blocked:</p>
27 - <div><b>Expected:</b></div>
28 - <div id="test-1-expected">
29 - </div>
30 - <div> -------------------------------------------------</div>
31 - <div> If you see the same above and below it's correct.
15 +</head>
16 +
17 +<body>
18 + <h1>Scheduler Fixture</h1>
19 + <p>
20 + This fixture is for manual testing purposes, and the patterns used in
21 + implementing it should not be used as a model. This is mainly for anyone
22 + working on making changes to the `schedule` module.
23 + </p>
24 + <h2>Tests:</h2>
25 + <ol>
26 + <li>
27 + <button onClick="runTestOne()">Run Test 1</button>
28 + <p>Calls the callback within the frame when not blocked:</p>
29 + <div><b>Expected:</b></div>
30 + <div id="test-1-expected">
31 + </div>
32 + <div> -------------------------------------------------</div>
33 + <div> If you see the same above and below it's correct.
34 <div> -------------------------------------------------</div>
35 <div><b>Actual:</b></div>
36 <div id="test-1"></div>
35 - </li>
36 - <li>
37 - <p>Accepts multiple callbacks and calls within frame when not blocked</p>
38 - <button onClick="runTestTwo()">Run Test 2</button>
39 - <div><b>Expected:</b></div>
40 - <div id="test-2-expected">
41 - </div>
42 - <div> -------------------------------------------------</div>
43 - <div> If you see the same above and below it's correct.
37 + </li>
38 + <li>
39 + <p>Accepts multiple callbacks and calls within frame when not blocked</p>
40 + <button onClick="runTestTwo()">Run Test 2</button>
41 + <div><b>Expected:</b></div>
42 + <div id="test-2-expected">
43 + </div>
44 + <div> -------------------------------------------------</div>
45 + <div> If you see the same above and below it's correct.
46 <div> -------------------------------------------------</div>
47 <div><b>Actual:</b></div>
48 <div id="test-2"></div>
47 - </li>
48 - <li>
49 - <p>Schedules callbacks in correct order when they use scheduleWork to schedule themselves</p>
50 - <button onClick="runTestThree()">Run Test 3</button>
51 - <div><b>Expected:</b></div>
52 - <div id="test-3-expected">
53 - </div>
54 - <div> -------------------------------------------------</div>
55 - <div> If you see the same above and below it's correct.
49 + </li>
50 + <li>
51 + <p>Schedules callbacks in correct order when they use scheduleWork to schedule themselves</p>
52 + <button onClick="runTestThree()">Run Test 3</button>
53 + <div><b>Expected:</b></div>
54 + <div id="test-3-expected">
55 + </div>
56 + <div> -------------------------------------------------</div>
57 + <div> If you see the same above and below it's correct.
58 <div> -------------------------------------------------</div>
59 <div><b>Actual:</b></div>
60 <div id="test-3"></div>
59 - </li>
60 - <li>
61 - <p>Calls timed out callbacks and then any more pending callbacks, defers others if time runs out</p>
62 - <button onClick="runTestFour()">Run Test 4</button>
63 - <div><b>Expected:</b></div>
64 - <div id="test-4-expected">
65 - </div>
66 - <div> -------------------------------------------------</div>
67 - <div> If you see the same above and below it's correct.
61 + </li>
62 + <li>
63 + <p>Calls timed out callbacks and then any more pending callbacks, defers others if time runs out</p>
64 + <button onClick="runTestFour()">Run Test 4</button>
65 + <div><b>Expected:</b></div>
66 + <div id="test-4-expected">
67 + </div>
68 + <div> -------------------------------------------------</div>
69 + <div> If you see the same above and below it's correct.
70 <div> -------------------------------------------------</div>
71 <div><b>Actual:</b></div>
72 <div id="test-4"></div>
71 - </li>
72 - <li>
73 - <p>When some callbacks throw errors, still calls them all within the same frame</p>
74 - <p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
75 - <button onClick="runTestFive()">Run Test 5</button>
76 - </li>
77 - <li>
78 - <p>When some callbacks throw errors <b> and some also time out</b>, still calls them all within the same frame</p>
79 - <p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
80 - <button onClick="runTestSix()">Run Test 6</button>
81 - </li>
82 - <li>
83 - <p>Continues calling callbacks even when user switches away from this tab</p>
84 - <button onClick="runTestSeven()">Run Test 7</button>
85 - <div><b>Click the button above, observe the counter, then switch to
86 - another tab and switch back:</b></div>
87 - <div id="test-7">
88 - </div>
89 - <div> If the counter advanced while you were away from this tab, it's correct.</div>
90 - </li>
91 - </ol>
92 - <script src="../../build/dist/react.development.js"></script>
93 - <script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
94 - <script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
95 - <script type="text/babel">
73 + </li>
74 + <li>
75 + <p>When some callbacks throw errors, still calls them all within the same frame</p>
76 + <p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
77 + <button onClick="runTestFive()">Run Test 5</button>
78 + </li>
79 + <li>
80 + <p>When some callbacks throw errors <b> and some also time out</b>, still calls them all within the same frame</p>
81 + <p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
82 + <button onClick="runTestSix()">Run Test 6</button>
83 + </li>
84 + <li>
85 + <p>Continues calling callbacks even when user switches away from this tab</p>
86 + <button onClick="runTestSeven()">Run Test 7</button>
87 + <div><b>Click the button above, observe the counter, then switch to
88 + another tab and switch back:</b></div>
89 + <div id="test-7">
90 + </div>
91 + <div> If the counter advanced while you were away from this tab, it's correct.</div>
92 + </li>
93 + </ol>
94 + <script src="../../build/dist/react.development.js"></script>
95 + <script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
96 + <script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
97 + <script type="text/babel">
98 const {
97 - unstable_scheduleWork: scheduleWork,
98 - unstable_cancelWork: cancelWork,
99 + unstable_scheduleCallback: scheduleCallback,
100 + unstable_cancelCallback: cancelCallback,
101 unstable_now: now
102 } = Scheduler;
103 function displayTestResult(testNumber) {
@@ -496,4 +498,4 @@ function runTestSeven() {
498 }
499 </script type="text/babel">
500 </body>
499 -</html>
501 +</html>
\ No newline at end of file
fixtures/tracing/script.js
+2 -2
@@ -30,8 +30,8 @@ function checkSchedulerAPI() {
30 if (
31 typeof Scheduler === 'undefined' ||
32 typeof Scheduler.unstable_now !== 'function' ||
33 - typeof Scheduler.unstable_scheduleWork !== 'function' ||
34 - typeof Scheduler.unstable_cancelScheduledWork !== 'function'
33 + typeof Scheduler.unstable_scheduleCallback !== 'function' ||
34 + typeof Scheduler.unstable_cancelCallback !== 'function'
35 ) {
36 throw 'API is not defined';
37 }
fixtures/unstable-async/suspense/src/components/App.js
+2 -2
@@ -1,5 +1,5 @@
1 import React, {Placeholder, PureComponent} from 'react';
2 -import {unstable_scheduleWork} from 'scheduler';
2 +import {unstable_scheduleCallback} from 'scheduler';
3 import {
4 unstable_trace as trace,
5 unstable_wrap as wrap,
@@ -38,7 +38,7 @@ export default class App extends PureComponent {
38 currentId: id,
39 })
40 );
41 - unstable_scheduleWork(
41 + unstable_scheduleCallback(
42 wrap(() =>
43 trace(`View ${id} (low-pri)`, performance.now(), () =>
44 this.setState({
fixtures/unstable-async/time-slicing/src/index.js
+3 -3
@@ -1,6 +1,6 @@
1 import React, {PureComponent} from 'react';
2 import {flushSync, render} from 'react-dom';
3 -import {unstable_scheduleWork} from 'scheduler';
3 +import {unstable_scheduleCallback} from 'scheduler';
4 import _ from 'lodash';
5 import Charts from './Charts';
6 import Clock from './Clock';
@@ -67,7 +67,7 @@ class App extends PureComponent {
67 }
68 this._ignoreClick = true;
69
70 - unstable_scheduleWork(() => {
70 + unstable_scheduleCallback(() => {
71 this.setState({showDemo: true}, () => {
72 this._ignoreClick = false;
73 });
@@ -107,7 +107,7 @@ class App extends PureComponent {
107 this.debouncedHandleChange(value);
108 break;
109 case 'async':
110 - unstable_scheduleWork(() => {
110 + unstable_scheduleCallback(() => {
111 this.setState({value});
112 });
113 break;
packages/react-art/src/ReactARTHostConfig.js
+2 -2
@@ -7,8 +7,8 @@
7
8 export {
9 unstable_now as now,
10 - unstable_scheduleWork as scheduleDeferredCallback,
11 - unstable_cancelScheduledWork as cancelDeferredCallback,
10 + unstable_scheduleCallback as scheduleDeferredCallback,
11 + unstable_cancelCallback as cancelDeferredCallback,
12 } from 'scheduler';
13 import Transform from 'art/core/transform';
14 import Mode from 'art/modes/current';
packages/react-dom/src/client/ReactDOMHostConfig.js
+2 -2
@@ -62,8 +62,8 @@ export type NoTimeout = -1;
62
63 export {
64 unstable_now as now,
65 - unstable_scheduleWork as scheduleDeferredCallback,
66 - unstable_cancelScheduledWork as cancelDeferredCallback,
65 + unstable_scheduleCallback as scheduleDeferredCallback,
66 + unstable_cancelCallback as cancelDeferredCallback,
67 } from 'scheduler';
68
69 let SUPPRESS_HYDRATION_WARNING;
packages/react/src/ReactSharedInternals.js
+10 -4
@@ -7,9 +7,12 @@
7
8 import assign from 'object-assign';
9 import {
10 - unstable_cancelScheduledWork,
10 + unstable_cancelCallback,
11 unstable_now,
12 - unstable_scheduleWork,
12 + unstable_scheduleCallback,
13 + unstable_runWithPriority,
14 + unstable_wrapCallback,
15 + unstable_getCurrentPriorityLevel,
16 } from 'scheduler';
17 import {
18 __interactionsRef,
@@ -39,9 +42,12 @@ if (__UMD__) {
42 // CJS bundles use the shared NPM package.
43 Object.assign(ReactSharedInternals, {
44 Scheduler: {
42 - unstable_cancelScheduledWork,
45 + unstable_cancelCallback,
46 unstable_now,
44 - unstable_scheduleWork,
47 + unstable_scheduleCallback,
48 + unstable_runWithPriority,
49 + unstable_wrapCallback,
50 + unstable_getCurrentPriorityLevel,
51 },
52 SchedulerTracing: {
53 __interactionsRef,
packages/scheduler/npm/umd/scheduler.development.js
+32 -6
@@ -7,6 +7,8 @@
7 * LICENSE file in the root directory of this source tree.
8 */
9
10 +/* eslint-disable max-len */
11 +
12 'use strict';
13
14 (function(global, factory) {
@@ -23,15 +25,36 @@
25 );
26 }
27
26 - function unstable_scheduleWork() {
27 - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_scheduleWork.apply(
28 + function unstable_scheduleCallback() {
29 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_scheduleCallback.apply(
30 + this,
31 + arguments
32 + );
33 + }
34 +
35 + function unstable_cancelCallback() {
36 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_cancelCallback.apply(
37 + this,
38 + arguments
39 + );
40 + }
41 +
42 + function unstable_runWithPriority() {
43 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
44 + this,
45 + arguments
46 + );
47 + }
48 +
49 + function unstable_wrapCallback() {
50 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
51 this,
52 arguments
53 );
54 }
55
33 - function unstable_cancelScheduledWork() {
34 - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_cancelScheduledWork.apply(
56 + function unstable_getCurrentPriorityLevel() {
57 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_getCurrentPriorityLevel.apply(
58 this,
59 arguments
60 );
@@ -39,7 +62,10 @@
62
63 return Object.freeze({
64 unstable_now: unstable_now,
42 - unstable_scheduleWork: unstable_scheduleWork,
43 - unstable_cancelScheduledWork: unstable_cancelScheduledWork,
65 + unstable_scheduleCallback: unstable_scheduleCallback,
66 + unstable_cancelCallback: unstable_cancelCallback,
67 + unstable_runWithPriority: unstable_runWithPriority,
68 + unstable_wrapCallback: unstable_wrapCallback,
69 + unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
70 });
71 });
packages/scheduler/npm/umd/scheduler.production.min.js
+32 -6
@@ -7,6 +7,8 @@
7 * LICENSE file in the root directory of this source tree.
8 */
9
10 +/* eslint-disable max-len */
11 +
12 'use strict';
13
14 (function(global, factory) {
@@ -23,15 +25,36 @@
25 );
26 }
27
26 - function unstable_scheduleWork() {
27 - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_scheduleWork.apply(
28 + function unstable_scheduleCallback() {
29 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_scheduleCallback.apply(
30 + this,
31 + arguments
32 + );
33 + }
34 +
35 + function unstable_cancelCallback() {
36 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_cancelCallback.apply(
37 + this,
38 + arguments
39 + );
40 + }
41 +
42 + function unstable_runWithPriority() {
43 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
44 + this,
45 + arguments
46 + );
47 + }
48 +
49 + function unstable_wrapCallback() {
50 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
51 this,
52 arguments
53 );
54 }
55
33 - function unstable_cancelScheduledWork() {
34 - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_cancelScheduledWork.apply(
56 + function unstable_getCurrentPriorityLevel() {
57 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_getCurrentPriorityLevel.apply(
58 this,
59 arguments
60 );
@@ -39,7 +62,10 @@
62
63 return Object.freeze({
64 unstable_now: unstable_now,
42 - unstable_scheduleWork: unstable_scheduleWork,
43 - unstable_cancelScheduledWork: unstable_cancelScheduledWork,
65 + unstable_scheduleCallback: unstable_scheduleCallback,
66 + unstable_cancelCallback: unstable_cancelCallback,
67 + unstable_runWithPriority: unstable_runWithPriority,
68 + unstable_wrapCallback: unstable_wrapCallback,
69 + unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
70 });
71 });
packages/scheduler/npm/umd/scheduler.profiling.min.js
+32 -6
@@ -7,6 +7,8 @@
7 * LICENSE file in the root directory of this source tree.
8 */
9
10 +/* eslint-disable max-len */
11 +
12 'use strict';
13
14 (function(global, factory) {
@@ -23,15 +25,36 @@
25 );
26 }
27
26 - function unstable_scheduleWork() {
27 - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_scheduleWork.apply(
28 + function unstable_scheduleCallback() {
29 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_scheduleCallback.apply(
30 + this,
31 + arguments
32 + );
33 + }
34 +
35 + function unstable_cancelCallback() {
36 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_cancelCallback.apply(
37 + this,
38 + arguments
39 + );
40 + }
41 +
42 + function unstable_runWithPriority() {
43 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_runWithPriority.apply(
44 + this,
45 + arguments
46 + );
47 + }
48 +
49 + function unstable_wrapCallback() {
50 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
51 this,
52 arguments
53 );
54 }
55
33 - function unstable_cancelScheduledWork() {
34 - return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_cancelScheduledWork.apply(
56 + function unstable_getCurrentPriorityLevel() {
57 + return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_getCurrentPriorityLevel.apply(
58 this,
59 arguments
60 );
@@ -39,7 +62,10 @@
62
63 return Object.freeze({
64 unstable_now: unstable_now,
42 - unstable_scheduleWork: unstable_scheduleWork,
43 - unstable_cancelScheduledWork: unstable_cancelScheduledWork,
65 + unstable_scheduleCallback: unstable_scheduleCallback,
66 + unstable_cancelCallback: unstable_cancelCallback,
67 + unstable_runWithPriority: unstable_runWithPriority,
68 + unstable_wrapCallback: unstable_wrapCallback,
69 + unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
70 });
71 });
packages/scheduler/src/Scheduler.js
+247 -50
@@ -8,14 +8,34 @@
8
9 /* eslint-disable no-var */
10
11 -// TODO: Currently there's only a single priority level, Deferred. Will add
12 -// additional priorities.
13 -var DEFERRED_TIMEOUT = 5000;
11 +// TODO: Use symbols?
12 +var ImmediatePriority = 1;
13 +var InteractivePriority = 2;
14 +var NormalPriority = 3;
15 +var WheneverPriority = 4;
16 +
17 +// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
18 +// Math.pow(2, 30) - 1
19 +// 0b111111111111111111111111111111
20 +var maxSigned31BitInt = 1073741823;
21 +
22 +// Times out immediately
23 +var IMMEDIATE_PRIORITY_TIMEOUT = -1;
24 +// Eventually times out
25 +var INTERACTIVE_PRIORITY_TIMEOUT = 250;
26 +var NORMAL_PRIORITY_TIMEOUT = 5000;
27 +// Never times out
28 +var WHENEVER_PRIORITY_TIMEOUT = maxSigned31BitInt;
29
30 // Callbacks are stored as a circular, doubly linked list.
31 var firstCallbackNode = null;
32
18 -var isPerformingWork = false;
33 +var currentPriorityLevel = NormalPriority;
34 +var currentEventStartTime = -1;
35 +var currentExpirationTime = -1;
36 +
37 +// This is set when a callback is being executed, to prevent re-entrancy.
38 +var isExecutingCallback = false;
39
40 var isHostCallbackScheduled = false;
41
@@ -25,6 +45,14 @@ var hasNativePerformanceNow =
45 var timeRemaining;
46 if (hasNativePerformanceNow) {
47 timeRemaining = function() {
48 + if (
49 + firstCallbackNode !== null &&
50 + firstCallbackNode.expirationTime < currentExpirationTime
51 + ) {
52 + // A higher priority callback was scheduled. Yield so we can switch to
53 + // working on that.
54 + return 0;
55 + }
56 // We assume that if we have a performance timer that the rAF callback
57 // gets a performance timer value. Not sure if this is always true.
58 var remaining = getFrameDeadline() - performance.now();
@@ -33,6 +61,12 @@ if (hasNativePerformanceNow) {
61 } else {
62 timeRemaining = function() {
63 // Fallback to Date.now()
64 + if (
65 + firstCallbackNode !== null &&
66 + firstCallbackNode.expirationTime < currentExpirationTime
67 + ) {
68 + return 0;
69 + }
70 var remaining = getFrameDeadline() - Date.now();
71 return remaining > 0 ? remaining : 0;
72 };
@@ -44,22 +78,22 @@ var deadlineObject = {
78 };
79
80 function ensureHostCallbackIsScheduled() {
47 - if (isPerformingWork) {
81 + if (isExecutingCallback) {
82 // Don't schedule work yet; wait until the next time we yield.
83 return;
84 }
51 - // Schedule the host callback using the earliest timeout in the list.
52 - var timesOutAt = firstCallbackNode.timesOutAt;
85 + // Schedule the host callback using the earliest expiration in the list.
86 + var expirationTime = firstCallbackNode.expirationTime;
87 if (!isHostCallbackScheduled) {
88 isHostCallbackScheduled = true;
89 } else {
90 // Cancel the existing host callback.
57 - cancelCallback();
91 + cancelHostCallback();
92 }
59 - requestCallback(flushWork, timesOutAt);
93 + requestHostCallback(flushWork, expirationTime);
94 }
95
62 -function flushFirstCallback(node) {
96 +function flushFirstCallback() {
97 var flushedNode = firstCallbackNode;
98
99 // Remove the node from the list before calling the callback. That way the
@@ -70,35 +104,124 @@ function flushFirstCallback(node) {
104 firstCallbackNode = null;
105 next = null;
106 } else {
73 - var previous = firstCallbackNode.previous;
74 - firstCallbackNode = previous.next = next;
75 - next.previous = previous;
107 + var lastCallbackNode = firstCallbackNode.previous;
108 + firstCallbackNode = lastCallbackNode.next = next;
109 + next.previous = lastCallbackNode;
110 }
111
112 flushedNode.next = flushedNode.previous = null;
113
114 // Now it's safe to call the callback.
115 var callback = flushedNode.callback;
82 - callback(deadlineObject);
116 + var expirationTime = flushedNode.expirationTime;
117 + var priorityLevel = flushedNode.priorityLevel;
118 + var previousPriorityLevel = currentPriorityLevel;
119 + var previousExpirationTime = currentExpirationTime;
120 + currentPriorityLevel = priorityLevel;
121 + currentExpirationTime = expirationTime;
122 + var continuationCallback;
123 + try {
124 + continuationCallback = callback(deadlineObject);
125 + } finally {
126 + currentPriorityLevel = previousPriorityLevel;
127 + currentExpirationTime = previousExpirationTime;
128 + }
129 +
130 + // A callback may return a continuation. The continuation should be scheduled
131 + // with the same priority and expiration as the just-finished callback.
132 + if (typeof continuationCallback === 'function') {
133 + var continuationNode: CallbackNode = {
134 + callback: continuationCallback,
135 + priorityLevel,
136 + expirationTime,
137 + next: null,
138 + previous: null,
139 + };
140 +
141 + // Insert the new callback into the list, sorted by its expiration. This is
142 + // almost the same as the code in `scheduleCallback`, except the callback
143 + // is inserted into the list *before* callbacks of equal expiration instead
144 + // of after.
145 + if (firstCallbackNode === null) {
146 + // This is the first callback in the list.
147 + firstCallbackNode = continuationNode.next = continuationNode.previous = continuationNode;
148 + } else {
149 + var nextAfterContinuation = null;
150 + var node = firstCallbackNode;
151 + do {
152 + if (node.expirationTime >= expirationTime) {
153 + // This callback expires at or after the continuation. We will insert
154 + // the continuation *before* this callback.
155 + nextAfterContinuation = node;
156 + break;
157 + }
158 + node = node.next;
159 + } while (node !== firstCallbackNode);
160 +
161 + if (nextAfterContinuation === null) {
162 + // No equal or lower priority callback was found, which means the new
163 + // callback is the lowest priority callback in the list.
164 + nextAfterContinuation = firstCallbackNode;
165 + } else if (nextAfterContinuation === firstCallbackNode) {
166 + // The new callback is the highest priority callback in the list.
167 + firstCallbackNode = continuationNode;
168 + ensureHostCallbackIsScheduled(firstCallbackNode);
169 + }
170 +
171 + var previous = nextAfterContinuation.previous;
172 + previous.next = nextAfterContinuation.previous = continuationNode;
173 + continuationNode.next = nextAfterContinuation;
174 + continuationNode.previous = previous;
175 + }
176 + }
177 +}
178 +
179 +function flushImmediateWork() {
180 + if (
181 + // Confirm we've exited the outer most event handler
182 + currentEventStartTime === -1 &&
183 + firstCallbackNode !== null &&
184 + firstCallbackNode.priorityLevel === ImmediatePriority
185 + ) {
186 + isExecutingCallback = true;
187 + deadlineObject.didTimeout = true;
188 + try {
189 + do {
190 + flushFirstCallback();
191 + } while (
192 + // Keep flushing until there are no more immediate callbacks
193 + firstCallbackNode !== null &&
194 + firstCallbackNode.priorityLevel === ImmediatePriority
195 + );
196 + } finally {
197 + isExecutingCallback = false;
198 + if (firstCallbackNode !== null) {
199 + // There's still work remaining. Request another callback.
200 + ensureHostCallbackIsScheduled(firstCallbackNode);
201 + } else {
202 + isHostCallbackScheduled = false;
203 + }
204 + }
205 + }
206 }
207
208 function flushWork(didTimeout) {
86 - isPerformingWork = true;
209 + isExecutingCallback = true;
210 deadlineObject.didTimeout = didTimeout;
211 try {
212 if (didTimeout) {
90 - // Flush all the timed out callbacks without yielding.
213 + // Flush all the expired callbacks without yielding.
214 while (firstCallbackNode !== null) {
215 // Read the current time. Flush all the callbacks that expire at or
216 // earlier than that time. Then read the current time again and repeat.
217 // This optimizes for as few performance.now calls as possible.
218 var currentTime = getCurrentTime();
96 - if (firstCallbackNode.timesOutAt <= currentTime) {
219 + if (firstCallbackNode.expirationTime <= currentTime) {
220 do {
221 flushFirstCallback();
222 } while (
223 firstCallbackNode !== null &&
101 - firstCallbackNode.timesOutAt <= currentTime
224 + firstCallbackNode.expirationTime <= currentTime
225 );
226 continue;
227 }
@@ -116,41 +239,104 @@ function flushWork(didTimeout) {
239 }
240 }
241 } finally {
119 - isPerformingWork = false;
242 + isExecutingCallback = false;
243 if (firstCallbackNode !== null) {
244 // There's still work remaining. Request another callback.
245 ensureHostCallbackIsScheduled(firstCallbackNode);
246 } else {
247 isHostCallbackScheduled = false;
248 }
249 + // Before exiting, flush all the immediate work that was scheduled.
250 + flushImmediateWork();
251 }
252 }
253
129 -function unstable_scheduleWork(callback, options) {
130 - var currentTime = getCurrentTime();
254 +function unstable_runWithPriority(priorityLevel, eventHandler) {
255 + switch (priorityLevel) {
256 + case ImmediatePriority:
257 + case InteractivePriority:
258 + case NormalPriority:
259 + case WheneverPriority:
260 + break;
261 + default:
262 + priorityLevel = NormalPriority;
263 + }
264 +
265 + var previousPriorityLevel = currentPriorityLevel;
266 + var previousEventStartTime = currentEventStartTime;
267 + currentPriorityLevel = priorityLevel;
268 + currentEventStartTime = getCurrentTime();
269 +
270 + try {
271 + return eventHandler();
272 + } finally {
273 + currentPriorityLevel = previousPriorityLevel;
274 + currentEventStartTime = previousEventStartTime;
275 +
276 + // Before exiting, flush all the immediate work that was scheduled.
277 + flushImmediateWork();
278 + }
279 +}
280
132 - var timesOutAt;
281 +function unstable_wrapCallback(callback) {
282 + var parentPriorityLevel = currentPriorityLevel;
283 + return function() {
284 + // This is a fork of runWithPriority, inlined for performance.
285 + var previousPriorityLevel = currentPriorityLevel;
286 + var previousEventStartTime = currentEventStartTime;
287 + currentPriorityLevel = parentPriorityLevel;
288 + currentEventStartTime = getCurrentTime();
289 +
290 + try {
291 + return callback.apply(this, arguments);
292 + } finally {
293 + currentPriorityLevel = previousPriorityLevel;
294 + currentEventStartTime = previousEventStartTime;
295 + flushImmediateWork();
296 + }
297 + };
298 +}
299 +
300 +function unstable_scheduleCallback(callback, deprecated_options) {
301 + var startTime =
302 + currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();
303 +
304 + var expirationTime;
305 if (
134 - options !== undefined &&
135 - options !== null &&
136 - options.timeout !== null &&
137 - options.timeout !== undefined
306 + typeof deprecated_options === 'object' &&
307 + deprecated_options !== null &&
308 + typeof deprecated_options.timeout === 'number'
309 ) {
139 - // Check for an explicit timeout
140 - timesOutAt = currentTime + options.timeout;
310 + // FIXME: Remove this branch once we lift expiration times out of React.
311 + expirationTime = startTime + deprecated_options.timeout;
312 } else {
142 - // Compute an absolute timeout using the default constant.
143 - timesOutAt = currentTime + DEFERRED_TIMEOUT;
313 + switch (currentPriorityLevel) {
314 + case ImmediatePriority:
315 + expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
316 + break;
317 + case InteractivePriority:
318 + expirationTime = startTime + INTERACTIVE_PRIORITY_TIMEOUT;
319 + break;
320 + case WheneverPriority:
321 + expirationTime = startTime + WHENEVER_PRIORITY_TIMEOUT;
322 + break;
323 + case NormalPriority:
324 + default:
325 + expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
326 + }
327 }
328
329 var newNode = {
330 callback,
148 - timesOutAt,
331 + priorityLevel: currentPriorityLevel,
332 + expirationTime,
333 next: null,
334 previous: null,
335 };
336
153 - // Insert the new callback into the list, sorted by its timeout.
337 + // Insert the new callback into the list, ordered first by expiration, then
338 + // by insertion. So the new callback is inserted any other callback with
339 + // equal expiration.
340 if (firstCallbackNode === null) {
341 // This is the first callback in the list.
342 firstCallbackNode = newNode.next = newNode.previous = newNode;
@@ -159,8 +345,8 @@ function unstable_scheduleWork(callback, options) {
345 var next = null;
346 var node = firstCallbackNode;
347 do {
162 - if (node.timesOutAt > timesOutAt) {
163 - // The new callback times out before this one.
348 + if (node.expirationTime > expirationTime) {
349 + // The new callback expires before this one.
350 next = node;
351 break;
352 }
@@ -168,11 +354,11 @@ function unstable_scheduleWork(callback, options) {
354 } while (node !== firstCallbackNode);
355
356 if (next === null) {
171 - // No callback with a later timeout was found, which means the new
172 - // callback has the latest timeout in the list.
357 + // No callback with a later expiration was found, which means the new
358 + // callback has the latest expiration in the list.
359 next = firstCallbackNode;
360 } else if (next === firstCallbackNode) {
175 - // The new callback has the earliest timeout in the entire list.
361 + // The new callback has the earliest expiration in the entire list.
362 firstCallbackNode = newNode;
363 ensureHostCallbackIsScheduled(firstCallbackNode);
364 }
@@ -186,7 +372,7 @@ function unstable_scheduleWork(callback, options) {
372 return newNode;
373 }
374
189 -function unstable_cancelScheduledWork(callbackNode) {
375 +function unstable_cancelCallback(callbackNode) {
376 var next = callbackNode.next;
377 if (next === null) {
378 // Already cancelled.
@@ -209,6 +395,10 @@ function unstable_cancelScheduledWork(callbackNode) {
395 callbackNode.next = callbackNode.previous = null;
396 }
397
398 +function unstable_getCurrentPriorityLevel() {
399 + return currentPriorityLevel;
400 +}
401 +
402 // The remaining code is essentially a polyfill for requestIdleCallback. It
403 // works by scheduling a requestAnimationFrame, storing the time for the start
404 // of the frame, then scheduling a postMessage which gets scheduled after paint.
@@ -274,18 +464,18 @@ if (hasNativePerformanceNow) {
464 };
465 }
466
277 -var requestCallback;
278 -var cancelCallback;
467 +var requestHostCallback;
468 +var cancelHostCallback;
469 var getFrameDeadline;
470
471 if (typeof window === 'undefined') {
472 // If this accidentally gets imported in a non-browser environment, fallback
473 // to a naive implementation.
474 var timeoutID = -1;
285 - requestCallback = function(callback, absoluteTimeout) {
475 + requestHostCallback = function(callback, absoluteTimeout) {
476 timeoutID = setTimeout(callback, 0, true);
477 };
288 - cancelCallback = function() {
478 + cancelHostCallback = function() {
479 clearTimeout(timeoutID);
480 };
481 getFrameDeadline = function() {
@@ -294,11 +484,12 @@ if (typeof window === 'undefined') {
484 } else if (window._schedMock) {
485 // Dynamic injection, only for testing purposes.
486 var impl = window._schedMock;
297 - requestCallback = impl[0];
298 - cancelCallback = impl[1];
487 + requestHostCallback = impl[0];
488 + cancelHostCallback = impl[1];
489 getFrameDeadline = impl[2];
490 } else {
491 if (typeof console !== 'undefined') {
492 + // TODO: Remove fb.me link
493 if (typeof localRequestAnimationFrame !== 'function') {
494 console.error(
495 "This browser doesn't support requestAnimationFrame. " +
@@ -416,11 +607,10 @@ if (typeof window === 'undefined') {
607 }
608 };
609
419 - requestCallback = function(callback, absoluteTimeout) {
610 + requestHostCallback = function(callback, absoluteTimeout) {
611 scheduledCallback = callback;
612 timeoutTime = absoluteTimeout;
422 - if (isPerformingIdleWork) {
423 - // If we're already performing idle work, an error must have been thrown.
613 + if (isPerformingIdleWork || absoluteTimeout < 0) {
614 // Don't wait for the next frame. Continue working ASAP, in a new event.
615 window.postMessage(messageKey, '*');
616 } else if (!isAnimationFrameScheduled) {
@@ -433,7 +623,7 @@ if (typeof window === 'undefined') {
623 }
624 };
625
436 - cancelCallback = function() {
626 + cancelHostCallback = function() {
627 scheduledCallback = null;
628 isIdleScheduled = false;
629 timeoutTime = -1;
@@ -441,7 +631,14 @@ if (typeof window === 'undefined') {
631 }
632
633 export {
444 - unstable_scheduleWork,
445 - unstable_cancelScheduledWork,
634 + ImmediatePriority as unstable_ImmediatePriority,
635 + InteractivePriority as unstable_InteractivePriority,
636 + NormalPriority as unstable_NormalPriority,
637 + WheneverPriority as unstable_WheneverPriority,
638 + unstable_runWithPriority,
639 + unstable_scheduleCallback,
640 + unstable_cancelCallback,
641 + unstable_wrapCallback,
642 + unstable_getCurrentPriorityLevel,
643 getCurrentTime as unstable_now,
644 };
packages/scheduler/src/__tests__/Scheduler-test.internal.js
+380 -52
@@ -9,8 +9,14 @@
9
10 'use strict';
11
12 -let scheduleWork;
13 -let cancelScheduledWork;
12 +let runWithPriority;
13 +let ImmediatePriority;
14 +let InteractivePriority;
15 +let NormalPriority;
16 +let scheduleCallback;
17 +let cancelCallback;
18 +let wrapCallback;
19 +let getCurrentPriorityLevel;
20 let flushWork;
21 let advanceTime;
22 let doWork;
@@ -24,12 +30,16 @@ describe('Scheduler', () => {
30 jest.resetModules();
31
32 let _flushWork = null;
33 + let isFlushing = false;
34 let timeoutID = -1;
35 let endOfFrame = -1;
36
37 let currentTime = 0;
38
39 flushWork = frameSize => {
40 + if (isFlushing) {
41 + throw new Error('Already flushing work.');
42 + }
43 if (frameSize === null || frameSize === undefined) {
44 frameSize = Infinity;
45 }
@@ -39,8 +49,10 @@ describe('Scheduler', () => {
49 timeoutID = -1;
50 endOfFrame = currentTime + frameSize;
51 try {
42 - _flushWork();
52 + isFlushing = true;
53 + _flushWork(false);
54 } finally {
55 + isFlushing = false;
56 endOfFrame = -1;
57 }
58 const yields = yieldedValues;
@@ -54,6 +66,9 @@ describe('Scheduler', () => {
66 };
67
68 doWork = (label, timeCost) => {
69 + if (typeof timeCost !== 'number') {
70 + throw new Error('Second arg must be a number.');
71 + }
72 advanceTime(timeCost);
73 yieldValue(label);
74 };
@@ -69,16 +84,32 @@ describe('Scheduler', () => {
84 return yields;
85 };
86
72 - function requestCallback(fw, absoluteTimeout) {
87 + function onTimeout() {
88 + if (_flushWork === null) {
89 + return;
90 + }
91 + if (isFlushing) {
92 + // Jest fires timers synchronously when jest.advanceTimersByTime is
93 + // called. Use setImmediate to prevent re-entrancy.
94 + setImmediate(onTimeout);
95 + } else {
96 + try {
97 + isFlushing = true;
98 + _flushWork(true);
99 + } finally {
100 + isFlushing = false;
101 + }
102 + }
103 + }
104 +
105 + function requestHostCallback(fw, absoluteTimeout) {
106 if (_flushWork !== null) {
107 throw new Error('Work is already scheduled.');
108 }
109 _flushWork = fw;
77 - timeoutID = setTimeout(() => {
78 - _flushWork(true);
79 - }, absoluteTimeout - currentTime);
110 + timeoutID = setTimeout(onTimeout, absoluteTimeout - currentTime);
111 }
81 - function cancelCallback() {
112 + function cancelHostCallback() {
113 if (_flushWork === null) {
114 throw new Error('No work is scheduled.');
115 }
@@ -91,19 +122,31 @@ describe('Scheduler', () => {
122
123 // Override host implementation
124 delete global.performance;
94 - global.Date.now = () => currentTime;
95 - window._schedMock = [requestCallback, cancelCallback, getTimeRemaining];
125 + global.Date.now = () => {
126 + return currentTime;
127 + };
128 + window._schedMock = [
129 + requestHostCallback,
130 + cancelHostCallback,
131 + getTimeRemaining,
132 + ];
133
97 - const Scheduler = require('scheduler');
98 - scheduleWork = Scheduler.unstable_scheduleWork;
99 - cancelScheduledWork = Scheduler.unstable_cancelScheduledWork;
134 + const Schedule = require('scheduler');
135 + runWithPriority = Schedule.unstable_runWithPriority;
136 + ImmediatePriority = Schedule.unstable_ImmediatePriority;
137 + InteractivePriority = Schedule.unstable_InteractivePriority;
138 + NormalPriority = Schedule.unstable_NormalPriority;
139 + scheduleCallback = Schedule.unstable_scheduleCallback;
140 + cancelCallback = Schedule.unstable_cancelCallback;
141 + wrapCallback = Schedule.unstable_wrapCallback;
142 + getCurrentPriorityLevel = Schedule.unstable_getCurrentPriorityLevel;
143 });
144
145 it('flushes work incrementally', () => {
103 - scheduleWork(() => doWork('A', 100));
104 - scheduleWork(() => doWork('B', 200));
105 - scheduleWork(() => doWork('C', 300));
106 - scheduleWork(() => doWork('D', 400));
146 + scheduleCallback(() => doWork('A', 100));
147 + scheduleCallback(() => doWork('B', 200));
148 + scheduleCallback(() => doWork('C', 300));
149 + scheduleCallback(() => doWork('D', 400));
150
151 expect(flushWork(300)).toEqual(['A', 'B']);
152 expect(flushWork(300)).toEqual(['C']);
@@ -111,11 +154,11 @@ describe('Scheduler', () => {
154 });
155
156 it('cancels work', () => {
114 - scheduleWork(() => doWork('A', 100));
115 - const callbackHandleB = scheduleWork(() => doWork('B', 200));
116 - scheduleWork(() => doWork('C', 300));
157 + scheduleCallback(() => doWork('A', 100));
158 + const callbackHandleB = scheduleCallback(() => doWork('B', 200));
159 + scheduleCallback(() => doWork('C', 300));
160
118 - cancelScheduledWork(callbackHandleB);
161 + cancelCallback(callbackHandleB);
162
163 expect(flushWork()).toEqual([
164 'A',
@@ -124,51 +167,336 @@ describe('Scheduler', () => {
167 ]);
168 });
169
127 - it('prioritizes callbacks according to their timeouts', () => {
128 - scheduleWork(() => doWork('A', 10), {timeout: 5000});
129 - scheduleWork(() => doWork('B', 20), {timeout: 5000});
130 - scheduleWork(() => doWork('C', 30), {timeout: 1000});
131 - scheduleWork(() => doWork('D', 40), {timeout: 5000});
170 + it('executes the highest priority callbacks first', () => {
171 + scheduleCallback(() => doWork('A', 100));
172 + scheduleCallback(() => doWork('B', 100));
173 +
174 + // Yield before B is flushed
175 + expect(flushWork(100)).toEqual(['A']);
176 +
177 + runWithPriority(InteractivePriority, () => {
178 + scheduleCallback(() => doWork('C', 100));
179 + scheduleCallback(() => doWork('D', 100));
180 + });
181 +
182 + // C and D should come first, because they are higher priority
183 + expect(flushWork()).toEqual(['C', 'D', 'B']);
184 + });
185 +
186 + it('expires work', () => {
187 + scheduleCallback(() => doWork('A', 100));
188 + runWithPriority(InteractivePriority, () => {
189 + scheduleCallback(() => doWork('B', 100));
190 + });
191 + scheduleCallback(() => doWork('C', 100));
192 + runWithPriority(InteractivePriority, () => {
193 + scheduleCallback(() => doWork('D', 100));
194 + });
195 +
196 + // Advance time, but not by enough to expire any work
197 + advanceTime(249);
198 + expect(clearYieldedValues()).toEqual([]);
199 +
200 + // Advance by just a bit more to expire the high pri callbacks
201 + advanceTime(1);
202 + expect(clearYieldedValues()).toEqual(['B', 'D']);
203
133 - // C should be first because it has the earliest timeout
134 - expect(flushWork()).toEqual(['C', 'A', 'B', 'D']);
204 + // Expire the rest
205 + advanceTime(10000);
206 + expect(clearYieldedValues()).toEqual(['A', 'C']);
207 });
208
137 - it('times out work', () => {
138 - scheduleWork(() => doWork('A', 100), {timeout: 5000});
139 - scheduleWork(() => doWork('B', 200), {timeout: 5000});
140 - scheduleWork(() => doWork('C', 300), {timeout: 1000});
141 - scheduleWork(() => doWork('D', 400), {timeout: 5000});
209 + it('has a default expiration of ~5 seconds', () => {
210 + scheduleCallback(() => doWork('A', 100));
211
143 - // Advance time, but not by enough to flush any work
144 - advanceTime(999);
212 + advanceTime(4999);
213 expect(clearYieldedValues()).toEqual([]);
214
147 - // Advance by just a bit more to flush C
215 advanceTime(1);
149 - expect(clearYieldedValues()).toEqual(['C']);
216 + expect(clearYieldedValues()).toEqual(['A']);
217 + });
218 +
219 + it('continues working on same task after yielding', () => {
220 + scheduleCallback(() => doWork('A', 100));
221 + scheduleCallback(() => doWork('B', 100));
222 +
223 + const tasks = [['C1', 100], ['C2', 100], ['C3', 100]];
224 + const C = deadline => {
225 + while (tasks.length > 0) {
226 + doWork(...tasks.shift());
227 + if (
228 + tasks.length > 0 &&
229 + !deadline.didTimeout &&
230 + deadline.timeRemaining() <= 0
231 + ) {
232 + yieldValue('Yield!');
233 + return C;
234 + }
235 + }
236 + };
237 +
238 + scheduleCallback(C);
239 +
240 + scheduleCallback(() => doWork('D', 100));
241 + scheduleCallback(() => doWork('E', 100));
242 +
243 + expect(flushWork(300)).toEqual(['A', 'B', 'C1', 'Yield!']);
244 +
245 + expect(flushWork()).toEqual(['C2', 'C3', 'D', 'E']);
246 + });
247 +
248 + it('continuation callbacks inherit the expiration of the previous callback', () => {
249 + const tasks = [['A', 125], ['B', 125], ['C', 125], ['D', 125]];
250 + const work = deadline => {
251 + while (tasks.length > 0) {
252 + doWork(...tasks.shift());
253 + if (
254 + tasks.length > 0 &&
255 + !deadline.didTimeout &&
256 + deadline.timeRemaining() <= 0
257 + ) {
258 + yieldValue('Yield!');
259 + return work;
260 + }
261 + }
262 + };
263 +
264 + // Schedule a high priority callback
265 + runWithPriority(InteractivePriority, () => scheduleCallback(work));
266 +
267 + // Flush until just before the expiration time
268 + expect(flushWork(249)).toEqual(['A', 'B', 'Yield!']);
269 +
270 + // Advance time by just a bit more. This should expire all the remaining work.
271 + advanceTime(1);
272 + expect(clearYieldedValues()).toEqual(['C', 'D']);
273 + });
274 +
275 + it('nested callbacks inherit the priority of the currently executing callback', () => {
276 + runWithPriority(InteractivePriority, () => {
277 + scheduleCallback(() => {
278 + doWork('Parent callback', 100);
279 + scheduleCallback(() => {
280 + doWork('Nested callback', 100);
281 + });
282 + });
283 + });
284
151 - // Flush the rest
152 - advanceTime(4000);
153 - expect(clearYieldedValues()).toEqual(['A', 'B', 'D']);
285 + expect(flushWork(100)).toEqual(['Parent callback']);
286 +
287 + // The nested callback has interactive priority, so it should
288 + // expire quickly.
289 + advanceTime(250 + 100);
290 + expect(clearYieldedValues()).toEqual(['Nested callback']);
291 });
292
156 - it('has a default timeout of 5 seconds', () => {
157 - scheduleWork(() => doWork('A', 100));
158 - scheduleWork(() => doWork('B', 200));
159 - scheduleWork(() => doWork('C', 300), {timeout: 1000});
160 - scheduleWork(() => doWork('D', 400));
293 + it('continuations are interrupted by higher priority work', () => {
294 + const tasks = [['A', 100], ['B', 100], ['C', 100], ['D', 100]];
295 + const work = deadline => {
296 + while (tasks.length > 0) {
297 + doWork(...tasks.shift());
298 + if (
299 + tasks.length > 0 &&
300 + !deadline.didTimeout &&
301 + deadline.timeRemaining() <= 0
302 + ) {
303 + yieldValue('Yield!');
304 + return work;
305 + }
306 + }
307 + };
308 + scheduleCallback(work);
309 + expect(flushWork(100)).toEqual(['A', 'Yield!']);
310 +
311 + runWithPriority(InteractivePriority, () => {
312 + scheduleCallback(() => doWork('High pri', 100));
313 + });
314 +
315 + expect(flushWork()).toEqual(['High pri', 'B', 'C', 'D']);
316 + });
317
162 - // Flush C
163 - advanceTime(1000);
164 - expect(clearYieldedValues()).toEqual(['C']);
318 + it(
319 + 'continutations are interrupted by higher priority work scheduled ' +
320 + 'inside an executing callback',
321 + () => {
322 + const tasks = [['A', 100], ['B', 100], ['C', 100], ['D', 100]];
323 + const work = deadline => {
324 + while (tasks.length > 0) {
325 + const task = tasks.shift();
326 + doWork(...task);
327 + if (task[0] === 'B') {
328 + // Schedule high pri work from inside another callback
329 + yieldValue('Schedule high pri');
330 + runWithPriority(InteractivePriority, () =>
331 + scheduleCallback(() => doWork('High pri', 100)),
332 + );
333 + }
334 + if (
335 + tasks.length > 0 &&
336 + !deadline.didTimeout &&
337 + deadline.timeRemaining() <= 0
338 + ) {
339 + yieldValue('Yield!');
340 + return work;
341 + }
342 + }
343 + };
344 + scheduleCallback(work);
345 + expect(flushWork()).toEqual([
346 + 'A',
347 + 'B',
348 + 'Schedule high pri',
349 + // Even though there's time left in the frame, the low pri callback
350 + // should yield to the high pri callback
351 + 'Yield!',
352 + 'High pri',
353 + // Continue low pri work
354 + 'C',
355 + 'D',
356 + ]);
357 + },
358 + );
359
166 - // Advance time until right before the rest of the work expires
167 - advanceTime(3699);
360 + it('immediate callbacks fire at the end of outermost event', () => {
361 + runWithPriority(ImmediatePriority, () => {
362 + scheduleCallback(() => yieldValue('A'));
363 + scheduleCallback(() => yieldValue('B'));
364 + // Nested event
365 + runWithPriority(ImmediatePriority, () => {
366 + scheduleCallback(() => yieldValue('C'));
367 + // Nothing should have fired yet
368 + expect(clearYieldedValues()).toEqual([]);
369 + });
370 + // Nothing should have fired yet
371 + expect(clearYieldedValues()).toEqual([]);
372 + });
373 + // The callbacks were called at the end of the outer event
374 + expect(clearYieldedValues()).toEqual(['A', 'B', 'C']);
375 + });
376 +
377 + it('wrapped callbacks have same signature as original callback', () => {
378 + const wrappedCallback = wrapCallback((...args) => ({args}));
379 + expect(wrappedCallback('a', 'b')).toEqual({args: ['a', 'b']});
380 + });
381 +
382 + it('wrapped callbacks inherit the current priority', () => {
383 + const wrappedCallback = wrapCallback(() => {
384 + scheduleCallback(() => {
385 + doWork('Normal', 100);
386 + });
387 + });
388 + const wrappedInteractiveCallback = runWithPriority(
389 + InteractivePriority,
390 + () =>
391 + wrapCallback(() => {
392 + scheduleCallback(() => {
393 + doWork('Interactive', 100);
394 + });
395 + }),
396 + );
397 +
398 + // This should schedule a normal callback
399 + wrappedCallback();
400 + // This should schedule an interactive callback
401 + wrappedInteractiveCallback();
402 +
403 + advanceTime(249);
404 expect(clearYieldedValues()).toEqual([]);
405 + advanceTime(1);
406 + expect(clearYieldedValues()).toEqual(['Interactive']);
407
170 - // Now advance by just a bit more
408 + advanceTime(10000);
409 + expect(clearYieldedValues()).toEqual(['Normal']);
410 + });
411 +
412 + it('wrapped callbacks inherit the current priority even when nested', () => {
413 + const wrappedCallback = wrapCallback(() => {
414 + scheduleCallback(() => {
415 + doWork('Normal', 100);
416 + });
417 + });
418 + const wrappedInteractiveCallback = runWithPriority(
419 + InteractivePriority,
420 + () =>
421 + wrapCallback(() => {
422 + scheduleCallback(() => {
423 + doWork('Interactive', 100);
424 + });
425 + }),
426 + );
427 +
428 + runWithPriority(InteractivePriority, () => {
429 + // This should schedule a normal callback
430 + wrappedCallback();
431 + // This should schedule an interactive callback
432 + wrappedInteractiveCallback();
433 + });
434 +
435 + advanceTime(249);
436 + expect(clearYieldedValues()).toEqual([]);
437 advanceTime(1);
172 - expect(clearYieldedValues()).toEqual(['A', 'B', 'D']);
438 + expect(clearYieldedValues()).toEqual(['Interactive']);
439 +
440 + advanceTime(10000);
441 + expect(clearYieldedValues()).toEqual(['Normal']);
442 + });
443 +
444 + it('immediate callbacks fire at the end of callback', () => {
445 + const immediateCallback = runWithPriority(ImmediatePriority, () =>
446 + wrapCallback(() => {
447 + scheduleCallback(() => yieldValue('callback'));
448 + }),
449 + );
450 + immediateCallback();
451 +
452 + // The callback was called at the end of the outer event
453 + expect(clearYieldedValues()).toEqual(['callback']);
454 + });
455 +
456 + it("immediate callbacks fire even if there's an error", () => {
457 + expect(() => {
458 + runWithPriority(ImmediatePriority, () => {
459 + scheduleCallback(() => {
460 + yieldValue('A');
461 + throw new Error('Oops A');
462 + });
463 + scheduleCallback(() => {
464 + yieldValue('B');
465 + });
466 + scheduleCallback(() => {
467 + yieldValue('C');
468 + throw new Error('Oops C');
469 + });
470 + });
471 + }).toThrow('Oops A');
472 +
473 + expect(clearYieldedValues()).toEqual(['A']);
474 +
475 + // B and C flush in a subsequent event. That way, the second error is not
476 + // swallowed.
477 + expect(() => flushWork(0)).toThrow('Oops C');
478 + expect(clearYieldedValues()).toEqual(['B', 'C']);
479 + });
480 +
481 + it('exposes the current priority level', () => {
482 + yieldValue(getCurrentPriorityLevel());
483 + runWithPriority(ImmediatePriority, () => {
484 + yieldValue(getCurrentPriorityLevel());
485 + runWithPriority(NormalPriority, () => {
486 + yieldValue(getCurrentPriorityLevel());
487 + runWithPriority(InteractivePriority, () => {
488 + yieldValue(getCurrentPriorityLevel());
489 + });
490 + });
491 + yieldValue(getCurrentPriorityLevel());
492 + });
493 +
494 + expect(clearYieldedValues()).toEqual([
495 + NormalPriority,
496 + ImmediatePriority,
497 + NormalPriority,
498 + InteractivePriority,
499 + ImmediatePriority,
500 + ]);
501 });
502 });
packages/scheduler/src/__tests__/SchedulerDOM-test.js
+86 -86
@@ -97,11 +97,11 @@ describe('SchedulerDOM', () => {
97 Scheduler = require('scheduler');
98 });
99
100 - describe('scheduleWork', () => {
100 + describe('scheduleCallback', () => {
101 it('calls the callback within the frame when not blocked', () => {
102 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
102 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
103 const cb = jest.fn();
104 - scheduleWork(cb);
104 + scheduleCallback(cb);
105 advanceOneFrame({timeLeftInFrame: 15});
106 expect(cb).toHaveBeenCalledTimes(1);
107 // should not have timed out and should include a timeRemaining method
@@ -111,15 +111,15 @@ describe('SchedulerDOM', () => {
111
112 describe('with multiple callbacks', () => {
113 it('accepts multiple callbacks and calls within frame when not blocked', () => {
114 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
114 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
115 const callbackLog = [];
116 const callbackA = jest.fn(() => callbackLog.push('A'));
117 const callbackB = jest.fn(() => callbackLog.push('B'));
118 - scheduleWork(callbackA);
118 + scheduleCallback(callbackA);
119 // initially waits to call the callback
120 expect(callbackLog).toEqual([]);
121 // waits while second callback is passed
122 - scheduleWork(callbackB);
122 + scheduleCallback(callbackB);
123 expect(callbackLog).toEqual([]);
124 // after a delay, calls as many callbacks as it has time for
125 advanceOneFrame({timeLeftInFrame: 15});
@@ -137,17 +137,17 @@ describe('SchedulerDOM', () => {
137 });
138
139 it("accepts callbacks betweeen animationFrame and postMessage and doesn't stall", () => {
140 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
140 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
141 const callbackLog = [];
142 const callbackA = jest.fn(() => callbackLog.push('A'));
143 const callbackB = jest.fn(() => callbackLog.push('B'));
144 const callbackC = jest.fn(() => callbackLog.push('C'));
145 - scheduleWork(callbackA);
145 + scheduleCallback(callbackA);
146 // initially waits to call the callback
147 expect(callbackLog).toEqual([]);
148 runRAFCallbacks();
149 // this should schedule work *after* the requestAnimationFrame but before the message handler
150 - scheduleWork(callbackB);
150 + scheduleCallback(callbackB);
151 expect(callbackLog).toEqual([]);
152 // now it should drain the message queue and do all scheduled work
153 runPostMessageCallbacks({timeLeftInFrame: 15});
@@ -157,7 +157,7 @@ describe('SchedulerDOM', () => {
157 advanceOneFrame({timeLeftInFrame: 15});
158
159 // see if more work can be done now.
160 - scheduleWork(callbackC);
160 + scheduleCallback(callbackC);
161 expect(callbackLog).toEqual(['A', 'B']);
162 advanceOneFrame({timeLeftInFrame: 15});
163 expect(callbackLog).toEqual(['A', 'B', 'C']);
@@ -167,11 +167,11 @@ describe('SchedulerDOM', () => {
167 'schedules callbacks in correct order and' +
168 'keeps calling them if there is time',
169 () => {
170 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
170 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
171 const callbackLog = [];
172 const callbackA = jest.fn(() => {
173 callbackLog.push('A');
174 - scheduleWork(callbackC);
174 + scheduleCallback(callbackC);
175 });
176 const callbackB = jest.fn(() => {
177 callbackLog.push('B');
@@ -180,11 +180,11 @@ describe('SchedulerDOM', () => {
180 callbackLog.push('C');
181 });
182
183 - scheduleWork(callbackA);
183 + scheduleCallback(callbackA);
184 // initially waits to call the callback
185 expect(callbackLog).toEqual([]);
186 // continues waiting while B is scheduled
187 - scheduleWork(callbackB);
187 + scheduleCallback(callbackB);
188 expect(callbackLog).toEqual([]);
189 // after a delay, calls the scheduled callbacks,
190 // and also calls new callbacks scheduled by current callbacks
@@ -193,18 +193,18 @@ describe('SchedulerDOM', () => {
193 },
194 );
195
196 - it('schedules callbacks in correct order when callbacks have many nested scheduleWork calls', () => {
197 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
196 + it('schedules callbacks in correct order when callbacks have many nested scheduleCallback calls', () => {
197 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
198 const callbackLog = [];
199 const callbackA = jest.fn(() => {
200 callbackLog.push('A');
201 - scheduleWork(callbackC);
202 - scheduleWork(callbackD);
201 + scheduleCallback(callbackC);
202 + scheduleCallback(callbackD);
203 });
204 const callbackB = jest.fn(() => {
205 callbackLog.push('B');
206 - scheduleWork(callbackE);
207 - scheduleWork(callbackF);
206 + scheduleCallback(callbackE);
207 + scheduleCallback(callbackF);
208 });
209 const callbackC = jest.fn(() => {
210 callbackLog.push('C');
@@ -219,8 +219,8 @@ describe('SchedulerDOM', () => {
219 callbackLog.push('F');
220 });
221
222 - scheduleWork(callbackA);
223 - scheduleWork(callbackB);
222 + scheduleCallback(callbackA);
223 + scheduleCallback(callbackB);
224 // initially waits to call the callback
225 expect(callbackLog).toEqual([]);
226 // while flushing callbacks, calls as many as it has time for
@@ -228,23 +228,23 @@ describe('SchedulerDOM', () => {
228 expect(callbackLog).toEqual(['A', 'B', 'C', 'D', 'E', 'F']);
229 });
230
231 - it('schedules callbacks in correct order when they use scheduleWork to schedule themselves', () => {
232 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
231 + it('schedules callbacks in correct order when they use scheduleCallback to schedule themselves', () => {
232 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
233 const callbackLog = [];
234 let callbackAIterations = 0;
235 const callbackA = jest.fn(() => {
236 if (callbackAIterations < 1) {
237 - scheduleWork(callbackA);
237 + scheduleCallback(callbackA);
238 }
239 callbackLog.push('A' + callbackAIterations);
240 callbackAIterations++;
241 });
242 const callbackB = jest.fn(() => callbackLog.push('B'));
243
244 - scheduleWork(callbackA);
244 + scheduleCallback(callbackA);
245 // initially waits to call the callback
246 expect(callbackLog).toEqual([]);
247 - scheduleWork(callbackB);
247 + scheduleCallback(callbackB);
248 expect(callbackLog).toEqual([]);
249 // after a delay, calls the latest callback passed
250 advanceOneFrame({timeLeftInFrame: 15});
@@ -260,7 +260,7 @@ describe('SchedulerDOM', () => {
260
261 describe('when there is no more time left in the frame', () => {
262 it('calls any callback which has timed out, waits for others', () => {
263 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
263 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
264 startOfLatestFrame = 1000000000000;
265 currentTime = startOfLatestFrame - 10;
266 const callbackLog = [];
@@ -269,9 +269,9 @@ describe('SchedulerDOM', () => {
269 const callbackB = jest.fn(() => callbackLog.push('B'));
270 const callbackC = jest.fn(() => callbackLog.push('C'));
271
272 - scheduleWork(callbackA); // won't time out
273 - scheduleWork(callbackB, {timeout: 100}); // times out later
274 - scheduleWork(callbackC, {timeout: 2}); // will time out fast
272 + scheduleCallback(callbackA); // won't time out
273 + scheduleCallback(callbackB, {timeout: 100}); // times out later
274 + scheduleCallback(callbackC, {timeout: 2}); // will time out fast
275
276 // push time ahead a bit so that we have no idle time
277 advanceOneFrame({timePastFrameDeadline: 16});
@@ -295,7 +295,7 @@ describe('SchedulerDOM', () => {
295
296 describe('when there is some time left in the frame', () => {
297 it('calls timed out callbacks and then any more pending callbacks, defers others if time runs out', () => {
298 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
298 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
299 startOfLatestFrame = 1000000000000;
300 currentTime = startOfLatestFrame - 10;
301 const callbackLog = [];
@@ -309,10 +309,10 @@ describe('SchedulerDOM', () => {
309 const callbackC = jest.fn(() => callbackLog.push('C'));
310 const callbackD = jest.fn(() => callbackLog.push('D'));
311
312 - scheduleWork(callbackA, {timeout: 100}); // won't time out
313 - scheduleWork(callbackB, {timeout: 100}); // times out later
314 - scheduleWork(callbackC, {timeout: 2}); // will time out fast
315 - scheduleWork(callbackD, {timeout: 200}); // won't time out
312 + scheduleCallback(callbackA, {timeout: 100}); // won't time out
313 + scheduleCallback(callbackB, {timeout: 100}); // times out later
314 + scheduleCallback(callbackC, {timeout: 2}); // will time out fast
315 + scheduleCallback(callbackD, {timeout: 200}); // won't time out
316
317 advanceOneFrame({timeLeftInFrame: 15}); // runs rAF and postMessage callbacks
318
@@ -341,16 +341,16 @@ describe('SchedulerDOM', () => {
341 });
342 });
343
344 - describe('cancelScheduledWork', () => {
344 + describe('cancelCallback', () => {
345 it('cancels the scheduled callback', () => {
346 const {
347 - unstable_scheduleWork: scheduleWork,
348 - unstable_cancelScheduledWork: cancelScheduledWork,
347 + unstable_scheduleCallback: scheduleCallback,
348 + unstable_cancelCallback: cancelCallback,
349 } = Scheduler;
350 const cb = jest.fn();
351 - const callbackId = scheduleWork(cb);
351 + const callbackId = scheduleCallback(cb);
352 expect(cb).toHaveBeenCalledTimes(0);
353 - cancelScheduledWork(callbackId);
353 + cancelCallback(callbackId);
354 advanceOneFrame({timeLeftInFrame: 15});
355 expect(cb).toHaveBeenCalledTimes(0);
356 });
@@ -358,19 +358,19 @@ describe('SchedulerDOM', () => {
358 describe('with multiple callbacks', () => {
359 it('when called more than once', () => {
360 const {
361 - unstable_scheduleWork: scheduleWork,
362 - unstable_cancelScheduledWork: cancelScheduledWork,
361 + unstable_scheduleCallback: scheduleCallback,
362 + unstable_cancelCallback: cancelCallback,
363 } = Scheduler;
364 const callbackLog = [];
365 const callbackA = jest.fn(() => callbackLog.push('A'));
366 const callbackB = jest.fn(() => callbackLog.push('B'));
367 const callbackC = jest.fn(() => callbackLog.push('C'));
368 - scheduleWork(callbackA);
369 - const callbackId = scheduleWork(callbackB);
370 - scheduleWork(callbackC);
371 - cancelScheduledWork(callbackId);
372 - cancelScheduledWork(callbackId);
373 - cancelScheduledWork(callbackId);
368 + scheduleCallback(callbackA);
369 + const callbackId = scheduleCallback(callbackB);
370 + scheduleCallback(callbackC);
371 + cancelCallback(callbackId);
372 + cancelCallback(callbackId);
373 + cancelCallback(callbackId);
374 // Initially doesn't call anything
375 expect(callbackLog).toEqual([]);
376 advanceOneFrame({timeLeftInFrame: 15});
@@ -382,18 +382,18 @@ describe('SchedulerDOM', () => {
382
383 it('when one callback cancels the next one', () => {
384 const {
385 - unstable_scheduleWork: scheduleWork,
386 - unstable_cancelScheduledWork: cancelScheduledWork,
385 + unstable_scheduleCallback: scheduleCallback,
386 + unstable_cancelCallback: cancelCallback,
387 } = Scheduler;
388 const callbackLog = [];
389 let callbackBId;
390 const callbackA = jest.fn(() => {
391 callbackLog.push('A');
392 - cancelScheduledWork(callbackBId);
392 + cancelCallback(callbackBId);
393 });
394 const callbackB = jest.fn(() => callbackLog.push('B'));
395 - scheduleWork(callbackA);
396 - callbackBId = scheduleWork(callbackB);
395 + scheduleCallback(callbackA);
396 + callbackBId = scheduleCallback(callbackB);
397 // Initially doesn't call anything
398 expect(callbackLog).toEqual([]);
399 advanceOneFrame({timeLeftInFrame: 15});
@@ -421,7 +421,7 @@ describe('SchedulerDOM', () => {
421 *
422 */
423 it('still calls all callbacks within same frame', () => {
424 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
424 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
425 const callbackLog = [];
426 const callbackA = jest.fn(() => callbackLog.push('A'));
427 const callbackB = jest.fn(() => {
@@ -434,11 +434,11 @@ describe('SchedulerDOM', () => {
434 throw new Error('D error');
435 });
436 const callbackE = jest.fn(() => callbackLog.push('E'));
437 - scheduleWork(callbackA);
438 - scheduleWork(callbackB);
439 - scheduleWork(callbackC);
440 - scheduleWork(callbackD);
441 - scheduleWork(callbackE);
437 + scheduleCallback(callbackA);
438 + scheduleCallback(callbackB);
439 + scheduleCallback(callbackC);
440 + scheduleCallback(callbackD);
441 + scheduleCallback(callbackE);
442 // Initially doesn't call anything
443 expect(callbackLog).toEqual([]);
444 catchPostMessageErrors = true;
@@ -467,7 +467,7 @@ describe('SchedulerDOM', () => {
467 *
468 */
469 it('and with some timed out callbacks, still calls all callbacks within same frame', () => {
470 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
470 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
471 const callbackLog = [];
472 const callbackA = jest.fn(() => {
473 callbackLog.push('A');
@@ -480,11 +480,11 @@ describe('SchedulerDOM', () => {
480 throw new Error('D error');
481 });
482 const callbackE = jest.fn(() => callbackLog.push('E'));
483 - scheduleWork(callbackA);
484 - scheduleWork(callbackB);
485 - scheduleWork(callbackC, {timeout: 2}); // times out fast
486 - scheduleWork(callbackD, {timeout: 2}); // times out fast
487 - scheduleWork(callbackE, {timeout: 2}); // times out fast
483 + scheduleCallback(callbackA);
484 + scheduleCallback(callbackB);
485 + scheduleCallback(callbackC, {timeout: 2}); // times out fast
486 + scheduleCallback(callbackD, {timeout: 2}); // times out fast
487 + scheduleCallback(callbackE, {timeout: 2}); // times out fast
488 // Initially doesn't call anything
489 expect(callbackLog).toEqual([]);
490 catchPostMessageErrors = true;
@@ -513,7 +513,7 @@ describe('SchedulerDOM', () => {
513 *
514 */
515 it('still calls all callbacks within same frame', () => {
516 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
516 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
517 const callbackLog = [];
518 const callbackA = jest.fn(() => {
519 callbackLog.push('A');
@@ -535,11 +535,11 @@ describe('SchedulerDOM', () => {
535 callbackLog.push('E');
536 throw new Error('E error');
537 });
538 - scheduleWork(callbackA);
539 - scheduleWork(callbackB);
540 - scheduleWork(callbackC);
541 - scheduleWork(callbackD);
542 - scheduleWork(callbackE);
538 + scheduleCallback(callbackA);
539 + scheduleCallback(callbackB);
540 + scheduleCallback(callbackC);
541 + scheduleCallback(callbackD);
542 + scheduleCallback(callbackE);
543 // Initially doesn't call anything
544 expect(callbackLog).toEqual([]);
545 catchPostMessageErrors = true;
@@ -574,7 +574,7 @@ describe('SchedulerDOM', () => {
574 *
575 */
576 it('and with all timed out callbacks, still calls all callbacks within same frame', () => {
577 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
577 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
578 const callbackLog = [];
579 const callbackA = jest.fn(() => {
580 callbackLog.push('A');
@@ -596,11 +596,11 @@ describe('SchedulerDOM', () => {
596 callbackLog.push('E');
597 throw new Error('E error');
598 });
599 - scheduleWork(callbackA, {timeout: 2}); // times out fast
600 - scheduleWork(callbackB, {timeout: 2}); // times out fast
601 - scheduleWork(callbackC, {timeout: 2}); // times out fast
602 - scheduleWork(callbackD, {timeout: 2}); // times out fast
603 - scheduleWork(callbackE, {timeout: 2}); // times out fast
599 + scheduleCallback(callbackA, {timeout: 2}); // times out fast
600 + scheduleCallback(callbackB, {timeout: 2}); // times out fast
601 + scheduleCallback(callbackC, {timeout: 2}); // times out fast
602 + scheduleCallback(callbackD, {timeout: 2}); // times out fast
603 + scheduleCallback(callbackE, {timeout: 2}); // times out fast
604 // Initially doesn't call anything
605 expect(callbackLog).toEqual([]);
606 catchPostMessageErrors = true;
@@ -655,7 +655,7 @@ describe('SchedulerDOM', () => {
655 *
656 */
657 it('still calls all callbacks within same frame', () => {
658 - const {unstable_scheduleWork: scheduleWork} = Scheduler;
658 + const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
659 startOfLatestFrame = 1000000000000;
660 currentTime = startOfLatestFrame - 10;
661 catchPostMessageErrors = true;
@@ -689,13 +689,13 @@ describe('SchedulerDOM', () => {
689 });
690 const callbackG = jest.fn(() => callbackLog.push('G'));
691
692 - scheduleWork(callbackA);
693 - scheduleWork(callbackB);
694 - scheduleWork(callbackC);
695 - scheduleWork(callbackD);
696 - scheduleWork(callbackE);
697 - scheduleWork(callbackF);
698 - scheduleWork(callbackG);
692 + scheduleCallback(callbackA);
693 + scheduleCallback(callbackB);
694 + scheduleCallback(callbackC);
695 + scheduleCallback(callbackD);
696 + scheduleCallback(callbackE);
697 + scheduleCallback(callbackF);
698 + scheduleCallback(callbackG);
699
700 // does nothing initially
701 expect(callbackLog).toEqual([]);
packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js
+2 -1
@@ -17,7 +17,8 @@ describe('Scheduling UMD bundle', () => {
17 });
18
19 function filterPrivateKeys(name) {
20 - return !name.startsWith('_');
20 + // TODO: Figure out how to forward priority levels.
21 + return !name.startsWith('_') && !name.endsWith('Priority');
22 }
23
24 function validateForwardedAPIs(api, forwardedAPIs) {
packages/shared/forks/Scheduler.umd.js
+3 -3
@@ -12,9 +12,9 @@ import React from 'react';
12 const ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
13
14 const {
15 - unstable_cancelScheduledWork,
15 + unstable_cancelCallback,
16 unstable_now,
17 - unstable_scheduleWork,
17 + unstable_scheduleCallback,
18 } = ReactInternals.Scheduler;
19
20 -export {unstable_cancelScheduledWork, unstable_now, unstable_scheduleWork};
20 +export {unstable_cancelCallback, unstable_now, unstable_scheduleCallback};