738
// operations that should be the same whether the current and work-in-progress Fiber is used.
739
const idToDevToolsInstanceMap: Map<number, DevToolsInstance> = new Map();
740
741
-// Map of resource DOM nodes to all the Fibers that depend on it.
742
-const hostResourceToFiberMap: Map<HostInstance, Set<Fiber>> = new Map();
741
+// Map of canonical HostInstances to the nearest parent DevToolsInstance.
742
+const publicInstanceToDevToolsInstanceMap: Map<HostInstance, DevToolsInstance> =
743
+ new Map();
744
+// Map of resource DOM nodes to all the nearest DevToolsInstances that depend on it.
745
+const hostResourceToDevToolsInstanceMap: Map<
746
+ HostInstance,
747
+ Set<DevToolsInstance>,
748
+> = new Map();
749
+
750
+function getPublicInstance(instance: HostInstance): HostInstance {
751
+ // Typically the PublicInstance and HostInstance is the same thing but not in Fabric.
752
+ // So we need to detect this and use that as the public instance.
753
+ return typeof instance === 'object' &&
754
+ instance !== null &&
755
+ typeof instance.canonical === 'object'
756
+ ? (instance.canonical: any)
757
+ : typeof instance._nativeTag === 'number'
758
+ ? instance._nativeTag
759
+ : instance;
760
+}
761
+
762
+function aquireHostInstance(
763
+ nearestInstance: DevToolsInstance,
764
+ hostInstance: HostInstance,
765
+): void {
766
+ const publicInstance = getPublicInstance(hostInstance);
767
+ publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);
768
+}
769
+
770
+function releaseHostInstance(
771
+ nearestInstance: DevToolsInstance,
772
+ hostInstance: HostInstance,
773
+): void {
774
+ const publicInstance = getPublicInstance(hostInstance);
775
+ if (
776
+ publicInstanceToDevToolsInstanceMap.get(publicInstance) === nearestInstance
777
+ ) {
778
+ publicInstanceToDevToolsInstanceMap.delete(publicInstance);
779
+ }
780
+}
781
782
function aquireHostResource(
745
- fiber: Fiber,
783
+ nearestInstance: DevToolsInstance,
784
resource: ?{instance?: HostInstance},
785
): void {
786
const hostInstance = resource && resource.instance;
787
if (hostInstance) {
750
- let resourceFibers = hostResourceToFiberMap.get(hostInstance);
751
- if (resourceFibers === undefined) {
752
- resourceFibers = new Set();
753
- hostResourceToFiberMap.set(hostInstance, resourceFibers);
788
+ const publicInstance = getPublicInstance(hostInstance);
789
+ let resourceInstances =
790
+ hostResourceToDevToolsInstanceMap.get(publicInstance);
791
+ if (resourceInstances === undefined) {
792
+ resourceInstances = new Set();
793
+ hostResourceToDevToolsInstanceMap.set(publicInstance, resourceInstances);
794
+ // Store the first match in the main map for quick access when selecting DOM node.
795
+ publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);
796
}
755
- resourceFibers.add(fiber);
797
+ resourceInstances.add(nearestInstance);
798
}
799
}
800
801
function releaseHostResource(
760
- fiber: Fiber,
802
+ nearestInstance: DevToolsInstance,
803
resource: ?{instance?: HostInstance},
804
): void {
805
const hostInstance = resource && resource.instance;
806
if (hostInstance) {
765
- const resourceFibers = hostResourceToFiberMap.get(hostInstance);
766
- if (resourceFibers !== undefined) {
767
- resourceFibers.delete(fiber);
768
- if (resourceFibers.size === 0) {
769
- hostResourceToFiberMap.delete(hostInstance);
807
+ const publicInstance = getPublicInstance(hostInstance);
808
+ const resourceInstances =
809
+ hostResourceToDevToolsInstanceMap.get(publicInstance);
810
+ if (resourceInstances !== undefined) {
811
+ resourceInstances.delete(nearestInstance);
812
+ if (resourceInstances.size === 0) {
813
+ hostResourceToDevToolsInstanceMap.delete(publicInstance);
814
+ publicInstanceToDevToolsInstanceMap.delete(publicInstance);
815
+ } else if (
816
+ publicInstanceToDevToolsInstanceMap.get(publicInstance) ===
817
+ nearestInstance
818
+ ) {
819
+ // This was the first one. Store the next first one in the main map for easy access.
820
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
821
+ for (const firstInstance of resourceInstances) {
822
+ publicInstanceToDevToolsInstanceMap.set(
823
+ firstInstance,
824
+ nearestInstance,
825
+ );
826
+ break;
827
+ }
828
}
829
}
830
}
1525
1526
// Removes a Fiber (and its alternate) from the Maps used to track their id.
1527
// This method should always be called when a Fiber is unmounting.
1470
- function untrackFiber(fiberInstance: FiberInstance) {
1528
+ function untrackFiber(nearestInstance: DevToolsInstance, fiber: Fiber) {
1529
if (__DEBUG__) {
1472
- debug('untrackFiber()', fiberInstance.data, null);
1473
- }
1474
-
1475
- idToDevToolsInstanceMap.delete(fiberInstance.id);
1476
-
1477
- const fiber = fiberInstance.data;
1478
-
1479
- // Restore any errors/warnings associated with this fiber to the pending
1480
- // map. I.e. treat it as before we tracked the instances. This lets us
1481
- // restore them if we remount the same Fibers later. Otherwise we rely
1482
- // on the GC of the Fibers to clean them up.
1483
- if (fiberInstance.errors !== null) {
1484
- pendingFiberToErrorsMap.set(fiber, fiberInstance.errors);
1485
- fiberInstance.errors = null;
1486
- }
1487
- if (fiberInstance.warnings !== null) {
1488
- pendingFiberToWarningsMap.set(fiber, fiberInstance.warnings);
1489
- fiberInstance.warnings = null;
1530
+ debug('untrackFiber()', fiber, null);
1531
}
1532
+ // TODO: Consider using a WeakMap instead. The only thing where that doesn't work
1533
+ // is React Native Paper which tracks tags but that support is eventually going away
1534
+ // and can use the old findFiberByHostInstance strategy.
1535
1492
- if (fiberInstance.flags & FORCE_ERROR) {
1493
- fiberInstance.flags &= ~FORCE_ERROR;
1494
- forceErrorCount--;
1495
- if (forceErrorCount === 0 && setErrorHandler != null) {
1496
- setErrorHandler(shouldErrorFiberAlwaysNull);
1497
- }
1498
- }
1499
- if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) {
1500
- fiberInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
1501
- forceFallbackCount--;
1502
- if (forceFallbackCount === 0 && setSuspenseHandler != null) {
1503
- setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
1504
- }
1536
+ if (fiber.tag === HostHoistable) {
1537
+ releaseHostResource(nearestInstance, fiber.memoizedState);
1538
+ } else if (
1539
+ fiber.tag === HostComponent ||
1540
+ fiber.tag === HostText ||
1541
+ fiber.tag === HostSingleton
1542
+ ) {
1543
+ releaseHostInstance(nearestInstance, fiber.stateNode);
1544
}
1545
1507
- if (fiberToFiberInstanceMap.get(fiber) === fiberInstance) {
1508
- fiberToFiberInstanceMap.delete(fiber);
1509
- }
1510
- const {alternate} = fiber;
1511
- if (alternate !== null) {
1512
- if (fiberToFiberInstanceMap.get(alternate) === fiberInstance) {
1513
- fiberToFiberInstanceMap.delete(alternate);
1546
+ // Recursively clean up any filtered Fibers below this one as well since
1547
+ // we won't recordUnmount on those.
1548
+ for (let child = fiber.child; child !== null; child = child.sibling) {
1549
+ if (shouldFilterFiber(child)) {
1550
+ untrackFiber(nearestInstance, child);
1551
}
1552
}
1553
}
2392
pendingRealUnmountedIDs.push(id);
2393
}
2394
2358
- untrackFiber(fiberInstance);
2395
+ idToDevToolsInstanceMap.delete(fiberInstance.id);
2396
+
2397
+ // Restore any errors/warnings associated with this fiber to the pending
2398
+ // map. I.e. treat it as before we tracked the instances. This lets us
2399
+ // restore them if we remount the same Fibers later. Otherwise we rely
2400
+ // on the GC of the Fibers to clean them up.
2401
+ if (fiberInstance.errors !== null) {
2402
+ pendingFiberToErrorsMap.set(fiber, fiberInstance.errors);
2403
+ fiberInstance.errors = null;
2404
+ }
2405
+ if (fiberInstance.warnings !== null) {
2406
+ pendingFiberToWarningsMap.set(fiber, fiberInstance.warnings);
2407
+ fiberInstance.warnings = null;
2408
+ }
2409
+
2410
+ if (fiberInstance.flags & FORCE_ERROR) {
2411
+ fiberInstance.flags &= ~FORCE_ERROR;
2412
+ forceErrorCount--;
2413
+ if (forceErrorCount === 0 && setErrorHandler != null) {
2414
+ setErrorHandler(shouldErrorFiberAlwaysNull);
2415
+ }
2416
+ }
2417
+ if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) {
2418
+ fiberInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
2419
+ forceFallbackCount--;
2420
+ if (forceFallbackCount === 0 && setSuspenseHandler != null) {
2421
+ setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
2422
+ }
2423
+ }
2424
+
2425
+ if (fiberToFiberInstanceMap.get(fiber) === fiberInstance) {
2426
+ fiberToFiberInstanceMap.delete(fiber);
2427
+ }
2428
+ const {alternate} = fiber;
2429
+ if (alternate !== null) {
2430
+ if (fiberToFiberInstanceMap.get(alternate) === fiberInstance) {
2431
+ fiberToFiberInstanceMap.delete(alternate);
2432
+ }
2433
+ }
2434
+
2435
+ untrackFiber(fiberInstance, fiber);
2436
}
2437
2438
// Running state of the remaining children from the previous version of this parent that
2747
}
2748
2749
if (fiber.tag === HostHoistable) {
2673
- aquireHostResource(fiber, fiber.memoizedState);
2750
+ const nearestInstance = reconcilingParent;
2751
+ if (nearestInstance === null) {
2752
+ throw new Error('Did not expect a host hoistable to be the root');
2753
+ }
2754
+ aquireHostResource(nearestInstance, fiber.memoizedState);
2755
+ } else if (
2756
+ fiber.tag === HostComponent ||
2757
+ fiber.tag === HostText ||
2758
+ fiber.tag === HostSingleton
2759
+ ) {
2760
+ const nearestInstance = reconcilingParent;
2761
+ if (nearestInstance === null) {
2762
+ throw new Error('Did not expect a host hoistable to be the root');
2763
+ }
2764
+ aquireHostInstance(nearestInstance, fiber.stateNode);
2765
}
2766
2767
if (fiber.tag === SuspenseComponent) {
3382
}
3383
try {
3384
if (nextFiber.tag === HostHoistable) {
3294
- releaseHostResource(prevFiber, prevFiber.memoizedState);
3295
- aquireHostResource(nextFiber, nextFiber.memoizedState);
3385
+ const nearestInstance = reconcilingParent;
3386
+ if (nearestInstance === null) {
3387
+ throw new Error('Did not expect a host hoistable to be the root');
3388
+ }
3389
+ releaseHostResource(nearestInstance, prevFiber.memoizedState);
3390
+ aquireHostResource(nearestInstance, nextFiber.memoizedState);
3391
}
3392
3393
const isSuspense = nextFiber.tag === SuspenseComponent;
3875
}
3876
}
3877
3783
- function getNearestMountedHostInstance(
3784
- hostInstance: HostInstance,
3785
- ): null | HostInstance {
3786
- const mountedFiber = renderer.findFiberByHostInstance(hostInstance);
3787
- if (mountedFiber != null) {
3788
- if (mountedFiber.stateNode !== hostInstance) {
3789
- // If it's not a perfect match the specific one might be a resource.
3790
- // We don't need to look at any parents because host resources don't have
3791
- // children so it won't be in any parent if it's not this one.
3792
- if (hostResourceToFiberMap.has(hostInstance)) {
3793
- return hostInstance;
3794
- }
3795
- }
3796
- return mountedFiber.stateNode;
3797
- }
3798
- if (hostResourceToFiberMap.has(hostInstance)) {
3799
- return hostInstance;
3800
- }
3801
- return null;
3802
- }
3803
-
3804
- function findNearestUnfilteredElementID(searchFiber: Fiber) {
3805
- let fiber: null | Fiber = searchFiber;
3806
- while (fiber !== null) {
3807
- const fiberInstance = getFiberInstanceUnsafe(fiber);
3808
- if (fiberInstance !== null) {
3809
- // TODO: Ideally we would not have any filtered FiberInstances which
3810
- // would make this logic much simpler. Unfortunately, we sometimes
3811
- // eagerly add to the map and some times don't eagerly clean it up.
3812
- // TODO: If the fiber is filtered, the FiberInstance wouldn't really
3813
- // exist which would mean that we also don't have a way to get to the
3814
- // VirtualInstances.
3815
- if (!shouldFilterFiber(fiberInstance.data)) {
3816
- return fiberInstance.id;
3817
- }
3818
- // We couldn't use this Fiber but we might have a VirtualInstance
3819
- // that is the nearest unfiltered instance.
3820
- const parentInstance = fiberInstance.parent;
3821
- if (
3822
- parentInstance !== null &&
3823
- parentInstance.kind === VIRTUAL_INSTANCE
3824
- ) {
3825
- // Virtual Instances only exist if they're unfiltered.
3826
- return parentInstance.id;
3827
- }
3828
- // If we find a parent Fiber, it might not be the nearest parent
3829
- // so we break out and continue walking the Fiber tree instead.
3830
- }
3831
- fiber = fiber.return;
3878
+ function getNearestMountedDOMNode(publicInstance: Element): null | Element {
3879
+ let domNode: null | Element = publicInstance;
3880
+ while (domNode && !publicInstanceToDevToolsInstanceMap.has(domNode)) {
3881
+ // $FlowFixMe: In practice this is either null or Element.
3882
+ domNode = domNode.parentNode;
3883
}
3833
- return null;
3884
+ return domNode;
3885
}
3886
3887
function getElementIDForHostInstance(
3837
- hostInstance: HostInstance,
3838
- findNearestUnfilteredAncestor: boolean = false,
3888
+ publicInstance: HostInstance,
3889
): number | null {
3840
- const resourceFibers = hostResourceToFiberMap.get(hostInstance);
3841
- if (resourceFibers !== undefined) {
3842
- // This is a resource. Find the first unfiltered instance.
3843
- // eslint-disable-next-line no-for-of-loops/no-for-of-loops
3844
- for (const resourceFiber of resourceFibers) {
3845
- const elementID = findNearestUnfilteredElementID(resourceFiber);
3846
- if (elementID !== null) {
3847
- return elementID;
3848
- }
3849
- }
3850
- // If we don't find one, fallthrough to select the parent instead.
3851
- }
3852
- const fiber = renderer.findFiberByHostInstance(hostInstance);
3853
- if (fiber != null) {
3854
- if (!findNearestUnfilteredAncestor) {
3855
- // TODO: Remove this option. It's not used.
3856
- return getFiberIDThrows(fiber);
3857
- }
3858
- return findNearestUnfilteredElementID(fiber);
3890
+ const instance = publicInstanceToDevToolsInstanceMap.get(publicInstance);
3891
+ if (instance !== undefined) {
3892
+ return instance.id;
3893
}
3894
return null;
3895
}
5822
flushInitialOperations,
5823
getBestMatchForTrackedPath,
5824
getDisplayNameForElementID,
5791
- getNearestMountedHostInstance,
5825
+ getNearestMountedDOMNode,
5826
getElementIDForHostInstance,
5827
getInstanceAndStyle,
5828
getOwnersList,