From 5e0c951b58a98feed034e2bb92f25ae6d0616855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 16 Sep 2025 10:20:40 -0400 Subject: [PATCH 1/4] Add forwards fill mode to animations in view transition fixture (#34502) It turns out that View Transitions can sometimes overshoot and then we need to ensure it fills. It can otherwise sometimes flash in Chrome. This is something users might hit as well. --- .../view-transition/src/components/Transitions.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fixtures/view-transition/src/components/Transitions.module.css b/fixtures/view-transition/src/components/Transitions.module.css index 8c7b66b446d44..0cfe870cfb6ff 100644 --- a/fixtures/view-transition/src/components/Transitions.module.css +++ b/fixtures/view-transition/src/components/Transitions.module.css @@ -56,10 +56,10 @@ } ::view-transition-new(.enter-slide-right):only-child { - animation: enter-slide-right ease-in 0.25s; + animation: enter-slide-right ease-in 0.25s forwards; } ::view-transition-old(.exit-slide-left):only-child { - animation: exit-slide-left ease-in 0.25s; + animation: exit-slide-left ease-in 0.25s forwards; } :root:active-view-transition-type(navigation-back) { From 851bad0c88cd0c2933035034109a060689d49f27 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Tue, 16 Sep 2025 18:54:52 +0200 Subject: [PATCH 2/4] [DevTools] Ignore repeated removals of the same IO (#34495) --- .../src/backend/fiber/renderer.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index eb86ffea713fa..37e1c0a87d15b 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -2902,9 +2902,22 @@ export function attach( // Let's remove it from the parent SuspenseNode. const ioInfo = asyncInfo.awaited; const suspendedBySet = parentSuspenseNode.suspendedBy.get(ioInfo); + // A boundary can await the same IO multiple times. + // We still want to error if we're trying to remove IO that isn't present on + // this boundary so we need to check if we've already removed it. + // We're assuming previousSuspendedBy is a small array so this should be faster + // than allocating and maintaining a Set. + let alreadyRemovedIO = false; + for (let j = 0; j < i; j++) { + const removedIOInfo = previousSuspendedBy[j].awaited; + if (removedIOInfo === ioInfo) { + alreadyRemovedIO = true; + break; + } + } if ( suspendedBySet === undefined || - !suspendedBySet.delete(instance) + (!alreadyRemovedIO && !suspendedBySet.delete(instance)) ) { throw new Error( 'We are cleaning up async info that was not on the parent Suspense boundary. ' + From 941cd803a7e29c5b2d88d0310ae79f2b2c8c1777 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Tue, 16 Sep 2025 19:17:28 +0200 Subject: [PATCH 3/4] [DevTools] Don't keep stale root instances we never mounted around (#34504) --- .../src/backend/fiber/renderer.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 37e1c0a87d15b..22300ec5f7f99 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -1516,7 +1516,7 @@ export function attach( currentRoot = rootInstance; unmountInstanceRecursively(rootInstance); rootToFiberInstanceMap.delete(root); - flushPendingEvents(root); + flushPendingEvents(); currentRoot = (null: any); }); @@ -1541,7 +1541,7 @@ export function attach( currentRoot = newRoot; setRootPseudoKey(currentRoot.id, root.current); mountFiberRecursively(root.current, false); - flushPendingEvents(root); + flushPendingEvents(); currentRoot = (null: any); }); @@ -2099,7 +2099,7 @@ export function attach( } } - function flushPendingEvents(root: Object): void { + function flushPendingEvents(): void { if (shouldBailoutWithPendingOperations()) { // If we aren't profiling, we can just bail out here. // No use sending an empty update over the bridge. @@ -5349,7 +5349,7 @@ export function attach( mountFiberRecursively(root.current, false); - flushPendingEvents(root); + flushPendingEvents(); needsToFlushComponentLogs = false; currentRoot = (null: any); @@ -5452,6 +5452,9 @@ export function attach( unmountInstanceRecursively(rootInstance); removeRootPseudoKey(currentRoot.id); rootToFiberInstanceMap.delete(root); + } else if (!prevWasMounted && !nextIsMounted) { + // We don't need this root anymore. + rootToFiberInstanceMap.delete(root); } if (isProfiling && isProfilingSupported) { @@ -5475,7 +5478,7 @@ export function attach( } // We're done here. - flushPendingEvents(root); + flushPendingEvents(); needsToFlushComponentLogs = false; From a51f925217e7c2a92fe4df707487acb29cb12b1e Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Tue, 16 Sep 2025 19:55:03 +0200 Subject: [PATCH 4/4] [DevTools] Only check if we previously removed IO if its removal failed (#34506) --- .../src/backend/fiber/renderer.js | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 22300ec5f7f99..33786a41877b8 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -2902,29 +2902,32 @@ export function attach( // Let's remove it from the parent SuspenseNode. const ioInfo = asyncInfo.awaited; const suspendedBySet = parentSuspenseNode.suspendedBy.get(ioInfo); - // A boundary can await the same IO multiple times. - // We still want to error if we're trying to remove IO that isn't present on - // this boundary so we need to check if we've already removed it. - // We're assuming previousSuspendedBy is a small array so this should be faster - // than allocating and maintaining a Set. - let alreadyRemovedIO = false; - for (let j = 0; j < i; j++) { - const removedIOInfo = previousSuspendedBy[j].awaited; - if (removedIOInfo === ioInfo) { - alreadyRemovedIO = true; - break; - } - } + if ( suspendedBySet === undefined || - (!alreadyRemovedIO && !suspendedBySet.delete(instance)) + !suspendedBySet.delete(instance) ) { - throw new Error( - 'We are cleaning up async info that was not on the parent Suspense boundary. ' + - 'This is a bug in React.', - ); + // A boundary can await the same IO multiple times. + // We still want to error if we're trying to remove IO that isn't present on + // this boundary so we need to check if we've already removed it. + // We're assuming previousSuspendedBy is a small array so this should be faster + // than allocating and maintaining a Set. + let alreadyRemovedIO = false; + for (let j = 0; j < i; j++) { + const removedIOInfo = previousSuspendedBy[j].awaited; + if (removedIOInfo === ioInfo) { + alreadyRemovedIO = true; + break; + } + } + if (!alreadyRemovedIO) { + throw new Error( + 'We are cleaning up async info that was not on the parent Suspense boundary. ' + + 'This is a bug in React.', + ); + } } - if (suspendedBySet.size === 0) { + if (suspendedBySet !== undefined && suspendedBySet.size === 0) { parentSuspenseNode.suspendedBy.delete(asyncInfo.awaited); } if (