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 `<ViewTransition />`. 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.
Jack Pope committed
May 21, 2025 at 13:57 UTC
3710c4d4f9ffb6aa07e291b822e4ec7d69ed5a32
3 files changed
+36
-4
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+30
-2
@@ -1549,6 +1549,19 @@ export function cancelRootViewTransitionName(rootContainer: Container): void {
1549
rootContainer.nodeType === DOCUMENT_NODE
1550
? (rootContainer: any).documentElement
1551
: rootContainer.ownerDocument.documentElement;
1552
+
1553
+ if (
1554
+ !disableCommentsAsDOMContainers &&
1555
+ rootContainer.nodeType === COMMENT_NODE
1556
+ ) {
1557
+ if (__DEV__) {
1558
+ console.warn(
1559
+ 'Cannot cancel root view transition on a comment node. All view transitions will be globally scoped.',
1560
+ );
1561
+ }
1562
+ return;
1563
+ }
1564
+
1565
if (
1566
documentElement !== null &&
1567
// $FlowFixMe[prop-missing]
@@ -1593,8 +1606,16 @@ export function restoreRootViewTransitionName(rootContainer: Container): void {
1606
// clone the whole document outside of the React too.
1607
containerInstance = (rootContainer: any);
1608
}
1596
- // $FlowFixMe[prop-missing]
1597
- if (containerInstance.style.viewTransitionName === 'root') {
1609
+ if (
1610
+ !disableCommentsAsDOMContainers &&
1611
+ containerInstance.nodeType === COMMENT_NODE
1612
+ ) {
1613
+ return;
1614
+ }
1615
+ if (
1616
+ // $FlowFixMe[prop-missing]
1617
+ containerInstance.style.viewTransitionName === 'root'
1618
+ ) {
1619
// If we moved the root view transition name to the container in a gesture
1620
// we need to restore it now.
1621
containerInstance.style.viewTransitionName = '';
@@ -1708,6 +1729,13 @@ export function cloneRootViewTransitionContainer(
1729
containerInstance = (rootContainer: any).body;
1730
} else if (rootContainer.nodeName === 'HTML') {
1731
containerInstance = (rootContainer.ownerDocument.body: any);
1732
+ } else if (
1733
+ !disableCommentsAsDOMContainers &&
1734
+ rootContainer.nodeType === COMMENT_NODE
1735
+ ) {
1736
+ throw new Error(
1737
+ 'Cannot use a startGestureTransition() with a comment node root.',
1738
+ );
1739
} else {
1740
// If the container is not the whole document, then we ideally should probably
1741
// clone the whole document outside of the React too.
packages/react-reconciler/src/ReactFiberCommitWork.js
+4
-1
@@ -306,6 +306,7 @@ export let shouldFireAfterActiveInstanceBlur: boolean = false;
306
let viewTransitionContextChanged: boolean = false;
307
let inUpdateViewTransition: boolean = false;
308
let rootViewTransitionAffected: boolean = false;
309
+let rootViewTransitionNameCanceled: boolean = false;
310
311
function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean {
312
if (finishedWork.tag === ActivityComponent) {
@@ -2737,6 +2738,7 @@ function commitAfterMutationEffectsOnFiber(
2738
switch (finishedWork.tag) {
2739
case HostRoot: {
2740
viewTransitionContextChanged = false;
2741
+ rootViewTransitionNameCanceled = false;
2742
pushViewTransitionCancelableScope();
2743
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2744
if (!viewTransitionContextChanged && !rootViewTransitionAffected) {
@@ -2755,6 +2757,7 @@ function commitAfterMutationEffectsOnFiber(
2757
}
2758
// We also cancel the root itself.
2759
cancelRootViewTransitionName(root.containerInfo);
2760
+ rootViewTransitionNameCanceled = true;
2761
}
2762
popViewTransitionCancelableScope(null);
2763
break;
@@ -3613,7 +3616,7 @@ function commitPassiveMountOnFiber(
3616
}
3617
3618
if (isViewTransitionEligible) {
3616
- if (supportsMutation) {
3619
+ if (supportsMutation && rootViewTransitionNameCanceled) {
3620
restoreRootViewTransitionName(finishedRoot.containerInfo);
3621
}
3622
}
scripts/error-codes/codes.json
+2
-1
@@ -544,5 +544,6 @@
544
"556": "Expected prepareToHydrateHostActivityInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.",
545
"557": "Expected to have a hydrated activity instance. This error is likely caused by a bug in React. Please file an issue.",
546
"558": "Client rendering an Activity suspended it again. This is a bug in React.",
547
- "559": "Expected to find a host node. This is a bug in React."
547
+ "559": "Expected to find a host node. This is a bug in React.",
548
+ "560": "Cannot use a startGestureTransition() with a comment node root."
549
}