From 59e1ea7fe9a989de4ead49878dfeaf8d8d0a2a6d Mon Sep 17 00:00:00 2001 From: Leo Giovanetti Date: Mon, 27 Oct 2025 22:17:11 -0300 Subject: [PATCH 1/2] Improve completeSegment for safer DOM manipulation Enhance completeSegment function to handle cases where segment or placeholder nodes may not exist, preventing potential DOM exceptions. --- .../ReactDOMFizzInstructionSetShared.js | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js index 1f27c7e0cdb75..c9e35f0493ee7 100644 --- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js @@ -572,17 +572,37 @@ export function completeBoundaryWithStyles( export function completeSegment(containerID, placeholderID) { const segmentContainer = document.getElementById(containerID); const placeholderNode = document.getElementById(placeholderID); - // We always expect both nodes to exist here because, while we might - // have navigated away from the main tree, we still expect the detached - // tree to exist. - segmentContainer.parentNode.removeChild(segmentContainer); + + // This segment may already be gone if hydration finished or the user + // navigated away before the server completed streaming this part of the + // tree. In those cases both the detached container and its placeholder + // might have been removed. We can safely bail because there is nothing + // left to reveal, and any nested boundaries would already be handled + // by their parent boundary. + if (!segmentContainer || !placeholderNode) { + return; + } + + const containerParent = segmentContainer.parentNode; + const placeholderParent = placeholderNode.parentNode; + + // If either parent is missing, the segment is no longer attached to a + // live subtree. Removing or inserting children at this point would cause + // DOM exceptions, so we skip reveal logic and allow hydration to recover. + if (!containerParent || !placeholderParent) { + return; + } + + containerParent.removeChild(segmentContainer); + while (segmentContainer.firstChild) { - placeholderNode.parentNode.insertBefore( + placeholderParent.insertBefore( segmentContainer.firstChild, - placeholderNode, + placeholderNode ); } - placeholderNode.parentNode.removeChild(placeholderNode); + + placeholderParent.removeChild(placeholderNode); } // This is the exact URL string we expect that Fizz renders if we provide a function action. From 6b1e8379ed6261e8bb5ab380b590387807e9d274 Mon Sep 17 00:00:00 2001 From: Leo Giovanetti Date: Mon, 27 Oct 2025 22:44:41 -0300 Subject: [PATCH 2/2] Autogenerated source, prettier fix --- .../ReactDOMFizzInstructionSetInlineCodeStrings.js | 2 +- .../fizz-instruction-set/ReactDOMFizzInstructionSetShared.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js index 4e7fb4b73f6fa..f1e28c4586bfc 100644 --- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js @@ -12,6 +12,6 @@ export const completeBoundaryUpgradeToViewTransitions = export const completeBoundaryWithStyles = '$RM=new Map;$RR=function(n,w,p){function u(q){this._p=null;q()}for(var r=new Map,t=document,h,b,e=t.querySelectorAll("link[data-precedence],style[data-precedence]"),v=[],k=0;b=e[k++];)"not all"===b.getAttribute("media")?v.push(b):("LINK"===b.tagName&&$RM.set(b.getAttribute("href"),b),r.set(b.dataset.precedence,h=b));e=0;b=[];var l,a;for(k=!0;;){if(k){var f=p[e++];if(!f){k=!1;e=0;continue}var c=!1,m=0;var d=f[m++];if(a=$RM.get(d)){var g=a._p;c=!0}else{a=t.createElement("link");a.href=d;a.rel=\n"stylesheet";for(a.dataset.precedence=l=f[m++];g=f[m++];)a.setAttribute(g,f[m++]);g=a._p=new Promise(function(q,x){a.onload=u.bind(a,q);a.onerror=u.bind(a,x)});$RM.set(d,a)}d=a.getAttribute("media");!g||d&&!matchMedia(d).matches||b.push(g);if(c)continue}else{a=v[e++];if(!a)break;l=a.getAttribute("data-precedence");a.removeAttribute("media")}c=r.get(l)||h;c===h&&(h=a);r.set(l,a);c?c.parentNode.insertBefore(a,c.nextSibling):(c=t.head,c.insertBefore(a,c.firstChild))}if(p=document.getElementById(n))p.previousSibling.data=\n"$~";Promise.all(b).then($RC.bind(null,n,w),$RX.bind(null,n,"CSS failed to load"))};'; export const completeSegment = - '$RS=function(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)};'; + '$RS=function(a,b){a=document.getElementById(a);b=document.getElementById(b);if(a&&b){var d=a.parentNode,c=b.parentNode;if(d&&c){for(d.removeChild(a);a.firstChild;)c.insertBefore(a.firstChild,b);c.removeChild(b)}}};'; export const formReplaying = 'addEventListener("submit",function(a){if(!a.defaultPrevented){var c=a.target,d=a.submitter,e=c.action,b=d;if(d){var f=d.getAttribute("formAction");null!=f&&(e=f,b=null)}"javascript:throw new Error(\'React form unexpectedly submitted.\')"===e&&(a.preventDefault(),b?(a=document.createElement("input"),a.name=b.name,a.value=b.value,b.parentNode.insertBefore(a,b),b=new FormData(c),a.parentNode.removeChild(a)):b=new FormData(c),a=c.ownerDocument||c,(a.$$reactFormReplay=a.$$reactFormReplay||[]).push(c,d,b))}});'; diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js index c9e35f0493ee7..16a4f7bacdb5a 100644 --- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js @@ -598,7 +598,7 @@ export function completeSegment(containerID, placeholderID) { while (segmentContainer.firstChild) { placeholderParent.insertBefore( segmentContainer.firstChild, - placeholderNode + placeholderNode, ); }