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