From 3710c4d4f9ffb6aa07e291b822e4ec7d69ed5a32 Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Wed, 21 May 2025 13:57:35 -0400 Subject: [PATCH] Prevent errors from comment node roots with enableViewTransition (#33205) We have many cases internally where the `containerInstance` resolves to a comment node. `restoreRootViewTransitionName` is called when `enableViewTransition` is on, even without introducing a ``. So that means it can crash pages because `containerInstance.style` is `undefined` just by turning on the flag. This skips cancel/restore of root view transition name if a comment node is the root. --- .../src/client/ReactFiberConfigDOM.js | 32 +++++++++++++++++-- .../src/ReactFiberCommitWork.js | 5 ++- scripts/error-codes/codes.json | 3 +- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 5aa26b26340d5..4a3d5b00bf25a 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -1549,6 +1549,19 @@ export function cancelRootViewTransitionName(rootContainer: Container): void { rootContainer.nodeType === DOCUMENT_NODE ? (rootContainer: any).documentElement : rootContainer.ownerDocument.documentElement; + + if ( + !disableCommentsAsDOMContainers && + rootContainer.nodeType === COMMENT_NODE + ) { + if (__DEV__) { + console.warn( + 'Cannot cancel root view transition on a comment node. All view transitions will be globally scoped.', + ); + } + return; + } + if ( documentElement !== null && // $FlowFixMe[prop-missing] @@ -1593,8 +1606,16 @@ export function restoreRootViewTransitionName(rootContainer: Container): void { // clone the whole document outside of the React too. containerInstance = (rootContainer: any); } - // $FlowFixMe[prop-missing] - if (containerInstance.style.viewTransitionName === 'root') { + if ( + !disableCommentsAsDOMContainers && + containerInstance.nodeType === COMMENT_NODE + ) { + return; + } + if ( + // $FlowFixMe[prop-missing] + containerInstance.style.viewTransitionName === 'root' + ) { // If we moved the root view transition name to the container in a gesture // we need to restore it now. containerInstance.style.viewTransitionName = ''; @@ -1708,6 +1729,13 @@ export function cloneRootViewTransitionContainer( containerInstance = (rootContainer: any).body; } else if (rootContainer.nodeName === 'HTML') { containerInstance = (rootContainer.ownerDocument.body: any); + } else if ( + !disableCommentsAsDOMContainers && + rootContainer.nodeType === COMMENT_NODE + ) { + throw new Error( + 'Cannot use a startGestureTransition() with a comment node root.', + ); } else { // If the container is not the whole document, then we ideally should probably // clone the whole document outside of the React too. diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index e21271f3b3eea..89d51bb42e6ca 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -306,6 +306,7 @@ export let shouldFireAfterActiveInstanceBlur: boolean = false; let viewTransitionContextChanged: boolean = false; let inUpdateViewTransition: boolean = false; let rootViewTransitionAffected: boolean = false; +let rootViewTransitionNameCanceled: boolean = false; function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean { if (finishedWork.tag === ActivityComponent) { @@ -2737,6 +2738,7 @@ function commitAfterMutationEffectsOnFiber( switch (finishedWork.tag) { case HostRoot: { viewTransitionContextChanged = false; + rootViewTransitionNameCanceled = false; pushViewTransitionCancelableScope(); recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes); if (!viewTransitionContextChanged && !rootViewTransitionAffected) { @@ -2755,6 +2757,7 @@ function commitAfterMutationEffectsOnFiber( } // We also cancel the root itself. cancelRootViewTransitionName(root.containerInfo); + rootViewTransitionNameCanceled = true; } popViewTransitionCancelableScope(null); break; @@ -3613,7 +3616,7 @@ function commitPassiveMountOnFiber( } if (isViewTransitionEligible) { - if (supportsMutation) { + if (supportsMutation && rootViewTransitionNameCanceled) { restoreRootViewTransitionName(finishedRoot.containerInfo); } } diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index d81f7489f99f6..3c64fcc4719a2 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -544,5 +544,6 @@ "556": "Expected prepareToHydrateHostActivityInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.", "557": "Expected to have a hydrated activity instance. This error is likely caused by a bug in React. Please file an issue.", "558": "Client rendering an Activity suspended it again. This is a bug in React.", - "559": "Expected to find a host node. This is a bug in React." + "559": "Expected to find a host node. This is a bug in React.", + "560": "Cannot use a startGestureTransition() with a comment node root." }