diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 97114903039b..8750763b5c21 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -1,6 +1,6 @@ { "packages": ["packages/react", "packages/react-dom", "packages/scheduler"], - "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", + "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", "node": "12", "publishDirectory": { "react": "build/node_modules/react", diff --git a/fixtures/dom/src/__tests__/wrong-act-test.js b/fixtures/dom/src/__tests__/wrong-act-test.js index 3e8912276e17..a3fe716ff07c 100644 --- a/fixtures/dom/src/__tests__/wrong-act-test.js +++ b/fixtures/dom/src/__tests__/wrong-act-test.js @@ -81,12 +81,12 @@ it("doesn't warn when you use the right act + renderer: test", () => { }); }); -it('resets correctly across renderers', () => { +it('resets correctly across renderers', async () => { function Effecty() { React.useEffect(() => {}, []); return null; } - TestUtils.act(() => { + await TestUtils.act(async () => { TestRenderer.act(() => {}); expect(() => { TestRenderer.create(); diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsActUnmockedScheduler-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsActUnmockedScheduler-test.js index 6f1587e7042e..f7145f657c72 100644 --- a/packages/react-dom/src/__tests__/ReactTestUtilsActUnmockedScheduler-test.js +++ b/packages/react-dom/src/__tests__/ReactTestUtilsActUnmockedScheduler-test.js @@ -33,9 +33,7 @@ function unmount(dom) { beforeEach(() => { jest.resetModules(); - jest.mock('scheduler', () => - require.requireActual('scheduler/unstable_no_dom'), - ); + jest.unmock('scheduler'); yields = []; React = require('react'); ReactDOM = require('react-dom'); diff --git a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js index 6c443a832aa1..501b01336b1b 100644 --- a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js @@ -17,7 +17,7 @@ function App() { beforeEach(() => { jest.resetModules(); - jest.mock('scheduler', () => require('scheduler/unstable_no_dom')); + jest.unmock('scheduler'); React = require('react'); ReactDOM = require('react-dom'); ReactFeatureFlags = require('shared/ReactFeatureFlags'); diff --git a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js index 3091c3d1af78..960154dbea25 100644 --- a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js +++ b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js @@ -16,9 +16,7 @@ function App() { beforeEach(() => { jest.resetModules(); - jest.mock('scheduler', () => - require.requireActual('scheduler/unstable_no_dom'), - ); + jest.unmock('scheduler'); React = require('react'); ReactDOM = require('react-dom'); }); diff --git a/packages/scheduler/index.js b/packages/scheduler/index.js index a5e913d1dd26..db916ce5d4d9 100644 --- a/packages/scheduler/index.js +++ b/packages/scheduler/index.js @@ -7,4 +7,4 @@ 'use strict'; -export * from './src/forks/SchedulerDOM'; +export * from './src/forks/Scheduler'; diff --git a/packages/scheduler/npm/index.js b/packages/scheduler/npm/index.js index 68a443aa1de8..77770b0c219e 100644 --- a/packages/scheduler/npm/index.js +++ b/packages/scheduler/npm/index.js @@ -1,8 +1,6 @@ 'use strict'; -if (typeof window === 'undefined' || typeof MessageChannel !== 'function') { - module.exports = require('./unstable_no_dom'); -} else if (process.env.NODE_ENV === 'production') { +if (process.env.NODE_ENV === 'production') { module.exports = require('./cjs/scheduler.production.min.js'); } else { module.exports = require('./cjs/scheduler.development.js'); diff --git a/packages/scheduler/npm/unstable_no_dom.js b/packages/scheduler/npm/unstable_no_dom.js deleted file mode 100644 index 2ba763765a0c..000000000000 --- a/packages/scheduler/npm/unstable_no_dom.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/scheduler-unstable_no_dom.production.min.js'); -} else { - module.exports = require('./cjs/scheduler-unstable_no_dom.development.js'); -} diff --git a/packages/scheduler/package.json b/packages/scheduler/package.json index e8ac46571b73..b22fc085290e 100644 --- a/packages/scheduler/package.json +++ b/packages/scheduler/package.json @@ -26,7 +26,6 @@ "build-info.json", "index.js", "unstable_mock.js", - "unstable_no_dom.js", "unstable_post_task.js", "cjs/", "umd/" diff --git a/packages/scheduler/src/__tests__/SchedulerDOM-test.js b/packages/scheduler/src/__tests__/Scheduler-test.js similarity index 93% rename from packages/scheduler/src/__tests__/SchedulerDOM-test.js rename to packages/scheduler/src/__tests__/Scheduler-test.js index f6dd4be5db18..29abe65c1eae 100644 --- a/packages/scheduler/src/__tests__/SchedulerDOM-test.js +++ b/packages/scheduler/src/__tests__/Scheduler-test.js @@ -32,11 +32,9 @@ let NormalPriority; describe('SchedulerBrowser', () => { beforeEach(() => { jest.resetModules(); - - // Un-mock scheduler - jest.mock('scheduler', () => require.requireActual('scheduler')); - runtime = installMockBrowserRuntime(); + jest.unmock('scheduler'); + performance = global.performance; Scheduler = require('scheduler'); cancelCallback = Scheduler.unstable_cancelCallback; @@ -68,21 +66,16 @@ describe('SchedulerBrowser', () => { }, }; - const window = {}; - global.window = window; - - // TODO: Scheduler no longer requires these methods to be polyfilled. But - // maybe we want to continue warning if they don't exist, to preserve the - // option to rely on it in the future? - window.requestAnimationFrame = window.cancelAnimationFrame = () => {}; + // Delete node provide setImmediate so we fall through to MessageChannel. + delete global.setImmediate; - window.setTimeout = (cb, delay) => { + global.setTimeout = (cb, delay) => { const id = timerIDCounter++; log(`Set Timer`); // TODO return id; }; - window.clearTimeout = id => { + global.clearTimeout = id => { // TODO }; diff --git a/packages/scheduler/src/__tests__/SchedulerPostTask-test.js b/packages/scheduler/src/__tests__/SchedulerPostTask-test.js index 19b6e330b82f..883452120b12 100644 --- a/packages/scheduler/src/__tests__/SchedulerPostTask-test.js +++ b/packages/scheduler/src/__tests__/SchedulerPostTask-test.js @@ -30,8 +30,6 @@ let IdlePriority; describe('SchedulerPostTask', () => { beforeEach(() => { jest.resetModules(); - - // Un-mock scheduler jest.mock('scheduler', () => require.requireActual('scheduler/unstable_post_task'), ); diff --git a/packages/scheduler/src/__tests__/SchedulerPostTaskOnly-test.js b/packages/scheduler/src/__tests__/SchedulerPostTaskOnly-test.js deleted file mode 100644 index bc25f3dbb148..000000000000 --- a/packages/scheduler/src/__tests__/SchedulerPostTaskOnly-test.js +++ /dev/null @@ -1,275 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - * @jest-environment node - */ - -/* eslint-disable no-for-of-loops/no-for-of-loops */ - -'use strict'; - -let Scheduler; -let runtime; -let performance; -let cancelCallback; -let scheduleCallback; -let NormalPriority; - -describe('SchedulerPostTaskOnly', () => { - beforeEach(() => { - if (!process.env.IS_BUILD) { - jest.resetModules(); - - // Un-mock scheduler - jest.mock('scheduler', () => - require.requireActual('scheduler/unstable_post_task_only'), - ); - - runtime = installMockBrowserRuntime(); - performance = window.performance; - Scheduler = require('scheduler'); - cancelCallback = Scheduler.unstable_cancelCallback; - scheduleCallback = Scheduler.unstable_scheduleCallback; - NormalPriority = Scheduler.unstable_NormalPriority; - } - }); - - afterEach(() => { - if (!process.env.IS_BUILD) { - if (!runtime.isLogEmpty()) { - throw Error('Test exited without clearing log.'); - } - } - }); - - function installMockBrowserRuntime() { - let hasPendingTask = false; - let timerIDCounter = 0; - let eventLog = []; - - // Mock window functions - const window = {}; - global.window = window; - - // TODO: Scheduler no longer requires these methods to be polyfilled. But - // maybe we want to continue warning if they don't exist, to preserve the - // option to rely on it in the future? - window.requestAnimationFrame = window.cancelAnimationFrame = () => {}; - - let currentTime = 0; - window.performance = { - now() { - return currentTime; - }, - }; - - window.setTimeout = (cb, delay) => { - const id = timerIDCounter++; - log(`Set Timer`); - // TODO - return id; - }; - window.clearTimeout = id => { - // TODO - }; - - // Mock browser scheduler. - const scheduler = {}; - global.scheduler = scheduler; - - let nextTask; - scheduler.postTask = function(callback) { - if (hasPendingTask) { - throw Error('Task already scheduled'); - } - log('Post Task'); - hasPendingTask = true; - nextTask = callback; - }; - - function ensureLogIsEmpty() { - if (eventLog.length !== 0) { - throw Error('Log is not empty. Call assertLog before continuing.'); - } - } - - function advanceTime(ms) { - currentTime += ms; - } - - function fireNextTask() { - ensureLogIsEmpty(); - if (!hasPendingTask) { - throw Error('No task was scheduled'); - } - hasPendingTask = false; - - log('Task Event'); - - // If there's a continuation, it will call postTask again - // which will set nextTask. That means we need to clear - // nextTask before the invocation, otherwise we would - // delete the continuation task. - const task = nextTask; - nextTask = null; - task(); - } - - function log(val) { - eventLog.push(val); - } - - function isLogEmpty() { - return eventLog.length === 0; - } - - function assertLog(expected) { - const actual = eventLog; - eventLog = []; - expect(actual).toEqual(expected); - } - - return { - advanceTime, - fireNextTask, - log, - isLogEmpty, - assertLog, - }; - } - - // @gate source - it('task that finishes before deadline', () => { - scheduleCallback(NormalPriority, () => { - runtime.log('Task'); - }); - runtime.assertLog(['Post Task']); - runtime.fireNextTask(); - runtime.assertLog(['Task Event', 'Task']); - }); - - // @gate source - it('task with continuation', () => { - scheduleCallback(NormalPriority, () => { - runtime.log('Task'); - while (!Scheduler.unstable_shouldYield()) { - runtime.advanceTime(1); - } - runtime.log(`Yield at ${performance.now()}ms`); - return () => { - runtime.log('Continuation'); - }; - }); - runtime.assertLog(['Post Task']); - - runtime.fireNextTask(); - runtime.assertLog(['Task Event', 'Task', 'Yield at 5ms', 'Post Task']); - - runtime.fireNextTask(); - runtime.assertLog(['Task Event', 'Continuation']); - }); - - // @gate source - it('multiple tasks', () => { - scheduleCallback(NormalPriority, () => { - runtime.log('A'); - }); - scheduleCallback(NormalPriority, () => { - runtime.log('B'); - }); - runtime.assertLog(['Post Task']); - runtime.fireNextTask(); - runtime.assertLog(['Task Event', 'A', 'B']); - }); - - // @gate source - it('multiple tasks with a yield in between', () => { - scheduleCallback(NormalPriority, () => { - runtime.log('A'); - runtime.advanceTime(4999); - }); - scheduleCallback(NormalPriority, () => { - runtime.log('B'); - }); - runtime.assertLog(['Post Task']); - runtime.fireNextTask(); - runtime.assertLog([ - 'Task Event', - 'A', - // Ran out of time. Post a continuation event. - 'Post Task', - ]); - runtime.fireNextTask(); - runtime.assertLog(['Task Event', 'B']); - }); - - // @gate source - it('cancels tasks', () => { - const task = scheduleCallback(NormalPriority, () => { - runtime.log('Task'); - }); - runtime.assertLog(['Post Task']); - cancelCallback(task); - runtime.assertLog([]); - }); - - // @gate source - it('throws when a task errors then continues in a new event', () => { - scheduleCallback(NormalPriority, () => { - runtime.log('Oops!'); - throw Error('Oops!'); - }); - scheduleCallback(NormalPriority, () => { - runtime.log('Yay'); - }); - runtime.assertLog(['Post Task']); - - expect(() => runtime.fireNextTask()).toThrow('Oops!'); - runtime.assertLog(['Task Event', 'Oops!', 'Post Task']); - - runtime.fireNextTask(); - runtime.assertLog(['Task Event', 'Yay']); - }); - - // @gate source - it('schedule new task after queue has emptied', () => { - scheduleCallback(NormalPriority, () => { - runtime.log('A'); - }); - - runtime.assertLog(['Post Task']); - runtime.fireNextTask(); - runtime.assertLog(['Task Event', 'A']); - - scheduleCallback(NormalPriority, () => { - runtime.log('B'); - }); - runtime.assertLog(['Post Task']); - runtime.fireNextTask(); - runtime.assertLog(['Task Event', 'B']); - }); - - // @gate source - it('schedule new task after a cancellation', () => { - const handle = scheduleCallback(NormalPriority, () => { - runtime.log('A'); - }); - - runtime.assertLog(['Post Task']); - cancelCallback(handle); - - runtime.fireNextTask(); - runtime.assertLog(['Task Event']); - - scheduleCallback(NormalPriority, () => { - runtime.log('B'); - }); - runtime.assertLog(['Post Task']); - runtime.fireNextTask(); - runtime.assertLog(['Task Event', 'B']); - }); -}); diff --git a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js b/packages/scheduler/src/__tests__/SchedulerSetImmediate-test.js similarity index 84% rename from packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js rename to packages/scheduler/src/__tests__/SchedulerSetImmediate-test.js index 04fecab12951..219618adc41f 100644 --- a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js +++ b/packages/scheduler/src/__tests__/SchedulerSetImmediate-test.js @@ -18,6 +18,7 @@ let performance; let cancelCallback; let scheduleCallback; let NormalPriority; +let UserBlockingPriority; // The Scheduler implementation uses browser APIs like `MessageChannel` and // `setTimeout` to schedule work on the main thread. Most of our tests treat @@ -32,16 +33,15 @@ let NormalPriority; describe('SchedulerDOMSetImmediate', () => { beforeEach(() => { jest.resetModules(); - - // Un-mock scheduler - jest.mock('scheduler', () => require.requireActual('scheduler')); - runtime = installMockBrowserRuntime(); + jest.unmock('scheduler'); + performance = global.performance; Scheduler = require('scheduler'); cancelCallback = Scheduler.unstable_cancelCallback; scheduleCallback = Scheduler.unstable_scheduleCallback; NormalPriority = Scheduler.unstable_NormalPriority; + UserBlockingPriority = Scheduler.unstable_UserBlockingPriority; }); afterEach(() => { @@ -66,21 +66,12 @@ describe('SchedulerDOMSetImmediate', () => { }, }; - const window = {}; - global.window = window; - - // TODO: Scheduler no longer requires these methods to be polyfilled. But - // maybe we want to continue warning if they don't exist, to preserve the - // option to rely on it in the future? - window.requestAnimationFrame = window.cancelAnimationFrame = () => {}; - - window.setTimeout = (cb, delay) => { + global.setTimeout = (cb, delay) => { const id = timerIDCounter++; log(`Set Timer`); - // TODO return id; }; - window.clearTimeout = id => { + global.clearTimeout = id => { // TODO }; @@ -97,7 +88,7 @@ describe('SchedulerDOMSetImmediate', () => { }; let pendingSetImmediateCallback = null; - window.setImmediate = function(cb) { + global.setImmediate = function(cb) { if (pendingSetImmediateCallback) { throw Error('Message event already scheduled'); } @@ -143,6 +134,19 @@ describe('SchedulerDOMSetImmediate', () => { }; } + it('does not use setImmediate override', () => { + global.setImmediate = () => { + throw new Error('Should not throw'); + }; + + scheduleCallback(NormalPriority, () => { + runtime.log('Task'); + }); + runtime.assertLog(['Set Immediate']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'Task']); + }); + it('task that finishes before deadline', () => { scheduleCallback(NormalPriority, () => { runtime.log('Task'); @@ -189,6 +193,18 @@ describe('SchedulerDOMSetImmediate', () => { runtime.assertLog(['setImmediate Callback', 'A', 'B']); }); + it('multiple tasks at different priority', () => { + scheduleCallback(NormalPriority, () => { + runtime.log('A'); + }); + scheduleCallback(UserBlockingPriority, () => { + runtime.log('B'); + }); + runtime.assertLog(['Set Immediate']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'B', 'A']); + }); + it('multiple tasks with a yield in between', () => { scheduleCallback(NormalPriority, () => { runtime.log('A'); @@ -271,3 +287,17 @@ describe('SchedulerDOMSetImmediate', () => { runtime.assertLog(['setImmediate Callback', 'B']); }); }); + +it('does not crash if setImmediate is undefined', () => { + jest.resetModules(); + const originalSetImmediate = global.setImmediate; + try { + delete global.setImmediate; + jest.unmock('scheduler'); + expect(() => { + require('scheduler'); + }).not.toThrow(); + } finally { + global.setImmediate = originalSetImmediate; + } +}); diff --git a/packages/scheduler/src/__tests__/SchedulerNoDOM-test.js b/packages/scheduler/src/__tests__/SchedulerSetTimeout-test.js similarity index 67% rename from packages/scheduler/src/__tests__/SchedulerNoDOM-test.js rename to packages/scheduler/src/__tests__/SchedulerSetTimeout-test.js index 8d3920fd920f..c4a950e1f1fd 100644 --- a/packages/scheduler/src/__tests__/SchedulerNoDOM-test.js +++ b/packages/scheduler/src/__tests__/SchedulerSetTimeout-test.js @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @emails react-core + * @jest-environment node */ 'use strict'; @@ -16,21 +17,16 @@ let UserBlockingPriority; let NormalPriority; describe('SchedulerNoDOM', () => { - // If Scheduler runs in a non-DOM environment, it falls back to a naive - // implementation using setTimeout. This only meant to be used for testing - // purposes, like with jest's fake timer API. + // Scheduler falls back to a naive implementation using setTimeout. + // This is only meant to be used for testing purposes, like with jest's fake timer API. beforeEach(() => { jest.resetModules(); jest.useFakeTimers(); - - // Un-mock scheduler - jest.mock('scheduler', () => - require.requireActual('scheduler/unstable_no_dom'), - ); + delete global.setImmediate; + jest.unmock('scheduler'); Scheduler = require('scheduler'); scheduleCallback = Scheduler.unstable_scheduleCallback; - ImmediatePriority = Scheduler.unstable_ImmediatePriority; UserBlockingPriority = Scheduler.unstable_UserBlockingPriority; NormalPriority = Scheduler.unstable_NormalPriority; }); @@ -99,3 +95,34 @@ describe('SchedulerNoDOM', () => { expect(log).toEqual(['B', 'C']); }); }); + +// See: https://github.com/facebook/react/pull/13088 +describe('does not crash non-node SSR environments', () => { + it('if setTimeout is undefined', () => { + jest.resetModules(); + const originalSetTimeout = global.setTimeout; + try { + delete global.setTimeout; + jest.unmock('scheduler'); + expect(() => { + require('scheduler'); + }).not.toThrow(); + } finally { + global.setTimeout = originalSetTimeout; + } + }); + + it('if clearTimeout is undefined', () => { + jest.resetModules(); + const originalClearTimeout = global.clearTimeout; + try { + delete global.clearTimeout; + jest.unmock('scheduler'); + expect(() => { + require('scheduler'); + }).not.toThrow(); + } finally { + global.clearTimeout = originalClearTimeout; + } + }); +}); diff --git a/packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js b/packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js index fde4ee8af20e..8aa1a387b269 100644 --- a/packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js +++ b/packages/scheduler/src/__tests__/SchedulerUMDBundle-test.internal.js @@ -21,19 +21,12 @@ describe('Scheduling UMD bundle', () => { global.__UMD__ = true; jest.resetModules(); + jest.unmock('scheduler'); - jest.mock('scheduler', () => require.requireActual('scheduler')); - - // Mock a browser environment since we're testing UMD modules. - global.window = { - requestAnimationFrame: jest.fn(), - cancelAnimationFrame: jest.fn(), - }; global.MessageChannel = MockMessageChannel; }); afterEach(() => { - global.window = undefined; global.MessageChannel = undefined; }); diff --git a/packages/scheduler/src/forks/SchedulerDOM.js b/packages/scheduler/src/forks/Scheduler.js similarity index 92% rename from packages/scheduler/src/forks/SchedulerDOM.js rename to packages/scheduler/src/forks/Scheduler.js index aa72972296b7..6d0599f7068c 100644 --- a/packages/scheduler/src/forks/SchedulerDOM.js +++ b/packages/scheduler/src/forks/Scheduler.js @@ -85,34 +85,11 @@ var isHostCallbackScheduled = false; var isHostTimeoutScheduled = false; // Capture local references to native APIs, in case a polyfill overrides them. -const setTimeout = window.setTimeout; -const clearTimeout = window.clearTimeout; -const setImmediate = window.setImmediate; // IE and Node.js + jsdom - -if (typeof console !== 'undefined') { - // TODO: Scheduler no longer requires these methods to be polyfilled. But - // maybe we want to continue warning if they don't exist, to preserve the - // option to rely on it in the future? - const requestAnimationFrame = window.requestAnimationFrame; - const cancelAnimationFrame = window.cancelAnimationFrame; - - if (typeof requestAnimationFrame !== 'function') { - // Using console['error'] to evade Babel and ESLint - console['error']( - "This browser doesn't support requestAnimationFrame. " + - 'Make sure that you load a ' + - 'polyfill in older browsers. https://reactjs.org/link/react-polyfills', - ); - } - if (typeof cancelAnimationFrame !== 'function') { - // Using console['error'] to evade Babel and ESLint - console['error']( - "This browser doesn't support cancelAnimationFrame. " + - 'Make sure that you load a ' + - 'polyfill in older browsers. https://reactjs.org/link/react-polyfills', - ); - } -} +const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null; +const localClearTimeout = + typeof clearTimeout === 'function' ? clearTimeout : null; +const localSetImmediate = + typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom function advanceTimers(currentTime) { // Check for tasks that are no longer delayed and add them to the queue. @@ -552,7 +529,7 @@ const performWorkUntilDeadline = () => { }; let schedulePerformWorkUntilDeadline; -if (typeof setImmediate === 'function') { +if (typeof localSetImmediate === 'function') { // Node.js and old IE. // There's a few reasons for why we prefer setImmediate. // @@ -565,15 +542,22 @@ if (typeof setImmediate === 'function') { // If other browsers ever implement it, it's better to use it. // Although both of these would be inferior to native scheduling. schedulePerformWorkUntilDeadline = () => { - setImmediate(performWorkUntilDeadline); + localSetImmediate(performWorkUntilDeadline); }; -} else { +} else if (typeof MessageChannel !== 'undefined') { + // DOM and Worker environments. + // We prefer MessageChannel because of the 4ms setTimeout clamping. const channel = new MessageChannel(); const port = channel.port2; channel.port1.onmessage = performWorkUntilDeadline; schedulePerformWorkUntilDeadline = () => { port.postMessage(null); }; +} else { + // We should only fallback here in non-browser environments. + schedulePerformWorkUntilDeadline = () => { + localSetTimeout(performWorkUntilDeadline, 0); + }; } function requestHostCallback(callback) { @@ -585,13 +569,13 @@ function requestHostCallback(callback) { } function requestHostTimeout(callback, ms) { - taskTimeoutID = setTimeout(() => { + taskTimeoutID = localSetTimeout(() => { callback(getCurrentTime()); }, ms); } function cancelHostTimeout() { - clearTimeout(taskTimeoutID); + localClearTimeout(taskTimeoutID); taskTimeoutID = -1; } diff --git a/packages/scheduler/src/forks/SchedulerNoDOM.js b/packages/scheduler/src/forks/SchedulerNoDOM.js deleted file mode 100644 index ed6d7891fd24..000000000000 --- a/packages/scheduler/src/forks/SchedulerNoDOM.js +++ /dev/null @@ -1,473 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -/* eslint-disable no-var */ - -import { - enableSchedulerDebugging, - enableProfiling, -} from '../SchedulerFeatureFlags'; -import {push, pop, peek} from '../SchedulerMinHeap'; - -// TODO: Use symbols? -import { - ImmediatePriority, - UserBlockingPriority, - NormalPriority, - LowPriority, - IdlePriority, -} from '../SchedulerPriorities'; -import { - markTaskRun, - markTaskYield, - markTaskCompleted, - markTaskCanceled, - markTaskErrored, - markSchedulerSuspended, - markSchedulerUnsuspended, - markTaskStart, - stopLoggingProfilingEvents, - startLoggingProfilingEvents, -} from '../SchedulerProfiling'; - -// Max 31 bit integer. The max integer size in V8 for 32-bit systems. -// Math.pow(2, 30) - 1 -// 0b111111111111111111111111111111 -var maxSigned31BitInt = 1073741823; - -// Times out immediately -var IMMEDIATE_PRIORITY_TIMEOUT = -1; -// Eventually times out -var USER_BLOCKING_PRIORITY_TIMEOUT = 250; -var NORMAL_PRIORITY_TIMEOUT = 5000; -var LOW_PRIORITY_TIMEOUT = 10000; -// Never times out -var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; - -// Tasks are stored on a min heap -var taskQueue = []; -var timerQueue = []; - -// Incrementing id counter. Used to maintain insertion order. -var taskIdCounter = 1; - -// Pausing the scheduler is useful for debugging. -var isSchedulerPaused = false; - -var currentTask = null; -var currentPriorityLevel = NormalPriority; - -// This is set while performing work, to prevent re-entrancy. -var isPerformingWork = false; - -var isHostCallbackScheduled = false; -var isHostTimeoutScheduled = false; - -let getCurrentTime; -const hasPerformanceNow = - typeof performance === 'object' && typeof performance.now === 'function'; - -if (hasPerformanceNow) { - const localPerformance = performance; - getCurrentTime = () => localPerformance.now(); -} else { - const localDate = Date; - const initialTime = localDate.now(); - getCurrentTime = () => localDate.now() - initialTime; -} - -function advanceTimers(currentTime) { - // Check for tasks that are no longer delayed and add them to the queue. - let timer = peek(timerQueue); - while (timer !== null) { - if (timer.callback === null) { - // Timer was cancelled. - pop(timerQueue); - } else if (timer.startTime <= currentTime) { - // Timer fired. Transfer to the task queue. - pop(timerQueue); - timer.sortIndex = timer.expirationTime; - push(taskQueue, timer); - if (enableProfiling) { - markTaskStart(timer, currentTime); - timer.isQueued = true; - } - } else { - // Remaining timers are pending. - return; - } - timer = peek(timerQueue); - } -} - -function handleTimeout(currentTime) { - isHostTimeoutScheduled = false; - advanceTimers(currentTime); - - if (!isHostCallbackScheduled) { - if (peek(taskQueue) !== null) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } else { - const firstTimer = peek(timerQueue); - if (firstTimer !== null) { - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); - } - } - } -} - -function flushWork(hasTimeRemaining, initialTime) { - if (enableProfiling) { - markSchedulerUnsuspended(initialTime); - } - - // We'll need a host callback the next time work is scheduled. - isHostCallbackScheduled = false; - if (isHostTimeoutScheduled) { - // We scheduled a timeout but it's no longer needed. Cancel it. - isHostTimeoutScheduled = false; - cancelHostTimeout(); - } - - isPerformingWork = true; - const previousPriorityLevel = currentPriorityLevel; - try { - if (enableProfiling) { - try { - return workLoop(hasTimeRemaining, initialTime); - } catch (error) { - if (currentTask !== null) { - const currentTime = getCurrentTime(); - markTaskErrored(currentTask, currentTime); - currentTask.isQueued = false; - } - throw error; - } - } else { - // No catch in prod code path. - return workLoop(hasTimeRemaining, initialTime); - } - } finally { - currentTask = null; - currentPriorityLevel = previousPriorityLevel; - isPerformingWork = false; - if (enableProfiling) { - const currentTime = getCurrentTime(); - markSchedulerSuspended(currentTime); - } - } -} - -function workLoop(hasTimeRemaining, initialTime) { - let currentTime = initialTime; - advanceTimers(currentTime); - currentTask = peek(taskQueue); - while ( - currentTask !== null && - !(enableSchedulerDebugging && isSchedulerPaused) - ) { - if ( - currentTask.expirationTime > currentTime && - (!hasTimeRemaining || shouldYieldToHost()) - ) { - // This currentTask hasn't expired, and we've reached the deadline. - break; - } - const callback = currentTask.callback; - if (typeof callback === 'function') { - currentTask.callback = null; - currentPriorityLevel = currentTask.priorityLevel; - const didUserCallbackTimeout = currentTask.expirationTime <= currentTime; - if (enableProfiling) { - markTaskRun(currentTask, currentTime); - } - const continuationCallback = callback(didUserCallbackTimeout); - currentTime = getCurrentTime(); - if (typeof continuationCallback === 'function') { - currentTask.callback = continuationCallback; - if (enableProfiling) { - markTaskYield(currentTask, currentTime); - } - } else { - if (enableProfiling) { - markTaskCompleted(currentTask, currentTime); - currentTask.isQueued = false; - } - if (currentTask === peek(taskQueue)) { - pop(taskQueue); - } - } - advanceTimers(currentTime); - } else { - pop(taskQueue); - } - currentTask = peek(taskQueue); - } - // Return whether there's additional work - if (currentTask !== null) { - return true; - } else { - const firstTimer = peek(timerQueue); - if (firstTimer !== null) { - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); - } - return false; - } -} - -function unstable_runWithPriority(priorityLevel, eventHandler) { - switch (priorityLevel) { - case ImmediatePriority: - case UserBlockingPriority: - case NormalPriority: - case LowPriority: - case IdlePriority: - break; - default: - priorityLevel = NormalPriority; - } - - var previousPriorityLevel = currentPriorityLevel; - currentPriorityLevel = priorityLevel; - - try { - return eventHandler(); - } finally { - currentPriorityLevel = previousPriorityLevel; - } -} - -function unstable_next(eventHandler) { - var priorityLevel; - switch (currentPriorityLevel) { - case ImmediatePriority: - case UserBlockingPriority: - case NormalPriority: - // Shift down to normal priority - priorityLevel = NormalPriority; - break; - default: - // Anything lower than normal priority should remain at the current level. - priorityLevel = currentPriorityLevel; - break; - } - - var previousPriorityLevel = currentPriorityLevel; - currentPriorityLevel = priorityLevel; - - try { - return eventHandler(); - } finally { - currentPriorityLevel = previousPriorityLevel; - } -} - -function unstable_wrapCallback(callback) { - var parentPriorityLevel = currentPriorityLevel; - return function() { - // This is a fork of runWithPriority, inlined for performance. - var previousPriorityLevel = currentPriorityLevel; - currentPriorityLevel = parentPriorityLevel; - - try { - return callback.apply(this, arguments); - } finally { - currentPriorityLevel = previousPriorityLevel; - } - }; -} - -function unstable_scheduleCallback(priorityLevel, callback, options) { - var currentTime = getCurrentTime(); - - var startTime; - if (typeof options === 'object' && options !== null) { - var delay = options.delay; - if (typeof delay === 'number' && delay > 0) { - startTime = currentTime + delay; - } else { - startTime = currentTime; - } - } else { - startTime = currentTime; - } - - var timeout; - switch (priorityLevel) { - case ImmediatePriority: - timeout = IMMEDIATE_PRIORITY_TIMEOUT; - break; - case UserBlockingPriority: - timeout = USER_BLOCKING_PRIORITY_TIMEOUT; - break; - case IdlePriority: - timeout = IDLE_PRIORITY_TIMEOUT; - break; - case LowPriority: - timeout = LOW_PRIORITY_TIMEOUT; - break; - case NormalPriority: - default: - timeout = NORMAL_PRIORITY_TIMEOUT; - break; - } - - var expirationTime = startTime + timeout; - - var newTask = { - id: taskIdCounter++, - callback, - priorityLevel, - startTime, - expirationTime, - sortIndex: -1, - }; - if (enableProfiling) { - newTask.isQueued = false; - } - - if (startTime > currentTime) { - // This is a delayed task. - newTask.sortIndex = startTime; - push(timerQueue, newTask); - if (peek(taskQueue) === null && newTask === peek(timerQueue)) { - // All tasks are delayed, and this is the task with the earliest delay. - if (isHostTimeoutScheduled) { - // Cancel an existing timeout. - cancelHostTimeout(); - } else { - isHostTimeoutScheduled = true; - } - // Schedule a timeout. - requestHostTimeout(handleTimeout, startTime - currentTime); - } - } else { - newTask.sortIndex = expirationTime; - push(taskQueue, newTask); - if (enableProfiling) { - markTaskStart(newTask, currentTime); - newTask.isQueued = true; - } - // Schedule a host callback, if needed. If we're already performing work, - // wait until the next time we yield. - if (!isHostCallbackScheduled && !isPerformingWork) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } - } - - return newTask; -} - -function unstable_pauseExecution() { - isSchedulerPaused = true; -} - -function unstable_continueExecution() { - isSchedulerPaused = false; - if (!isHostCallbackScheduled && !isPerformingWork) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } -} - -function unstable_getFirstCallbackNode() { - return peek(taskQueue); -} - -function unstable_cancelCallback(task) { - if (enableProfiling) { - if (task.isQueued) { - const currentTime = getCurrentTime(); - markTaskCanceled(task, currentTime); - task.isQueued = false; - } - } - - // Null out the callback to indicate the task has been canceled. (Can't - // remove from the queue because you can't remove arbitrary nodes from an - // array based heap, only the first one.) - task.callback = null; -} - -function unstable_getCurrentPriorityLevel() { - return currentPriorityLevel; -} - -// If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore, -// fallback to a naive implementation. -let _callback = null; -let _timeoutID = null; -const _flushCallback = function() { - if (_callback !== null) { - try { - const currentTime = getCurrentTime(); - const hasRemainingTime = true; - _callback(hasRemainingTime, currentTime); - _callback = null; - } catch (e) { - setTimeout(_flushCallback, 0); - throw e; - } - } -}; - -function requestHostCallback(cb) { - if (_callback !== null) { - // Protect against re-entrancy. - setTimeout(requestHostCallback, 0, cb); - } else { - _callback = cb; - setTimeout(_flushCallback, 0); - } -} - -function requestHostTimeout(cb, ms) { - _timeoutID = setTimeout(cb, ms); -} - -function cancelHostTimeout() { - clearTimeout(_timeoutID); -} - -function shouldYieldToHost() { - return false; -} - -function forceFrameRate() {} - -function requestPaint() {} - -export { - ImmediatePriority as unstable_ImmediatePriority, - UserBlockingPriority as unstable_UserBlockingPriority, - NormalPriority as unstable_NormalPriority, - IdlePriority as unstable_IdlePriority, - LowPriority as unstable_LowPriority, - unstable_runWithPriority, - unstable_next, - unstable_scheduleCallback, - unstable_cancelCallback, - unstable_wrapCallback, - unstable_getCurrentPriorityLevel, - shouldYieldToHost as unstable_shouldYield, - requestPaint as unstable_requestPaint, - unstable_continueExecution, - unstable_pauseExecution, - unstable_getFirstCallbackNode, - getCurrentTime as unstable_now, - forceFrameRate as unstable_forceFrameRate, -}; - -export const unstable_Profiling = enableProfiling - ? { - startLoggingProfilingEvents, - stopLoggingProfilingEvents, - } - : null; diff --git a/packages/scheduler/src/forks/SchedulerPostTaskOnly.js b/packages/scheduler/src/forks/SchedulerPostTaskOnly.js deleted file mode 100644 index 0f7e1b99e252..000000000000 --- a/packages/scheduler/src/forks/SchedulerPostTaskOnly.js +++ /dev/null @@ -1,598 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -/* eslint-disable no-var */ - -import { - enableSchedulerDebugging, - enableProfiling, -} from '../SchedulerFeatureFlags'; - -import {push, pop, peek} from '../SchedulerMinHeap'; - -// TODO: Use symbols? -import { - ImmediatePriority, - UserBlockingPriority, - NormalPriority, - LowPriority, - IdlePriority, -} from '../SchedulerPriorities'; -import { - markTaskRun, - markTaskYield, - markTaskCompleted, - markTaskCanceled, - markTaskErrored, - markSchedulerSuspended, - markSchedulerUnsuspended, - markTaskStart, - stopLoggingProfilingEvents, - startLoggingProfilingEvents, -} from '../SchedulerProfiling'; - -import {enableIsInputPending} from '../SchedulerFeatureFlags'; - -const perf = window.performance; - -function getCurrentTime() { - return perf.now(); -} - -// Max 31 bit integer. The max integer size in V8 for 32-bit systems. -// Math.pow(2, 30) - 1 -// 0b111111111111111111111111111111 -var maxSigned31BitInt = 1073741823; - -// Times out immediately -var IMMEDIATE_PRIORITY_TIMEOUT = -1; -// Eventually times out -var USER_BLOCKING_PRIORITY_TIMEOUT = 250; -var NORMAL_PRIORITY_TIMEOUT = 5000; -var LOW_PRIORITY_TIMEOUT = 10000; -// Never times out -var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; - -// Tasks are stored on a min heap -var taskQueue = []; -var timerQueue = []; - -// Incrementing id counter. Used to maintain insertion order. -var taskIdCounter = 1; - -// Pausing the scheduler is useful for debugging. -var isSchedulerPaused = false; - -var currentTask = null; -var currentPriorityLevel = NormalPriority; - -// This is set while performing work, to prevent re-entrancy. -var isPerformingWork = false; - -var isHostCallbackScheduled = false; -var isHostTimeoutScheduled = false; - -// Capture local references to native APIs, in case a polyfill overrides them. -const setTimeout = window.setTimeout; -const clearTimeout = window.clearTimeout; - -if (typeof console !== 'undefined') { - // TODO: Scheduler no longer requires these methods to be polyfilled. But - // maybe we want to continue warning if they don't exist, to preserve the - // option to rely on it in the future? - const requestAnimationFrame = window.requestAnimationFrame; - const cancelAnimationFrame = window.cancelAnimationFrame; - - if (typeof requestAnimationFrame !== 'function') { - // Using console['error'] to evade Babel and ESLint - console['error']( - "This browser doesn't support requestAnimationFrame. " + - 'Make sure that you load a ' + - 'polyfill in older browsers. https://reactjs.org/link/react-polyfills', - ); - } - if (typeof cancelAnimationFrame !== 'function') { - // Using console['error'] to evade Babel and ESLint - console['error']( - "This browser doesn't support cancelAnimationFrame. " + - 'Make sure that you load a ' + - 'polyfill in older browsers. https://reactjs.org/link/react-polyfills', - ); - } -} - -function advanceTimers(currentTime) { - // Check for tasks that are no longer delayed and add them to the queue. - let timer = peek(timerQueue); - while (timer !== null) { - if (timer.callback === null) { - // Timer was cancelled. - pop(timerQueue); - } else if (timer.startTime <= currentTime) { - // Timer fired. Transfer to the task queue. - pop(timerQueue); - timer.sortIndex = timer.expirationTime; - push(taskQueue, timer); - if (enableProfiling) { - markTaskStart(timer, currentTime); - timer.isQueued = true; - } - } else { - // Remaining timers are pending. - return; - } - timer = peek(timerQueue); - } -} - -function handleTimeout(currentTime) { - isHostTimeoutScheduled = false; - advanceTimers(currentTime); - - if (!isHostCallbackScheduled) { - if (peek(taskQueue) !== null) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } else { - const firstTimer = peek(timerQueue); - if (firstTimer !== null) { - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); - } - } - } -} - -function flushWork(hasTimeRemaining, initialTime) { - if (enableProfiling) { - markSchedulerUnsuspended(initialTime); - } - - // We'll need a host callback the next time work is scheduled. - isHostCallbackScheduled = false; - if (isHostTimeoutScheduled) { - // We scheduled a timeout but it's no longer needed. Cancel it. - isHostTimeoutScheduled = false; - cancelHostTimeout(); - } - - isPerformingWork = true; - const previousPriorityLevel = currentPriorityLevel; - try { - if (enableProfiling) { - try { - return workLoop(hasTimeRemaining, initialTime); - } catch (error) { - if (currentTask !== null) { - const currentTime = getCurrentTime(); - markTaskErrored(currentTask, currentTime); - currentTask.isQueued = false; - } - throw error; - } - } else { - // No catch in prod code path. - return workLoop(hasTimeRemaining, initialTime); - } - } finally { - currentTask = null; - currentPriorityLevel = previousPriorityLevel; - isPerformingWork = false; - if (enableProfiling) { - const currentTime = getCurrentTime(); - markSchedulerSuspended(currentTime); - } - } -} - -function workLoop(hasTimeRemaining, initialTime) { - let currentTime = initialTime; - advanceTimers(currentTime); - currentTask = peek(taskQueue); - while ( - currentTask !== null && - !(enableSchedulerDebugging && isSchedulerPaused) - ) { - if ( - currentTask.expirationTime > currentTime && - (!hasTimeRemaining || shouldYieldToHost()) - ) { - // This currentTask hasn't expired, and we've reached the deadline. - break; - } - const callback = currentTask.callback; - if (typeof callback === 'function') { - currentTask.callback = null; - currentPriorityLevel = currentTask.priorityLevel; - const didUserCallbackTimeout = currentTask.expirationTime <= currentTime; - if (enableProfiling) { - markTaskRun(currentTask, currentTime); - } - const continuationCallback = callback(didUserCallbackTimeout); - currentTime = getCurrentTime(); - if (typeof continuationCallback === 'function') { - currentTask.callback = continuationCallback; - if (enableProfiling) { - markTaskYield(currentTask, currentTime); - } - } else { - if (enableProfiling) { - markTaskCompleted(currentTask, currentTime); - currentTask.isQueued = false; - } - if (currentTask === peek(taskQueue)) { - pop(taskQueue); - } - } - advanceTimers(currentTime); - } else { - pop(taskQueue); - } - currentTask = peek(taskQueue); - } - // Return whether there's additional work - if (currentTask !== null) { - return true; - } else { - const firstTimer = peek(timerQueue); - if (firstTimer !== null) { - requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime); - } - return false; - } -} - -function unstable_runWithPriority(priorityLevel, eventHandler) { - switch (priorityLevel) { - case ImmediatePriority: - case UserBlockingPriority: - case NormalPriority: - case LowPriority: - case IdlePriority: - break; - default: - priorityLevel = NormalPriority; - } - - var previousPriorityLevel = currentPriorityLevel; - currentPriorityLevel = priorityLevel; - - try { - return eventHandler(); - } finally { - currentPriorityLevel = previousPriorityLevel; - } -} - -function unstable_next(eventHandler) { - var priorityLevel; - switch (currentPriorityLevel) { - case ImmediatePriority: - case UserBlockingPriority: - case NormalPriority: - // Shift down to normal priority - priorityLevel = NormalPriority; - break; - default: - // Anything lower than normal priority should remain at the current level. - priorityLevel = currentPriorityLevel; - break; - } - - var previousPriorityLevel = currentPriorityLevel; - currentPriorityLevel = priorityLevel; - - try { - return eventHandler(); - } finally { - currentPriorityLevel = previousPriorityLevel; - } -} - -function unstable_wrapCallback(callback) { - var parentPriorityLevel = currentPriorityLevel; - return function() { - // This is a fork of runWithPriority, inlined for performance. - var previousPriorityLevel = currentPriorityLevel; - currentPriorityLevel = parentPriorityLevel; - - try { - return callback.apply(this, arguments); - } finally { - currentPriorityLevel = previousPriorityLevel; - } - }; -} - -function unstable_scheduleCallback(priorityLevel, callback, options) { - var currentTime = getCurrentTime(); - - var startTime; - if (typeof options === 'object' && options !== null) { - var delay = options.delay; - if (typeof delay === 'number' && delay > 0) { - startTime = currentTime + delay; - } else { - startTime = currentTime; - } - } else { - startTime = currentTime; - } - - var timeout; - switch (priorityLevel) { - case ImmediatePriority: - timeout = IMMEDIATE_PRIORITY_TIMEOUT; - break; - case UserBlockingPriority: - timeout = USER_BLOCKING_PRIORITY_TIMEOUT; - break; - case IdlePriority: - timeout = IDLE_PRIORITY_TIMEOUT; - break; - case LowPriority: - timeout = LOW_PRIORITY_TIMEOUT; - break; - case NormalPriority: - default: - timeout = NORMAL_PRIORITY_TIMEOUT; - break; - } - - var expirationTime = startTime + timeout; - - var newTask = { - id: taskIdCounter++, - callback, - priorityLevel, - startTime, - expirationTime, - sortIndex: -1, - }; - if (enableProfiling) { - newTask.isQueued = false; - } - - if (startTime > currentTime) { - // This is a delayed task. - newTask.sortIndex = startTime; - push(timerQueue, newTask); - if (peek(taskQueue) === null && newTask === peek(timerQueue)) { - // All tasks are delayed, and this is the task with the earliest delay. - if (isHostTimeoutScheduled) { - // Cancel an existing timeout. - cancelHostTimeout(); - } else { - isHostTimeoutScheduled = true; - } - // Schedule a timeout. - requestHostTimeout(handleTimeout, startTime - currentTime); - } - } else { - newTask.sortIndex = expirationTime; - push(taskQueue, newTask); - if (enableProfiling) { - markTaskStart(newTask, currentTime); - newTask.isQueued = true; - } - // Schedule a host callback, if needed. If we're already performing work, - // wait until the next time we yield. - if (!isHostCallbackScheduled && !isPerformingWork) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } - } - - return newTask; -} - -function unstable_pauseExecution() { - isSchedulerPaused = true; -} - -function unstable_continueExecution() { - isSchedulerPaused = false; - if (!isHostCallbackScheduled && !isPerformingWork) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } -} - -function unstable_getFirstCallbackNode() { - return peek(taskQueue); -} - -function unstable_cancelCallback(task) { - if (enableProfiling) { - if (task.isQueued) { - const currentTime = getCurrentTime(); - markTaskCanceled(task, currentTime); - task.isQueued = false; - } - } - - // Null out the callback to indicate the task has been canceled. (Can't - // remove from the queue because you can't remove arbitrary nodes from an - // array based heap, only the first one.) - task.callback = null; -} - -function unstable_getCurrentPriorityLevel() { - return currentPriorityLevel; -} - -let isTaskLoopRunning = false; -let scheduledHostCallback = null; -let taskTimeoutID = -1; - -// Scheduler periodically yields in case there is other work on the main -// thread, like user events. By default, it yields multiple times per frame. -// It does not attempt to align with frame boundaries, since most tasks don't -// need to be frame aligned; for those that do, use requestAnimationFrame. -let yieldInterval = 5; -let deadline = 0; - -// TODO: Make this configurable -// TODO: Adjust this based on priority? -const maxYieldInterval = 300; -let needsPaint = false; - -function shouldYieldToHost() { - if ( - enableIsInputPending && - navigator !== undefined && - navigator.scheduling !== undefined && - navigator.scheduling.isInputPending !== undefined - ) { - const scheduling = navigator.scheduling; - const currentTime = getCurrentTime(); - if (currentTime >= deadline) { - // There's no time left. We may want to yield control of the main - // thread, so the browser can perform high priority tasks. The main ones - // are painting and user input. If there's a pending paint or a pending - // input, then we should yield. But if there's neither, then we can - // yield less often while remaining responsive. We'll eventually yield - // regardless, since there could be a pending paint that wasn't - // accompanied by a call to `requestPaint`, or other main thread tasks - // like network events. - if (needsPaint || scheduling.isInputPending()) { - // There is either a pending paint or a pending input. - return true; - } - // There's no pending input. Only yield if we've reached the max - // yield interval. - return currentTime >= maxYieldInterval; - } else { - // There's still time left in the frame. - return false; - } - } else { - // `isInputPending` is not available. Since we have no way of knowing if - // there's pending input, always yield at the end of the frame. - return getCurrentTime() >= deadline; - } -} - -function requestPaint() { - if ( - enableIsInputPending && - navigator !== undefined && - navigator.scheduling !== undefined && - navigator.scheduling.isInputPending !== undefined - ) { - needsPaint = true; - } - - // Since we yield every frame regardless, `requestPaint` has no effect. -} - -function forceFrameRate(fps) { - if (fps < 0 || fps > 125) { - // Using console['error'] to evade Babel and ESLint - console['error']( - 'forceFrameRate takes a positive int between 0 and 125, ' + - 'forcing frame rates higher than 125 fps is not supported', - ); - return; - } - if (fps > 0) { - yieldInterval = Math.floor(1000 / fps); - } else { - // reset the framerate - yieldInterval = 5; - } -} - -const performWorkUntilDeadline = () => { - if (scheduledHostCallback !== null) { - const currentTime = getCurrentTime(); - // Yield after `yieldInterval` ms, regardless of where we are in the vsync - // cycle. This means there's always time remaining at the beginning of - // the message event. - deadline = currentTime + yieldInterval; - const hasTimeRemaining = true; - - // If a scheduler task throws, exit the current browser task so the - // error can be observed. - // - // Intentionally not using a try-catch, since that makes some debugging - // techniques harder. Instead, if `scheduledHostCallback` errors, then - // `hasMoreWork` will remain true, and we'll continue the work loop. - let hasMoreWork = true; - try { - hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime); - } finally { - if (hasMoreWork) { - // If there's more work, schedule the next browser task at the end of - // the preceding one. - postTask(performWorkUntilDeadline); - } else { - isTaskLoopRunning = false; - scheduledHostCallback = null; - } - } - } else { - isTaskLoopRunning = false; - } - // Yielding to the browser will give it a chance to paint, so we can - // reset this. - needsPaint = false; -}; - -function postTask(callback) { - // Use experimental Chrome Scheduler postTask API. - global.scheduler.postTask(callback); -} - -function requestHostCallback(callback) { - scheduledHostCallback = callback; - if (!isTaskLoopRunning) { - isTaskLoopRunning = true; - postTask(performWorkUntilDeadline); - } -} - -function requestHostTimeout(callback, ms) { - taskTimeoutID = setTimeout(() => { - callback(getCurrentTime()); - }, ms); -} - -function cancelHostTimeout() { - clearTimeout(taskTimeoutID); - taskTimeoutID = -1; -} - -const unstable_requestPaint = requestPaint; - -export { - ImmediatePriority as unstable_ImmediatePriority, - UserBlockingPriority as unstable_UserBlockingPriority, - NormalPriority as unstable_NormalPriority, - IdlePriority as unstable_IdlePriority, - LowPriority as unstable_LowPriority, - unstable_runWithPriority, - unstable_next, - unstable_scheduleCallback, - unstable_cancelCallback, - unstable_wrapCallback, - unstable_getCurrentPriorityLevel, - shouldYieldToHost as unstable_shouldYield, - unstable_requestPaint, - unstable_continueExecution, - unstable_pauseExecution, - unstable_getFirstCallbackNode, - getCurrentTime as unstable_now, - forceFrameRate as unstable_forceFrameRate, -}; - -export const unstable_Profiling = enableProfiling - ? { - startLoggingProfilingEvents, - stopLoggingProfilingEvents, - } - : null; diff --git a/packages/scheduler/unstable_no_dom.js b/packages/scheduler/unstable_no_dom.js deleted file mode 100644 index bf235f9b1514..000000000000 --- a/packages/scheduler/unstable_no_dom.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict'; - -export * from './src/forks/SchedulerNoDOM'; diff --git a/packages/scheduler/unstable_post_task_only.js b/packages/scheduler/unstable_post_task_only.js deleted file mode 100644 index d6e22c230162..000000000000 --- a/packages/scheduler/unstable_post_task_only.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict'; - -export * from './src/forks/SchedulerPostTaskOnly'; diff --git a/packages/shared/__tests__/ReactDOMFrameScheduling-test.js b/packages/shared/__tests__/ReactDOMFrameScheduling-test.js index 3d6407ebe384..d46a3403ca84 100644 --- a/packages/shared/__tests__/ReactDOMFrameScheduling-test.js +++ b/packages/shared/__tests__/ReactDOMFrameScheduling-test.js @@ -13,43 +13,14 @@ describe('ReactDOMFrameScheduling', () => { beforeEach(() => { jest.resetModules(); - // Un-mock scheduler - jest.mock('scheduler', () => require.requireActual('scheduler')); - }); - - it('warns when requestAnimationFrame is not polyfilled in the browser', () => { - const previousRAF = global.requestAnimationFrame; - const previousMessageChannel = global.MessageChannel; - try { - delete global.requestAnimationFrame; - global.MessageChannel = function MessageChannel() { - return { - port1: {}, - port2: {}, - }; - }; - spyOnDevAndProd(console, 'error'); - require('react-dom'); - expect(console.error.calls.count()).toEqual(1); - expect(console.error.calls.argsFor(0)[0]).toMatch( - "This browser doesn't support requestAnimationFrame.", - ); - } finally { - global.MessageChannel = previousMessageChannel; - global.requestAnimationFrame = previousRAF; - } + jest.unmock('scheduler'); }); // We're just testing importing, not using it. // It is important because even isomorphic components may import it. - // @gate !source it('can import findDOMNode in Node environment', () => { - const previousRAF = global.requestAnimationFrame; - const previousRIC = global.requestIdleCallback; const prevWindow = global.window; try { - global.requestAnimationFrame = undefined; - global.requestIdleCallback = undefined; // Simulate the Node environment: delete global.window; jest.resetModules(); @@ -57,8 +28,6 @@ describe('ReactDOMFrameScheduling', () => { require('react-dom'); }).not.toThrow(); } finally { - global.requestAnimationFrame = previousRAF; - global.requestIdleCallback = previousRIC; global.window = prevWindow; } }); diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index b34c0077873a..7e0cd766d657 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -735,39 +735,6 @@ const bundles = [ externals: [], }, - /******* React Scheduler Post Task Only (experimental) *******/ - { - bundleTypes: [ - NODE_DEV, - NODE_PROD, - FB_WWW_DEV, - FB_WWW_PROD, - FB_WWW_PROFILING, - ], - moduleType: ISOMORPHIC, - entry: 'scheduler/unstable_post_task_only', - global: 'SchedulerPostTaskOnly', - externals: [], - }, - - /******* React Scheduler No DOM (experimental) *******/ - { - bundleTypes: [ - NODE_DEV, - NODE_PROD, - FB_WWW_DEV, - FB_WWW_PROD, - FB_WWW_PROFILING, - RN_FB_DEV, - RN_FB_PROD, - RN_FB_PROFILING, - ], - moduleType: ISOMORPHIC, - entry: 'scheduler/unstable_no_dom', - global: 'SchedulerNoDOM', - externals: [], - }, - /******* Jest React (experimental) *******/ { bundleTypes: [NODE_DEV, NODE_PROD], diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js index b6941e4f2caf..a20cdeb4a37e 100644 --- a/scripts/rollup/validate/eslintrc.rn.js +++ b/scripts/rollup/validate/eslintrc.rn.js @@ -24,7 +24,8 @@ module.exports = { nativeFabricUIManager: true, // Trusted Types trustedTypes: true, - + // RN supports this + setImmediate: true, // Scheduler profiling Int32Array: true, ArrayBuffer: true, diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js index 8bec6d92296d..95ef9550dd6a 100644 --- a/scripts/rollup/validate/eslintrc.umd.js +++ b/scripts/rollup/validate/eslintrc.umd.js @@ -26,6 +26,8 @@ module.exports = { require: true, globalThis: true, global: true, + // Internet Explorer + setImmediate: true, // Trusted Types trustedTypes: true,