@samitouri / QOS-React-1 / commits / 91ac1fea1a

[Fizz] Pass batch as argument to revealCompletedBoundaries (#33330)

Follow up to #33293. This solves a race condition when boundaries are added to the batch after the `startViewTransition` call. This doesn't matter yet but it will once we start assigning names before the `startViewTransition` call. A possible alternative solution might be to ensure the names are added synchronously in the event that adds to the batch. It's possible to keep adding to a batch until the snapshot has happened.

Sebastian Markbåge committed May 22, 2025 at 10:25 UTC 91ac1fea1aacf80c9eb2815956e230921b55808c
2 files changed +18 -10
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js
+2 -2
@@ -6,9 +6,9 @@ export const markShellTime =
6 export const clientRenderBoundary =
7 '$RX=function(b,c,d,e,f){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),f&&(a.cstck=f),b._reactRetry&&b._reactRetry())};';
8 export const completeBoundary =
9 - '$RB=[];$RV=function(){$RT=performance.now();var d=$RB;$RB=[];for(var a=0;a<d.length;a+=2){var b=d[a],h=d[a+1],e=b.parentNode;if(e){var f=b.previousSibling,g=0;do{if(b&&8===b.nodeType){var c=b.data;if("/$"===c||"/&"===c)if(0===g)break;else g--;else"$"!==c&&"$?"!==c&&"$~"!==c&&"$!"!==c&&"&"!==c||g++}c=b.nextSibling;e.removeChild(b);b=c}while(b);for(;h.firstChild;)e.insertBefore(h.firstChild,b);f.data="$";f._reactRetry&&f._reactRetry()}}};$RC=function(d,a){if(a=document.getElementById(a))if(a.parentNode.removeChild(a),d=document.getElementById(d))d.previousSibling.data="$~",$RB.push(d,a),2===$RB.length&&setTimeout($RV,("number"!==typeof $RT?0:$RT)+300-performance.now())};';
9 + '$RB=[];$RV=function(c){$RT=performance.now();for(var a=0;a<c.length;a+=2){var b=c[a],h=c[a+1],e=b.parentNode;if(e){var f=b.previousSibling,g=0;do{if(b&&8===b.nodeType){var d=b.data;if("/$"===d||"/&"===d)if(0===g)break;else g--;else"$"!==d&&"$?"!==d&&"$~"!==d&&"$!"!==d&&"&"!==d||g++}d=b.nextSibling;e.removeChild(b);b=d}while(b);for(;h.firstChild;)e.insertBefore(h.firstChild,b);f.data="$";f._reactRetry&&f._reactRetry()}}c.length=0};$RC=function(c,a){if(a=document.getElementById(a))if(a.parentNode.removeChild(a),c=document.getElementById(c))c.previousSibling.data="$~",$RB.push(c,a),2===$RB.length&&setTimeout($RV.bind(null,$RB),("number"!==typeof $RT?0:$RT)+300-performance.now())};';
10 export const completeBoundaryUpgradeToViewTransitions =
11 - '$RV=function(a){try{var b=document.__reactViewTransition;if(b){b.finished.then($RV,$RV);return}if(window._useVT){var c=document.__reactViewTransition=document.startViewTransition({update:a,types:[]});c.finished.finally(function(){document.__reactViewTransition===c&&(document.__reactViewTransition=null)});return}}catch(d){}a()}.bind(null,$RV);';
11 + '$RV=function(c,a){try{var d=document.__reactViewTransition;if(d){d.finished.finally($RV.bind(null,a));return}if(window._useVT){var b=document.__reactViewTransition=document.startViewTransition({update:c.bind(null,a),types:[]});b.ready.finally(function(){});b.finished.finally(function(){document.__reactViewTransition===b&&(document.__reactViewTransition=null)});$RB=[];return}}catch(e){}c(a)}.bind(null,$RV);';
12 export const completeBoundaryWithStyles =
13 '$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"))};';
14 export const completeSegment =
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js
+16 -8
@@ -18,10 +18,8 @@ const SUSPENSE_FALLBACK_START_DATA = '$!';
18 // working. Closure converts it to a dot access anyway, though, so it's not an
19 // urgent issue.
20
21 -export function revealCompletedBoundaries() {
21 +export function revealCompletedBoundaries(batch) {
22 window['$RT'] = performance.now();
23 - const batch = window['$RB'];
24 - window['$RB'] = [];
23 for (let i = 0; i < batch.length; i += 2) {
24 const suspenseIdNode = batch[i];
25 const contentNode = batch[i + 1];
@@ -79,14 +77,18 @@ export function revealCompletedBoundaries() {
77 suspenseNode['_reactRetry']();
78 }
79 }
80 + batch.length = 0;
81 }
82
84 -export function revealCompletedBoundariesWithViewTransitions(revealBoundaries) {
83 +export function revealCompletedBoundariesWithViewTransitions(
84 + revealBoundaries,
85 + batch,
86 +) {
87 try {
88 const existingTransition = document['__reactViewTransition'];
89 if (existingTransition) {
90 // Retry after the previous ViewTransition finishes.
89 - existingTransition.finished.then(window['$RV'], window['$RV']);
91 + existingTransition.finished.finally(window['$RV'].bind(null, batch));
92 return;
93 }
94 const shouldStartViewTransition = window['_useVT']; // TODO: Detect.
@@ -94,14 +96,20 @@ export function revealCompletedBoundariesWithViewTransitions(revealBoundaries) {
96 const transition = (document['__reactViewTransition'] = document[
97 'startViewTransition'
98 ]({
97 - update: revealBoundaries,
99 + update: revealBoundaries.bind(null, batch),
100 types: [], // TODO: Add a hard coded type for Suspense reveals.
101 }));
102 + transition.ready.finally(() => {
103 + // TODO
104 + });
105 transition.finished.finally(() => {
106 if (document['__reactViewTransition'] === transition) {
107 document['__reactViewTransition'] = null;
108 }
109 });
110 + // Queue any future completions into its own batch since they won't have been
111 + // snapshotted by this one.
112 + window['$RB'] = [];
113 return;
114 }
115 // Fall through to reveal.
@@ -109,7 +117,7 @@ export function revealCompletedBoundariesWithViewTransitions(revealBoundaries) {
117 // Fall through to reveal.
118 }
119 // ViewTransitions v2 not supported or no ViewTransitions found. Reveal immediately.
112 - revealBoundaries();
120 + revealBoundaries(batch);
121 }
122
123 export function clientRenderBoundary(
@@ -182,7 +190,7 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
190 // We always schedule the flush in a timer even if it's very low or negative to allow
191 // for multiple completeBoundary calls that are already queued to have a chance to
192 // make the batch.
185 - setTimeout(window['$RV'], msUntilTimeout);
193 + setTimeout(window['$RV'].bind(null, window['$RB']), msUntilTimeout);
194 }
195 }
196