@@ -81,6 +81,7 @@ import {
8181 completeBoundaryWithStyles as styleInsertionFunction ,
8282 completeSegment as completeSegmentFunction ,
8383 formReplaying as formReplayingRuntime ,
84+ markShellTime ,
8485} from './fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings' ;
8586
8687import { getValueDescriptorExpectingObjectForWarning } from '../shared/ReactDOMResourceValidation' ;
@@ -120,13 +121,14 @@ const ScriptStreamingFormat: StreamingFormat = 0;
120121const DataStreamingFormat : StreamingFormat = 1 ;
121122
122123export type InstructionState = number ;
123- const NothingSent /* */ = 0b000000 ;
124- const SentCompleteSegmentFunction /* */ = 0b000001 ;
125- const SentCompleteBoundaryFunction /* */ = 0b000010 ;
126- const SentClientRenderFunction /* */ = 0b000100 ;
127- const SentStyleInsertionFunction /* */ = 0b001000 ;
128- const SentFormReplayingRuntime /* */ = 0b010000 ;
129- const SentCompletedShellId /* */ = 0b100000 ;
124+ const NothingSent /* */ = 0b0000000 ;
125+ const SentCompleteSegmentFunction /* */ = 0b0000001 ;
126+ const SentCompleteBoundaryFunction /* */ = 0b0000010 ;
127+ const SentClientRenderFunction /* */ = 0b0000100 ;
128+ const SentStyleInsertionFunction /* */ = 0b0001000 ;
129+ const SentFormReplayingRuntime /* */ = 0b0010000 ;
130+ const SentCompletedShellId /* */ = 0b0100000 ;
131+ const SentMarkShellTime /* */ = 0b1000000 ;
130132
131133// Per request, global state that is not contextual to the rendering subtree.
132134// This cannot be resumed and therefore should only contain things that are
@@ -4107,21 +4109,53 @@ function writeBootstrap(
41074109 return true ;
41084110}
41094111
4112+ const shellTimeRuntimeScript = stringToPrecomputedChunk ( markShellTime ) ;
4113+
4114+ function writeShellTimeInstruction (
4115+ destination : Destination ,
4116+ resumableState : ResumableState ,
4117+ renderState : RenderState ,
4118+ ) : boolean {
4119+ if (
4120+ enableFizzExternalRuntime &&
4121+ resumableState . streamingFormat !== ScriptStreamingFormat
4122+ ) {
4123+ // External runtime always tracks the shell time in the runtime.
4124+ return true ;
4125+ }
4126+ if ( ( resumableState . instructions & SentMarkShellTime ) !== NothingSent ) {
4127+ // We already sent this instruction.
4128+ return true ;
4129+ }
4130+ resumableState . instructions |= SentMarkShellTime ;
4131+ writeChunk ( destination , renderState . startInlineScript ) ;
4132+ writeCompletedShellIdAttribute ( destination , resumableState ) ;
4133+ writeChunk ( destination , endOfStartTag ) ;
4134+ writeChunk ( destination , shellTimeRuntimeScript ) ;
4135+ return writeChunkAndReturn ( destination , endInlineScript ) ;
4136+ }
4137+
41104138export function writeCompletedRoot (
41114139 destination : Destination ,
41124140 resumableState : ResumableState ,
41134141 renderState : RenderState ,
4142+ isComplete : boolean ,
41144143) : boolean {
4144+ if ( ! isComplete ) {
4145+ // If we're not already fully complete, we might complete another boundary. If so,
4146+ // we need to track the paint time of the shell so we know how much to throttle the reveal.
4147+ writeShellTimeInstruction ( destination , resumableState , renderState ) ;
4148+ }
41154149 const preamble = renderState . preamble ;
41164150 if ( preamble . htmlChunks || preamble . headChunks ) {
41174151 // If we rendered the whole document, then we emitted a rel="expect" that needs a
41184152 // matching target. Normally we use one of the bootstrap scripts for this but if
41194153 // there are none, then we need to emit a tag to complete the shell.
41204154 if ( ( resumableState . instructions & SentCompletedShellId ) === NothingSent ) {
4121- const bootstrapChunks = renderState . bootstrapChunks ;
4122- bootstrapChunks . push ( startChunkForTag ( 'template' ) ) ;
4123- pushCompletedShellIdAttribute ( bootstrapChunks , resumableState ) ;
4124- bootstrapChunks . push ( endOfStartTag , endChunkForTag ( 'template' ) ) ;
4155+ writeChunk ( destination , startChunkForTag ( 'template' ) ) ;
4156+ writeCompletedShellIdAttribute ( destination , resumableState ) ;
4157+ writeChunk ( destination , endOfStartTag ) ;
4158+ writeChunk ( destination , endChunkForTag ( 'template' ) ) ;
41254159 }
41264160 }
41274161 return writeBootstrap ( destination , renderState ) ;
@@ -4482,14 +4516,14 @@ export function writeCompletedSegmentInstruction(
44824516 }
44834517}
44844518
4519+ const completeBoundaryScriptFunctionOnly = stringToPrecomputedChunk (
4520+ completeBoundaryFunction ,
4521+ ) ;
44854522const completeBoundaryScript1Full = stringToPrecomputedChunk (
44864523 completeBoundaryFunction + '$RC("' ,
44874524) ;
44884525const completeBoundaryScript1Partial = stringToPrecomputedChunk ( '$RC("' ) ;
44894526
4490- const completeBoundaryWithStylesScript1FullBoth = stringToPrecomputedChunk (
4491- completeBoundaryFunction + styleInsertionFunction + '$RR("' ,
4492- ) ;
44934527const completeBoundaryWithStylesScript1FullPartial = stringToPrecomputedChunk (
44944528 styleInsertionFunction + '$RR("' ,
44954529) ;
@@ -4531,19 +4565,27 @@ export function writeCompletedBoundaryInstruction(
45314565 writeChunk ( destination , renderState . startInlineScript ) ;
45324566 writeChunk ( destination , endOfStartTag ) ;
45334567 if ( requiresStyleInsertion ) {
4568+ if (
4569+ ( resumableState . instructions & SentClientRenderFunction ) ===
4570+ NothingSent
4571+ ) {
4572+ // The completeBoundaryWithStyles function depends on the client render function.
4573+ resumableState . instructions |= SentClientRenderFunction ;
4574+ writeChunk ( destination , clientRenderScriptFunctionOnly ) ;
4575+ }
45344576 if (
45354577 ( resumableState . instructions & SentCompleteBoundaryFunction ) ===
45364578 NothingSent
45374579 ) {
4538- resumableState . instructions |=
4539- SentStyleInsertionFunction | SentCompleteBoundaryFunction ;
4540- writeChunk ( destination , completeBoundaryWithStylesScript1FullBoth ) ;
4541- } else if (
4580+ // The completeBoundaryWithStyles function depends on the complete boundary function.
4581+ resumableState . instructions |= SentCompleteBoundaryFunction ;
4582+ writeChunk ( destination , completeBoundaryScriptFunctionOnly ) ;
4583+ }
4584+ if (
45424585 ( resumableState . instructions & SentStyleInsertionFunction ) ===
45434586 NothingSent
45444587 ) {
45454588 resumableState . instructions |= SentStyleInsertionFunction ;
4546-
45474589 writeChunk ( destination , completeBoundaryWithStylesScript1FullPartial ) ;
45484590 } else {
45494591 writeChunk ( destination , completeBoundaryWithStylesScript1Partial ) ;
@@ -4608,6 +4650,9 @@ export function writeCompletedBoundaryInstruction(
46084650 return writeBootstrap ( destination , renderState ) && writeMore ;
46094651}
46104652
4653+ const clientRenderScriptFunctionOnly =
4654+ stringToPrecomputedChunk ( clientRenderFunction ) ;
4655+
46114656const clientRenderScript1Full = stringToPrecomputedChunk (
46124657 clientRenderFunction + ';$RX("' ,
46134658) ;
@@ -5004,6 +5049,21 @@ function writeBlockingRenderInstruction(
50045049
50055050const completedShellIdAttributeStart = stringToPrecomputedChunk ( ' id="' ) ;
50065051
5052+ function writeCompletedShellIdAttribute (
5053+ destination : Destination ,
5054+ resumableState : ResumableState ,
5055+ ) : void {
5056+ if ( ( resumableState . instructions & SentCompletedShellId ) !== NothingSent ) {
5057+ return ;
5058+ }
5059+ resumableState . instructions |= SentCompletedShellId ;
5060+ const idPrefix = resumableState . idPrefix ;
5061+ const shellId = '\u00AB' + idPrefix + 'R\u00BB' ;
5062+ writeChunk ( destination , completedShellIdAttributeStart ) ;
5063+ writeChunk ( destination , stringToChunk ( escapeTextForBrowser ( shellId ) ) ) ;
5064+ writeChunk ( destination , attributeEnd ) ;
5065+ }
5066+
50075067function pushCompletedShellIdAttribute (
50085068 target : Array < Chunk | PrecomputedChunk > ,
50095069 resumableState : ResumableState ,
@@ -5029,15 +5089,10 @@ export function writePreambleStart(
50295089 destination : Destination ,
50305090 resumableState : ResumableState ,
50315091 renderState : RenderState ,
5032- willFlushAllSegments : boolean ,
50335092 skipExpect ?: boolean , // Used as an override by ReactFizzConfigMarkup
50345093) : void {
50355094 // This function must be called exactly once on every request
5036- if (
5037- enableFizzExternalRuntime &&
5038- ! willFlushAllSegments &&
5039- renderState . externalRuntimeScript
5040- ) {
5095+ if ( enableFizzExternalRuntime && renderState . externalRuntimeScript ) {
50415096 // If the root segment is incomplete due to suspended tasks
50425097 // (e.g. willFlushAllSegments = false) and we are using data
50435098 // streaming format, ensure the external runtime is sent.
0 commit comments