@samitouri / QOS-React / commits / 3710c4d4f9

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 }