@@ -696,6 +696,40 @@ const fiberToFiberInstanceMap: Map<Fiber, FiberInstance> = new Map();
696
696
// operations that should be the same whether the current and work-in-progress Fiber is used.
697
697
const idToDevToolsInstanceMap : Map < number , DevToolsInstance > = new Map ( ) ;
698
698
699
+ // Map of resource DOM nodes to all the Fibers that depend on it.
700
+ const hostResourceToFiberMap : Map < HostInstance , Set < Fiber >> = new Map ( ) ;
701
+
702
+ function aquireHostResource (
703
+ fiber : Fiber ,
704
+ resource : ?{ instance ?: HostInstance } ,
705
+ ) : void {
706
+ const hostInstance = resource && resource . instance ;
707
+ if ( hostInstance ) {
708
+ let resourceFibers = hostResourceToFiberMap . get ( hostInstance ) ;
709
+ if ( resourceFibers === undefined ) {
710
+ resourceFibers = new Set ( ) ;
711
+ hostResourceToFiberMap . set ( hostInstance , resourceFibers ) ;
712
+ }
713
+ resourceFibers . add ( fiber ) ;
714
+ }
715
+ }
716
+
717
+ function releaseHostResource (
718
+ fiber : Fiber ,
719
+ resource : ?{ instance ?: HostInstance } ,
720
+ ) : void {
721
+ const hostInstance = resource && resource . instance ;
722
+ if ( hostInstance ) {
723
+ const resourceFibers = hostResourceToFiberMap . get ( hostInstance ) ;
724
+ if ( resourceFibers !== undefined ) {
725
+ resourceFibers . delete ( fiber ) ;
726
+ if ( resourceFibers . size === 0 ) {
727
+ hostResourceToFiberMap . delete ( hostInstance ) ;
728
+ }
729
+ }
730
+ }
731
+ }
732
+
699
733
export function attach (
700
734
hook : DevToolsHook ,
701
735
rendererID : number ,
@@ -2283,6 +2317,10 @@ export function attach(
2283
2317
// because we don't want to highlight every host node inside of a newly mounted subtree.
2284
2318
}
2285
2319
2320
+ if ( fiber . tag = = = HostHoistable ) {
2321
+ aquireHostResource ( fiber , fiber . memoizedState ) ;
2322
+ }
2323
+
2286
2324
if ( fiber . tag = = = SuspenseComponent ) {
2287
2325
const isTimedOut = fiber . memoizedState !== null ;
2288
2326
if ( isTimedOut ) {
@@ -2344,8 +2382,11 @@ export function attach(
2344
2382
2345
2383
// We might meet a nested Suspense on our way.
2346
2384
const isTimedOutSuspense =
2347
- fiber . tag === ReactTypeOfWork . SuspenseComponent &&
2348
- fiber . memoizedState !== null ;
2385
+ fiber . tag === SuspenseComponent && fiber . memoizedState !== null ;
2386
+
2387
+ if ( fiber . tag === HostHoistable ) {
2388
+ releaseHostResource ( fiber , fiber . memoizedState ) ;
2389
+ }
2349
2390
2350
2391
let child = fiber . child ;
2351
2392
if ( isTimedOutSuspense ) {
@@ -2621,6 +2662,12 @@ export function attach(
2621
2662
const newParentInstance = shouldIncludeInTree
2622
2663
? fiberInstance
2623
2664
: parentInstance;
2665
+
2666
+ if (nextFiber.tag === HostHoistable) {
2667
+ releaseHostResource ( prevFiber , prevFiber . memoizedState ) ;
2668
+ aquireHostResource ( nextFiber , nextFiber . memoizedState ) ;
2669
+ }
2670
+
2624
2671
const isSuspense = nextFiber.tag === SuspenseComponent;
2625
2672
let shouldResetChildren = false;
2626
2673
// The behavior of timed-out Suspense trees is unique.
@@ -3070,9 +3117,55 @@ export function attach(
3070
3117
function getNearestMountedHostInstance(
3071
3118
hostInstance: HostInstance,
3072
3119
): null | HostInstance {
3073
- const mountedHostInstance = renderer . findFiberByHostInstance ( hostInstance ) ;
3074
- if ( mountedHostInstance != null ) {
3075
- return mountedHostInstance . stateNode;
3120
+ const mountedFiber = renderer . findFiberByHostInstance ( hostInstance ) ;
3121
+ if ( mountedFiber != null ) {
3122
+ if ( mountedFiber . stateNode !== hostInstance ) {
3123
+ // If it's not a perfect match the specific one might be a resource.
3124
+ // We don't need to look at any parents because host resources don't have
3125
+ // children so it won't be in any parent if it's not this one.
3126
+ if ( hostResourceToFiberMap . has ( hostInstance ) ) {
3127
+ return hostInstance ;
3128
+ }
3129
+ }
3130
+ return mountedFiber . stateNode ;
3131
+ }
3132
+ if ( hostResourceToFiberMap . has ( hostInstance ) ) {
3133
+ return hostInstance ;
3134
+ }
3135
+ return null;
3136
+ }
3137
+
3138
+ function findNearestUnfilteredElementID ( searchFiber : Fiber ) {
3139
+ let fiber : null | Fiber = searchFiber ;
3140
+ while ( fiber !== null ) {
3141
+ const fiberInstance = getFiberInstanceUnsafe ( fiber ) ;
3142
+ if ( fiberInstance !== null ) {
3143
+ // TODO: Ideally we would not have any filtered FiberInstances which
3144
+ // would make this logic much simpler. Unfortunately, we sometimes
3145
+ // eagerly add to the map and some times don't eagerly clean it up.
3146
+ // TODO: If the fiber is filtered, the FiberInstance wouldn't really
3147
+ // exist which would mean that we also don't have a way to get to the
3148
+ // VirtualInstances.
3149
+ if ( ! shouldFilterFiber ( fiberInstance . data ) ) {
3150
+ return fiberInstance . id ;
3151
+ }
3152
+ // We couldn't use this Fiber but we might have a VirtualInstance
3153
+ // that is the nearest unfiltered instance.
3154
+ let parentInstance = fiberInstance . parent ;
3155
+ while ( parentInstance !== null ) {
3156
+ if ( parentInstance . kind === FIBER_INSTANCE ) {
3157
+ // If we find a parent Fiber, it might not be the nearest parent
3158
+ // so we break out and continue walking the Fiber tree instead.
3159
+ break ;
3160
+ } else {
3161
+ if ( ! shouldFilterVirtual ( parentInstance . data ) ) {
3162
+ return parentInstance . id ;
3163
+ }
3164
+ }
3165
+ parentInstance = parentInstance . parent ;
3166
+ }
3167
+ }
3168
+ fiber = fiber . return ;
3076
3169
}
3077
3170
return null ;
3078
3171
}
@@ -3081,42 +3174,25 @@ export function attach(
3081
3174
hostInstance : HostInstance ,
3082
3175
findNearestUnfilteredAncestor : boolean = false ,
3083
3176
) : number | null {
3084
- let fiber = renderer . findFiberByHostInstance ( hostInstance ) ;
3177
+ const resourceFibers = hostResourceToFiberMap . get ( hostInstance ) ;
3178
+ if ( resourceFibers !== undefined ) {
3179
+ // This is a resource. Find the first unfiltered instance.
3180
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
3181
+ for ( const resourceFiber of resourceFibers ) {
3182
+ const elementID = findNearestUnfilteredElementID ( resourceFiber ) ;
3183
+ if ( elementID !== null ) {
3184
+ return elementID ;
3185
+ }
3186
+ }
3187
+ // If we don't find one, fallthrough to select the parent instead.
3188
+ }
3189
+ const fiber = renderer . findFiberByHostInstance ( hostInstance ) ;
3085
3190
if ( fiber != null ) {
3086
3191
if ( ! findNearestUnfilteredAncestor ) {
3087
3192
// TODO: Remove this option. It's not used.
3088
3193
return getFiberIDThrows ( fiber ) ;
3089
3194
}
3090
- while ( fiber !== null ) {
3091
- const fiberInstance = getFiberInstanceUnsafe ( fiber ) ;
3092
- if ( fiberInstance !== null ) {
3093
- // TODO: Ideally we would not have any filtered FiberInstances which
3094
- // would make this logic much simpler. Unfortunately, we sometimes
3095
- // eagerly add to the map and some times don't eagerly clean it up.
3096
- // TODO: If the fiber is filtered, the FiberInstance wouldn't really
3097
- // exist which would mean that we also don't have a way to get to the
3098
- // VirtualInstances.
3099
- if ( ! shouldFilterFiber ( fiberInstance . data ) ) {
3100
- return fiberInstance . id ;
3101
- }
3102
- // We couldn't use this Fiber but we might have a VirtualInstance
3103
- // that is the nearest unfiltered instance.
3104
- let parentInstance = fiberInstance . parent ;
3105
- while ( parentInstance !== null ) {
3106
- if ( parentInstance . kind === FIBER_INSTANCE ) {
3107
- // If we find a parent Fiber, it might not be the nearest parent
3108
- // so we break out and continue walking the Fiber tree instead.
3109
- break ;
3110
- } else {
3111
- if ( ! shouldFilterVirtual ( parentInstance . data ) ) {
3112
- return parentInstance . id ;
3113
- }
3114
- }
3115
- parentInstance = parentInstance . parent ;
3116
- }
3117
- }
3118
- fiber = fiber . return ;
3119
- }
3195
+ return findNearestUnfilteredElementID ( fiber ) ;
3120
3196
}
3121
3197
return null ;
3122
3198
}
0 commit comments