63
SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
64
TREE_OPERATION_ADD,
65
TREE_OPERATION_REMOVE,
66
- TREE_OPERATION_REMOVE_ROOT,
66
TREE_OPERATION_REORDER_CHILDREN,
67
TREE_OPERATION_SET_SUBTREE_MODE,
68
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
855
// (due to e.g. Suspense or error boundaries).
856
// onErrorOrWarning() adds Fibers and recordPendingErrorsAndWarnings() later clears them.
857
const fibersWithChangedErrorOrWarningCounts: Set<Fiber> = new Set();
859
- const pendingFiberToErrorsMap: Map<Fiber, Map<string, number>> = new Map();
860
- const pendingFiberToWarningsMap: Map<Fiber, Map<string, number>> = new Map();
858
+ const pendingFiberToErrorsMap: WeakMap<
859
+ Fiber,
860
+ Map<string, number>,
861
+ > = new WeakMap();
862
+ const pendingFiberToWarningsMap: WeakMap<
863
+ Fiber,
864
+ Map<string, number>,
865
+ > = new WeakMap();
866
867
function clearErrorsAndWarnings() {
868
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
881
882
function clearMessageCountHelper(
883
instanceID: number,
879
- pendingFiberToMessageCountMap: Map<Fiber, Map<string, number>>,
884
+ pendingFiberToMessageCountMap: WeakMap<Fiber, Map<string, number>>,
885
forError: boolean,
886
) {
887
const devtoolsInstance = idToDevToolsInstanceMap.get(instanceID);
907
if (devtoolsInstance.kind === FIBER_INSTANCE) {
908
const fiber = devtoolsInstance.data;
909
// Throw out any pending changes.
905
- pendingFiberToErrorsMap.delete(fiber);
910
+ pendingFiberToMessageCountMap.delete(fiber);
911
912
if (changed) {
913
// If previous flushed counts have changed, schedule an update too.
1148
1149
// Recursively unmount all roots.
1150
hook.getFiberRoots(rendererID).forEach(root => {
1146
- currentRootID = getOrGenerateFiberInstance(root.current).id;
1147
- // The TREE_OPERATION_REMOVE_ROOT operation serves two purposes:
1148
- // 1. It avoids sending unnecessary bridge traffic to clear a root.
1149
- // 2. It preserves Fiber IDs when remounting (below) which in turn ID to error/warning mapping.
1150
- pushOperation(TREE_OPERATION_REMOVE_ROOT);
1151
+ currentRootID = getFiberInstanceThrows(root.current).id;
1152
+ unmountFiberRecursively(root.current);
1153
flushPendingEvents(root);
1154
currentRootID = -1;
1155
});
1161
1162
// Recursively re-mount all roots with new filter criteria applied.
1163
hook.getFiberRoots(rendererID).forEach(root => {
1162
- currentRootID = getOrGenerateFiberInstance(root.current).id;
1164
+ const current = root.current;
1165
+ const alternate = current.alternate;
1166
+ const newRoot = createFiberInstance(current);
1167
+ idToDevToolsInstanceMap.set(newRoot.id, newRoot);
1168
+ fiberToFiberInstanceMap.set(current, newRoot);
1169
+ if (alternate) {
1170
+ fiberToFiberInstanceMap.set(alternate, newRoot);
1171
+ }
1172
+
1173
+ // Before the traversals, remember to start tracking
1174
+ // our path in case we have selection to restore.
1175
+ if (trackedPath !== null) {
1176
+ mightBeOnTrackedPath = true;
1177
+ }
1178
+
1179
+ currentRootID = newRoot.id;
1180
setRootPseudoKey(currentRootID, root.current);
1181
mountFiberRecursively(root.current, false);
1182
flushPendingEvents(root);
1339
// When a mount or update is in progress, this value tracks the root that is being operated on.
1340
let currentRootID: number = -1;
1341
1325
- // Returns the unique ID for a Fiber or generates and caches a new one if the Fiber hasn't been seen before.
1326
- // Once this method has been called for a Fiber, untrackFiberID() should always be called later to avoid leaking.
1327
- function getOrGenerateFiberInstance(fiber: Fiber): FiberInstance {
1328
- let fiberInstance = fiberToFiberInstanceMap.get(fiber);
1329
- if (fiberInstance === undefined) {
1330
- const {alternate} = fiber;
1331
- if (alternate !== null) {
1332
- fiberInstance = fiberToFiberInstanceMap.get(alternate);
1333
- if (fiberInstance !== undefined) {
1334
- // We found the other pair, so we need to make sure we track the other side.
1335
- fiberToFiberInstanceMap.set(fiber, fiberInstance);
1336
- }
1337
- }
1338
- }
1339
-
1340
- let didGenerateID = false;
1341
- if (fiberInstance === undefined) {
1342
- didGenerateID = true;
1343
- fiberInstance = createFiberInstance(fiber);
1344
- fiberToFiberInstanceMap.set(fiber, fiberInstance);
1345
- idToDevToolsInstanceMap.set(fiberInstance.id, fiberInstance);
1346
- }
1347
-
1348
- if (__DEBUG__) {
1349
- if (didGenerateID) {
1350
- debug(
1351
- 'getOrGenerateFiberInstance()',
1352
- fiber,
1353
- fiberInstance.parent,
1354
- 'Generated a new UID',
1355
- );
1356
- }
1357
- }
1358
-
1359
- return fiberInstance;
1360
- }
1361
-
1342
// Returns a FiberInstance if one has already been generated for the Fiber or throws.
1343
function getFiberInstanceThrows(fiber: Fiber): FiberInstance {
1344
const fiberInstance = getFiberInstanceUnsafe(fiber);
1392
1393
idToDevToolsInstanceMap.delete(fiberInstance.id);
1394
1415
- // Also clear any errors/warnings associated with this fiber.
1416
- clearErrorsForElementID(fiberInstance.id);
1417
- clearWarningsForElementID(fiberInstance.id);
1395
+ const fiber = fiberInstance.data;
1396
+
1397
+ // Restore any errors/warnings associated with this fiber to the pending
1398
+ // map. I.e. treat it as before we tracked the instances. This lets us
1399
+ // restore them if we remount the same Fibers later. Otherwise we rely
1400
+ // on the GC of the Fibers to clean them up.
1401
+ if (fiberInstance.errors !== null) {
1402
+ pendingFiberToErrorsMap.set(fiber, fiberInstance.errors);
1403
+ fiberInstance.errors = null;
1404
+ }
1405
+ if (fiberInstance.warnings !== null) {
1406
+ pendingFiberToWarningsMap.set(fiber, fiberInstance.warnings);
1407
+ fiberInstance.warnings = null;
1408
+ }
1409
+
1410
if (fiberInstance.flags & FORCE_ERROR) {
1411
fiberInstance.flags &= ~FORCE_ERROR;
1412
forceErrorCount--;
1422
}
1423
}
1424
1433
- const fiber = fiberInstance.data;
1425
fiberToFiberInstanceMap.delete(fiber);
1426
const {alternate} = fiber;
1427
if (alternate !== null) {
1736
1737
const pendingOperations: OperationsArray = [];
1738
const pendingRealUnmountedIDs: Array<number> = [];
1748
- const pendingSimulatedUnmountedIDs: Array<number> = [];
1739
let pendingOperationsQueue: Array<OperationsArray> | null = [];
1740
const pendingStringTable: Map<string, StringTableEntry> = new Map();
1741
let pendingStringTableLength: number = 0;
1766
return (
1767
pendingOperations.length === 0 &&
1768
pendingRealUnmountedIDs.length === 0 &&
1779
- pendingSimulatedUnmountedIDs.length === 0 &&
1769
pendingUnmountedRootID === null
1770
);
1771
}
1845
function mergeMapsAndGetCountHelper(
1846
fiber: Fiber,
1847
fiberID: number,
1859
- pendingFiberToMessageCountMap: Map<Fiber, Map<string, number>>,
1848
+ pendingFiberToMessageCountMap: WeakMap<Fiber, Map<string, number>>,
1849
forError: boolean,
1850
): number {
1851
let newCount = 0;
1921
pushOperation(fiberID);
1922
pushOperation(errorCount);
1923
pushOperation(warningCount);
1935
- }
1924
1937
- // Always clean up so that we don't leak.
1938
- pendingFiberToErrorsMap.delete(fiber);
1939
- pendingFiberToWarningsMap.delete(fiber);
1925
+ // Only clear the ones that we've already shown. Leave others in case
1926
+ // they mount later.
1927
+ pendingFiberToErrorsMap.delete(fiber);
1928
+ pendingFiberToWarningsMap.delete(fiber);
1929
+ }
1930
});
1931
fibersWithChangedErrorOrWarningCounts.clear();
1932
}
1933
1934
function flushPendingEvents(root: Object): void {
1935
// Add any pending errors and warnings to the operations array.
1946
- // We do this just before flushing, so we can ignore errors for no-longer-mounted Fibers.
1936
recordPendingErrorsAndWarnings();
1937
1938
if (shouldBailoutWithPendingOperations()) {
1949
1950
const numUnmountIDs =
1951
pendingRealUnmountedIDs.length +
1963
- pendingSimulatedUnmountedIDs.length +
1952
(pendingUnmountedRootID === null ? 0 : 1);
1953
1954
const operations = new Array<number>(
2001
for (let j = 0; j < pendingRealUnmountedIDs.length; j++) {
2002
operations[i++] = pendingRealUnmountedIDs[j];
2003
}
2016
- // Fill in the simulated unmounts (hidden Suspense subtrees) in their order.
2017
- // (We want children to go before parents.)
2018
- // They go *after* the real unmounts because we know for sure they won't be
2019
- // children of already pushed "real" IDs. If they were, we wouldn't be able
2020
- // to discover them during the traversal, as they would have been deleted.
2021
- for (let j = 0; j < pendingSimulatedUnmountedIDs.length; j++) {
2022
- operations[i + j] = pendingSimulatedUnmountedIDs[j];
2023
- }
2024
- i += pendingSimulatedUnmountedIDs.length;
2004
// The root ID should always be unmounted last.
2005
if (pendingUnmountedRootID !== null) {
2006
operations[i] = pendingUnmountedRootID;
2019
// Reset all of the pending state now that we've told the frontend about it.
2020
pendingOperations.length = 0;
2021
pendingRealUnmountedIDs.length = 0;
2043
- pendingSimulatedUnmountedIDs.length = 0;
2022
pendingUnmountedRootID = null;
2023
pendingStringTable.clear();
2024
pendingStringTableLength = 0;
2056
parentInstance: DevToolsInstance | null,
2057
): FiberInstance {
2058
const isRoot = fiber.tag === HostRoot;
2081
- const fiberInstance = getOrGenerateFiberInstance(fiber);
2059
+ let fiberInstance;
2060
+ if (isRoot) {
2061
+ const entry = fiberToFiberInstanceMap.get(fiber);
2062
+ if (entry === undefined) {
2063
+ throw new Error('The root should have been registered at this point');
2064
+ }
2065
+ fiberInstance = entry;
2066
+ } else if (
2067
+ fiberToFiberInstanceMap.has(fiber) ||
2068
+ (fiber.alternate !== null && fiberToFiberInstanceMap.has(fiber.alternate))
2069
+ ) {
2070
+ throw new Error('Did not expect to see this fiber being mounted twice.');
2071
+ } else {
2072
+ fiberInstance = createFiberInstance(fiber);
2073
+ }
2074
+ fiberToFiberInstanceMap.set(fiber, fiberInstance);
2075
+ idToDevToolsInstanceMap.set(fiberInstance.id, fiberInstance);
2076
+
2077
const id = fiberInstance.id;
2078
2079
if (__DEBUG__) {
2126
let ownerID: number;
2127
if (debugOwner != null) {
2128
if (typeof debugOwner.tag === 'number') {
2134
- ownerID = getOrGenerateFiberInstance((debugOwner: any)).id;
2129
+ const ownerFiberInstance = getFiberInstanceUnsafe((debugOwner: any));
2130
+ if (ownerFiberInstance !== null) {
2131
+ ownerID = ownerFiberInstance.id;
2132
+ } else {
2133
+ ownerID = 0;
2134
+ }
2135
} else {
2136
// TODO: Track Server Component Owners.
2137
ownerID = 0;
2183
return fiberInstance;
2184
}
2185
2186
- function recordUnmount(
2187
- fiber: Fiber,
2188
- isSimulated: boolean,
2189
- ): null | FiberInstance {
2186
+ function recordUnmount(fiber: Fiber): null | FiberInstance {
2187
if (__DEBUG__) {
2191
- debug(
2192
- 'recordUnmount()',
2193
- fiber,
2194
- null,
2195
- isSimulated ? 'unmount is simulated' : '',
2196
- );
2188
+ debug('recordUnmount()', fiber, null);
2189
}
2190
2191
if (trackedPathMatchFiber !== null) {
2215
const id = fiberInstance.id;
2216
const isRoot = fiber.tag === HostRoot;
2217
if (isRoot) {
2226
- // Roots must be removed only after all children (pending and simulated) have been removed.
2218
+ // Roots must be removed only after all children have been removed.
2219
// So we track it separately.
2220
pendingUnmountedRootID = id;
2221
} else if (!shouldFilterFiber(fiber)) {
2222
// To maintain child-first ordering,
2223
// we'll push it into one of these queues,
2224
// and later arrange them in the correct order.
2233
- if (isSimulated) {
2234
- pendingSimulatedUnmountedIDs.push(id);
2235
- } else {
2236
- pendingRealUnmountedIDs.push(id);
2237
- }
2225
+ pendingRealUnmountedIDs.push(id);
2226
}
2227
2240
- if (!fiber._debugNeedsRemount) {
2241
- untrackFiber(fiberInstance);
2228
+ untrackFiber(fiberInstance);
2229
2243
- const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
2244
- if (isProfilingSupported) {
2245
- idToRootMap.delete(id);
2246
- idToTreeBaseDurationMap.delete(id);
2247
- }
2230
+ const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
2231
+ if (isProfilingSupported) {
2232
+ idToRootMap.delete(id);
2233
+ idToTreeBaseDurationMap.delete(id);
2234
}
2235
return fiberInstance;
2236
}
2309
let child = remainingReconcilingChildren;
2310
while (child !== null) {
2311
if (child.kind === FIBER_INSTANCE) {
2326
- unmountFiberRecursively(child.data, false);
2312
+ unmountFiberRecursively(child.data);
2313
}
2314
removeChild(child);
2315
child = remainingReconcilingChildren;
2333
fiber: Fiber,
2334
traceNearestHostComponentUpdate: boolean,
2335
): void {
2350
- // Generate an ID even for filtered Fibers, in case it's needed later (e.g. for Profiling).
2351
- // TODO: Do we really need to do this eagerly?
2352
- getOrGenerateFiberInstance(fiber);
2353
-
2336
if (__DEBUG__) {
2337
debug('mountFiberRecursively()', fiber, reconcilingParent);
2338
}
2434
2435
// We use this to simulate unmounting for Suspense trees
2436
// when we switch from primary to fallback, or deleting a subtree.
2455
- function unmountFiberRecursively(fiber: Fiber, isSimulated: boolean) {
2437
+ function unmountFiberRecursively(fiber: Fiber) {
2438
if (__DEBUG__) {
2439
debug('unmountFiberRecursively()', fiber, null);
2440
}
2475
child = fallbackChildFragment ? fallbackChildFragment.child : null;
2476
}
2477
2496
- unmountChildrenRecursively(child, isSimulated);
2478
+ unmountChildrenRecursively(child);
2479
} finally {
2480
if (shouldIncludeInTree) {
2481
reconcilingParent = stashedParent;
2484
}
2485
}
2486
if (fiberInstance !== null) {
2505
- recordUnmount(fiber, isSimulated);
2487
+ recordUnmount(fiber);
2488
removeChild(fiberInstance);
2489
}
2490
}
2491
2510
- function unmountChildrenRecursively(
2511
- firstChild: null | Fiber,
2512
- isSimulated: boolean,
2513
- ) {
2492
+ function unmountChildrenRecursively(firstChild: null | Fiber) {
2493
let child: null | Fiber = firstChild;
2494
while (child !== null) {
2495
// Record simulated unmounts children-first.
2496
// We skip nodes without return because those are real unmounts.
2518
- unmountFiberRecursively(child, isSimulated);
2497
+ unmountFiberRecursively(child);
2498
child = child.sibling;
2499
}
2500
}
2704
prevFiber: Fiber,
2705
traceNearestHostComponentUpdate: boolean,
2706
): boolean {
2728
- // TODO: Do we really need to give this an instance eagerly if it's filtered?
2729
- const fiberInstance = getOrGenerateFiberInstance(nextFiber);
2730
- const id = fiberInstance.id;
2731
-
2707
if (__DEBUG__) {
2708
debug('updateFiberRecursively()', nextFiber, reconcilingParent);
2709
}
2733
}
2734
}
2735
2761
- if (
2762
- mostRecentlyInspectedElement !== null &&
2763
- mostRecentlyInspectedElement.id === id &&
2764
- didFiberRender(prevFiber, nextFiber)
2765
- ) {
2766
- // If this Fiber has updated, clear cached inspected data.
2767
- // If it is inspected again, it may need to be re-run to obtain updated hooks values.
2768
- hasElementUpdatedSinceLastInspected = true;
2769
- }
2770
-
2736
+ let fiberInstance: null | FiberInstance = null;
2737
const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
2738
if (shouldIncludeInTree) {
2739
+ const entry = fiberToFiberInstanceMap.get(prevFiber);
2740
+ if (entry === undefined) {
2741
+ throw new Error(
2742
+ 'The previous version of the fiber should have already been registered.',
2743
+ );
2744
+ }
2745
+ fiberInstance = entry;
2746
+ // Register the new alternate in case it's not already in.
2747
+ fiberToFiberInstanceMap.set(nextFiber, fiberInstance);
2748
+
2749
// Update the Fiber so we that we always keep the current Fiber on the data.
2750
fiberInstance.data = nextFiber;
2751
moveChild(fiberInstance);
2752
+
2753
+ if (
2754
+ mostRecentlyInspectedElement !== null &&
2755
+ mostRecentlyInspectedElement.id === fiberInstance.id &&
2756
+ didFiberRender(prevFiber, nextFiber)
2757
+ ) {
2758
+ // If this Fiber has updated, clear cached inspected data.
2759
+ // If it is inspected again, it may need to be re-run to obtain updated hooks values.
2760
+ hasElementUpdatedSinceLastInspected = true;
2761
+ }
2762
}
2763
+
2764
const stashedParent = reconcilingParent;
2765
const stashedPrevious = previouslyReconciledSibling;
2766
const stashedRemaining = remainingReconcilingChildren;
2780
- if (shouldIncludeInTree) {
2767
+ if (fiberInstance !== null) {
2768
// Push a new DevTools instance parent while reconciling this subtree.
2769
reconcilingParent = fiberInstance;
2770
previouslyReconciledSibling = null;
2843
} else if (!prevDidTimeout && nextDidTimeOut) {
2844
// Primary -> Fallback:
2845
// 1. Hide primary set
2859
- // This is not a real unmount, so it won't get reported by React.
2860
- // We need to manually walk the previous tree and record unmounts.
2861
- unmountChildrenRecursively(prevFiber.child, true);
2846
+ unmountChildrenRecursively(prevFiber.child);
2847
// 2. Mount fallback set
2848
const nextFiberChild = nextFiber.child;
2849
const nextFallbackChildSet = nextFiberChild
2871
}
2872
} else {
2873
// Children are unchanged.
2889
- if (shouldIncludeInTree) {
2874
+ if (fiberInstance !== null) {
2875
// All the remaining children will be children of this same fiber so we can just reuse them.
2876
// I.e. we just restore them by undoing what we did above.
2877
fiberInstance.firstChild = remainingReconcilingChildren;
2988
}
2989
// If we have not been profiling, then we can just walk the tree and build up its current state as-is.
2990
hook.getFiberRoots(rendererID).forEach(root => {
3006
- currentRootID = getOrGenerateFiberInstance(root.current).id;
2991
+ const current = root.current;
2992
+ const alternate = current.alternate;
2993
+ const newRoot = createFiberInstance(current);
2994
+ idToDevToolsInstanceMap.set(newRoot.id, newRoot);
2995
+ fiberToFiberInstanceMap.set(current, newRoot);
2996
+ if (alternate) {
2997
+ fiberToFiberInstanceMap.set(alternate, newRoot);
2998
+ }
2999
+ currentRootID = newRoot.id;
3000
setRootPseudoKey(currentRootID, root.current);
3001
3002
// Handle multi-renderer edge-case where only some v16 renderers support profiling.
3053
const current = root.current;
3054
const alternate = current.alternate;
3055
3063
- currentRootID = getOrGenerateFiberInstance(current).id;
3056
+ const existingRoot =
3057
+ fiberToFiberInstanceMap.get(current) ||
3058
+ (alternate && fiberToFiberInstanceMap.get(alternate));
3059
+ if (!existingRoot) {
3060
+ const newRoot = createFiberInstance(current);
3061
+ idToDevToolsInstanceMap.set(newRoot.id, newRoot);
3062
+ fiberToFiberInstanceMap.set(current, newRoot);
3063
+ if (alternate) {
3064
+ fiberToFiberInstanceMap.set(alternate, newRoot);
3065
+ }
3066
+ currentRootID = newRoot.id;
3067
+ } else {
3068
+ currentRootID = existingRoot.id;
3069
+ }
3070
3071
// Before the traversals, remember to start tracking
3072
// our path in case we have selection to restore.
3123
} else if (wasMounted && !isMounted) {
3124
// Unmount an existing root.
3125
removeRootPseudoKey(currentRootID);
3120
- unmountFiberRecursively(alternate, false);
3126
+ unmountFiberRecursively(alternate);
3127
}
3128
} else {
3129
// Mount a new root.
3623
while (owner != null) {
3624
if (typeof owner.tag === 'number') {
3625
const ownerFiber: Fiber = (owner: any); // Refined
3620
- owners.unshift(fiberToSerializedElement(ownerFiber));
3626
+ if (!shouldFilterFiber(ownerFiber)) {
3627
+ owners.unshift(fiberToSerializedElement(ownerFiber));
3628
+ }
3629
owner = ownerFiber._debugOwner;
3630
} else {
3631
// TODO: Track Server Component Owners.