Repository: facebook/react
Pull request: [compiler] Track refs through object expressions and property lookups

Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at bottom):
* __->__ #30821
* #30820

Summary:
This addresses the issue of the compiler being overly restrictive about refs escaping into object expressions. Rather than erroring whenever a ref flows into an object, we will now treat the object itself as a ref, and apply the same escape rules to it. Whenever we look up a property from a ref value, we now don't know whether that value is itself a ref or a ref value, so we assume it's both.

The same logic applies to ref-accessing functions--if such a function is stored in an object, we'll propagate that property to the object itself and any properties looked up from it.

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts
patch: @@ -11,7 +11,6 @@ import {
   IdentifierId,
   Place,
   SourceLocation,
-  isRefOrRefValue,
   isRefValueType,
   isUseRefType,
 } from '../HIR';

filename: packages/react-devtools-shared/src/backend/fiber/renderer.js
patch: @@ -158,12 +158,12 @@ type FiberInstance = {
   id: number,
   parent: null | DevToolsInstance, // filtered parent, including virtual
   firstChild: null | DevToolsInstance, // filtered first child, including virtual
-  previousSibling: null | DevToolsInstance, // filtered next sibling, including virtual
   nextSibling: null | DevToolsInstance, // filtered next sibling, including virtual
   flags: number, // Force Error/Suspense
   source: null | string | Error | Source, // source location of this component function, or owned child stack
   errors: null | Map<string, number>, // error messages and count
   warnings: null | Map<string, number>, // warning messages and count
+  treeBaseDuration: number, // the profiled time of the last render of this subtree
   data: Fiber, // one of a Fiber pair
 };
 
@@ -173,12 +173,12 @@ function createFiberInstance(fiber: Fiber): FiberInstance {
     id: getUID(),
     parent: null,
     firstChild: null,
-    previousSibling: null,
     nextSibling: null,
     flags: 0,
     source: null,
     errors: null,
     warnings: null,
+    treeBaseDuration: 0,
     data: fiber,
   };
 }
@@ -193,7 +193,6 @@ type VirtualInstance = {
   id: number,
   parent: null | DevToolsInstance, // filtered parent, including virtual
   firstChild: null | DevToolsInstance, // filtered first child, including virtual
-  previousSibling: null | DevToolsInstance, // filtered next sibling, including virtual
   nextSibling: null | DevToolsInstance, // filtered next sibling, including virtual
   flags: number,
   source: null | string | Error | Source, // source location of this server component, or owned child stack
@@ -202,6 +201,7 @@ type VirtualInstance = {
   // that old errors/warnings don't disappear when the instance is refreshed.
   errors: null | Map<string, number>, // error messages and count
   warnings: null | Map<string, number>, // warning messages and count
+  treeBaseDuration: number, // the profiled time of the last render of this subtree
   // The latest info for this instance. This can be updated over time and the
   // same info can appear in more than once ServerComponentInstance.
   data: ReactComponentInfo,
@@ -215,12 +215,12 @@ function createVirtualInstance(
     id: getUID(),
     parent: null,
     firstChild: null,
-    previousSibling: null,
     nextSibling: null,
     flags: 0,
     source: null,
     errors: null,
     warnings: null,
+    treeBaseDuration: 0,
     data: debugEntry,
   };
 }
@@ -1084,8 +1084,6 @@ export function attach(
         '  '.repeat(indent) + '- ' + instance.id + ' (' + name + ')',
         'parent',
         instance.parent === null ? ' ' : instance.parent.id,
-        'prev',
-        instance.previousSibling === null ? ' ' : instance.previousSibling.id,
         'next',
         instance.nextSibling === null ? ' ' : instance.nextSibling.id,
       );
@@ -1355,16 +1353,6 @@ export function attach(
     }
   }
 
-  // When profiling is supported, we store the latest tree base durations for each Fiber.
-  // This is so that we can quickly capture a snapshot of those values if profiling starts.
-  // If we didn't store these values, we'd have to crawl the tree when profiling started,
-  // and use a slow path to find each of the current Fibers.
-  const idToTreeBaseDurationMap: Map<number, number> = new Map();
-
-  // When profiling is supported, we store the latest tree base durations for each Fiber.
-  // This map enables us to filter these times by root when sending them to the frontend.
-  const idToRootMap: Map<number, number> = new Map();
-
   // When a mount or update is in progress, this value tracks the root that is being operated on.
   let currentRootID: number = -1;
 
