From cb2a78cf89872747f7598d3c95a2b50559cdd11c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 17:05:55 +0000 Subject: [PATCH 01/10] Move direct port access into a function --- packages/scheduler/src/forks/SchedulerDOM.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/scheduler/src/forks/SchedulerDOM.js b/packages/scheduler/src/forks/SchedulerDOM.js index e872b3ae65900..53e638d4eed2f 100644 --- a/packages/scheduler/src/forks/SchedulerDOM.js +++ b/packages/scheduler/src/forks/SchedulerDOM.js @@ -533,7 +533,7 @@ const performWorkUntilDeadline = () => { if (hasMoreWork) { // If there's more work, schedule the next message event at the end // of the preceding one. - port.postMessage(null); + schedulePerformWorkUntilDeadline(); } else { isMessageLoopRunning = false; scheduledHostCallback = null; @@ -551,11 +551,15 @@ const channel = new MessageChannel(); const port = channel.port2; channel.port1.onmessage = performWorkUntilDeadline; +const schedulePerformWorkUntilDeadline = () => { + port.postMessage(null); +}; + function requestHostCallback(callback) { scheduledHostCallback = callback; if (!isMessageLoopRunning) { isMessageLoopRunning = true; - port.postMessage(null); + schedulePerformWorkUntilDeadline(); } } From 50bde0ca6be0d525d426188fe778400e22a0be56 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 17:22:09 +0000 Subject: [PATCH 02/10] Fork based on presence of setImmediate --- packages/scheduler/src/forks/SchedulerDOM.js | 32 +++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/scheduler/src/forks/SchedulerDOM.js b/packages/scheduler/src/forks/SchedulerDOM.js index 53e638d4eed2f..9fada11a2053f 100644 --- a/packages/scheduler/src/forks/SchedulerDOM.js +++ b/packages/scheduler/src/forks/SchedulerDOM.js @@ -88,6 +88,7 @@ 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 if (typeof console !== 'undefined') { // TODO: Scheduler no longer requires these methods to be polyfilled. But @@ -547,13 +548,30 @@ const performWorkUntilDeadline = () => { needsPaint = false; }; -const channel = new MessageChannel(); -const port = channel.port2; -channel.port1.onmessage = performWorkUntilDeadline; - -const schedulePerformWorkUntilDeadline = () => { - port.postMessage(null); -}; +let schedulePerformWorkUntilDeadline; +if (typeof setImmediate === 'function') { + // Node.js and old IE. + // There's a few reasons for why we prefer setImmediate. + // + // Unlike MessageChannel, it doesn't prevent a Node.js process from exiting. + // (Even though this is a DOM fork of the Scheduler, you could get here + // with a mix of Node.js 15+, which has a MessageChannel, and jsdom.) + // https://github.com/facebook/react/issues/20756 + // + // But also, it runs earlier which is the semantic we want. + // 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); + }; +} else { + const channel = new MessageChannel(); + const port = channel.port2; + channel.port1.onmessage = performWorkUntilDeadline; + schedulePerformWorkUntilDeadline = () => { + port.postMessage(null); + }; +} function requestHostCallback(callback) { scheduledHostCallback = callback; From 02eb954084bcfd52e113562e0c3e8573adb4bf30 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 17:26:41 +0000 Subject: [PATCH 03/10] Copy SchedulerDOM-test into another file --- .../SchedulerDOMSetImmediate-test.js | 269 ++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js diff --git a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js new file mode 100644 index 0000000000000..f6dd4be5db18d --- /dev/null +++ b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js @@ -0,0 +1,269 @@ +/** + * 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; + +// The Scheduler implementation uses browser APIs like `MessageChannel` and +// `setTimeout` to schedule work on the main thread. Most of our tests treat +// these as implementation details; however, the sequence and timing of these +// APIs are not precisely specified, and can vary across browsers. +// +// To prevent regressions, we need the ability to simulate specific edge cases +// that we may encounter in various browsers. +// +// This test suite mocks all browser methods used in our implementation. It +// assumes as little as possible about the order and timing of events. +describe('SchedulerBrowser', () => { + beforeEach(() => { + jest.resetModules(); + + // Un-mock scheduler + jest.mock('scheduler', () => require.requireActual('scheduler')); + + runtime = installMockBrowserRuntime(); + performance = global.performance; + Scheduler = require('scheduler'); + cancelCallback = Scheduler.unstable_cancelCallback; + scheduleCallback = Scheduler.unstable_scheduleCallback; + NormalPriority = Scheduler.unstable_NormalPriority; + }); + + afterEach(() => { + delete global.performance; + + if (!runtime.isLogEmpty()) { + throw Error('Test exited without clearing log.'); + } + }); + + function installMockBrowserRuntime() { + let hasPendingMessageEvent = false; + + let timerIDCounter = 0; + // let timerIDs = new Map(); + + let eventLog = []; + + let currentTime = 0; + + global.performance = { + now() { + return currentTime; + }, + }; + + 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) => { + const id = timerIDCounter++; + log(`Set Timer`); + // TODO + return id; + }; + window.clearTimeout = id => { + // TODO + }; + + const port1 = {}; + const port2 = { + postMessage() { + if (hasPendingMessageEvent) { + throw Error('Message event already scheduled'); + } + log('Post Message'); + hasPendingMessageEvent = true; + }, + }; + global.MessageChannel = function MessageChannel() { + this.port1 = port1; + this.port2 = port2; + }; + + function ensureLogIsEmpty() { + if (eventLog.length !== 0) { + throw Error('Log is not empty. Call assertLog before continuing.'); + } + } + function advanceTime(ms) { + currentTime += ms; + } + function fireMessageEvent() { + ensureLogIsEmpty(); + if (!hasPendingMessageEvent) { + throw Error('No message event was scheduled'); + } + hasPendingMessageEvent = false; + const onMessage = port1.onmessage; + log('Message Event'); + onMessage(); + } + 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, + fireMessageEvent, + log, + isLogEmpty, + assertLog, + }; + } + + it('task that finishes before deadline', () => { + scheduleCallback(NormalPriority, () => { + runtime.log('Task'); + }); + runtime.assertLog(['Post Message']); + runtime.fireMessageEvent(); + runtime.assertLog(['Message Event', 'Task']); + }); + + 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 Message']); + + runtime.fireMessageEvent(); + runtime.assertLog([ + 'Message Event', + 'Task', + 'Yield at 5ms', + 'Post Message', + ]); + + runtime.fireMessageEvent(); + runtime.assertLog(['Message Event', 'Continuation']); + }); + + it('multiple tasks', () => { + scheduleCallback(NormalPriority, () => { + runtime.log('A'); + }); + scheduleCallback(NormalPriority, () => { + runtime.log('B'); + }); + runtime.assertLog(['Post Message']); + runtime.fireMessageEvent(); + runtime.assertLog(['Message Event', 'A', 'B']); + }); + + it('multiple tasks with a yield in between', () => { + scheduleCallback(NormalPriority, () => { + runtime.log('A'); + runtime.advanceTime(4999); + }); + scheduleCallback(NormalPriority, () => { + runtime.log('B'); + }); + runtime.assertLog(['Post Message']); + runtime.fireMessageEvent(); + runtime.assertLog([ + 'Message Event', + 'A', + // Ran out of time. Post a continuation event. + 'Post Message', + ]); + runtime.fireMessageEvent(); + runtime.assertLog(['Message Event', 'B']); + }); + + it('cancels tasks', () => { + const task = scheduleCallback(NormalPriority, () => { + runtime.log('Task'); + }); + runtime.assertLog(['Post Message']); + cancelCallback(task); + runtime.assertLog([]); + }); + + 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 Message']); + + expect(() => runtime.fireMessageEvent()).toThrow('Oops!'); + runtime.assertLog(['Message Event', 'Oops!', 'Post Message']); + + runtime.fireMessageEvent(); + runtime.assertLog(['Message Event', 'Yay']); + }); + + it('schedule new task after queue has emptied', () => { + scheduleCallback(NormalPriority, () => { + runtime.log('A'); + }); + + runtime.assertLog(['Post Message']); + runtime.fireMessageEvent(); + runtime.assertLog(['Message Event', 'A']); + + scheduleCallback(NormalPriority, () => { + runtime.log('B'); + }); + runtime.assertLog(['Post Message']); + runtime.fireMessageEvent(); + runtime.assertLog(['Message Event', 'B']); + }); + + it('schedule new task after a cancellation', () => { + const handle = scheduleCallback(NormalPriority, () => { + runtime.log('A'); + }); + + runtime.assertLog(['Post Message']); + cancelCallback(handle); + + runtime.fireMessageEvent(); + runtime.assertLog(['Message Event']); + + scheduleCallback(NormalPriority, () => { + runtime.log('B'); + }); + runtime.assertLog(['Post Message']); + runtime.fireMessageEvent(); + runtime.assertLog(['Message Event', 'B']); + }); +}); From 3f3cd836e1dc009c08a79fdfa57820b1d7d8725c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 17:32:29 +0000 Subject: [PATCH 04/10] Change the new test to use shimmed setImmediate --- .../SchedulerDOMSetImmediate-test.js | 115 +++++++++--------- 1 file changed, 55 insertions(+), 60 deletions(-) diff --git a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js index f6dd4be5db18d..4e782a3dd5cd3 100644 --- a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js +++ b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js @@ -29,7 +29,7 @@ let NormalPriority; // // This test suite mocks all browser methods used in our implementation. It // assumes as little as possible about the order and timing of events. -describe('SchedulerBrowser', () => { +describe('SchedulerDOMSetImmediate', () => { beforeEach(() => { jest.resetModules(); @@ -53,8 +53,6 @@ describe('SchedulerBrowser', () => { }); function installMockBrowserRuntime() { - let hasPendingMessageEvent = false; - let timerIDCounter = 0; // let timerIDs = new Map(); @@ -86,19 +84,16 @@ describe('SchedulerBrowser', () => { // TODO }; - const port1 = {}; - const port2 = { - postMessage() { - if (hasPendingMessageEvent) { - throw Error('Message event already scheduled'); - } - log('Post Message'); - hasPendingMessageEvent = true; - }, - }; - global.MessageChannel = function MessageChannel() { - this.port1 = port1; - this.port2 = port2; + // Unused: we expect setImmediate to be preferred. + window.MessageChannel = function() {}; + + let pendingSetImmediateCallback = null; + window.setImmediate = function(cb) { + if (pendingSetImmediateCallback) { + throw Error('Message event already scheduled'); + } + log('Set Immediate'); + pendingSetImmediateCallback = cb; }; function ensureLogIsEmpty() { @@ -109,15 +104,15 @@ describe('SchedulerBrowser', () => { function advanceTime(ms) { currentTime += ms; } - function fireMessageEvent() { + function fireSetImmediate() { ensureLogIsEmpty(); - if (!hasPendingMessageEvent) { - throw Error('No message event was scheduled'); + if (!pendingSetImmediateCallback) { + throw Error('No setImmediate was scheduled'); } - hasPendingMessageEvent = false; - const onMessage = port1.onmessage; - log('Message Event'); - onMessage(); + const cb = pendingSetImmediateCallback; + pendingSetImmediateCallback = null; + log('setImmediate Callback'); + cb(); } function log(val) { eventLog.push(val); @@ -132,7 +127,7 @@ describe('SchedulerBrowser', () => { } return { advanceTime, - fireMessageEvent, + fireSetImmediate, log, isLogEmpty, assertLog, @@ -143,9 +138,9 @@ describe('SchedulerBrowser', () => { scheduleCallback(NormalPriority, () => { runtime.log('Task'); }); - runtime.assertLog(['Post Message']); - runtime.fireMessageEvent(); - runtime.assertLog(['Message Event', 'Task']); + runtime.assertLog(['Set Immediate']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'Task']); }); it('task with continuation', () => { @@ -159,18 +154,18 @@ describe('SchedulerBrowser', () => { runtime.log('Continuation'); }; }); - runtime.assertLog(['Post Message']); + runtime.assertLog(['Set Immediate']); - runtime.fireMessageEvent(); + runtime.fireSetImmediate(); runtime.assertLog([ - 'Message Event', + 'setImmediate Callback', 'Task', 'Yield at 5ms', - 'Post Message', + 'Set Immediate', ]); - runtime.fireMessageEvent(); - runtime.assertLog(['Message Event', 'Continuation']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'Continuation']); }); it('multiple tasks', () => { @@ -180,9 +175,9 @@ describe('SchedulerBrowser', () => { scheduleCallback(NormalPriority, () => { runtime.log('B'); }); - runtime.assertLog(['Post Message']); - runtime.fireMessageEvent(); - runtime.assertLog(['Message Event', 'A', 'B']); + runtime.assertLog(['Set Immediate']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'A', 'B']); }); it('multiple tasks with a yield in between', () => { @@ -193,23 +188,23 @@ describe('SchedulerBrowser', () => { scheduleCallback(NormalPriority, () => { runtime.log('B'); }); - runtime.assertLog(['Post Message']); - runtime.fireMessageEvent(); + runtime.assertLog(['Set Immediate']); + runtime.fireSetImmediate(); runtime.assertLog([ - 'Message Event', + 'setImmediate Callback', 'A', // Ran out of time. Post a continuation event. - 'Post Message', + 'Set Immediate', ]); - runtime.fireMessageEvent(); - runtime.assertLog(['Message Event', 'B']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'B']); }); it('cancels tasks', () => { const task = scheduleCallback(NormalPriority, () => { runtime.log('Task'); }); - runtime.assertLog(['Post Message']); + runtime.assertLog(['Set Immediate']); cancelCallback(task); runtime.assertLog([]); }); @@ -222,13 +217,13 @@ describe('SchedulerBrowser', () => { scheduleCallback(NormalPriority, () => { runtime.log('Yay'); }); - runtime.assertLog(['Post Message']); + runtime.assertLog(['Set Immediate']); - expect(() => runtime.fireMessageEvent()).toThrow('Oops!'); - runtime.assertLog(['Message Event', 'Oops!', 'Post Message']); + expect(() => runtime.fireSetImmediate()).toThrow('Oops!'); + runtime.assertLog(['setImmediate Callback', 'Oops!', 'Set Immediate']); - runtime.fireMessageEvent(); - runtime.assertLog(['Message Event', 'Yay']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'Yay']); }); it('schedule new task after queue has emptied', () => { @@ -236,16 +231,16 @@ describe('SchedulerBrowser', () => { runtime.log('A'); }); - runtime.assertLog(['Post Message']); - runtime.fireMessageEvent(); - runtime.assertLog(['Message Event', 'A']); + runtime.assertLog(['Set Immediate']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'A']); scheduleCallback(NormalPriority, () => { runtime.log('B'); }); - runtime.assertLog(['Post Message']); - runtime.fireMessageEvent(); - runtime.assertLog(['Message Event', 'B']); + runtime.assertLog(['Set Immediate']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'B']); }); it('schedule new task after a cancellation', () => { @@ -253,17 +248,17 @@ describe('SchedulerBrowser', () => { runtime.log('A'); }); - runtime.assertLog(['Post Message']); + runtime.assertLog(['Set Immediate']); cancelCallback(handle); - runtime.fireMessageEvent(); - runtime.assertLog(['Message Event']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback']); scheduleCallback(NormalPriority, () => { runtime.log('B'); }); - runtime.assertLog(['Post Message']); - runtime.fireMessageEvent(); - runtime.assertLog(['Message Event', 'B']); + runtime.assertLog(['Set Immediate']); + runtime.fireSetImmediate(); + runtime.assertLog(['setImmediate Callback', 'B']); }); }); From 4917e27e289241b101b6d87510ef4f42fe6f9b9e Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 17:36:34 +0000 Subject: [PATCH 05/10] Clarify comment --- packages/scheduler/src/forks/SchedulerDOM.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scheduler/src/forks/SchedulerDOM.js b/packages/scheduler/src/forks/SchedulerDOM.js index 9fada11a2053f..dfd32ce7bf8bb 100644 --- a/packages/scheduler/src/forks/SchedulerDOM.js +++ b/packages/scheduler/src/forks/SchedulerDOM.js @@ -88,7 +88,7 @@ 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 +const setImmediate = window.setImmediate; // IE and Node.js + jsdom if (typeof console !== 'undefined') { // TODO: Scheduler no longer requires these methods to be polyfilled. But From a4682a222b124f69a0b71052aebb607f72018014 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 19:11:15 +0000 Subject: [PATCH 06/10] Fix test to work with existing feature detection --- .../scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js index 4e782a3dd5cd3..bd3cd126bf430 100644 --- a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js +++ b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js @@ -85,7 +85,9 @@ describe('SchedulerDOMSetImmediate', () => { }; // Unused: we expect setImmediate to be preferred. - window.MessageChannel = function() {}; + global.MessageChannel = function() { + throw Error('Should be unused'); + }; let pendingSetImmediateCallback = null; window.setImmediate = function(cb) { From 8d3f3db38b0eab2b96fc6a60edacee79fe42e4a6 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 20:19:07 +0000 Subject: [PATCH 07/10] Add flags --- packages/scheduler/src/SchedulerFeatureFlags.js | 1 + packages/scheduler/src/forks/SchedulerDOM.js | 3 ++- packages/scheduler/src/forks/SchedulerFeatureFlags.www.js | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/scheduler/src/SchedulerFeatureFlags.js b/packages/scheduler/src/SchedulerFeatureFlags.js index 848c299dd6df4..45539a6b30d33 100644 --- a/packages/scheduler/src/SchedulerFeatureFlags.js +++ b/packages/scheduler/src/SchedulerFeatureFlags.js @@ -9,3 +9,4 @@ export const enableSchedulerDebugging = false; export const enableIsInputPending = false; export const enableProfiling = __PROFILE__; +export const enableSetImmediate = true; diff --git a/packages/scheduler/src/forks/SchedulerDOM.js b/packages/scheduler/src/forks/SchedulerDOM.js index dfd32ce7bf8bb..6c7dc446c9a6a 100644 --- a/packages/scheduler/src/forks/SchedulerDOM.js +++ b/packages/scheduler/src/forks/SchedulerDOM.js @@ -11,6 +11,7 @@ import { enableSchedulerDebugging, enableProfiling, + enableSetImmediate, } from '../SchedulerFeatureFlags'; import {push, pop, peek} from '../SchedulerMinHeap'; @@ -549,7 +550,7 @@ const performWorkUntilDeadline = () => { }; let schedulePerformWorkUntilDeadline; -if (typeof setImmediate === 'function') { +if (enableSetImmediate && typeof setImmediate === 'function') { // Node.js and old IE. // There's a few reasons for why we prefer setImmediate. // diff --git a/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js b/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js index 9fd86c7f94b2e..b6daf14206439 100644 --- a/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js +++ b/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js @@ -10,6 +10,7 @@ export const { enableIsInputPending, enableSchedulerDebugging, enableProfiling: enableProfilingFeatureFlag, + enableSetImmediate, } = require('SchedulerFeatureFlags'); export const enableProfiling = __PROFILE__ && enableProfilingFeatureFlag; From dd51cb7f7833af137e675fa90d1a644ffe61c6f2 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 20:21:29 +0000 Subject: [PATCH 08/10] Disable OSS flag and skip tests --- packages/scheduler/src/SchedulerFeatureFlags.js | 5 ++++- .../scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/scheduler/src/SchedulerFeatureFlags.js b/packages/scheduler/src/SchedulerFeatureFlags.js index 45539a6b30d33..536fda1b36670 100644 --- a/packages/scheduler/src/SchedulerFeatureFlags.js +++ b/packages/scheduler/src/SchedulerFeatureFlags.js @@ -9,4 +9,7 @@ export const enableSchedulerDebugging = false; export const enableIsInputPending = false; export const enableProfiling = __PROFILE__; -export const enableSetImmediate = true; + +// TODO: enable to fix https://github.com/facebook/react/issues/20756. +// Once enabled, remove describe.skip() from SchedulerDOMSetImmediate-test. +export const enableSetImmediate = false; diff --git a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js index bd3cd126bf430..8a1d8da5a869e 100644 --- a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js +++ b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js @@ -29,7 +29,9 @@ let NormalPriority; // // This test suite mocks all browser methods used in our implementation. It // assumes as little as possible about the order and timing of events. -describe('SchedulerDOMSetImmediate', () => { + +// FIXME: temporarily skipped until we enable the flag. +describe.skip('SchedulerDOMSetImmediate', () => { beforeEach(() => { jest.resetModules(); From ae20fe9cf2e7529569599704420859356537b727 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 20:43:42 +0000 Subject: [PATCH 09/10] Use VARIANT to reenable tests --- .../scheduler/src/SchedulerFeatureFlags.js | 3 +-- .../SchedulerDOMSetImmediate-test.js | 21 +++++++++++++++---- scripts/jest/TestFlags.js | 5 +++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/scheduler/src/SchedulerFeatureFlags.js b/packages/scheduler/src/SchedulerFeatureFlags.js index 536fda1b36670..bd47c158313cd 100644 --- a/packages/scheduler/src/SchedulerFeatureFlags.js +++ b/packages/scheduler/src/SchedulerFeatureFlags.js @@ -11,5 +11,4 @@ export const enableIsInputPending = false; export const enableProfiling = __PROFILE__; // TODO: enable to fix https://github.com/facebook/react/issues/20756. -// Once enabled, remove describe.skip() from SchedulerDOMSetImmediate-test. -export const enableSetImmediate = false; +export const enableSetImmediate = __VARIANT__; diff --git a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js index 8a1d8da5a869e..aaad51f6a6dc0 100644 --- a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js +++ b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js @@ -29,9 +29,7 @@ let NormalPriority; // // This test suite mocks all browser methods used in our implementation. It // assumes as little as possible about the order and timing of events. - -// FIXME: temporarily skipped until we enable the flag. -describe.skip('SchedulerDOMSetImmediate', () => { +describe('SchedulerDOMSetImmediate', () => { beforeEach(() => { jest.resetModules(); @@ -88,7 +86,14 @@ describe.skip('SchedulerDOMSetImmediate', () => { // Unused: we expect setImmediate to be preferred. global.MessageChannel = function() { - throw Error('Should be unused'); + return { + port1: {}, + port2: { + postMessage() { + throw Error('Should be unused'); + } + } + } }; let pendingSetImmediateCallback = null; @@ -138,6 +143,7 @@ describe.skip('SchedulerDOMSetImmediate', () => { }; } + // @gate enableSchedulerSetImmediate it('task that finishes before deadline', () => { scheduleCallback(NormalPriority, () => { runtime.log('Task'); @@ -147,6 +153,7 @@ describe.skip('SchedulerDOMSetImmediate', () => { runtime.assertLog(['setImmediate Callback', 'Task']); }); + // @gate enableSchedulerSetImmediate it('task with continuation', () => { scheduleCallback(NormalPriority, () => { runtime.log('Task'); @@ -172,6 +179,7 @@ describe.skip('SchedulerDOMSetImmediate', () => { runtime.assertLog(['setImmediate Callback', 'Continuation']); }); + // @gate enableSchedulerSetImmediate it('multiple tasks', () => { scheduleCallback(NormalPriority, () => { runtime.log('A'); @@ -184,6 +192,7 @@ describe.skip('SchedulerDOMSetImmediate', () => { runtime.assertLog(['setImmediate Callback', 'A', 'B']); }); + // @gate enableSchedulerSetImmediate it('multiple tasks with a yield in between', () => { scheduleCallback(NormalPriority, () => { runtime.log('A'); @@ -204,6 +213,7 @@ describe.skip('SchedulerDOMSetImmediate', () => { runtime.assertLog(['setImmediate Callback', 'B']); }); + // @gate enableSchedulerSetImmediate it('cancels tasks', () => { const task = scheduleCallback(NormalPriority, () => { runtime.log('Task'); @@ -213,6 +223,7 @@ describe.skip('SchedulerDOMSetImmediate', () => { runtime.assertLog([]); }); + // @gate enableSchedulerSetImmediate it('throws when a task errors then continues in a new event', () => { scheduleCallback(NormalPriority, () => { runtime.log('Oops!'); @@ -230,6 +241,7 @@ describe.skip('SchedulerDOMSetImmediate', () => { runtime.assertLog(['setImmediate Callback', 'Yay']); }); + // @gate enableSchedulerSetImmediate it('schedule new task after queue has emptied', () => { scheduleCallback(NormalPriority, () => { runtime.log('A'); @@ -247,6 +259,7 @@ describe.skip('SchedulerDOMSetImmediate', () => { runtime.assertLog(['setImmediate Callback', 'B']); }); + // @gate enableSchedulerSetImmediate it('schedule new task after a cancellation', () => { const handle = scheduleCallback(NormalPriority, () => { runtime.log('A'); diff --git a/scripts/jest/TestFlags.js b/scripts/jest/TestFlags.js index d46a26fb942e9..533d3703de010 100644 --- a/scripts/jest/TestFlags.js +++ b/scripts/jest/TestFlags.js @@ -55,6 +55,7 @@ function getTestFlags() { // These are required on demand because some of our tests mutate them. We try // not to but there are exceptions. const featureFlags = require('shared/ReactFeatureFlags'); + const schedulerFeatureFlags = require('scheduler/src/SchedulerFeatureFlags'); // TODO: This is a heuristic to detect the release channel by checking a flag // that is known to only be enabled in www. What we should do instead is set @@ -89,6 +90,10 @@ function getTestFlags() { // tests, Jest doesn't expose the API correctly. Fix then remove // this override. enableCache: __EXPERIMENTAL__, + + // This is from SchedulerFeatureFlags. Needed because there's no equivalent + // of ReactFeatureFlags-www.dynamic for it. Remove when enableSetImmediate is gone. + enableSchedulerSetImmediate: schedulerFeatureFlags.enableSetImmediate, }, { get(flags, flagName) { From 267945046095fbcc3cd6b48654a0ffeeb8929927 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Feb 2021 20:52:12 +0000 Subject: [PATCH 10/10] lol --- .../src/__tests__/SchedulerDOMSetImmediate-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js index aaad51f6a6dc0..504fde8463295 100644 --- a/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js +++ b/packages/scheduler/src/__tests__/SchedulerDOMSetImmediate-test.js @@ -91,9 +91,9 @@ describe('SchedulerDOMSetImmediate', () => { port2: { postMessage() { throw Error('Should be unused'); - } - } - } + }, + }, + }; }; let pendingSetImmediateCallback = null;