diff --git a/fixtures/scheduler/index.html b/fixtures/scheduler/index.html index 5da9d66163010..4bc57fbd9a7cf 100644 --- a/fixtures/scheduler/index.html +++ b/fixtures/scheduler/index.html @@ -91,19 +91,8 @@

Tests:

If the counter advanced while you were away from this tab, it's correct.
  • -

    Can pause execution, dump scheduled callbacks, and continue where it left off

    - -
    Click the button above, press "continue" to finish the test after it pauses:
    - -
    Expected:
    -
    -
    -
    -------------------------------------------------
    -
    If the test didn't progress until you hit "continue" and
    -
    you see the same above and below afterwards it's correct. -
    -------------------------------------------------
    -
    Actual:
    -
    +

    Test Eight Removed

    +

    Test 8 was removed because it was testing a feature that was removed from the scheduler.

  • Can force a specific framerate

    @@ -156,9 +145,6 @@

    Tests:

    unstable_scheduleCallback: scheduleCallback, unstable_cancelCallback: cancelCallback, unstable_now: now, - unstable_getFirstCallbackNode: getFirstCallbackNode, - unstable_pauseExecution: pauseExecution, - unstable_continueExecution: continueExecution, unstable_forceFrameRate: forceFrameRate, unstable_shouldYield: shouldYield, unstable_NormalPriority: NormalPriority, @@ -587,50 +573,6 @@

    Tests:

    scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback); } -function runTestEight() { - // Test 8 - // Pauses execution, dumps the queue, and continues execution - clearTestResult(8); - - function countNodesInStack(firstCallbackNode) { - var node = firstCallbackNode; - var count = 0; - if (node !== null) { - do { - count = count + 1; - node = node.next; - } while (node !== firstCallbackNode); - } - return count; - } - - scheduleCallback(NormalPriority, () => { - - // size should be 0 - updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`); - updateTestResult(8, 'Pausing... press continue to resume.'); - pauseExecution(); - - scheduleCallback(NormalPriority, function () { - updateTestResult(8, 'Finishing...'); - displayTestResult(8); - }) - scheduleCallback(NormalPriority, function () { - updateTestResult(8, 'Done!'); - displayTestResult(8); - checkTestResult(8); - }) - - // new size should be 2 now - updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`); - displayTestResult(8); - }); -} - -function continueTestEight() { - continueExecution(); -} - function runTestNine() { clearTestResult(9); // We have this to make sure that the thing that goes right after it can get a full frame diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 1c673bf2543a0..5cef2d28437b0 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -106,17 +106,19 @@ ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = } if (__DEV__) { - if (typeof arguments[1] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[1] === 'function') { console.error( 'does not support the second callback argument. ' + 'To execute a side effect after rendering, declare it in a component body with useEffect().', ); - } else if (isValidContainer(arguments[1])) { + } else if (isValidContainer(args[1])) { console.error( 'You passed a container to the second argument of root.render(...). ' + "You don't need to pass it again since you already passed it to create the root.", ); - } else if (typeof arguments[1] !== 'undefined') { + } else if (typeof args[1] !== 'undefined') { console.error( 'You passed a second argument to root.render(...) but it only accepts ' + 'one argument.', @@ -131,7 +133,9 @@ ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount = // $FlowFixMe[missing-this-annot] function (): void { if (__DEV__) { - if (typeof arguments[0] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[0] === 'function') { console.error( 'does not support a callback argument. ' + 'To execute a side effect after rendering, declare it in a component body with useEffect().', diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 12b110b16d3c9..99dbea044690f 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -3626,7 +3626,9 @@ function dispatchReducerAction( action: A, ): void { if (__DEV__) { - if (typeof arguments[3] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[3] === 'function') { console.error( "State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + @@ -3666,7 +3668,9 @@ function dispatchSetState( action: A, ): void { if (__DEV__) { - if (typeof arguments[3] === 'function') { + // using a reference to `arguments` bails out of GCC optimizations which affect function arity + const args = arguments; + if (typeof args[3] === 'function') { console.error( "State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 42ffdf1ef0d2a..5a27f006179ba 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -3810,11 +3810,11 @@ function forwardDebugInfo( debugInfo: ReactDebugInfo, ) { for (let i = 0; i < debugInfo.length; i++) { - request.pendingChunks++; if (typeof debugInfo[i].time === 'number') { // When forwarding time we need to ensure to convert it to the time space of the payload. emitTimingChunk(request, id, debugInfo[i].time); } else { + request.pendingChunks++; if (typeof debugInfo[i].name === 'string') { // We outline this model eagerly so that we can refer to by reference as an owner. // If we had a smarter way to dedupe we might not have to do this if there ends up diff --git a/packages/react/src/__tests__/React-hooks-arity.js b/packages/react/src/__tests__/React-hooks-arity.js new file mode 100644 index 0000000000000..0ba63428d3989 --- /dev/null +++ b/packages/react/src/__tests__/React-hooks-arity.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) Meta Platforms, Inc. and 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 + */ + +'use strict'; + +let React; +let ReactNoop; + +describe('arity', () => { + beforeEach(() => { + jest.resetModules(); + + React = require('react'); + ReactNoop = require('react-noop-renderer'); + }); + + it("ensure useState setter's arity is correct", () => { + function Component() { + const [, setState] = React.useState(() => 'Halo!'); + + expect(setState.length).toBe(1); + return null; + } + + ReactNoop.render(); + }); + + it("ensure useReducer setter's arity is correct", () => { + function Component() { + const [, dispatch] = React.useReducer(() => 'Halo!'); + + expect(dispatch.length).toBe(1); + return null; + } + + ReactNoop.render(); + }); +}); diff --git a/packages/react/src/__tests__/ReactStrictMode-test.internal.js b/packages/react/src/__tests__/ReactStrictMode-test.internal.js index ed9ee70279648..2867e06483792 100644 --- a/packages/react/src/__tests__/ReactStrictMode-test.internal.js +++ b/packages/react/src/__tests__/ReactStrictMode-test.internal.js @@ -179,6 +179,40 @@ describe('ReactStrictMode', () => { 'B: useEffect mount', ]); }); + + it('should support nested strict mode on initial mount', async () => { + function Wrapper({children}) { + return children; + } + await act(() => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + root.render( + + + + , + + , + , + ); + }); + + expect(log).toEqual([ + 'A: render', + 'B: render', + 'B: render', + 'A: useLayoutEffect mount', + 'B: useLayoutEffect mount', + 'A: useEffect mount', + 'B: useEffect mount', + // TODO: this is currently broken + // 'B: useLayoutEffect unmount', + // 'B: useEffect unmount', + // 'B: useLayoutEffect mount', + // 'B: useEffect mount', + ]); + }); } }); }); diff --git a/packages/scheduler/src/SchedulerFeatureFlags.js b/packages/scheduler/src/SchedulerFeatureFlags.js index a5166f9813193..6828d0105ebb5 100644 --- a/packages/scheduler/src/SchedulerFeatureFlags.js +++ b/packages/scheduler/src/SchedulerFeatureFlags.js @@ -7,7 +7,6 @@ * @flow strict */ -export const enableSchedulerDebugging = false; export const enableProfiling = false; export const frameYieldMs = 5; diff --git a/packages/scheduler/src/forks/Scheduler.js b/packages/scheduler/src/forks/Scheduler.js index 3fe4d1720fc38..8b34f26bd0551 100644 --- a/packages/scheduler/src/forks/Scheduler.js +++ b/packages/scheduler/src/forks/Scheduler.js @@ -12,7 +12,6 @@ import type {PriorityLevel} from '../SchedulerPriorities'; import { - enableSchedulerDebugging, enableProfiling, frameYieldMs, userBlockingPriorityTimeout, @@ -83,9 +82,6 @@ var timerQueue: Array = []; // 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; @@ -193,10 +189,7 @@ function workLoop(initialTime: number) { let currentTime = initialTime; advanceTimers(currentTime); currentTask = peek(taskQueue); - while ( - currentTask !== null && - !(enableSchedulerDebugging && isSchedulerPaused) - ) { + while (currentTask !== null) { if (!enableAlwaysYieldScheduler) { if (currentTask.expirationTime > currentTime && shouldYieldToHost()) { // This currentTask hasn't expired, and we've reached the deadline. @@ -422,22 +415,6 @@ function unstable_scheduleCallback( return newTask; } -function unstable_pauseExecution() { - isSchedulerPaused = true; -} - -function unstable_continueExecution() { - isSchedulerPaused = false; - if (!isHostCallbackScheduled && !isPerformingWork) { - isHostCallbackScheduled = true; - requestHostCallback(); - } -} - -function unstable_getFirstCallbackNode(): Task | null { - return peek(taskQueue); -} - function unstable_cancelCallback(task: Task) { if (enableProfiling) { if (task.isQueued) { @@ -606,9 +583,6 @@ export { unstable_getCurrentPriorityLevel, shouldYieldToHost as unstable_shouldYield, requestPaint as unstable_requestPaint, - unstable_continueExecution, - unstable_pauseExecution, - unstable_getFirstCallbackNode, getCurrentTime as unstable_now, forceFrameRate as unstable_forceFrameRate, }; diff --git a/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js b/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js index 1d6b955eb8888..2873dee099787 100644 --- a/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js +++ b/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js @@ -12,7 +12,6 @@ const dynamicFeatureFlags = require('SchedulerFeatureFlags'); export const {enableRequestPaint} = dynamicFeatureFlags; -export const enableSchedulerDebugging = false; export const enableProfiling = __DEV__; export const frameYieldMs = 10; diff --git a/packages/scheduler/src/forks/SchedulerMock.js b/packages/scheduler/src/forks/SchedulerMock.js index 7f148c45be3e4..b638bf1eaf519 100644 --- a/packages/scheduler/src/forks/SchedulerMock.js +++ b/packages/scheduler/src/forks/SchedulerMock.js @@ -12,10 +12,7 @@ import type {PriorityLevel} from '../SchedulerPriorities'; -import { - enableSchedulerDebugging, - enableProfiling, -} from '../SchedulerFeatureFlags'; +import {enableProfiling} from '../SchedulerFeatureFlags'; import {push, pop, peek} from '../SchedulerMinHeap'; // TODO: Use symbols? @@ -72,9 +69,6 @@ var timerQueue: Array = []; // 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; @@ -195,10 +189,7 @@ function workLoop(hasTimeRemaining: boolean, initialTime: number): boolean { let currentTime = initialTime; advanceTimers(currentTime); currentTask = peek(taskQueue); - while ( - currentTask !== null && - !(enableSchedulerDebugging && isSchedulerPaused) - ) { + while (currentTask !== null) { if ( currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost()) @@ -422,22 +413,6 @@ function unstable_scheduleCallback( return newTask; } -function unstable_pauseExecution() { - isSchedulerPaused = true; -} - -function unstable_continueExecution() { - isSchedulerPaused = false; - if (!isHostCallbackScheduled && !isPerformingWork) { - isHostCallbackScheduled = true; - requestHostCallback(flushWork); - } -} - -function unstable_getFirstCallbackNode(): Task | null { - return peek(taskQueue); -} - function unstable_cancelCallback(task: Task) { if (enableProfiling) { if (task.isQueued) { @@ -679,9 +654,6 @@ export { unstable_getCurrentPriorityLevel, shouldYieldToHost as unstable_shouldYield, requestPaint as unstable_requestPaint, - unstable_continueExecution, - unstable_pauseExecution, - unstable_getFirstCallbackNode, getCurrentTime as unstable_now, forceFrameRate as unstable_forceFrameRate, unstable_flushAllWithoutAsserting, diff --git a/packages/scheduler/src/forks/SchedulerNative.js b/packages/scheduler/src/forks/SchedulerNative.js index 3832cbc69753e..33f9ae3313534 100644 --- a/packages/scheduler/src/forks/SchedulerNative.js +++ b/packages/scheduler/src/forks/SchedulerNative.js @@ -97,9 +97,6 @@ export const unstable_now: () => number | DOMHighResTimeStamp = export const unstable_next: any = throwNotImplemented; export const unstable_runWithPriority: any = throwNotImplemented; export const unstable_wrapCallback: any = throwNotImplemented; -export const unstable_continueExecution: any = throwNotImplemented; -export const unstable_pauseExecution: any = throwNotImplemented; -export const unstable_getFirstCallbackNode: any = throwNotImplemented; export const unstable_forceFrameRate: any = throwNotImplemented; export const unstable_Profiling: any = null; diff --git a/packages/scheduler/src/forks/SchedulerPostTask.js b/packages/scheduler/src/forks/SchedulerPostTask.js index a029fce0cbfcc..7465c38b92f8a 100644 --- a/packages/scheduler/src/forks/SchedulerPostTask.js +++ b/packages/scheduler/src/forks/SchedulerPostTask.js @@ -234,13 +234,5 @@ export function unstable_wrapCallback(callback: () => T): () => T { export function unstable_forceFrameRate() {} -export function unstable_pauseExecution() {} - -export function unstable_continueExecution() {} - -export function unstable_getFirstCallbackNode(): null { - return null; -} - // Currently no profiling build export const unstable_Profiling = null; diff --git a/scripts/jest/setupTests.www.js b/scripts/jest/setupTests.www.js index efee213861ca6..b0b653bb78ff9 100644 --- a/scripts/jest/setupTests.www.js +++ b/scripts/jest/setupTests.www.js @@ -34,9 +34,9 @@ jest.mock('scheduler/src/SchedulerFeatureFlags', () => { schedulerSrcPath + '/src/forks/SchedulerFeatureFlags.www' ); - // These flags are not a dynamic on www, but we still want to run - // tests in both versions. - actual.enableSchedulerDebugging = __VARIANT__; + // Add flags here that are not a dynamic on www, + // but we still want to run tests in both versions. + // return actual; }); diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index c91c298edf1be..b974ecee0d903 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -86,6 +86,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index 01f387e1d7188..6efc8838f0326 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -81,6 +81,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.esm.js b/scripts/rollup/validate/eslintrc.esm.js index 9f9938f204d21..20b5341a82ad8 100644 --- a/scripts/rollup/validate/eslintrc.esm.js +++ b/scripts/rollup/validate/eslintrc.esm.js @@ -83,6 +83,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js index 9f344c5aac3aa..9606d00b353ac 100644 --- a/scripts/rollup/validate/eslintrc.fb.js +++ b/scripts/rollup/validate/eslintrc.fb.js @@ -71,6 +71,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js index d18516f802304..941b1d1872efd 100644 --- a/scripts/rollup/validate/eslintrc.rn.js +++ b/scripts/rollup/validate/eslintrc.rn.js @@ -73,6 +73,17 @@ module.exports = { rules: { 'no-undef': 'error', 'no-shadow-restricted-names': 'error', + 'no-restricted-syntax': [ + 'error', + // TODO: Can be removed once we upgrade GCC to a version without `optimizeArgumentsArray` optimization. + { + selector: 'Identifier[name=/^JSCompiler_OptimizeArgumentsArray_/]', + message: + 'Google Closure Compiler optimized `arguments` access. ' + + 'This affects function arity. ' + + 'Create a reference to `arguments` to avoid this optimization', + }, + ], }, // These plugins aren't used, but eslint complains if an eslint-ignore comment