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

Clean up Scheduler forks (#20915)

* Clean up Scheduler forks * Un-shadow variables * Use timer globals directly, add a test for overrides * Remove more window references * Don't crash for undefined globals + tests * Update lint config globals * Fix test by using async act * Add test fixture * Delete test fixture

Ricky committed May 17, 2021 at 16:53 UTC e0f89aa056de95afc4c23909fce3d91fefb7dec7
24 files changed +119 -1535
.codesandbox/ci.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "packages": ["packages/react", "packages/react-dom", "packages/scheduler"],
3 - "buildCommand": "build --type=NODE react/index,react-dom/index,react-dom/server,react-dom/test-utils,scheduler/index,scheduler/unstable_no_dom,react/jsx-runtime,react/jsx-dev-runtime",
3 + "buildCommand": "build --type=NODE react/index,react-dom/index,react-dom/server,react-dom/test-utils,scheduler/index,react/jsx-runtime,react/jsx-dev-runtime",
4 "node": "12",
5 "publishDirectory": {
6 "react": "build/node_modules/react",
fixtures/dom/src/__tests__/wrong-act-test.js
+2 -2
@@ -81,12 +81,12 @@ it("doesn't warn when you use the right act + renderer: test", () => {
81 });
82 });
83
84 -it('resets correctly across renderers', () => {
84 +it('resets correctly across renderers', async () => {
85 function Effecty() {
86 React.useEffect(() => {}, []);
87 return null;
88 }
89 - TestUtils.act(() => {
89 + await TestUtils.act(async () => {
90 TestRenderer.act(() => {});
91 expect(() => {
92 TestRenderer.create(<Effecty />);
packages/react-dom/src/__tests__/ReactTestUtilsActUnmockedScheduler-test.js
+1 -3
@@ -33,9 +33,7 @@ function unmount(dom) {
33
34 beforeEach(() => {
35 jest.resetModules();
36 - jest.mock('scheduler', () =>
37 - require.requireActual('scheduler/unstable_no_dom'),
38 - );
36 + jest.unmock('scheduler');
37 yields = [];
38 React = require('react');
39 ReactDOM = require('react-dom');
packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js
+1 -1
@@ -17,7 +17,7 @@ function App() {
17
18 beforeEach(() => {
19 jest.resetModules();
20 - jest.mock('scheduler', () => require('scheduler/unstable_no_dom'));
20 + jest.unmock('scheduler');
21 React = require('react');
22 ReactDOM = require('react-dom');
23 ReactFeatureFlags = require('shared/ReactFeatureFlags');
packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
+1 -3
@@ -16,9 +16,7 @@ function App() {
16
17 beforeEach(() => {
18 jest.resetModules();
19 - jest.mock('scheduler', () =>
20 - require.requireActual('scheduler/unstable_no_dom'),
21 - );
19 + jest.unmock('scheduler');
20 React = require('react');
21 ReactDOM = require('react-dom');
22 });
packages/scheduler/index.js
+1 -1
@@ -7,4 +7,4 @@
7
8 'use strict';
9
10 -export * from './src/forks/SchedulerDOM';
10 +export * from './src/forks/Scheduler';
packages/scheduler/npm/index.js
+1 -3
@@ -1,8 +1,6 @@
1 'use strict';
2
3 -if (typeof window === 'undefined' || typeof MessageChannel !== 'function') {
4 - module.exports = require('./unstable_no_dom');
5 -} else if (process.env.NODE_ENV === 'production') {
3 +if (process.env.NODE_ENV === 'production') {
4 module.exports = require('./cjs/scheduler.production.min.js');
5 } else {
6 module.exports = require('./cjs/scheduler.development.js');
packages/scheduler/npm/unstable_no_dom.js deleted
-7
@@ -1,7 +0,0 @@
1 -'use strict';
2 -
3 -if (process.env.NODE_ENV === 'production') {
4 - module.exports = require('./cjs/scheduler-unstable_no_dom.production.min.js');
5 -} else {
6 - module.exports = require('./cjs/scheduler-unstable_no_dom.development.js');
7 -}
packages/scheduler/package.json
-1
@@ -26,7 +26,6 @@
26 "build-info.json",
27 "index.js",
28 "unstable_mock.js",
29 - "unstable_no_dom.js",
29 "unstable_post_task.js",
30 "cjs/",
31 "umd/"
packages/scheduler/src/__tests__/Scheduler-test.js renamed
+6 -13
@@ -32,11 +32,9 @@ let NormalPriority;
32 describe('SchedulerBrowser', () => {
33 beforeEach(() => {
34 jest.resetModules();
35 -
36 - // Un-mock scheduler
37 - jest.mock('scheduler', () => require.requireActual('scheduler'));
38 -
35 runtime = installMockBrowserRuntime();
36 + jest.unmock('scheduler');
37 +
38 performance = global.performance;
39 Scheduler = require('scheduler');
40 cancelCallback = Scheduler.unstable_cancelCallback;
@@ -68,21 +66,16 @@ describe('SchedulerBrowser', () => {
66 },
67 };
68
71 - const window = {};
72 - global.window = window;
73 -
74 - // TODO: Scheduler no longer requires these methods to be polyfilled. But
75 - // maybe we want to continue warning if they don't exist, to preserve the
76 - // option to rely on it in the future?
77 - window.requestAnimationFrame = window.cancelAnimationFrame = () => {};
69 + // Delete node provide setImmediate so we fall through to MessageChannel.
70 + delete global.setImmediate;
71
79 - window.setTimeout = (cb, delay) => {
72 + global.setTimeout = (cb, delay) => {
73 const id = timerIDCounter++;
74 log(`Set Timer`);
75 // TODO
76 return id;
77 };
85 - window.clearTimeout = id => {
78 + global.clearTimeout = id => {
79 // TODO
80 };
81
packages/scheduler/src/__tests__/SchedulerPostTask-test.js
-2
@@ -30,8 +30,6 @@ let IdlePriority;
30 describe('SchedulerPostTask', () => {
31 beforeEach(() => {
32 jest.resetModules();
33 -
34 - // Un-mock scheduler
33 jest.mock('scheduler', () =>
34 require.requireActual('scheduler/unstable_post_task'),
35 );
packages/scheduler/src/__tests__/SchedulerPostTaskOnly-test.js deleted
-275
@@ -1,275 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - * @jest-environment node
9 - */
10 -
11 -/* eslint-disable no-for-of-loops/no-for-of-loops */
12 -
13 -'use strict';
14 -
15 -let Scheduler;
16 -let runtime;
17 -let performance;
18 -let cancelCallback;
19 -let scheduleCallback;
20 -let NormalPriority;
21 -
22 -describe('SchedulerPostTaskOnly', () => {
23 - beforeEach(() => {
24 - if (!process.env.IS_BUILD) {
25 - jest.resetModules();
26 -
27 - // Un-mock scheduler
28 - jest.mock('scheduler', () =>
29 - require.requireActual('scheduler/unstable_post_task_only'),
30 - );
31 -
32 - runtime = installMockBrowserRuntime();
33 - performance = window.performance;
34 - Scheduler = require('scheduler');
35 - cancelCallback = Scheduler.unstable_cancelCallback;
36 - scheduleCallback = Scheduler.unstable_scheduleCallback;
37 - NormalPriority = Scheduler.unstable_NormalPriority;
38 - }
39 - });
40 -
41 - afterEach(() => {
42 - if (!process.env.IS_BUILD) {
43 - if (!runtime.isLogEmpty()) {
44 - throw Error('Test exited without clearing log.');
45 - }
46 - }
47 - });
48 -
49 - function installMockBrowserRuntime() {
50 - let hasPendingTask = false;
51 - let timerIDCounter = 0;
52 - let eventLog = [];
53 -
54 - // Mock window functions
55 - const window = {};
56 - global.window = window;
57 -
58 - // TODO: Scheduler no longer requires these methods to be polyfilled. But
59 - // maybe we want to continue warning if they don't exist, to preserve the
60 - // option to rely on it in the future?
61 - window.requestAnimationFrame = window.cancelAnimationFrame = () => {};
62 -
63 - let currentTime = 0;
64 - window.performance = {
65 - now() {
66 - return currentTime;
67 - },
68 - };
69 -
70 - window.setTimeout = (cb, delay) => {
71 - const id = timerIDCounter++;
72 - log(`Set Timer`);
73 - // TODO
74 - return id;
75 - };
76 - window.clearTimeout = id => {
77 - // TODO
78 - };
79 -
80 - // Mock browser scheduler.
81 - const scheduler = {};
82 - global.scheduler = scheduler;
83 -
84 - let nextTask;
85 - scheduler.postTask = function(callback) {
86 - if (hasPendingTask) {
87 - throw Error('Task already scheduled');
88 - }
89 - log('Post Task');
90 - hasPendingTask = true;
91 - nextTask = callback;
92 - };
93 -
94 - function ensureLogIsEmpty() {
95 - if (eventLog.length !== 0) {
96 - throw Error('Log is not empty. Call assertLog before continuing.');
97 - }
98 - }
99 -
100 - function advanceTime(ms) {
101 - currentTime += ms;
102 - }
103 -
104 - function fireNextTask() {
105 - ensureLogIsEmpty();
106 - if (!hasPendingTask) {
107 - throw Error('No task was scheduled');
108 - }
109 - hasPendingTask = false;
110 -
111 - log('Task Event');
112 -
113 - // If there's a continuation, it will call postTask again
114 - // which will set nextTask. That means we need to clear
115 - // nextTask before the invocation, otherwise we would
116 - // delete the continuation task.
117 - const task = nextTask;
118 - nextTask = null;
119 - task();
120 - }
121 -
122 - function log(val) {
123 - eventLog.push(val);
124 - }
125 -
126 - function isLogEmpty() {
127 - return eventLog.length === 0;
128 - }
129 -
130 - function assertLog(expected) {
131 - const actual = eventLog;
132 - eventLog = [];
133 - expect(actual).toEqual(expected);
134 - }
135 -
136 - return {
137 - advanceTime,
138 - fireNextTask,
139 - log,
140 - isLogEmpty,
141 - assertLog,
142 - };
143 - }
144 -
145 - // @gate source
146 - it('task that finishes before deadline', () => {
147 - scheduleCallback(NormalPriority, () => {
148 - runtime.log('Task');
149 - });
150 - runtime.assertLog(['Post Task']);
151 - runtime.fireNextTask();
152 - runtime.assertLog(['Task Event', 'Task']);
153 - });
154 -
155 - // @gate source
156 - it('task with continuation', () => {
157 - scheduleCallback(NormalPriority, () => {
158 - runtime.log('Task');
159 - while (!Scheduler.unstable_shouldYield()) {
160 - runtime.advanceTime(1);
161 - }
162 - runtime.log(`Yield at ${performance.now()}ms`);
163 - return () => {
164 - runtime.log('Continuation');
165 - };
166 - });
167 - runtime.assertLog(['Post Task']);
168 -
169 - runtime.fireNextTask();
170 - runtime.assertLog(['Task Event', 'Task', 'Yield at 5ms', 'Post Task']);
171 -
172 - runtime.fireNextTask();
173 - runtime.assertLog(['Task Event', 'Continuation']);
174 - });
175 -
176 - // @gate source
177 - it('multiple tasks', () => {
178 - scheduleCallback(NormalPriority, () => {
179 - runtime.log('A');
180 - });
181 - scheduleCallback(NormalPriority, () => {
182 - runtime.log('B');
183 - });
184 - runtime.assertLog(['Post Task']);
185 - runtime.fireNextTask();
186 - runtime.assertLog(['Task Event', 'A', 'B']);
187 - });
188 -
189 - // @gate source
190 - it('multiple tasks with a yield in between', () => {
191 - scheduleCallback(NormalPriority, () => {
192 - runtime.log('A');
193 - runtime.advanceTime(4999);
194 - });
195 - scheduleCallback(NormalPriority, () => {
196 - runtime.log('B');
197 - });
198 - runtime.assertLog(['Post Task']);
199 - runtime.fireNextTask();
200 - runtime.assertLog([
201 - 'Task Event',
202 - 'A',
203 - // Ran out of time. Post a continuation event.
204 - 'Post Task',
205 - ]);
206 - runtime.fireNextTask();
207 - runtime.assertLog(['Task Event', 'B']);
208 - });
209 -
210 - // @gate source
211 - it('cancels tasks', () => {
212 - const task = scheduleCallback(NormalPriority, () => {
213 - runtime.log('Task');
214 - });
215 - runtime.assertLog(['Post Task']);
216 - cancelCallback(task);
217 - runtime.assertLog([]);
218 - });
219 -
220 - // @gate source
221 - it('throws when a task errors then continues in a new event', () => {
222 - scheduleCallback(NormalPriority, () => {
223 - runtime.log('Oops!');
224 - throw Error('Oops!');
225 - });
226 - scheduleCallback(NormalPriority, () => {
227 - runtime.log('Yay');
228 - });
229 - runtime.assertLog(['Post Task']);
230 -
231 - expect(() => runtime.fireNextTask()).toThrow('Oops!');
232 - runtime.assertLog(['Task Event', 'Oops!', 'Post Task']);
233 -
234 - runtime.fireNextTask();
235 - runtime.assertLog(['Task Event', 'Yay']);
236 - });
237 -
238 - // @gate source
239 - it('schedule new task after queue has emptied', () => {
240 - scheduleCallback(NormalPriority, () => {
241 - runtime.log('A');
242 - });
243 -
244 - runtime.assertLog(['Post Task']);
245 - runtime.fireNextTask();
246 - runtime.assertLog(['Task Event', 'A']);
247 -
248 - scheduleCallback(NormalPriority, () => {
249 - runtime.log('B');
250 - });
251 - runtime.assertLog(['Post Task']);
252 - runtime.fireNextTask();
253 - runtime.assertLog(['Task Event', 'B']);
254 - });
255 -
256 - // @gate source
257 - it('schedule new task after a cancellation', () => {
258 - const handle = scheduleCallback(NormalPriority, () => {
259 - runtime.log('A');
260 - });
261 -
262 - runtime.assertLog(['Post Task']);
263 - cancelCallback(handle);
264 -
265 - runtime.fireNextTask();
266 - runtime.assertLog(['Task Event']);
267 -
268 - scheduleCallback(NormalPriority, () => {
269 - runtime.log('B');
270 - });
271 - runtime.assertLog(['Post Task']);
272 - runtime.fireNextTask();
273 - runtime.assertLog(['Task Event', 'B']);
274 - });
275 -});
packages/scheduler/src/__tests__/SchedulerSetImmediate-test.js renamed
+46 -16
@@ -18,6 +18,7 @@ let performance;
18 let cancelCallback;
19 let scheduleCallback;
20 let NormalPriority;
21 +let UserBlockingPriority;
22
23 // The Scheduler implementation uses browser APIs like `MessageChannel` and
24 // `setTimeout` to schedule work on the main thread. Most of our tests treat
@@ -32,16 +33,15 @@ let NormalPriority;
33 describe('SchedulerDOMSetImmediate', () => {
34 beforeEach(() => {
35 jest.resetModules();
35 -
36 - // Un-mock scheduler
37 - jest.mock('scheduler', () => require.requireActual('scheduler'));
38 -
36 runtime = installMockBrowserRuntime();
37 + jest.unmock('scheduler');
38 +
39 performance = global.performance;
40 Scheduler = require('scheduler');
41 cancelCallback = Scheduler.unstable_cancelCallback;
42 scheduleCallback = Scheduler.unstable_scheduleCallback;
43 NormalPriority = Scheduler.unstable_NormalPriority;
44 + UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
45 });
46
47 afterEach(() => {
@@ -66,21 +66,12 @@ describe('SchedulerDOMSetImmediate', () => {
66 },
67 };
68
69 - const window = {};
70 - global.window = window;
71 -
72 - // TODO: Scheduler no longer requires these methods to be polyfilled. But
73 - // maybe we want to continue warning if they don't exist, to preserve the
74 - // option to rely on it in the future?
75 - window.requestAnimationFrame = window.cancelAnimationFrame = () => {};
76 -
77 - window.setTimeout = (cb, delay) => {
69 + global.setTimeout = (cb, delay) => {
70 const id = timerIDCounter++;
71 log(`Set Timer`);
80 - // TODO
72 return id;
73 };
83 - window.clearTimeout = id => {
74 + global.clearTimeout = id => {
75 // TODO
76 };
77
@@ -97,7 +88,7 @@ describe('SchedulerDOMSetImmediate', () => {
88 };
89
90 let pendingSetImmediateCallback = null;
100 - window.setImmediate = function(cb) {
91 + global.setImmediate = function(cb) {
92 if (pendingSetImmediateCallback) {
93 throw Error('Message event already scheduled');
94 }
@@ -143,6 +134,19 @@ describe('SchedulerDOMSetImmediate', () => {
134 };
135 }
136
137 + it('does not use setImmediate override', () => {
138 + global.setImmediate = () => {
139 + throw new Error('Should not throw');
140 + };
141 +
142 + scheduleCallback(NormalPriority, () => {
143 + runtime.log('Task');
144 + });
145 + runtime.assertLog(['Set Immediate']);
146 + runtime.fireSetImmediate();
147 + runtime.assertLog(['setImmediate Callback', 'Task']);
148 + });
149 +
150 it('task that finishes before deadline', () => {
151 scheduleCallback(NormalPriority, () => {
152 runtime.log('Task');
@@ -189,6 +193,18 @@ describe('SchedulerDOMSetImmediate', () => {
193 runtime.assertLog(['setImmediate Callback', 'A', 'B']);
194 });
195
196 + it('multiple tasks at different priority', () => {
197 + scheduleCallback(NormalPriority, () => {
198 + runtime.log('A');
199 + });
200 + scheduleCallback(UserBlockingPriority, () => {
201 + runtime.log('B');
202 + });
203 + runtime.assertLog(['Set Immediate']);
204 + runtime.fireSetImmediate();
205 + runtime.assertLog(['setImmediate Callback', 'B', 'A']);
206 + });
207 +
208 it('multiple tasks with a yield in between', () => {
209 scheduleCallback(NormalPriority, () => {
210 runtime.log('A');
@@ -271,3 +287,17 @@ describe('SchedulerDOMSetImmediate', () => {
287 runtime.assertLog(['setImmediate Callback', 'B']);
288 });
289 });
290 +
291 +it('does not crash if setImmediate is undefined', () => {
292 + jest.resetModules();
293 + const originalSetImmediate = global.setImmediate;
294 + try {
295 + delete global.setImmediate;
296 + jest.unmock('scheduler');
297 + expect(() => {
298 + require('scheduler');
299 + }).not.toThrow();
300 + } finally {
301 + global.setImmediate = originalSetImmediate;
302 + }
303 +});
packages/scheduler/src/__tests__/SchedulerSetTimeout-test.js renamed
+36 -9
@@ -5,6 +5,7 @@
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails react-core
8 + * @jest-environment node
9 */
10
11 'use strict';
@@ -16,21 +17,16 @@ let UserBlockingPriority;
17 let NormalPriority;
18
19 describe('SchedulerNoDOM', () => {
19 - // If Scheduler runs in a non-DOM environment, it falls back to a naive
20 - // implementation using setTimeout. This only meant to be used for testing
21 - // purposes, like with jest's fake timer API.
20 + // Scheduler falls back to a naive implementation using setTimeout.
21 + // This is only meant to be used for testing purposes, like with jest's fake timer API.
22 beforeEach(() => {
23 jest.resetModules();
24 jest.useFakeTimers();
25 -
26 - // Un-mock scheduler
27 - jest.mock('scheduler', () =>
28 - require.requireActual('scheduler/unstable_no_dom'),
29 - );
25 + delete global.setImmediate;
26 + jest.unmock('scheduler');
27
28 Scheduler = require('scheduler');
29 scheduleCallback = Scheduler.unstable_scheduleCallback;
33 - ImmediatePriority = Scheduler.unstable_ImmediatePriority;
30 UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
31 NormalPriority = Scheduler.unstable_NormalPriority;
32 });
@@ -99,3 +95,34 @@ describe('SchedulerNoDOM', () => {
95 expect(log).toEqual(['B', 'C']);
96 });
97 });
98 +
99 +// See: https://github.com/facebook/react/pull/13088
100 +describe('does not crash non-node SSR environments', () => {
101 + it('if setTimeout is undefined', () => {
102 + jest.resetModules();
103 + const originalSetTimeout = global.setTimeout;
104 + try {
105 + delete global.setTimeout;
106 + jest.unmock('scheduler');
107 + expect(() => {
108 + require('scheduler');
109 + }).not.toThrow();
110 + } finally {
111 + global.setTimeout = originalSetTimeout;
112 + }
113 + });
114 +
115 + it('if clearTimeout is undefined', () => {
116 + jest.resetModules();
117 + const originalClearTimeout = global.clearTimeout;
118 + try {
119 + delete global.clearTimeout;
120 + jest.unmock('scheduler');
121 + expect(() => {
122 + require('scheduler');
123 + }).not.toThrow();
124 + } finally {
125 + global.clearTimeout = originalClearTimeout;
126 + }
127 + });
128 +});
packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js
+1 -8
@@ -21,19 +21,12 @@ describe('Scheduling UMD bundle', () => {
21 global.__UMD__ = true;
22
23 jest.resetModules();
24 + jest.unmock('scheduler');
25
25 - jest.mock('scheduler', () => require.requireActual('scheduler'));
26 -
27 - // Mock a browser environment since we're testing UMD modules.
28 - global.window = {
29 - requestAnimationFrame: jest.fn(),
30 - cancelAnimationFrame: jest.fn(),
31 - };
26 global.MessageChannel = MockMessageChannel;
27 });
28
29 afterEach(() => {
36 - global.window = undefined;
30 global.MessageChannel = undefined;
31 });
32
packages/scheduler/src/forks/Scheduler.js renamed
+17 -33
@@ -85,34 +85,11 @@ var isHostCallbackScheduled = false;
85 var isHostTimeoutScheduled = false;
86
87 // Capture local references to native APIs, in case a polyfill overrides them.
88 -const setTimeout = window.setTimeout;
89 -const clearTimeout = window.clearTimeout;
90 -const setImmediate = window.setImmediate; // IE and Node.js + jsdom
91 -
92 -if (typeof console !== 'undefined') {
93 - // TODO: Scheduler no longer requires these methods to be polyfilled. But
94 - // maybe we want to continue warning if they don't exist, to preserve the
95 - // option to rely on it in the future?
96 - const requestAnimationFrame = window.requestAnimationFrame;
97 - const cancelAnimationFrame = window.cancelAnimationFrame;
98 -
99 - if (typeof requestAnimationFrame !== 'function') {
100 - // Using console['error'] to evade Babel and ESLint
101 - console['error'](
102 - "This browser doesn't support requestAnimationFrame. " +
103 - 'Make sure that you load a ' +
104 - 'polyfill in older browsers. https://reactjs.org/link/react-polyfills',
105 - );
106 - }
107 - if (typeof cancelAnimationFrame !== 'function') {
108 - // Using console['error'] to evade Babel and ESLint
109 - console['error'](
110 - "This browser doesn't support cancelAnimationFrame. " +
111 - 'Make sure that you load a ' +
112 - 'polyfill in older browsers. https://reactjs.org/link/react-polyfills',
113 - );
114 - }
115 -}
88 +const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null;
89 +const localClearTimeout =
90 + typeof clearTimeout === 'function' ? clearTimeout : null;
91 +const localSetImmediate =
92 + typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
93
94 function advanceTimers(currentTime) {
95 // Check for tasks that are no longer delayed and add them to the queue.
@@ -552,7 +529,7 @@ const performWorkUntilDeadline = () => {
529 };
530
531 let schedulePerformWorkUntilDeadline;
555 -if (typeof setImmediate === 'function') {
532 +if (typeof localSetImmediate === 'function') {
533 // Node.js and old IE.
534 // There's a few reasons for why we prefer setImmediate.
535 //
@@ -565,15 +542,22 @@ if (typeof setImmediate === 'function') {
542 // If other browsers ever implement it, it's better to use it.
543 // Although both of these would be inferior to native scheduling.
544 schedulePerformWorkUntilDeadline = () => {
568 - setImmediate(performWorkUntilDeadline);
545 + localSetImmediate(performWorkUntilDeadline);
546 };
570 -} else {
547 +} else if (typeof MessageChannel !== 'undefined') {
548 + // DOM and Worker environments.
549 + // We prefer MessageChannel because of the 4ms setTimeout clamping.
550 const channel = new MessageChannel();
551 const port = channel.port2;
552 channel.port1.onmessage = performWorkUntilDeadline;
553 schedulePerformWorkUntilDeadline = () => {
554 port.postMessage(null);
555 };
556 +} else {
557 + // We should only fallback here in non-browser environments.
558 + schedulePerformWorkUntilDeadline = () => {
559 + localSetTimeout(performWorkUntilDeadline, 0);
560 + };
561 }
562
563 function requestHostCallback(callback) {
@@ -585,13 +569,13 @@ function requestHostCallback(callback) {
569 }
570
571 function requestHostTimeout(callback, ms) {
588 - taskTimeoutID = setTimeout(() => {
572 + taskTimeoutID = localSetTimeout(() => {
573 callback(getCurrentTime());
574 }, ms);
575 }
576
577 function cancelHostTimeout() {
594 - clearTimeout(taskTimeoutID);
578 + localClearTimeout(taskTimeoutID);
579 taskTimeoutID = -1;
580 }
581
packages/scheduler/src/forks/SchedulerNoDOM.js deleted
-473
@@ -1,473 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - */
8 -
9 -/* eslint-disable no-var */
10 -
11 -import {
12 - enableSchedulerDebugging,
13 - enableProfiling,
14 -} from '../SchedulerFeatureFlags';
15 -import {push, pop, peek} from '../SchedulerMinHeap';
16 -
17 -// TODO: Use symbols?
18 -import {
19 - ImmediatePriority,
20 - UserBlockingPriority,
21 - NormalPriority,
22 - LowPriority,
23 - IdlePriority,
24 -} from '../SchedulerPriorities';
25 -import {
26 - markTaskRun,
27 - markTaskYield,
28 - markTaskCompleted,
29 - markTaskCanceled,
30 - markTaskErrored,
31 - markSchedulerSuspended,
32 - markSchedulerUnsuspended,
33 - markTaskStart,
34 - stopLoggingProfilingEvents,
35 - startLoggingProfilingEvents,
36 -} from '../SchedulerProfiling';
37 -
38 -// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
39 -// Math.pow(2, 30) - 1
40 -// 0b111111111111111111111111111111
41 -var maxSigned31BitInt = 1073741823;
42 -
43 -// Times out immediately
44 -var IMMEDIATE_PRIORITY_TIMEOUT = -1;
45 -// Eventually times out
46 -var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
47 -var NORMAL_PRIORITY_TIMEOUT = 5000;
48 -var LOW_PRIORITY_TIMEOUT = 10000;
49 -// Never times out
50 -var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
51 -
52 -// Tasks are stored on a min heap
53 -var taskQueue = [];
54 -var timerQueue = [];
55 -
56 -// Incrementing id counter. Used to maintain insertion order.
57 -var taskIdCounter = 1;
58 -
59 -// Pausing the scheduler is useful for debugging.
60 -var isSchedulerPaused = false;
61 -
62 -var currentTask = null;
63 -var currentPriorityLevel = NormalPriority;
64 -
65 -// This is set while performing work, to prevent re-entrancy.
66 -var isPerformingWork = false;
67 -
68 -var isHostCallbackScheduled = false;
69 -var isHostTimeoutScheduled = false;
70 -
71 -let getCurrentTime;
72 -const hasPerformanceNow =
73 - typeof performance === 'object' && typeof performance.now === 'function';
74 -
75 -if (hasPerformanceNow) {
76 - const localPerformance = performance;
77 - getCurrentTime = () => localPerformance.now();
78 -} else {
79 - const localDate = Date;
80 - const initialTime = localDate.now();
81 - getCurrentTime = () => localDate.now() - initialTime;
82 -}
83 -
84 -function advanceTimers(currentTime) {
85 - // Check for tasks that are no longer delayed and add them to the queue.
86 - let timer = peek(timerQueue);
87 - while (timer !== null) {
88 - if (timer.callback === null) {
89 - // Timer was cancelled.
90 - pop(timerQueue);
91 - } else if (timer.startTime <= currentTime) {
92 - // Timer fired. Transfer to the task queue.
93 - pop(timerQueue);
94 - timer.sortIndex = timer.expirationTime;
95 - push(taskQueue, timer);
96 - if (enableProfiling) {
97 - markTaskStart(timer, currentTime);
98 - timer.isQueued = true;
99 - }
100 - } else {
101 - // Remaining timers are pending.
102 - return;
103 - }
104 - timer = peek(timerQueue);
105 - }
106 -}
107 -
108 -function handleTimeout(currentTime) {
109 - isHostTimeoutScheduled = false;
110 - advanceTimers(currentTime);
111 -
112 - if (!isHostCallbackScheduled) {
113 - if (peek(taskQueue) !== null) {
114 - isHostCallbackScheduled = true;
115 - requestHostCallback(flushWork);
116 - } else {
117 - const firstTimer = peek(timerQueue);
118 - if (firstTimer !== null) {
119 - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
120 - }
121 - }
122 - }
123 -}
124 -
125 -function flushWork(hasTimeRemaining, initialTime) {
126 - if (enableProfiling) {
127 - markSchedulerUnsuspended(initialTime);
128 - }
129 -
130 - // We'll need a host callback the next time work is scheduled.
131 - isHostCallbackScheduled = false;
132 - if (isHostTimeoutScheduled) {
133 - // We scheduled a timeout but it's no longer needed. Cancel it.
134 - isHostTimeoutScheduled = false;
135 - cancelHostTimeout();
136 - }
137 -
138 - isPerformingWork = true;
139 - const previousPriorityLevel = currentPriorityLevel;
140 - try {
141 - if (enableProfiling) {
142 - try {
143 - return workLoop(hasTimeRemaining, initialTime);
144 - } catch (error) {
145 - if (currentTask !== null) {
146 - const currentTime = getCurrentTime();
147 - markTaskErrored(currentTask, currentTime);
148 - currentTask.isQueued = false;
149 - }
150 - throw error;
151 - }
152 - } else {
153 - // No catch in prod code path.
154 - return workLoop(hasTimeRemaining, initialTime);
155 - }
156 - } finally {
157 - currentTask = null;
158 - currentPriorityLevel = previousPriorityLevel;
159 - isPerformingWork = false;
160 - if (enableProfiling) {
161 - const currentTime = getCurrentTime();
162 - markSchedulerSuspended(currentTime);
163 - }
164 - }
165 -}
166 -
167 -function workLoop(hasTimeRemaining, initialTime) {
168 - let currentTime = initialTime;
169 - advanceTimers(currentTime);
170 - currentTask = peek(taskQueue);
171 - while (
172 - currentTask !== null &&
173 - !(enableSchedulerDebugging && isSchedulerPaused)
174 - ) {
175 - if (
176 - currentTask.expirationTime > currentTime &&
177 - (!hasTimeRemaining || shouldYieldToHost())
178 - ) {
179 - // This currentTask hasn't expired, and we've reached the deadline.
180 - break;
181 - }
182 - const callback = currentTask.callback;
183 - if (typeof callback === 'function') {
184 - currentTask.callback = null;
185 - currentPriorityLevel = currentTask.priorityLevel;
186 - const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
187 - if (enableProfiling) {
188 - markTaskRun(currentTask, currentTime);
189 - }
190 - const continuationCallback = callback(didUserCallbackTimeout);
191 - currentTime = getCurrentTime();
192 - if (typeof continuationCallback === 'function') {
193 - currentTask.callback = continuationCallback;
194 - if (enableProfiling) {
195 - markTaskYield(currentTask, currentTime);
196 - }
197 - } else {
198 - if (enableProfiling) {
199 - markTaskCompleted(currentTask, currentTime);
200 - currentTask.isQueued = false;
201 - }
202 - if (currentTask === peek(taskQueue)) {
203 - pop(taskQueue);
204 - }
205 - }
206 - advanceTimers(currentTime);
207 - } else {
208 - pop(taskQueue);
209 - }
210 - currentTask = peek(taskQueue);
211 - }
212 - // Return whether there's additional work
213 - if (currentTask !== null) {
214 - return true;
215 - } else {
216 - const firstTimer = peek(timerQueue);
217 - if (firstTimer !== null) {
218 - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
219 - }
220 - return false;
221 - }
222 -}
223 -
224 -function unstable_runWithPriority(priorityLevel, eventHandler) {
225 - switch (priorityLevel) {
226 - case ImmediatePriority:
227 - case UserBlockingPriority:
228 - case NormalPriority:
229 - case LowPriority:
230 - case IdlePriority:
231 - break;
232 - default:
233 - priorityLevel = NormalPriority;
234 - }
235 -
236 - var previousPriorityLevel = currentPriorityLevel;
237 - currentPriorityLevel = priorityLevel;
238 -
239 - try {
240 - return eventHandler();
241 - } finally {
242 - currentPriorityLevel = previousPriorityLevel;
243 - }
244 -}
245 -
246 -function unstable_next(eventHandler) {
247 - var priorityLevel;
248 - switch (currentPriorityLevel) {
249 - case ImmediatePriority:
250 - case UserBlockingPriority:
251 - case NormalPriority:
252 - // Shift down to normal priority
253 - priorityLevel = NormalPriority;
254 - break;
255 - default:
256 - // Anything lower than normal priority should remain at the current level.
257 - priorityLevel = currentPriorityLevel;
258 - break;
259 - }
260 -
261 - var previousPriorityLevel = currentPriorityLevel;
262 - currentPriorityLevel = priorityLevel;
263 -
264 - try {
265 - return eventHandler();
266 - } finally {
267 - currentPriorityLevel = previousPriorityLevel;
268 - }
269 -}
270 -
271 -function unstable_wrapCallback(callback) {
272 - var parentPriorityLevel = currentPriorityLevel;
273 - return function() {
274 - // This is a fork of runWithPriority, inlined for performance.
275 - var previousPriorityLevel = currentPriorityLevel;
276 - currentPriorityLevel = parentPriorityLevel;
277 -
278 - try {
279 - return callback.apply(this, arguments);
280 - } finally {
281 - currentPriorityLevel = previousPriorityLevel;
282 - }
283 - };
284 -}
285 -
286 -function unstable_scheduleCallback(priorityLevel, callback, options) {
287 - var currentTime = getCurrentTime();
288 -
289 - var startTime;
290 - if (typeof options === 'object' && options !== null) {
291 - var delay = options.delay;
292 - if (typeof delay === 'number' && delay > 0) {
293 - startTime = currentTime + delay;
294 - } else {
295 - startTime = currentTime;
296 - }
297 - } else {
298 - startTime = currentTime;
299 - }
300 -
301 - var timeout;
302 - switch (priorityLevel) {
303 - case ImmediatePriority:
304 - timeout = IMMEDIATE_PRIORITY_TIMEOUT;
305 - break;
306 - case UserBlockingPriority:
307 - timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
308 - break;
309 - case IdlePriority:
310 - timeout = IDLE_PRIORITY_TIMEOUT;
311 - break;
312 - case LowPriority:
313 - timeout = LOW_PRIORITY_TIMEOUT;
314 - break;
315 - case NormalPriority:
316 - default:
317 - timeout = NORMAL_PRIORITY_TIMEOUT;
318 - break;
319 - }
320 -
321 - var expirationTime = startTime + timeout;
322 -
323 - var newTask = {
324 - id: taskIdCounter++,
325 - callback,
326 - priorityLevel,
327 - startTime,
328 - expirationTime,
329 - sortIndex: -1,
330 - };
331 - if (enableProfiling) {
332 - newTask.isQueued = false;
333 - }
334 -
335 - if (startTime > currentTime) {
336 - // This is a delayed task.
337 - newTask.sortIndex = startTime;
338 - push(timerQueue, newTask);
339 - if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
340 - // All tasks are delayed, and this is the task with the earliest delay.
341 - if (isHostTimeoutScheduled) {
342 - // Cancel an existing timeout.
343 - cancelHostTimeout();
344 - } else {
345 - isHostTimeoutScheduled = true;
346 - }
347 - // Schedule a timeout.
348 - requestHostTimeout(handleTimeout, startTime - currentTime);
349 - }
350 - } else {
351 - newTask.sortIndex = expirationTime;
352 - push(taskQueue, newTask);
353 - if (enableProfiling) {
354 - markTaskStart(newTask, currentTime);
355 - newTask.isQueued = true;
356 - }
357 - // Schedule a host callback, if needed. If we're already performing work,
358 - // wait until the next time we yield.
359 - if (!isHostCallbackScheduled && !isPerformingWork) {
360 - isHostCallbackScheduled = true;
361 - requestHostCallback(flushWork);
362 - }
363 - }
364 -
365 - return newTask;
366 -}
367 -
368 -function unstable_pauseExecution() {
369 - isSchedulerPaused = true;
370 -}
371 -
372 -function unstable_continueExecution() {
373 - isSchedulerPaused = false;
374 - if (!isHostCallbackScheduled && !isPerformingWork) {
375 - isHostCallbackScheduled = true;
376 - requestHostCallback(flushWork);
377 - }
378 -}
379 -
380 -function unstable_getFirstCallbackNode() {
381 - return peek(taskQueue);
382 -}
383 -
384 -function unstable_cancelCallback(task) {
385 - if (enableProfiling) {
386 - if (task.isQueued) {
387 - const currentTime = getCurrentTime();
388 - markTaskCanceled(task, currentTime);
389 - task.isQueued = false;
390 - }
391 - }
392 -
393 - // Null out the callback to indicate the task has been canceled. (Can't
394 - // remove from the queue because you can't remove arbitrary nodes from an
395 - // array based heap, only the first one.)
396 - task.callback = null;
397 -}
398 -
399 -function unstable_getCurrentPriorityLevel() {
400 - return currentPriorityLevel;
401 -}
402 -
403 -// If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore,
404 -// fallback to a naive implementation.
405 -let _callback = null;
406 -let _timeoutID = null;
407 -const _flushCallback = function() {
408 - if (_callback !== null) {
409 - try {
410 - const currentTime = getCurrentTime();
411 - const hasRemainingTime = true;
412 - _callback(hasRemainingTime, currentTime);
413 - _callback = null;
414 - } catch (e) {
415 - setTimeout(_flushCallback, 0);
416 - throw e;
417 - }
418 - }
419 -};
420 -
421 -function requestHostCallback(cb) {
422 - if (_callback !== null) {
423 - // Protect against re-entrancy.
424 - setTimeout(requestHostCallback, 0, cb);
425 - } else {
426 - _callback = cb;
427 - setTimeout(_flushCallback, 0);
428 - }
429 -}
430 -
431 -function requestHostTimeout(cb, ms) {
432 - _timeoutID = setTimeout(cb, ms);
433 -}
434 -
435 -function cancelHostTimeout() {
436 - clearTimeout(_timeoutID);
437 -}
438 -
439 -function shouldYieldToHost() {
440 - return false;
441 -}
442 -
443 -function forceFrameRate() {}
444 -
445 -function requestPaint() {}
446 -
447 -export {
448 - ImmediatePriority as unstable_ImmediatePriority,
449 - UserBlockingPriority as unstable_UserBlockingPriority,
450 - NormalPriority as unstable_NormalPriority,
451 - IdlePriority as unstable_IdlePriority,
452 - LowPriority as unstable_LowPriority,
453 - unstable_runWithPriority,
454 - unstable_next,
455 - unstable_scheduleCallback,
456 - unstable_cancelCallback,
457 - unstable_wrapCallback,
458 - unstable_getCurrentPriorityLevel,
459 - shouldYieldToHost as unstable_shouldYield,
460 - requestPaint as unstable_requestPaint,
461 - unstable_continueExecution,
462 - unstable_pauseExecution,
463 - unstable_getFirstCallbackNode,
464 - getCurrentTime as unstable_now,
465 - forceFrameRate as unstable_forceFrameRate,
466 -};
467 -
468 -export const unstable_Profiling = enableProfiling
469 - ? {
470 - startLoggingProfilingEvents,
471 - stopLoggingProfilingEvents,
472 - }
473 - : null;
packages/scheduler/src/forks/SchedulerPostTaskOnly.js deleted
-598
@@ -1,598 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - */
8 -
9 -/* eslint-disable no-var */
10 -
11 -import {
12 - enableSchedulerDebugging,
13 - enableProfiling,
14 -} from '../SchedulerFeatureFlags';
15 -
16 -import {push, pop, peek} from '../SchedulerMinHeap';
17 -
18 -// TODO: Use symbols?
19 -import {
20 - ImmediatePriority,
21 - UserBlockingPriority,
22 - NormalPriority,
23 - LowPriority,
24 - IdlePriority,
25 -} from '../SchedulerPriorities';
26 -import {
27 - markTaskRun,
28 - markTaskYield,
29 - markTaskCompleted,
30 - markTaskCanceled,
31 - markTaskErrored,
32 - markSchedulerSuspended,
33 - markSchedulerUnsuspended,
34 - markTaskStart,
35 - stopLoggingProfilingEvents,
36 - startLoggingProfilingEvents,
37 -} from '../SchedulerProfiling';
38 -
39 -import {enableIsInputPending} from '../SchedulerFeatureFlags';
40 -
41 -const perf = window.performance;
42 -
43 -function getCurrentTime() {
44 - return perf.now();
45 -}
46 -
47 -// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
48 -// Math.pow(2, 30) - 1
49 -// 0b111111111111111111111111111111
50 -var maxSigned31BitInt = 1073741823;
51 -
52 -// Times out immediately
53 -var IMMEDIATE_PRIORITY_TIMEOUT = -1;
54 -// Eventually times out
55 -var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
56 -var NORMAL_PRIORITY_TIMEOUT = 5000;
57 -var LOW_PRIORITY_TIMEOUT = 10000;
58 -// Never times out
59 -var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
60 -
61 -// Tasks are stored on a min heap
62 -var taskQueue = [];
63 -var timerQueue = [];
64 -
65 -// Incrementing id counter. Used to maintain insertion order.
66 -var taskIdCounter = 1;
67 -
68 -// Pausing the scheduler is useful for debugging.
69 -var isSchedulerPaused = false;
70 -
71 -var currentTask = null;
72 -var currentPriorityLevel = NormalPriority;
73 -
74 -// This is set while performing work, to prevent re-entrancy.
75 -var isPerformingWork = false;
76 -
77 -var isHostCallbackScheduled = false;
78 -var isHostTimeoutScheduled = false;
79 -
80 -// Capture local references to native APIs, in case a polyfill overrides them.
81 -const setTimeout = window.setTimeout;
82 -const clearTimeout = window.clearTimeout;
83 -
84 -if (typeof console !== 'undefined') {
85 - // TODO: Scheduler no longer requires these methods to be polyfilled. But
86 - // maybe we want to continue warning if they don't exist, to preserve the
87 - // option to rely on it in the future?
88 - const requestAnimationFrame = window.requestAnimationFrame;
89 - const cancelAnimationFrame = window.cancelAnimationFrame;
90 -
91 - if (typeof requestAnimationFrame !== 'function') {
92 - // Using console['error'] to evade Babel and ESLint
93 - console['error'](
94 - "This browser doesn't support requestAnimationFrame. " +
95 - 'Make sure that you load a ' +
96 - 'polyfill in older browsers. https://reactjs.org/link/react-polyfills',
97 - );
98 - }
99 - if (typeof cancelAnimationFrame !== 'function') {
100 - // Using console['error'] to evade Babel and ESLint
101 - console['error'](
102 - "This browser doesn't support cancelAnimationFrame. " +
103 - 'Make sure that you load a ' +
104 - 'polyfill in older browsers. https://reactjs.org/link/react-polyfills',
105 - );
106 - }
107 -}
108 -
109 -function advanceTimers(currentTime) {
110 - // Check for tasks that are no longer delayed and add them to the queue.
111 - let timer = peek(timerQueue);
112 - while (timer !== null) {
113 - if (timer.callback === null) {
114 - // Timer was cancelled.
115 - pop(timerQueue);
116 - } else if (timer.startTime <= currentTime) {
117 - // Timer fired. Transfer to the task queue.
118 - pop(timerQueue);
119 - timer.sortIndex = timer.expirationTime;
120 - push(taskQueue, timer);
121 - if (enableProfiling) {
122 - markTaskStart(timer, currentTime);
123 - timer.isQueued = true;
124 - }
125 - } else {
126 - // Remaining timers are pending.
127 - return;
128 - }
129 - timer = peek(timerQueue);
130 - }
131 -}
132 -
133 -function handleTimeout(currentTime) {
134 - isHostTimeoutScheduled = false;
135 - advanceTimers(currentTime);
136 -
137 - if (!isHostCallbackScheduled) {
138 - if (peek(taskQueue) !== null) {
139 - isHostCallbackScheduled = true;
140 - requestHostCallback(flushWork);
141 - } else {
142 - const firstTimer = peek(timerQueue);
143 - if (firstTimer !== null) {
144 - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
145 - }
146 - }
147 - }
148 -}
149 -
150 -function flushWork(hasTimeRemaining, initialTime) {
151 - if (enableProfiling) {
152 - markSchedulerUnsuspended(initialTime);
153 - }
154 -
155 - // We'll need a host callback the next time work is scheduled.
156 - isHostCallbackScheduled = false;
157 - if (isHostTimeoutScheduled) {
158 - // We scheduled a timeout but it's no longer needed. Cancel it.
159 - isHostTimeoutScheduled = false;
160 - cancelHostTimeout();
161 - }
162 -
163 - isPerformingWork = true;
164 - const previousPriorityLevel = currentPriorityLevel;
165 - try {
166 - if (enableProfiling) {
167 - try {
168 - return workLoop(hasTimeRemaining, initialTime);
169 - } catch (error) {
170 - if (currentTask !== null) {
171 - const currentTime = getCurrentTime();
172 - markTaskErrored(currentTask, currentTime);
173 - currentTask.isQueued = false;
174 - }
175 - throw error;
176 - }
177 - } else {
178 - // No catch in prod code path.
179 - return workLoop(hasTimeRemaining, initialTime);
180 - }
181 - } finally {
182 - currentTask = null;
183 - currentPriorityLevel = previousPriorityLevel;
184 - isPerformingWork = false;
185 - if (enableProfiling) {
186 - const currentTime = getCurrentTime();
187 - markSchedulerSuspended(currentTime);
188 - }
189 - }
190 -}
191 -
192 -function workLoop(hasTimeRemaining, initialTime) {
193 - let currentTime = initialTime;
194 - advanceTimers(currentTime);
195 - currentTask = peek(taskQueue);
196 - while (
197 - currentTask !== null &&
198 - !(enableSchedulerDebugging && isSchedulerPaused)
199 - ) {
200 - if (
201 - currentTask.expirationTime > currentTime &&
202 - (!hasTimeRemaining || shouldYieldToHost())
203 - ) {
204 - // This currentTask hasn't expired, and we've reached the deadline.
205 - break;
206 - }
207 - const callback = currentTask.callback;
208 - if (typeof callback === 'function') {
209 - currentTask.callback = null;
210 - currentPriorityLevel = currentTask.priorityLevel;
211 - const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
212 - if (enableProfiling) {
213 - markTaskRun(currentTask, currentTime);
214 - }
215 - const continuationCallback = callback(didUserCallbackTimeout);
216 - currentTime = getCurrentTime();
217 - if (typeof continuationCallback === 'function') {
218 - currentTask.callback = continuationCallback;
219 - if (enableProfiling) {
220 - markTaskYield(currentTask, currentTime);
221 - }
222 - } else {
223 - if (enableProfiling) {
224 - markTaskCompleted(currentTask, currentTime);
225 - currentTask.isQueued = false;
226 - }
227 - if (currentTask === peek(taskQueue)) {
228 - pop(taskQueue);
229 - }
230 - }
231 - advanceTimers(currentTime);
232 - } else {
233 - pop(taskQueue);
234 - }
235 - currentTask = peek(taskQueue);
236 - }
237 - // Return whether there's additional work
238 - if (currentTask !== null) {
239 - return true;
240 - } else {
241 - const firstTimer = peek(timerQueue);
242 - if (firstTimer !== null) {
243 - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
244 - }
245 - return false;
246 - }
247 -}
248 -
249 -function unstable_runWithPriority(priorityLevel, eventHandler) {
250 - switch (priorityLevel) {
251 - case ImmediatePriority:
252 - case UserBlockingPriority:
253 - case NormalPriority:
254 - case LowPriority:
255 - case IdlePriority:
256 - break;
257 - default:
258 - priorityLevel = NormalPriority;
259 - }
260 -
261 - var previousPriorityLevel = currentPriorityLevel;
262 - currentPriorityLevel = priorityLevel;
263 -
264 - try {
265 - return eventHandler();
266 - } finally {
267 - currentPriorityLevel = previousPriorityLevel;
268 - }
269 -}
270 -
271 -function unstable_next(eventHandler) {
272 - var priorityLevel;
273 - switch (currentPriorityLevel) {
274 - case ImmediatePriority:
275 - case UserBlockingPriority:
276 - case NormalPriority:
277 - // Shift down to normal priority
278 - priorityLevel = NormalPriority;
279 - break;
280 - default:
281 - // Anything lower than normal priority should remain at the current level.
282 - priorityLevel = currentPriorityLevel;
283 - break;
284 - }
285 -
286 - var previousPriorityLevel = currentPriorityLevel;
287 - currentPriorityLevel = priorityLevel;
288 -
289 - try {
290 - return eventHandler();
291 - } finally {
292 - currentPriorityLevel = previousPriorityLevel;
293 - }
294 -}
295 -
296 -function unstable_wrapCallback(callback) {
297 - var parentPriorityLevel = currentPriorityLevel;
298 - return function() {
299 - // This is a fork of runWithPriority, inlined for performance.
300 - var previousPriorityLevel = currentPriorityLevel;
301 - currentPriorityLevel = parentPriorityLevel;
302 -
303 - try {
304 - return callback.apply(this, arguments);
305 - } finally {
306 - currentPriorityLevel = previousPriorityLevel;
307 - }
308 - };
309 -}
310 -
311 -function unstable_scheduleCallback(priorityLevel, callback, options) {
312 - var currentTime = getCurrentTime();
313 -
314 - var startTime;
315 - if (typeof options === 'object' && options !== null) {
316 - var delay = options.delay;
317 - if (typeof delay === 'number' && delay > 0) {
318 - startTime = currentTime + delay;
319 - } else {
320 - startTime = currentTime;
321 - }
322 - } else {
323 - startTime = currentTime;
324 - }
325 -
326 - var timeout;
327 - switch (priorityLevel) {
328 - case ImmediatePriority:
329 - timeout = IMMEDIATE_PRIORITY_TIMEOUT;
330 - break;
331 - case UserBlockingPriority:
332 - timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
333 - break;
334 - case IdlePriority:
335 - timeout = IDLE_PRIORITY_TIMEOUT;
336 - break;
337 - case LowPriority:
338 - timeout = LOW_PRIORITY_TIMEOUT;
339 - break;
340 - case NormalPriority:
341 - default:
342 - timeout = NORMAL_PRIORITY_TIMEOUT;
343 - break;
344 - }
345 -
346 - var expirationTime = startTime + timeout;
347 -
348 - var newTask = {
349 - id: taskIdCounter++,
350 - callback,
351 - priorityLevel,
352 - startTime,
353 - expirationTime,
354 - sortIndex: -1,
355 - };
356 - if (enableProfiling) {
357 - newTask.isQueued = false;
358 - }
359 -
360 - if (startTime > currentTime) {
361 - // This is a delayed task.
362 - newTask.sortIndex = startTime;
363 - push(timerQueue, newTask);
364 - if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
365 - // All tasks are delayed, and this is the task with the earliest delay.
366 - if (isHostTimeoutScheduled) {
367 - // Cancel an existing timeout.
368 - cancelHostTimeout();
369 - } else {
370 - isHostTimeoutScheduled = true;
371 - }
372 - // Schedule a timeout.
373 - requestHostTimeout(handleTimeout, startTime - currentTime);
374 - }
375 - } else {
376 - newTask.sortIndex = expirationTime;
377 - push(taskQueue, newTask);
378 - if (enableProfiling) {
379 - markTaskStart(newTask, currentTime);
380 - newTask.isQueued = true;
381 - }
382 - // Schedule a host callback, if needed. If we're already performing work,
383 - // wait until the next time we yield.
384 - if (!isHostCallbackScheduled && !isPerformingWork) {
385 - isHostCallbackScheduled = true;
386 - requestHostCallback(flushWork);
387 - }
388 - }
389 -
390 - return newTask;
391 -}
392 -
393 -function unstable_pauseExecution() {
394 - isSchedulerPaused = true;
395 -}
396 -
397 -function unstable_continueExecution() {
398 - isSchedulerPaused = false;
399 - if (!isHostCallbackScheduled && !isPerformingWork) {
400 - isHostCallbackScheduled = true;
401 - requestHostCallback(flushWork);
402 - }
403 -}
404 -
405 -function unstable_getFirstCallbackNode() {
406 - return peek(taskQueue);
407 -}
408 -
409 -function unstable_cancelCallback(task) {
410 - if (enableProfiling) {
411 - if (task.isQueued) {
412 - const currentTime = getCurrentTime();
413 - markTaskCanceled(task, currentTime);
414 - task.isQueued = false;
415 - }
416 - }
417 -
418 - // Null out the callback to indicate the task has been canceled. (Can't
419 - // remove from the queue because you can't remove arbitrary nodes from an
420 - // array based heap, only the first one.)
421 - task.callback = null;
422 -}
423 -
424 -function unstable_getCurrentPriorityLevel() {
425 - return currentPriorityLevel;
426 -}
427 -
428 -let isTaskLoopRunning = false;
429 -let scheduledHostCallback = null;
430 -let taskTimeoutID = -1;
431 -
432 -// Scheduler periodically yields in case there is other work on the main
433 -// thread, like user events. By default, it yields multiple times per frame.
434 -// It does not attempt to align with frame boundaries, since most tasks don't
435 -// need to be frame aligned; for those that do, use requestAnimationFrame.
436 -let yieldInterval = 5;
437 -let deadline = 0;
438 -
439 -// TODO: Make this configurable
440 -// TODO: Adjust this based on priority?
441 -const maxYieldInterval = 300;
442 -let needsPaint = false;
443 -
444 -function shouldYieldToHost() {
445 - if (
446 - enableIsInputPending &&
447 - navigator !== undefined &&
448 - navigator.scheduling !== undefined &&
449 - navigator.scheduling.isInputPending !== undefined
450 - ) {
451 - const scheduling = navigator.scheduling;
452 - const currentTime = getCurrentTime();
453 - if (currentTime >= deadline) {
454 - // There's no time left. We may want to yield control of the main
455 - // thread, so the browser can perform high priority tasks. The main ones
456 - // are painting and user input. If there's a pending paint or a pending
457 - // input, then we should yield. But if there's neither, then we can
458 - // yield less often while remaining responsive. We'll eventually yield
459 - // regardless, since there could be a pending paint that wasn't
460 - // accompanied by a call to `requestPaint`, or other main thread tasks
461 - // like network events.
462 - if (needsPaint || scheduling.isInputPending()) {
463 - // There is either a pending paint or a pending input.
464 - return true;
465 - }
466 - // There's no pending input. Only yield if we've reached the max
467 - // yield interval.
468 - return currentTime >= maxYieldInterval;
469 - } else {
470 - // There's still time left in the frame.
471 - return false;
472 - }
473 - } else {
474 - // `isInputPending` is not available. Since we have no way of knowing if
475 - // there's pending input, always yield at the end of the frame.
476 - return getCurrentTime() >= deadline;
477 - }
478 -}
479 -
480 -function requestPaint() {
481 - if (
482 - enableIsInputPending &&
483 - navigator !== undefined &&
484 - navigator.scheduling !== undefined &&
485 - navigator.scheduling.isInputPending !== undefined
486 - ) {
487 - needsPaint = true;
488 - }
489 -
490 - // Since we yield every frame regardless, `requestPaint` has no effect.
491 -}
492 -
493 -function forceFrameRate(fps) {
494 - if (fps < 0 || fps > 125) {
495 - // Using console['error'] to evade Babel and ESLint
496 - console['error'](
497 - 'forceFrameRate takes a positive int between 0 and 125, ' +
498 - 'forcing frame rates higher than 125 fps is not supported',
499 - );
500 - return;
501 - }
502 - if (fps > 0) {
503 - yieldInterval = Math.floor(1000 / fps);
504 - } else {
505 - // reset the framerate
506 - yieldInterval = 5;
507 - }
508 -}
509 -
510 -const performWorkUntilDeadline = () => {
511 - if (scheduledHostCallback !== null) {
512 - const currentTime = getCurrentTime();
513 - // Yield after `yieldInterval` ms, regardless of where we are in the vsync
514 - // cycle. This means there's always time remaining at the beginning of
515 - // the message event.
516 - deadline = currentTime + yieldInterval;
517 - const hasTimeRemaining = true;
518 -
519 - // If a scheduler task throws, exit the current browser task so the
520 - // error can be observed.
521 - //
522 - // Intentionally not using a try-catch, since that makes some debugging
523 - // techniques harder. Instead, if `scheduledHostCallback` errors, then
524 - // `hasMoreWork` will remain true, and we'll continue the work loop.
525 - let hasMoreWork = true;
526 - try {
527 - hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
528 - } finally {
529 - if (hasMoreWork) {
530 - // If there's more work, schedule the next browser task at the end of
531 - // the preceding one.
532 - postTask(performWorkUntilDeadline);
533 - } else {
534 - isTaskLoopRunning = false;
535 - scheduledHostCallback = null;
536 - }
537 - }
538 - } else {
539 - isTaskLoopRunning = false;
540 - }
541 - // Yielding to the browser will give it a chance to paint, so we can
542 - // reset this.
543 - needsPaint = false;
544 -};
545 -
546 -function postTask(callback) {
547 - // Use experimental Chrome Scheduler postTask API.
548 - global.scheduler.postTask(callback);
549 -}
550 -
551 -function requestHostCallback(callback) {
552 - scheduledHostCallback = callback;
553 - if (!isTaskLoopRunning) {
554 - isTaskLoopRunning = true;
555 - postTask(performWorkUntilDeadline);
556 - }
557 -}
558 -
559 -function requestHostTimeout(callback, ms) {
560 - taskTimeoutID = setTimeout(() => {
561 - callback(getCurrentTime());
562 - }, ms);
563 -}
564 -
565 -function cancelHostTimeout() {
566 - clearTimeout(taskTimeoutID);
567 - taskTimeoutID = -1;
568 -}
569 -
570 -const unstable_requestPaint = requestPaint;
571 -
572 -export {
573 - ImmediatePriority as unstable_ImmediatePriority,
574 - UserBlockingPriority as unstable_UserBlockingPriority,
575 - NormalPriority as unstable_NormalPriority,
576 - IdlePriority as unstable_IdlePriority,
577 - LowPriority as unstable_LowPriority,
578 - unstable_runWithPriority,
579 - unstable_next,
580 - unstable_scheduleCallback,
581 - unstable_cancelCallback,
582 - unstable_wrapCallback,
583 - unstable_getCurrentPriorityLevel,
584 - shouldYieldToHost as unstable_shouldYield,
585 - unstable_requestPaint,
586 - unstable_continueExecution,
587 - unstable_pauseExecution,
588 - unstable_getFirstCallbackNode,
589 - getCurrentTime as unstable_now,
590 - forceFrameRate as unstable_forceFrameRate,
591 -};
592 -
593 -export const unstable_Profiling = enableProfiling
594 - ? {
595 - startLoggingProfilingEvents,
596 - stopLoggingProfilingEvents,
597 - }
598 - : null;
packages/scheduler/unstable_no_dom.js deleted
-10
@@ -1,10 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -
8 -'use strict';
9 -
10 -export * from './src/forks/SchedulerNoDOM';
packages/scheduler/unstable_post_task_only.js deleted
-10
@@ -1,10 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -
8 -'use strict';
9 -
10 -export * from './src/forks/SchedulerPostTaskOnly';
packages/shared/__tests__/ReactDOMFrameScheduling-test.js
+1 -32
@@ -13,43 +13,14 @@ describe('ReactDOMFrameScheduling', () => {
13 beforeEach(() => {
14 jest.resetModules();
15
16 - // Un-mock scheduler
17 - jest.mock('scheduler', () => require.requireActual('scheduler'));
18 - });
19 -
20 - it('warns when requestAnimationFrame is not polyfilled in the browser', () => {
21 - const previousRAF = global.requestAnimationFrame;
22 - const previousMessageChannel = global.MessageChannel;
23 - try {
24 - delete global.requestAnimationFrame;
25 - global.MessageChannel = function MessageChannel() {
26 - return {
27 - port1: {},
28 - port2: {},
29 - };
30 - };
31 - spyOnDevAndProd(console, 'error');
32 - require('react-dom');
33 - expect(console.error.calls.count()).toEqual(1);
34 - expect(console.error.calls.argsFor(0)[0]).toMatch(
35 - "This browser doesn't support requestAnimationFrame.",
36 - );
37 - } finally {
38 - global.MessageChannel = previousMessageChannel;
39 - global.requestAnimationFrame = previousRAF;
40 - }
16 + jest.unmock('scheduler');
17 });
18
19 // We're just testing importing, not using it.
20 // It is important because even isomorphic components may import it.
45 - // @gate !source
21 it('can import findDOMNode in Node environment', () => {
47 - const previousRAF = global.requestAnimationFrame;
48 - const previousRIC = global.requestIdleCallback;
22 const prevWindow = global.window;
23 try {
51 - global.requestAnimationFrame = undefined;
52 - global.requestIdleCallback = undefined;
24 // Simulate the Node environment:
25 delete global.window;
26 jest.resetModules();
@@ -57,8 +28,6 @@ describe('ReactDOMFrameScheduling', () => {
28 require('react-dom');
29 }).not.toThrow();
30 } finally {
60 - global.requestAnimationFrame = previousRAF;
61 - global.requestIdleCallback = previousRIC;
31 global.window = prevWindow;
32 }
33 });
scripts/rollup/bundles.js
-33
@@ -735,39 +735,6 @@ const bundles = [
735 externals: [],
736 },
737
738 - /******* React Scheduler Post Task Only (experimental) *******/
739 - {
740 - bundleTypes: [
741 - NODE_DEV,
742 - NODE_PROD,
743 - FB_WWW_DEV,
744 - FB_WWW_PROD,
745 - FB_WWW_PROFILING,
746 - ],
747 - moduleType: ISOMORPHIC,
748 - entry: 'scheduler/unstable_post_task_only',
749 - global: 'SchedulerPostTaskOnly',
750 - externals: [],
751 - },
752 -
753 - /******* React Scheduler No DOM (experimental) *******/
754 - {
755 - bundleTypes: [
756 - NODE_DEV,
757 - NODE_PROD,
758 - FB_WWW_DEV,
759 - FB_WWW_PROD,
760 - FB_WWW_PROFILING,
761 - RN_FB_DEV,
762 - RN_FB_PROD,
763 - RN_FB_PROFILING,
764 - ],
765 - moduleType: ISOMORPHIC,
766 - entry: 'scheduler/unstable_no_dom',
767 - global: 'SchedulerNoDOM',
768 - externals: [],
769 - },
770 -
738 /******* Jest React (experimental) *******/
739 {
740 bundleTypes: [NODE_DEV, NODE_PROD],
scripts/rollup/validate/eslintrc.rn.js
+2 -1
@@ -24,7 +24,8 @@ module.exports = {
24 nativeFabricUIManager: true,
25 // Trusted Types
26 trustedTypes: true,
27 -
27 + // RN supports this
28 + setImmediate: true,
29 // Scheduler profiling
30 Int32Array: true,
31 ArrayBuffer: true,
scripts/rollup/validate/eslintrc.umd.js
+2
@@ -26,6 +26,8 @@ module.exports = {
26 require: true,
27 globalThis: true,
28 global: true,
29 + // Internet Explorer
30 + setImmediate: true,
31 // Trusted Types
32 trustedTypes: true,
33