@@ -207,6 +207,9 @@ const SUSPENSE_START_DATA = '$';
207
207
const SUSPENSE_END_DATA = '/$' ;
208
208
const SUSPENSE_PENDING_START_DATA = '$?' ;
209
209
const SUSPENSE_FALLBACK_START_DATA = '$!' ;
210
+ const PREAMBLE_CONTRIBUTION_HTML = 0b001 ;
211
+ const PREAMBLE_CONTRIBUTION_BODY = 0b010 ;
212
+ const PREAMBLE_CONTRIBUTION_HEAD = 0b100 ;
210
213
const FORM_STATE_IS_MATCHING = 'F!' ;
211
214
const FORM_STATE_IS_NOT_MATCHING = 'F' ;
212
215
@@ -963,6 +966,7 @@ export function clearSuspenseBoundary(
963
966
suspenseInstance : SuspenseInstance ,
964
967
) : void {
965
968
let node : Node = suspenseInstance ;
969
+ let possiblePreambleContribution : number = 0 ;
966
970
// Delete all nodes within this suspense boundary.
967
971
// There might be nested nodes so we need to keep track of how
968
972
// deep we are and only break out when we're back on top.
@@ -973,6 +977,36 @@ export function clearSuspenseBoundary(
973
977
if ( nextNode && nextNode . nodeType === COMMENT_NODE ) {
974
978
const data = ( ( nextNode : any ) . data : string ) ;
975
979
if ( data === SUSPENSE_END_DATA ) {
980
+ if (
981
+ // represents 3 bits where at least one bit is set (1-7)
982
+ possiblePreambleContribution > 0 &&
983
+ possiblePreambleContribution < 8
984
+ ) {
985
+ const code = possiblePreambleContribution ;
986
+ // It's not normally possible to insert a comment immediately preceding Suspense boundary
987
+ // closing comment marker so we can infer that if the comment preceding starts with "1" through "7"
988
+ // then it is in fact a preamble contribution marker comment. We do this value test to avoid the case
989
+ // where the Suspense boundary is empty and the preceding comment marker is the Suspense boundary
990
+ // opening marker or the closing marker of an inner boundary. In those cases the first character won't
991
+ // have the requisite value to be interpreted as a Preamble contribution
992
+ const ownerDocument = parentInstance . ownerDocument ;
993
+ if ( code & PREAMBLE_CONTRIBUTION_HTML ) {
994
+ const documentElement : Element =
995
+ ( ownerDocument . documentElement : any ) ;
996
+ releaseSingletonInstance ( documentElement ) ;
997
+ }
998
+ if ( code & PREAMBLE_CONTRIBUTION_BODY ) {
999
+ const body : Element = ( ownerDocument . body : any ) ;
1000
+ releaseSingletonInstance ( body ) ;
1001
+ }
1002
+ if ( code & PREAMBLE_CONTRIBUTION_HEAD ) {
1003
+ const head : Element = ( ownerDocument . head : any ) ;
1004
+ releaseSingletonInstance ( head ) ;
1005
+ // We need to clear the head because this is the only singleton that can have children that
1006
+ // were part of this boundary but are not inside this boundary.
1007
+ clearHead ( head ) ;
1008
+ }
1009
+ }
976
1010
if ( depth === 0 ) {
977
1011
parentInstance . removeChild ( nextNode ) ;
978
1012
// Retry if any event replaying was blocked on this.
@@ -987,7 +1021,11 @@ export function clearSuspenseBoundary(
987
1021
data === SUSPENSE_FALLBACK_START_DATA
988
1022
) {
989
1023
depth ++ ;
1024
+ } else {
1025
+ possiblePreambleContribution = data . charCodeAt ( 0 ) - 48 ;
990
1026
}
1027
+ } else {
1028
+ possiblePreambleContribution = 0 ;
991
1029
}
992
1030
// $FlowFixMe[incompatible-type] we bail out when we get a null
993
1031
node = nextNode ;
@@ -1501,7 +1539,7 @@ function clearContainerSparingly(container: Node) {
1501
1539
case 'STYLE ': {
1502
1540
continue ;
1503
1541
}
1504
- // Stylesheet tags are retained because tehy may likely come from 3rd party scripts and extensions
1542
+ // Stylesheet tags are retained because they may likely come from 3rd party scripts and extensions
1505
1543
case 'LINK ': {
1506
1544
if ( ( ( node : any ) : HTMLLinkElement ) . rel . toLowerCase ( ) === 'stylesheet' ) {
1507
1545
continue ;
@@ -1513,6 +1551,27 @@ function clearContainerSparingly(container: Node) {
1513
1551
return ;
1514
1552
}
1515
1553
1554
+ function clearHead ( head : Element ) : void {
1555
+ let node = head . firstChild ;
1556
+ while ( node ) {
1557
+ const nextNode = node . nextSibling ;
1558
+ const nodeName = node . nodeName ;
1559
+ if (
1560
+ isMarkedHoistable ( node ) ||
1561
+ nodeName === 'SCRIPT' ||
1562
+ nodeName === 'STYLE' ||
1563
+ ( nodeName === 'LINK' &&
1564
+ ( ( node : any ) : HTMLLinkElement ) . rel . toLowerCase ( ) === 'stylesheet' )
1565
+ ) {
1566
+ // retain these nodes
1567
+ } else {
1568
+ head . removeChild ( node ) ;
1569
+ }
1570
+ node = nextNode ;
1571
+ }
1572
+ return ;
1573
+ }
1574
+
1516
1575
// Making this so we can eventually move all of the instance caching to the commit phase.
1517
1576
// Currently this is only used to associate fiber and props to instances for hydrating
1518
1577
// HostSingletons. The reason we need it here is we only want to make this binding on commit
@@ -1874,7 +1933,20 @@ export function getFirstHydratableChild(
1874
1933
export function getFirstHydratableChildWithinContainer (
1875
1934
parentContainer : Container ,
1876
1935
) : null | HydratableInstance {
1877
- return getNextHydratable ( parentContainer . firstChild ) ;
1936
+ let parentElement : Element ;
1937
+ switch ( parentContainer . nodeType ) {
1938
+ case DOCUMENT_NODE :
1939
+ parentElement = ( parentContainer : any ) . body ;
1940
+ break ;
1941
+ default : {
1942
+ if ( parentContainer . nodeName === 'HTML' ) {
1943
+ parentElement = ( parentContainer : any ) . ownerDocument . body ;
1944
+ } else {
1945
+ parentElement = ( parentContainer : any ) ;
1946
+ }
1947
+ }
1948
+ }
1949
+ return getNextHydratable ( parentElement . firstChild ) ;
1878
1950
}
1879
1951
1880
1952
export function getFirstHydratableChildWithinSuspenseInstance (
@@ -1883,6 +1955,40 @@ export function getFirstHydratableChildWithinSuspenseInstance(
1883
1955
return getNextHydratable ( parentInstance . nextSibling ) ;
1884
1956
}
1885
1957
1958
+ // If it were possible to have more than one scope singleton in a DOM tree
1959
+ // we would need to model this as a stack but since you can only have one <head>
1960
+ // and head is the only singleton that is a scope in DOM we can get away with
1961
+ // tracking this as a single value.
1962
+ let previousHydratableOnEnteringScopedSingleton : null | HydratableInstance =
1963
+ null ;
1964
+
1965
+ export function getFirstHydratableChildWithinSingleton (
1966
+ type : string ,
1967
+ singletonInstance : Instance ,
1968
+ currentHydratableInstance : null | HydratableInstance ,
1969
+ ) : null | HydratableInstance {
1970
+ if ( isSingletonScope ( type ) ) {
1971
+ previousHydratableOnEnteringScopedSingleton = currentHydratableInstance ;
1972
+ return getNextHydratable ( singletonInstance . firstChild ) ;
1973
+ } else {
1974
+ return currentHydratableInstance ;
1975
+ }
1976
+ }
1977
+
1978
+ export function getNextHydratableSiblingAfterSingleton (
1979
+ type : string ,
1980
+ currentHydratableInstance : null | HydratableInstance ,
1981
+ ) : null | HydratableInstance {
1982
+ if ( isSingletonScope ( type ) ) {
1983
+ const previousHydratableInstance =
1984
+ previousHydratableOnEnteringScopedSingleton ;
1985
+ previousHydratableOnEnteringScopedSingleton = null ;
1986
+ return previousHydratableInstance ;
1987
+ } else {
1988
+ return currentHydratableInstance ;
1989
+ }
1990
+ }
1991
+
1886
1992
export function describeHydratableInstanceForDevWarnings (
1887
1993
instance : HydratableInstance ,
1888
1994
) : string | { type : string , props : $ReadOnly < Props > } {
0 commit comments