@@ -2211,9 +2199,7 @@ export function attach(
     }
 
     if (isProfilingSupported) {
-      idToRootMap.set(id, currentRootID);
-
-      recordProfilingDurations(fiber);
+      recordProfilingDurations(fiberInstance);
     }
     return fiberInstance;
   }
@@ -2226,8 +2212,6 @@ export function attach(
 
     idToDevToolsInstanceMap.set(id, instance);
 
-    const isProfilingSupported = false; // TODO: Support Tree Base Duration Based on Children.
-
     const componentInfo = instance.data;
 
     const key =
@@ -2274,12 +2258,6 @@ export function attach(
     pushOperation(ownerID);
     pushOperation(displayNameStringID);
     pushOperation(keyStringID);
-
-    if (isProfilingSupported) {
-      idToRootMap.set(id, currentRootID);
-      // TODO: Include tree base duration of children somehow.
-      // recordProfilingDurations(...);
-    }
   }
 
   function recordUnmount(fiberInstance: FiberInstance): void {
@@ -2314,12 +2292,6 @@ export function attach(
     }
 
     untrackFiber(fiberInstance);
-
-    const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
-    if (isProfilingSupported) {
-      idToRootMap.delete(id);
-      idToTreeBaseDurationMap.delete(id);
-    }
   }
 
   // Running state of the remaining children from the previous version of this parent that
@@ -2343,30 +2315,32 @@ export function attach(
     if (previouslyReconciledSibling === null) {
       previouslyReconciledSibling = instance;
       parentInstance.firstChild = instance;
-      instance.previousSibling = null;
     } else {
       previouslyReconciledSibling.nextSibling = instance;
-      instance.previousSibling = previouslyReconciledSibling;
       previouslyReconciledSibling = instance;
     }
     instance.nextSibling = null;
   }
 
-  function moveChild(instance: DevToolsInstance): void {
-    removeChild(instance);
+  function moveChild(
+    instance: DevToolsInstance,
+    previousSibling: null | DevToolsInstance,
+  ): void {
+    removeChild(instance, previousSibling);
     insertChild(instance);
   }
 
-  function removeChild(instance: DevToolsInstance): void {
+  function removeChild(
+    instance: DevToolsInstance,
+    previousSibling: null | DevToolsInstance,
+  ): void {
     if (instance.parent === null) {
       if (remainingReconcilingChildren === instance) {
         throw new Error(
           'Remaining children should not have items with no parent',
         );
       } else if (instance.nextSibling !== null) {
         throw new Error('A deleted instance should not have next siblings');
-      } else if (instance.previousSibling !== null) {
-        throw new Error('A deleted instance should not have previous siblings');
       }
       // Already deleted.
       return;
@@ -2382,7 +2356,7 @@ export function attach(
     }
     // Remove an existing child from its current position, which we assume is in the
     // remainingReconcilingChildren set.
-    if (instance.previousSibling === null) {
+    if (previousSibling === null) {
       // We're first in the remaining set. Remove us.
       if (remainingReconcilingChildren !== instance) {
         throw new Error(
@@ -2391,13 +2365,9 @@ export function attach(
       }
       remainingReconcilingChildren = instance.nextSibling;
     } else {
-      instance.previousSibling.nextSibling = instance.nextSibling;
-    }
-    if (instance.nextSibling !== null) {
-      instance.nextSibling.previousSibling = instance.previousSibling;
+      previousSibling.nextSibling = instance.nextSibling;
     }
     instance.nextSibling = null;
-    instance.previousSibling = null;
     instance.parent = null;
   }
 
@@ -2430,6 +2400,8 @@ export function attach(
         traceNearestHostComponentUpdate,
         virtualLevel + 1,
       );
+      // Must be called after all children have been appended.
+      recordVirtualProfilingDurations(virtualInstance);
     } finally {
       reconcilingParent = stashedParent;
       previouslyReconciledSibling = stashedPrevious;
@@ -2445,12 +2417,6 @@ export function attach(
 
     const id = instance.id;
     pendingRealUnmountedIDs.push(id);
-
-    const isProfilingSupported = false; // TODO: Profiling support.
-    if (isProfilingSupported) {
-      idToRootMap.delete(id);
-      idToTreeBaseDurationMap.delete(id);
-    }
   }
 
   function mountVirtualChildrenRecursively(
@@ -2681,14 +2647,15 @@ export function attach(
     } else {
       recordVirtualUnmount(instance);
     }
-    removeChild(instance);
+    removeChild(instance, null);
   }
 
-  function recordProfilingDurations(fiber: Fiber) {
-    const id = getFiberIDThrows(fiber);
+  function recordProfilingDurations(fiberInstance: FiberInstance) {
+    const id = fiberInstance.id;
+    const fiber = fiberInstance.data;
     const {actualDuration, treeBaseDuration} = fiber;
 
-    idToTreeBaseDurationMap.set(id, treeBaseDuration || 0);
+    fiberInstance.treeBaseDuration = treeBaseDuration || 0;
 
     if (isProfiling) {
       const {alternate} = fiber;
@@ -2751,6 +2718,38 @@ export function attach(
     }
   }
 
+  function recordVirtualProfilingDurations(virtualInstance: VirtualInstance) {
+    const id = virtualInstance.id;
+
+    let treeBaseDuration = 0;
+    // Add up the base duration of the child instances. The virtual base duration
+    // will be the same as children's duration since we don't take up any render
+    // time in the virtual instance.
+    for (
+      let child = virtualInstance.firstChild;
+      child !== null;
+      child = child.nextSibling
+    ) {
+      treeBaseDuration += child.treeBaseDuration;
+    }
+
+    if (isProfiling) {
+      const previousTreeBaseDuration = virtualInstance.treeBaseDuration;
+      if (treeBaseDuration !== previousTreeBaseDuration) {
+        // Tree base duration updates are included in the operations typed array.
+        // So we have to convert them from milliseconds to microseconds so we can send them as ints.
+        const convertedTreeBaseDuration = Math.floor(
+          (treeBaseDuration || 0) * 1000,
+        );
+        pushOperation(TREE_OPERATION_UPDATE_TREE_BASE_DURATION);
+        pushOperation(id);
+        pushOperation(convertedTreeBaseDuration);
+      }
+    }
+
+    virtualInstance.treeBaseDuration = treeBaseDuration;
+  }
+
   function recordResetChildren(parentInstance: DevToolsInstance) {
     if (__DEBUG__) {
       if (
@@ -2818,6 +2817,8 @@ export function attach(
       ) {
         recordResetChildren(virtualInstance);
       }
+      // Must be called after all children have been appended.
+      recordVirtualProfilingDurations(virtualInstance);
     } finally {
       unmountRemainingChildren();
       reconcilingParent = stashedParent;
@@ -2880,8 +2881,7 @@ export function attach(
                   );
                 }
               }
-              // TODO: Find the best matching existing child based on the key if defined.
-
+              let previousSiblingOfBestMatch = null;
               let bestMatch = remainingReconcilingChildren;
               if (componentInfo.key != null) {
                 // If there is a key try to find a matching key in the set.
@@ -2893,6 +2893,7 @@ export function attach(
                   ) {
                     break;
                   }
+                  previousSiblingOfBestMatch = bestMatch;
                   bestMatch = bestMatch.nextSibling;
                 }
               }
@@ -2907,7 +2908,7 @@ export function attach(
                 // with the same name, then we claim it and reuse it for this update.
                 // Update it with the latest entry.
                 bestMatch.data = componentInfo;
-                moveChild(bestMatch);
+                moveChild(bestMatch, previousSiblingOfBestMatch);
                 previousVirtualInstance = bestMatch;
                 previousVirtualInstanceWasMount = false;
               } else {
@@ -2956,42 +2957,93 @@ export function attach(
           }
           previousVirtualInstance = null;
         }
+
         // We've reached the end of the virtual levels, but not beyond,
         // and now continue with the regular fiber.
+
+        // Do a fast pass over the remaining children to find the previous instance.
+        // TODO: This doesn't have the best O(n) for a large set of children that are
+        // reordered. Consider using a temporary map if it's not the very next one.
+        let prevChild;
         if (prevChildAtSameIndex === nextChild) {
           // This set is unchanged. We're just going through it to place all the
           // children again.
+          prevChild = nextChild;
+        } else {
+          // We don't actually need to rely on the alternate here. We could also
+          // reconcile against stateNode, key or whatever. Doesn't have to be same
+          // Fiber pair.
+          prevChild = nextChild.alternate;
+        }
+        let previousSiblingOfExistingInstance = null;
+        let existingInstance = null;
+        if (prevChild !== null) {
+          existingInstance = remainingReconcilingChildren;
+          while (existingInstance !== null) {
+            if (existingInstance.data === prevChild) {
+              break;
+            }
+            previousSiblingOfExistingInstance = existingInstance;
+            existingInstance = existingInstance.nextSibling;
+          }
+        }
+        if (existingInstance !== null) {
+          // Common case. Match in the same parent.
+          const fiberInstance: FiberInstance = (existingInstance: any); // Only matches if it's a Fiber.
+
+          // We keep track if the order of the children matches the previous order.
+          // They are always different referentially, but if the instances line up
+          // conceptually we'll want to know that.
+          if (prevChild !== prevChildAtSameIndex) {
+            shouldResetChildren = true;
+          }
+
+          // Register the new alternate in case it's not already in.
+          fiberToFiberInstanceMap.set(nextChild, fiberInstance);
+
+          // Update the Fiber so we that we always keep the current Fiber on the data.
+          fiberInstance.data = nextChild;
+          moveChild(fiberInstance, previousSiblingOfExistingInstance);
+
           if (
             updateFiberRecursively(
+              fiberInstance,
               nextChild,
-              nextChild,
+              (prevChild: any),
               traceNearestHostComponentUpdate,
             )
           ) {
-            throw new Error('Updating the same fiber should not cause reorder');
+            // If a nested tree child order changed but it can't handle its own
+            // child order invalidation (e.g. because it's filtered out like host nodes),
+            // propagate the need to reset child order upwards to this Fiber.
+            shouldResetChildren = true;
           }
-        } else if (nextChild.alternate) {
-          const prevChild = nextChild.alternate;
+        } else if (prevChild !== null && shouldFilterFiber(nextChild)) {
+          // If this Fiber should be filtered, we need to still update its children.
+          // This relies on an alternate since we don't have an Instance with the previous
+          // child on it. Ideally, the reconciliation wouldn't need previous Fibers that
+          // are filtered from the tree.
           if (
             updateFiberRecursively(
+              null,
               nextChild,
               prevChild,
               traceNearestHostComponentUpdate,
             )
           ) {
-            // If a nested tree child order changed but it can't handle its own
-            // child order invalidation (e.g. because it's filtered out like host nodes),
-            // propagate the need to reset child order upwards to this Fiber.
-            shouldResetChildren = true;
-          }
-          // However we also keep track if the order of the children matches
-          // the previous order. They are always different referentially, but
-          // if the instances line up conceptually we'll want to know that.
-          if (prevChild !== prevChildAtSameIndex) {
             shouldResetChildren = true;
           }
         } else {
+          // It's possible for a FiberInstance to be reparented when virtual parents
+          // get their sequence split or change structure with the same render result.
+          // In this case we unmount the and remount the FiberInstances.
+          // This might cause us to lose the selection but it's an edge case.
+
+          // We let the previous instance remain in the "remaining queue" it is
+          // in to be deleted at the end since it'll have no match.
+
           mountFiberRecursively(nextChild, traceNearestHostComponentUpdate);
+          // Need to mark the parent set to remount the new instance.
           shouldResetChildren = true;
         }
       }
@@ -3050,6 +3102,7 @@ export function attach(
 
   // Returns whether closest unfiltered fiber parent needs to reset its child list.
   function updateFiberRecursively(
+    fiberInstance: null | FiberInstance, // null if this should be filtered
     nextFiber: Fiber,
     prevFiber: Fiber,
     traceNearestHostComponentUpdate: boolean,
@@ -3083,34 +3136,10 @@ export function attach(
       }
     }
 
-    let fiberInstance: null | FiberInstance = null;
-    const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
-    if (shouldIncludeInTree) {
-      const entry = fiberToFiberInstanceMap.get(prevFiber);
-      if (entry !== undefined && entry.parent === reconcilingParent) {
-        // Common case. Match in the same parent.
-        fiberInstance = entry;
-        // Register the new alternate in case it's not already in.
-        fiberToFiberInstanceMap.set(nextFiber, fiberInstance);
-
-        // Update the Fiber so we that we always keep the current Fiber on the data.
-        fiberInstance.data = nextFiber;
-        moveChild(fiberInstance);
-      } else {
-        // It's possible for a FiberInstance to be reparented when virtual parents
-        // get their sequence split or change structure with the same render result.
-        // In this case we unmount the and remount the FiberInstances.
-        // This might cause us to lose the selection but it's an edge case.
-
-        // We let the previous instance remain in the "remaining queue" it is
-        // in to be deleted at the end since it'll have no match.
-
-        mountFiberRecursively(nextFiber, traceNearestHostComponentUpdate);
-
-        // Need to mark the parent set to remount the new instance.
-        return true;
-      }
-
+    const stashedParent = reconcilingParent;
+    const stashedPrevious = previouslyReconciledSibling;
+    const stashedRemaining = remainingReconcilingChildren;
+    if (fiberInstance !== null) {
       if (
         mostRecentlyInspectedElement !== null &&
         mostRecentlyInspectedElement.id === fiberInstance.id &&
@@ -3120,12 +3149,6 @@ export function attach(
         // If it is inspected again, it may need to be re-run to obtain updated hooks values.
         hasElementUpdatedSinceLastInspected = true;
       }
-    }
-
-    const stashedParent = reconcilingParent;
-    const stashedPrevious = previouslyReconciledSibling;
-    const stashedRemaining = remainingReconcilingChildren;
-    if (fiberInstance !== null) {
       // Push a new DevTools instance parent while reconciling this subtree.
       reconcilingParent = fiberInstance;
       previouslyReconciledSibling = null;
@@ -3180,7 +3203,7 @@ export function attach(
         if (
           nextFallbackChildSet != null &&
           prevFallbackChildSet != null &&
-          updateFiberRecursively(
+          updateChildrenRecursively(
             nextFallbackChildSet,
             prevFallbackChildSet,
             traceNearestHostComponentUpdate,
@@ -3265,20 +3288,18 @@ export function attach(
         }
       }
 
-      if (shouldIncludeInTree) {
+      if (fiberInstance !== null) {
         const isProfilingSupported =
           nextFiber.hasOwnProperty('treeBaseDuration');
         if (isProfilingSupported) {
-          recordProfilingDurations(nextFiber);
+          recordProfilingDurations(fiberInstance);
         }
       }
       if (shouldResetChildren) {
         // We need to crawl the subtree for closest non-filtered Fibers
         // so that we can display them in a flat children set.
-        if (shouldIncludeInTree) {
-          if (reconcilingParent !== null) {
-            recordResetChildren(reconcilingParent);
-          }
+        if (fiberInstance !== null) {
+          recordResetChildren(fiberInstance);
           // We've handled the child order change for this Fiber.
           // Since it's included, there's no need to invalidate parent child order.
           return false;
@@ -3290,7 +3311,7 @@ export function attach(
         return false;
       }
     } finally {
-      if (shouldIncludeInTree) {
+      if (fiberInstance !== null) {
         unmountRemainingChildren();
         reconcilingParent = stashedParent;
         previouslyReconciledSibling = stashedPrevious;
@@ -3480,7 +3501,7 @@ export function attach(
         mountFiberRecursively(current, false);
       } else if (wasMounted && isMounted) {
         // Update an existing root.
-        updateFiberRecursively(current, alternate, false);
+        updateFiberRecursively(rootInstance, current, alternate, false);
       } else if (wasMounted && !isMounted) {
         // Unmount an existing root.
         removeRootPseudoKey(currentRootID);
@@ -3541,7 +3562,7 @@ export function attach(
     fiberInstance: FiberInstance,
   ): $ReadOnlyArray<HostInstance> {
     const hostInstances = [];
-    const fiber = findCurrentFiberUsingSlowPathByFiberInstance(fiberInstance);
+    const fiber = fiberInstance.data;
     if (!fiber) {
       return hostInstances;
     }
@@ -3592,8 +3613,7 @@ export function attach(
         // TODO: Handle VirtualInstance.
         return null;
       }
-      const fiber =
-        findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
+      const fiber = devtoolsInstance.data;
       if (fiber === null) {
         return null;
       }
@@ -3701,208 +3721,6 @@ export function attach(
     return null;
   }
 
-  // This function is copied from React and should be kept in sync:
-  // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberTreeReflection.js
-  function assertIsMounted(fiber: Fiber) {
-    if (getNearestMountedFiber(fiber) !== fiber) {
-      throw new Error('Unable to find node on an unmounted component.');
-    }
-  }
-
-  // This function is copied from React and should be kept in sync:
-  // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberTreeReflection.js
-  function getNearestMountedFiber(fiber: Fiber): null | Fiber {
-    let node = fiber;
-    let nearestMounted: null | Fiber = fiber;
-    if (!fiber.alternate) {
-      // If there is no alternate, this might be a new tree that isn't inserted
-      // yet. If it is, then it will have a pending insertion effect on it.
-      let nextNode: Fiber = node;
-      do {
-        node = nextNode;
-        // TODO: This function, and these flags, are a leaked implementation
-        // detail. Once we start releasing DevTools in lockstep with React, we
-        // should import a function from the reconciler instead.
-        const Placement = 0b000000000000000000000000010;
-        const Hydrating = 0b000000000000001000000000000;
-        if ((node.flags & (Placement | Hydrating)) !== 0) {
-          // This is an insertion or in-progress hydration. The nearest possible
-          // mounted fiber is the parent but we need to continue to figure out
-          // if that one is still mounted.
-          nearestMounted = node.return;
-        }
-        // $FlowFixMe[incompatible-type] we bail out when we get a null
-        nextNode = node.return;
-      } while (nextNode);
-    } else {
-      while (node.return) {
-        node = node.return;
-      }
-    }
-    if (node.tag === HostRoot) {
-      // TODO: Check if this was a nested HostRoot when used with
-      // renderContainerIntoSubtree.
-      return nearestMounted;
-    }
-    // If we didn't hit the root, that means that we're in an disconnected tree
-    // that has been unmounted.
-    return null;
-  }
-
-  // This function is copied from React and should be kept in sync:
-  // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberTreeReflection.js
-  // It would be nice if we updated React to inject this function directly (vs just indirectly via findDOMNode).
-  // BEGIN copied code
-  function findCurrentFiberUsingSlowPathByFiberInstance(
-    fiberInstance: FiberInstance,
-  ): Fiber | null {
-    const fiber = fiberInstance.data;
-    const alternate = fiber.alternate;
-    if (!alternate) {
-      // If there is no alternate, then we only need to check if it is mounted.
-      const nearestMounted = getNearestMountedFiber(fiber);
-
-      if (nearestMounted === null) {
-        throw new Error('Unable to find node on an unmounted component.');
-      }
-
-      if (nearestMounted !== fiber) {
-        return null;
-      }
-      return fiber;
-    }
-    // If we have two possible branches, we'll walk backwards up to the root
-    // to see what path the root points to. On the way we may hit one of the
-    // special cases and we'll deal with them.
-    let a: Fiber = fiber;
-    let b: Fiber = alternate;
-    while (true) {
-      const parentA = a.return;
-      if (parentA === null) {
-        // We're at the root.
-        break;
-      }
-      const parentB = parentA.alternate;
-      if (parentB === null) {
-        // There is no alternate. This is an unusual case. Currently, it only
-        // happens when a Suspense component is hidden. An extra fragment fiber
-        // is inserted in between the Suspense fiber and its children. Skip
-        // over this extra fragment fiber and proceed to the next parent.
-        const nextParent = parentA.return;
-        if (nextParent !== null) {
-          a = b = nextParent;
-          continue;
-        }
-        // If there's no parent, we're at the root.
-        break;
-      }
-
-      // If both copies of the parent fiber point to the same child, we can
-      // assume that the child is current. This happens when we bailout on low
-      // priority: the bailed out fiber's child reuses the current child.
-      if (parentA.child === parentB.child) {
-        let child = parentA.child;
-        while (child) {
-          if (child === a) {
-            // We've determined that A is the current branch.
-            assertIsMounted(parentA);
-            return fiber;
-          }
-          if (child === b) {
-            // We've determined that B is the current branch.
-            assertIsMounted(parentA);
-            return alternate;
-          }
-          child = child.sibling;
-        }
-
-        // We should never have an alternate for any mounting node. So the only
-        // way this could possibly happen is if this was unmounted, if at all.
-        throw new Error('Unable to find node on an unmounted component.');
-      }
-
-      if (a.return !== b.return) {
-        // The return pointer of A and the return pointer of B point to different
-        // fibers. We assume that return pointers never criss-cross, so A must
-        // belong to the child set of A.return, and B must belong to the child
-        // set of B.return.
-        a = parentA;
-        b = parentB;
-      } else {
-        // The return pointers point to the same fiber. We'll have to use the
-        // default, slow path: scan the child sets of each parent alternate to see
-        // which child belongs to which set.
-        //
-        // Search parent A's child set
-        let didFindChild = false;
-        let child = parentA.child;
-        while (child) {
-          if (child === a) {
-            didFindChild = true;
-            a = parentA;
-            b = parentB;
-            break;
-          }
-          if (child === b) {
-            didFindChild = true;
-            b = parentA;
-            a = parentB;
-            break;
-          }
-          child = child.sibling;
-        }
-        if (!didFindChild) {
-          // Search parent B's child set
-          child = parentB.child;
-          while (child) {
-            if (child === a) {
-              didFindChild = true;
-              a = parentB;
-              b = parentA;
-              break;
-            }
-            if (child === b) {
-              didFindChild = true;
-              b = parentB;
-              a = parentA;
-              break;
-            }
-            child = child.sibling;
-          }
-
-          if (!didFindChild) {
-            throw new Error(
-              'Child was not found in either parent set. This indicates a bug ' +
-                'in React related to the return pointer. Please file an issue.',
-            );
-          }
-        }
-      }
-
-      if (a.alternate !== b) {
-        throw new Error(
-          "Return fibers should always be each others' alternates. " +
-            'This error is likely caused by a bug in React. Please file an issue.',
-        );
-      }
-    }
-
-    // If the root is not a host container, we're in a disconnected tree. I.e.
-    // unmounted.
-    if (a.tag !== HostRoot) {
-      throw new Error('Unable to find node on an unmounted component.');
-    }
-
-    if (a.stateNode.current === a) {
-      // We've determined that A is the current branch.
-      return fiber;
-    }
-    // Otherwise B has to be current branch.
-    return alternate;
-  }
-
-  // END copied code
-
   function getElementAttributeByPath(
     id: number,
     path: Array<string | number>,
@@ -4087,8 +3905,7 @@ export function attach(
       return {instance, style};
     }
 
-    const fiber =
-      findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
+    const fiber = devtoolsInstance.data;
     if (fiber !== null) {
       instance = fiber.stateNode;
 
@@ -4144,7 +3961,7 @@ export function attach(
   function inspectFiberInstanceRaw(
     fiberInstance: FiberInstance,
   ): InspectedElement | null {
-    const fiber = findCurrentFiberUsingSlowPathByFiberInstance(fiberInstance);
+    const fiber = fiberInstance.data;
     if (fiber == null) {
       return null;
     }
@@ -4904,8 +4721,7 @@ export function attach(
       // TODO: Handle VirtualInstance.
       return;
     }
-    const fiber =
-      findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
+    const fiber = devtoolsInstance.data;
     if (fiber !== null) {
       const instance = fiber.stateNode;
 
@@ -4970,8 +4786,7 @@ export function attach(
       // TODO: Handle VirtualInstance.
       return;
     }
-    const fiber =
-      findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
+    const fiber = devtoolsInstance.data;
     if (fiber !== null) {
       const instance = fiber.stateNode;
 
@@ -5046,8 +4861,7 @@ export function attach(
       // TODO: Handle VirtualInstance.
       return;
     }
-    const fiber =
-      findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
+    const fiber = devtoolsInstance.data;
     if (fiber !== null) {
       const instance = fiber.stateNode;
 
@@ -5121,8 +4935,8 @@ export function attach(
   let currentCommitProfilingMetadata: CommitProfilingData | null = null;
   let displayNamesByRootID: DisplayNamesByRootID | null = null;
   let idToContextsMap: Map<number, any> | null = null;
-  let initialTreeBaseDurationsMap: Map<number, number> | null = null;
-  let initialIDToRootMap: Map<number, number> | null = null;
+  let initialTreeBaseDurationsMap: Map<number, Array<[number, number]>> | null =
+    null;
   let isProfiling: boolean = false;
   let profilingStartTime: number = 0;
   let recordChangeDescriptions: boolean = false;
@@ -5141,24 +4955,15 @@ export function attach(
     rootToCommitProfilingMetadataMap.forEach(
       (commitProfilingMetadata, rootID) => {
         const commitData: Array<CommitDataBackend> = [];
-        const initialTreeBaseDurations: Array<[number, number]> = [];
 
         const displayName =
           (displayNamesByRootID !== null && displayNamesByRootID.get(rootID)) ||
           'Unknown';
 
-        if (initialTreeBaseDurationsMap != null) {
-          initialTreeBaseDurationsMap.forEach((treeBaseDuration, id) => {
-            if (
-              initialIDToRootMap != null &&
-              initialIDToRootMap.get(id) === rootID
-            ) {
-              // We don't need to convert milliseconds to microseconds in this case,
-              // because the profiling summary is JSON serialized.
-              initialTreeBaseDurations.push([id, treeBaseDuration]);
-            }
-          });
-        }
+        const initialTreeBaseDurations: Array<[number, number]> =
+          (initialTreeBaseDurationsMap !== null &&
+            initialTreeBaseDurationsMap.get(rootID)) ||
+          [];
 
         commitProfilingMetadata.forEach((commitProfilingData, commitIndex) => {
           const {
@@ -5245,6 +5050,22 @@ export function attach(
     };
   }
 
+  function snapshotTreeBaseDurations(
+    instance: DevToolsInstance,
+    target: Array<[number, number]>,
+  ) {
+    // We don't need to convert milliseconds to microseconds in this case,
+    // because the profiling summary is JSON serialized.
+    target.push([instance.id, instance.treeBaseDuration]);
+    for (
+      let child = instance.firstChild;
+      child !== null;
+      child = child.nextSibling
+    ) {
+      snapshotTreeBaseDurations(child, target);
+    }
+  }
+
   function startProfiling(shouldRecordChangeDescriptions: boolean) {
     if (isProfiling) {
       return;
@@ -5257,16 +5078,19 @@ export function attach(
     // since either of these may change during the profiling session
     // (e.g. when a fiber is re-rendered or when a fiber gets removed).
     displayNamesByRootID = new Map();
-    initialTreeBaseDurationsMap = new Map(idToTreeBaseDurationMap);
-    initialIDToRootMap = new Map(idToRootMap);
+    initialTreeBaseDurationsMap = new Map();
     idToContextsMap = new Map();
 
     hook.getFiberRoots(rendererID).forEach(root => {
-      const rootID = getFiberIDThrows(root.current);
+      const rootInstance = getFiberInstanceThrows(root.current);
+      const rootID = rootInstance.id;
       ((displayNamesByRootID: any): DisplayNamesByRootID).set(
         rootID,
         getDisplayNameForRoot(root.current),
       );
+      const initialTreeBaseDurations: Array<[number, number]> = [];
+      snapshotTreeBaseDurations(rootInstance, initialTreeBaseDurations);
+      (initialTreeBaseDurationsMap: any).set(rootID, initialTreeBaseDurations);
 
       if (shouldRecordChangeDescriptions) {
         // Record all contexts at the time profiling is started.

filename: packages/react-devtools-shared/src/devtools/views/Profiler/HoveredFiberInfo.js
patch: @@ -93,7 +93,7 @@ export default function HoveredFiberInfo({fiberData}: Props): React.Node {
         )}
 
         <div className={styles.Content}>
-          {renderDurationInfo || <div>Did not render.</div>}
+          {renderDurationInfo || <div>Did not client render.</div>}
 
           <WhatChanged fiberID={id} />
         </div>

filename: packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js
patch: @@ -142,7 +142,7 @@ export default function SidebarSelectedFiberInfo(): React.Node {
           </div>
         )}
         {listItems.length === 0 && (
-          <div>Did not render during this profiling session.</div>
+          <div>Did not render on the client during this profiling session.</div>
         )}
       </div>
     </Fragment>