@@ -11,15 +11,23 @@ import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
1111import type { Container , SuspenseInstance } from '../client/ReactDOMHostConfig' ;
1212import type { DOMTopLevelEventType } from 'legacy-events/TopLevelEventTypes' ;
1313import type { EventSystemFlags } from 'legacy-events/EventSystemFlags' ;
14+ import type { FiberRoot } from 'react-reconciler/src/ReactFiberRoot' ;
1415
1516import {
1617 enableFlareAPI ,
1718 enableSelectiveHydration ,
1819} from 'shared/ReactFeatureFlags' ;
1920import {
21+ unstable_runWithPriority as runWithPriority ,
2022 unstable_scheduleCallback as scheduleCallback ,
2123 unstable_NormalPriority as NormalPriority ,
24+ unstable_getCurrentPriorityLevel as getCurrentPriorityLevel ,
2225} from 'scheduler' ;
26+ import {
27+ getNearestMountedFiber ,
28+ getContainerFromFiber ,
29+ getSuspenseInstanceFromFiber ,
30+ } from 'react-reconciler/reflection' ;
2331import {
2432 attemptToDispatchEvent ,
2533 trapEventForResponderEventSystem ,
@@ -28,8 +36,12 @@ import {
2836 getListeningSetForElement ,
2937 listenToTopLevel ,
3038} from './ReactBrowserEventEmitter' ;
31- import { getInstanceFromNode } from '../client/ReactDOMComponentTree' ;
39+ import {
40+ getInstanceFromNode ,
41+ getClosestInstanceFromNode ,
42+ } from '../client/ReactDOMComponentTree' ;
3243import { unsafeCastDOMTopLevelTypeToString } from 'legacy-events/TopLevelEventTypes' ;
44+ import { HostRoot , SuspenseComponent } from 'shared/ReactWorkTags' ;
3345
3446let attemptSynchronousHydration : ( fiber : Object ) => void ;
3547
@@ -49,6 +61,14 @@ export function setAttemptContinuousHydration(fn: (fiber: Object) => void) {
4961 attemptContinuousHydration = fn ;
5062}
5163
64+ let attemptHydrationAtCurrentPriority : ( fiber : Object ) => void ;
65+
66+ export function setAttemptHydrationAtCurrentPriority (
67+ fn : ( fiber : Object ) = > void ,
68+ ) {
69+ attemptHydrationAtCurrentPriority = fn ;
70+ }
71+
5272// TODO: Upgrade this definition once we're on a newer version of Flow that
5373// has this definition built-in.
5474type PointerEvent = Event & {
@@ -124,6 +144,13 @@ let queuedPointers: Map<number, QueuedReplayableEvent> = new Map();
124144let queuedPointerCaptures : Map < number , QueuedReplayableEvent > = new Map ( ) ;
125145// We could consider replaying selectionchange and touchmoves too.
126146
147+ type QueuedHydrationTarget = { |
148+ blockedOn : null | Container | SuspenseInstance ,
149+ target : Node ,
150+ priority : number ,
151+ | } ;
152+ let queuedExplicitHydrationTargets : Array < QueuedHydrationTarget > = [ ] ;
153+
127154export function hasQueuedDiscreteEvents ( ) : boolean {
128155 return queuedDiscreteEvents . length > 0 ;
129156}
@@ -422,6 +449,64 @@ export function queueIfContinuousEvent(
422449 return false ;
423450}
424451
452+ // Check if this target is unblocked. Returns true if it's unblocked.
453+ function attemptExplicitHydrationTarget (
454+ queuedTarget : QueuedHydrationTarget ,
455+ ) : void {
456+ // TODO: This function shares a lot of logic with attemptToDispatchEvent.
457+ // Try to unify them. It's a bit tricky since it would require two return
458+ // values.
459+ let targetInst = getClosestInstanceFromNode ( queuedTarget . target ) ;
460+ if ( targetInst !== null ) {
461+ let nearestMounted = getNearestMountedFiber ( targetInst ) ;
462+ if ( nearestMounted !== null ) {
463+ const tag = nearestMounted . tag ;
464+ if ( tag === SuspenseComponent ) {
465+ let instance = getSuspenseInstanceFromFiber ( nearestMounted ) ;
466+ if ( instance !== null ) {
467+ // We're blocked on hydrating this boundary.
468+ // Increase its priority.
469+ queuedTarget . blockedOn = instance ;
470+ runWithPriority ( queuedTarget . priority , ( ) => {
471+ attemptHydrationAtCurrentPriority ( nearestMounted ) ;
472+ } ) ;
473+ return ;
474+ }
475+ } else if ( tag === HostRoot ) {
476+ const root : FiberRoot = nearestMounted . stateNode ;
477+ if ( root . hydrate ) {
478+ queuedTarget . blockedOn = getContainerFromFiber ( nearestMounted ) ;
479+ // We don't currently have a way to increase the priority of
480+ // a root other than sync.
481+ return ;
482+ }
483+ }
484+ }
485+ }
486+ queuedTarget . blockedOn = null ;
487+ }
488+
489+ export function queueExplicitHydrationTarget ( target : Node ) : void {
490+ if ( enableSelectiveHydration ) {
491+ let priority = getCurrentPriorityLevel ( ) ;
492+ const queuedTarget : QueuedHydrationTarget = {
493+ blockedOn : null ,
494+ target : target ,
495+ priority : priority ,
496+ } ;
497+ let i = 0 ;
498+ for ( ; i < queuedExplicitHydrationTargets . length ; i ++ ) {
499+ if ( priority <= queuedExplicitHydrationTargets [ i ] . priority ) {
500+ break ;
501+ }
502+ }
503+ queuedExplicitHydrationTargets . splice ( i , 0 , queuedTarget ) ;
504+ if ( i === 0 ) {
505+ attemptExplicitHydrationTarget ( queuedTarget ) ;
506+ }
507+ }
508+ }
509+
425510function attemptReplayContinuousQueuedEvent (
426511 queuedEvent : QueuedReplayableEvent ,
427512) : boolean {
@@ -544,4 +629,25 @@ export function retryIfBlockedOn(
544629 scheduleCallbackIfUnblocked ( queuedEvent , unblocked ) ;
545630 queuedPointers . forEach ( unblock ) ;
546631 queuedPointerCaptures . forEach ( unblock ) ;
632+
633+ for ( let i = 0 ; i < queuedExplicitHydrationTargets . length ; i ++ ) {
634+ let queuedTarget = queuedExplicitHydrationTargets [ i ] ;
635+ if ( queuedTarget . blockedOn === unblocked ) {
636+ queuedTarget . blockedOn = null ;
637+ }
638+ }
639+
640+ while ( queuedExplicitHydrationTargets . length > 0 ) {
641+ let nextExplicitTarget = queuedExplicitHydrationTargets [ 0 ] ;
642+ if ( nextExplicitTarget . blockedOn !== null ) {
643+ // We're still blocked.
644+ break ;
645+ } else {
646+ attemptExplicitHydrationTarget ( nextExplicitTarget ) ;
647+ if ( nextExplicitTarget . blockedOn === null ) {
648+ // We're unblocked.
649+ queuedExplicitHydrationTargets . shift ( ) ;
650+ }
651+ }
652+ }
547653}
0 commit comments