bippy 0.7.2 → 0.7.3-dev.09bae5a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -79
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +59 -9
- package/dist/errors.d.ts +59 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +34 -58
- package/dist/index.d.ts +36 -60
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +11 -11
- package/dist/source.d.cts +47 -18
- package/dist/source.d.ts +47 -18
- package/dist/source.js +12 -12
- package/package.json +7 -35
- package/src/call-listener.ts +13 -0
- package/src/core.ts +367 -282
- package/src/rdt-hook.ts +159 -87
- package/src/react-internals/current-fiber.ts +84 -0
- package/src/react-internals/generated/react-work-tags.ts +3 -0
- package/src/react-internals/index.ts +38 -11
- package/src/react-internals/types.ts +33 -6
- package/src/react.ts +141 -55
- package/src/source/constants.ts +11 -9
- package/src/source/get-display-name-from-source.ts +10 -3
- package/src/source/get-source.ts +42 -157
- package/src/source/index.ts +17 -3
- package/src/source/inspect-hooks.ts +198 -49
- package/src/source/normalize-file-name.ts +416 -0
- package/src/source/owner-stack.ts +43 -35
- package/src/source/parse-debug-stack.ts +1 -1
- package/src/source/parse-hook-names.ts +15 -8
- package/src/source/parse-stack.ts +114 -64
- package/src/source/renderer-dispatchers.ts +9 -4
- package/src/source/symbolication.ts +131 -70
package/src/core.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// React must remain a type-only import because this module loads immediately after the DevTools hook.
|
|
2
2
|
import type * as React from "react";
|
|
3
|
+
import { callListener } from "./call-listener.js";
|
|
3
4
|
|
|
4
5
|
import type {
|
|
5
6
|
Fiber,
|
|
@@ -10,19 +11,20 @@ import type {
|
|
|
10
11
|
} from "./react-internals/index.js";
|
|
11
12
|
|
|
12
13
|
import {
|
|
13
|
-
_onActiveListeners,
|
|
14
14
|
BIPPY_INSTRUMENTATION_STRING,
|
|
15
15
|
createUnsubscribe,
|
|
16
16
|
getRDTHook,
|
|
17
17
|
hasRDTHook,
|
|
18
|
+
isFiberRootUnmounted,
|
|
18
19
|
isReactRefresh,
|
|
19
20
|
isRealReactDevtools,
|
|
20
21
|
onRDTHookReplace,
|
|
22
|
+
removeActiveListener,
|
|
21
23
|
} from "./rdt-hook.js";
|
|
22
|
-
import type { Unsubscribe } from "./rdt-hook.js";
|
|
24
|
+
import type { ReactDevToolsTarget, Unsubscribe } from "./rdt-hook.js";
|
|
25
|
+
import { getCurrentFiberFromRoot } from "./react-internals/current-fiber.js";
|
|
23
26
|
import {
|
|
24
27
|
getReactWorkTagsForFiber,
|
|
25
|
-
MutationMask,
|
|
26
28
|
ReactBuildType,
|
|
27
29
|
ReactFiberFlags,
|
|
28
30
|
ReactSymbols,
|
|
@@ -64,8 +66,8 @@ export const isValidElement = (element: unknown): element is React.ReactElement
|
|
|
64
66
|
typeof element === "object" &&
|
|
65
67
|
element !== null &&
|
|
66
68
|
"$$typeof" in element &&
|
|
67
|
-
(
|
|
68
|
-
|
|
69
|
+
(element.$$typeof === Symbol.for("react.element") ||
|
|
70
|
+
element.$$typeof === Symbol.for("react.transitional.element"));
|
|
69
71
|
|
|
70
72
|
/**
|
|
71
73
|
* Returns `true` if object is a React Fiber.
|
|
@@ -78,7 +80,7 @@ export const isFiber = (fiber: unknown): fiber is Fiber =>
|
|
|
78
80
|
"return" in fiber &&
|
|
79
81
|
"child" in fiber &&
|
|
80
82
|
"sibling" in fiber &&
|
|
81
|
-
"flags" in fiber;
|
|
83
|
+
("flags" in fiber || "effectTag" in fiber);
|
|
82
84
|
|
|
83
85
|
const isFiberRoot = (fiberRoot: unknown): fiberRoot is FiberRoot =>
|
|
84
86
|
typeof fiberRoot === "object" &&
|
|
@@ -128,7 +130,7 @@ export const isCompositeFiber = (fiber: Fiber): boolean => {
|
|
|
128
130
|
*/
|
|
129
131
|
export const didFiberRender = (fiber: Fiber): boolean => {
|
|
130
132
|
const nextProps = fiber.memoizedProps;
|
|
131
|
-
const prevProps = fiber.alternate?.memoizedProps
|
|
133
|
+
const prevProps = fiber.alternate?.memoizedProps;
|
|
132
134
|
const flags = fiber.flags ?? fiber.effectTag ?? 0;
|
|
133
135
|
const workTags = getReactWorkTagsForFiber(fiber);
|
|
134
136
|
|
|
@@ -152,16 +154,6 @@ export const didFiberRender = (fiber: Fiber): boolean => {
|
|
|
152
154
|
}
|
|
153
155
|
};
|
|
154
156
|
|
|
155
|
-
/**
|
|
156
|
-
* Returns `true` if the {@link Fiber} has committed. Note that this does not mean the fiber has committed in the current commit, just that it has committed in the past.
|
|
157
|
-
*/
|
|
158
|
-
export const didFiberCommit = (fiber: Fiber): boolean => {
|
|
159
|
-
return Boolean(
|
|
160
|
-
(fiber.flags & (MutationMask | ReactFiberFlags.Cloned)) !== 0 ||
|
|
161
|
-
(fiber.subtreeFlags & (MutationMask | ReactFiberFlags.Cloned)) !== 0,
|
|
162
|
-
);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
157
|
/**
|
|
166
158
|
* Returns `true` if the {@link Fiber} should be filtered out during reconciliation.
|
|
167
159
|
*/
|
|
@@ -176,16 +168,20 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
176
168
|
// https://github.com/bvaughn/react-devtools-experimental/issues/197
|
|
177
169
|
return true;
|
|
178
170
|
|
|
179
|
-
case workTags.
|
|
171
|
+
case workTags.HostPortal:
|
|
180
172
|
case workTags.HostText:
|
|
181
173
|
case workTags.LegacyHiddenComponent:
|
|
182
174
|
case workTags.OffscreenComponent:
|
|
175
|
+
case workTags.Throw:
|
|
183
176
|
return true;
|
|
184
177
|
|
|
185
178
|
case workTags.HostRoot:
|
|
186
179
|
// It is never valid to filter the root element.
|
|
187
180
|
return false;
|
|
188
181
|
|
|
182
|
+
case workTags.Fragment:
|
|
183
|
+
return fiber.key === null;
|
|
184
|
+
|
|
189
185
|
default: {
|
|
190
186
|
const symbolOrNumber =
|
|
191
187
|
typeof fiber.type === "object" && fiber.type !== null ? fiber.type.$$typeof : fiber.type;
|
|
@@ -193,7 +189,8 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
193
189
|
if (typeof symbolOrNumber === "symbol") {
|
|
194
190
|
return (
|
|
195
191
|
symbolOrNumber.description === ReactSymbols.CONCURRENT_MODE_SYMBOL_DESCRIPTION ||
|
|
196
|
-
symbolOrNumber.description === ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION
|
|
192
|
+
symbolOrNumber.description === ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION ||
|
|
193
|
+
symbolOrNumber.description === ReactSymbols.STRICT_MODE_SYMBOL_DESCRIPTION
|
|
197
194
|
);
|
|
198
195
|
}
|
|
199
196
|
|
|
@@ -201,6 +198,8 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
201
198
|
case ReactSymbols.CONCURRENT_MODE_NUMBER:
|
|
202
199
|
case ReactSymbols.CONCURRENT_MODE_SYMBOL_STRING:
|
|
203
200
|
case ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
|
|
201
|
+
case ReactSymbols.STRICT_MODE_NUMBER:
|
|
202
|
+
case ReactSymbols.STRICT_MODE_SYMBOL_STRING:
|
|
204
203
|
return true;
|
|
205
204
|
|
|
206
205
|
default:
|
|
@@ -210,73 +209,64 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
210
209
|
}
|
|
211
210
|
};
|
|
212
211
|
|
|
212
|
+
interface TraverseFiber {
|
|
213
|
+
(
|
|
214
|
+
fiber: Fiber | null,
|
|
215
|
+
selector: (node: Fiber) => boolean | void,
|
|
216
|
+
ascending?: boolean,
|
|
217
|
+
): Fiber | null;
|
|
218
|
+
(
|
|
219
|
+
fiber: Fiber | null,
|
|
220
|
+
selector: (node: Fiber) => Promise<boolean | void>,
|
|
221
|
+
ascending?: boolean,
|
|
222
|
+
): Promise<Fiber | null>;
|
|
223
|
+
(
|
|
224
|
+
fiber: Fiber | null,
|
|
225
|
+
selector: FiberSelector,
|
|
226
|
+
ascending?: boolean,
|
|
227
|
+
): Fiber | null | Promise<Fiber | null>;
|
|
228
|
+
}
|
|
229
|
+
|
|
213
230
|
/**
|
|
214
231
|
* Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
|
|
215
232
|
*/
|
|
216
|
-
export
|
|
217
|
-
fiber: Fiber | null,
|
|
218
|
-
selector: (node: Fiber) => boolean | void,
|
|
219
|
-
ascending?: boolean,
|
|
220
|
-
): Fiber | null;
|
|
221
|
-
export function traverseFiber(
|
|
222
|
-
fiber: Fiber | null,
|
|
223
|
-
selector: (node: Fiber) => Promise<boolean | void>,
|
|
224
|
-
ascending?: boolean,
|
|
225
|
-
): Promise<Fiber | null>;
|
|
226
|
-
export function traverseFiber(
|
|
227
|
-
fiber: Fiber | null,
|
|
228
|
-
selector: FiberSelector,
|
|
229
|
-
ascending?: boolean,
|
|
230
|
-
): Fiber | null | Promise<Fiber | null>;
|
|
231
|
-
export function traverseFiber(
|
|
233
|
+
export const traverseFiber = ((
|
|
232
234
|
fiber: Fiber | null,
|
|
233
235
|
selector: FiberSelector,
|
|
234
236
|
ascending = false,
|
|
235
|
-
): Fiber | null | Promise<Fiber | null> {
|
|
236
|
-
if (!fiber) return null;
|
|
237
|
-
|
|
238
|
-
const selection = selector(fiber);
|
|
239
|
-
if (isPromiseLike<boolean | void>(selection)) {
|
|
240
|
-
return Promise.resolve(selection).then((didSelectFiber) =>
|
|
241
|
-
didSelectFiber === true ? fiber : traverseFiberChildren(fiber, selector, ascending),
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
if (selection === true) return fiber;
|
|
245
|
-
|
|
246
|
-
return traverseFiberChildren(fiber, selector, ascending);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
|
|
250
|
-
(typeof value === "object" || typeof value === "function") &&
|
|
251
|
-
value !== null &&
|
|
252
|
-
"then" in value &&
|
|
253
|
-
typeof value.then === "function";
|
|
254
|
-
|
|
255
|
-
const traverseFiberChildren = (
|
|
256
|
-
fiber: Fiber,
|
|
257
|
-
selector: FiberSelector,
|
|
258
|
-
ascending: boolean,
|
|
259
|
-
): Fiber | null | Promise<Fiber | null> => {
|
|
260
|
-
const firstChild = ascending ? fiber.return : fiber.child;
|
|
261
|
-
return traverseFiberSiblings(firstChild, selector, ascending);
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
const traverseFiberSiblings = (
|
|
265
|
-
fiber: Fiber | null,
|
|
266
|
-
selector: FiberSelector,
|
|
267
|
-
ascending: boolean,
|
|
268
237
|
): Fiber | null | Promise<Fiber | null> => {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
238
|
+
const pendingSiblings: Fiber[] = [];
|
|
239
|
+
const getNextFiber = (currentFiber: Fiber): Fiber | null => {
|
|
240
|
+
if (ascending) return currentFiber.return;
|
|
241
|
+
if (currentFiber !== fiber && currentFiber.sibling) {
|
|
242
|
+
pendingSiblings.push(currentFiber.sibling);
|
|
243
|
+
}
|
|
244
|
+
return currentFiber.child ?? pendingSiblings.pop() ?? null;
|
|
245
|
+
};
|
|
246
|
+
const visit = (firstFiber: Fiber | null): Fiber | null | Promise<Fiber | null> => {
|
|
247
|
+
let currentFiber = firstFiber;
|
|
248
|
+
while (currentFiber) {
|
|
249
|
+
const selectedFiber = currentFiber;
|
|
250
|
+
const selection = selector(selectedFiber);
|
|
251
|
+
const then =
|
|
252
|
+
selection !== null && (typeof selection === "object" || typeof selection === "function")
|
|
253
|
+
? selection.then
|
|
254
|
+
: null;
|
|
255
|
+
if (typeof then === "function") {
|
|
256
|
+
return Promise.resolve<boolean | void>({
|
|
257
|
+
// oxlint-disable-next-line unicorn/no-thenable -- Assimilate the captured method without reading the accessor twice.
|
|
258
|
+
then: (resolve, reject) => Reflect.apply(then, selection, [resolve, reject]),
|
|
259
|
+
}).then((didSelectFiber) =>
|
|
260
|
+
didSelectFiber === true ? selectedFiber : visit(getNextFiber(selectedFiber)),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
if (selection === true) return selectedFiber;
|
|
264
|
+
currentFiber = getNextFiber(selectedFiber);
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
};
|
|
268
|
+
return visit(fiber);
|
|
269
|
+
}) as TraverseFiber;
|
|
280
270
|
|
|
281
271
|
/**
|
|
282
272
|
* Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
|
|
@@ -291,7 +281,14 @@ export const hasMemoCache = (fiber: Fiber): boolean => {
|
|
|
291
281
|
export const getType = (type: unknown): null | React.ComponentType<unknown> => {
|
|
292
282
|
if (isComponentType(type)) return type;
|
|
293
283
|
if (typeof type !== "object" || type === null) return null;
|
|
294
|
-
|
|
284
|
+
const visitedTypes = new Set<object>();
|
|
285
|
+
let currentType: unknown = type;
|
|
286
|
+
while (typeof currentType === "object" && currentType !== null) {
|
|
287
|
+
if (visitedTypes.has(currentType)) return null;
|
|
288
|
+
visitedTypes.add(currentType);
|
|
289
|
+
currentType = Reflect.get(currentType, "type") ?? Reflect.get(currentType, "render");
|
|
290
|
+
}
|
|
291
|
+
return isComponentType(currentType) ? currentType : null;
|
|
295
292
|
};
|
|
296
293
|
|
|
297
294
|
/**
|
|
@@ -312,10 +309,7 @@ export const getDisplayName = (type: unknown): null | string => {
|
|
|
312
309
|
*/
|
|
313
310
|
export const detectReactBuildType = (renderer: ReactRenderer): "development" | "production" => {
|
|
314
311
|
try {
|
|
315
|
-
if (
|
|
316
|
-
typeof renderer.version === "string" &&
|
|
317
|
-
renderer.bundleType === ReactBuildType.Development
|
|
318
|
-
) {
|
|
312
|
+
if (typeof renderer.version === "string" && renderer.bundleType > ReactBuildType.Production) {
|
|
319
313
|
return "development";
|
|
320
314
|
}
|
|
321
315
|
} catch {}
|
|
@@ -325,8 +319,8 @@ export const detectReactBuildType = (renderer: ReactRenderer): "development" | "
|
|
|
325
319
|
/**
|
|
326
320
|
* Returns `true` if bippy's instrumentation is active.
|
|
327
321
|
*/
|
|
328
|
-
export const isInstrumentationActive = (): boolean => {
|
|
329
|
-
const rdtHook =
|
|
322
|
+
export const isInstrumentationActive = (target: ReactDevToolsTarget = globalThis): boolean => {
|
|
323
|
+
const rdtHook = target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
330
324
|
return (
|
|
331
325
|
Boolean(rdtHook?._instrumentationIsActive) ||
|
|
332
326
|
isRealReactDevtools(rdtHook) ||
|
|
@@ -336,6 +330,8 @@ export const isInstrumentationActive = (): boolean => {
|
|
|
336
330
|
|
|
337
331
|
export const _fiberRoots = new Set<FiberRoot>();
|
|
338
332
|
const rootRendererIds = new WeakMap<FiberRoot, number>();
|
|
333
|
+
const rootHooks = new WeakMap<FiberRoot, ReactDevToolsGlobalHook>();
|
|
334
|
+
let currentUnmountingFiber: Fiber | null = null;
|
|
339
335
|
|
|
340
336
|
/**
|
|
341
337
|
* Returns the latest fiber (since it may be double-buffered).
|
|
@@ -343,6 +339,10 @@ const rootRendererIds = new WeakMap<FiberRoot, number>();
|
|
|
343
339
|
export const getLatestFiber = (fiber: Fiber): Fiber => {
|
|
344
340
|
const alternate = fiber.alternate;
|
|
345
341
|
if (!alternate) return fiber;
|
|
342
|
+
const currentFiber = getCurrentFiberFromRoot(fiber);
|
|
343
|
+
const isUnmountingPair = fiber === currentUnmountingFiber || alternate === currentUnmountingFiber;
|
|
344
|
+
if (currentFiber && (!isUnmountingPair || currentFiber === currentUnmountingFiber))
|
|
345
|
+
return currentFiber;
|
|
346
346
|
|
|
347
347
|
let rootFiber = fiber;
|
|
348
348
|
while (rootFiber.return) {
|
|
@@ -354,6 +354,7 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
|
|
|
354
354
|
});
|
|
355
355
|
if (latestFiber) return latestFiber;
|
|
356
356
|
}
|
|
357
|
+
if (currentFiber) return currentFiber;
|
|
357
358
|
|
|
358
359
|
if (alternate.actualStartTime && fiber.actualStartTime) {
|
|
359
360
|
return alternate.actualStartTime > fiber.actualStartTime ? alternate : fiber;
|
|
@@ -370,29 +371,71 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
|
|
|
370
371
|
/**
|
|
371
372
|
* Returns the renderer that owns the {@link Fiber}.
|
|
372
373
|
*/
|
|
373
|
-
export const getRenderer = (
|
|
374
|
-
|
|
375
|
-
|
|
374
|
+
export const getRenderer = (
|
|
375
|
+
fiber: Fiber,
|
|
376
|
+
target: ReactDevToolsTarget = globalThis,
|
|
377
|
+
): ReactRenderer | null => {
|
|
376
378
|
let rootFiber = fiber;
|
|
377
|
-
while (rootFiber.return)
|
|
378
|
-
rootFiber = rootFiber.return;
|
|
379
|
-
}
|
|
380
|
-
|
|
379
|
+
while (rootFiber.return) rootFiber = rootFiber.return;
|
|
381
380
|
const fiberRoot = rootFiber.stateNode;
|
|
382
381
|
if (!isFiberRoot(fiberRoot)) return null;
|
|
383
382
|
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
383
|
+
const trackedRendererId = rootRendererIds.get(fiberRoot);
|
|
384
|
+
const trackedHook = rootHooks.get(fiberRoot);
|
|
385
|
+
if (trackedRendererId !== undefined && trackedHook) {
|
|
386
|
+
return trackedHook.renderers.get(trackedRendererId) ?? null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const rdtHook = target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
390
|
+
if (!rdtHook?.getFiberRoots) return null;
|
|
391
|
+
for (const [rendererId, renderer] of rdtHook.renderers) {
|
|
392
|
+
if (!rdtHook.getFiberRoots(rendererId).has(fiberRoot)) continue;
|
|
393
|
+
_fiberRoots.add(fiberRoot);
|
|
394
|
+
rootRendererIds.set(fiberRoot, rendererId);
|
|
395
|
+
rootHooks.set(fiberRoot, rdtHook);
|
|
396
|
+
setReactWorkTagsForFiber(fiberRoot.current, renderer);
|
|
397
|
+
return renderer;
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
387
400
|
};
|
|
388
401
|
|
|
389
402
|
export type RenderPhase = "mount" | "unmount" | "update";
|
|
390
403
|
|
|
404
|
+
interface FiberReference {
|
|
405
|
+
deref: () => Fiber | undefined;
|
|
406
|
+
}
|
|
407
|
+
|
|
391
408
|
let nextFiberId = 0;
|
|
392
409
|
const fiberIdMap = new WeakMap<Fiber, number>();
|
|
410
|
+
const fiberByIdMap = new Map<number, FiberReference>();
|
|
411
|
+
const fiberIdFinalizationRegistry =
|
|
412
|
+
typeof FinalizationRegistry === "function"
|
|
413
|
+
? new FinalizationRegistry<number>((fiberId) => {
|
|
414
|
+
if (!fiberByIdMap.get(fiberId)?.deref()) fiberByIdMap.delete(fiberId);
|
|
415
|
+
})
|
|
416
|
+
: null;
|
|
417
|
+
|
|
418
|
+
const createFiberReference = (fiber: Fiber): FiberReference =>
|
|
419
|
+
typeof WeakRef === "function" ? new WeakRef(fiber) : { deref: () => fiber };
|
|
420
|
+
|
|
421
|
+
const getNextFiberId = (): number => {
|
|
422
|
+
if (!Number.isSafeInteger(nextFiberId)) throw new RangeError("Fiber ID space exhausted");
|
|
423
|
+
return nextFiberId++;
|
|
424
|
+
};
|
|
393
425
|
|
|
394
|
-
export const setFiberId = (fiber: Fiber, fiberId: number =
|
|
426
|
+
export const setFiberId = (fiber: Fiber, fiberId: number = getNextFiberId()): void => {
|
|
427
|
+
const previousFiberId = fiberIdMap.get(fiber);
|
|
428
|
+
if (
|
|
429
|
+
previousFiberId !== undefined &&
|
|
430
|
+
previousFiberId !== fiberId &&
|
|
431
|
+
fiberByIdMap.get(previousFiberId)?.deref() === fiber
|
|
432
|
+
) {
|
|
433
|
+
fiberByIdMap.delete(previousFiberId);
|
|
434
|
+
}
|
|
395
435
|
fiberIdMap.set(fiber, fiberId);
|
|
436
|
+
fiberByIdMap.set(fiberId, createFiberReference(fiber));
|
|
437
|
+
if (previousFiberId !== undefined) fiberIdFinalizationRegistry?.unregister(fiber);
|
|
438
|
+
fiberIdFinalizationRegistry?.register(fiber, fiberId, fiber);
|
|
396
439
|
if (Number.isSafeInteger(fiberId) && fiberId >= nextFiberId) {
|
|
397
440
|
nextFiberId = fiberId + 1;
|
|
398
441
|
}
|
|
@@ -402,139 +445,135 @@ export const getFiberId = (fiber: Fiber): number => {
|
|
|
402
445
|
let currentFiberId = fiberIdMap.get(fiber);
|
|
403
446
|
if (currentFiberId === undefined && fiber.alternate) {
|
|
404
447
|
currentFiberId = fiberIdMap.get(fiber.alternate);
|
|
448
|
+
if (currentFiberId !== undefined) {
|
|
449
|
+
const assignedFiber = fiberByIdMap.get(currentFiberId)?.deref();
|
|
450
|
+
if (assignedFiber && assignedFiber !== fiber.alternate) {
|
|
451
|
+
fiberIdMap.set(fiber, currentFiberId);
|
|
452
|
+
} else {
|
|
453
|
+
setFiberId(fiber, currentFiberId);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
405
456
|
}
|
|
406
457
|
if (currentFiberId === undefined) {
|
|
407
|
-
currentFiberId =
|
|
458
|
+
currentFiberId = getNextFiberId();
|
|
408
459
|
setFiberId(fiber, currentFiberId);
|
|
409
460
|
}
|
|
410
461
|
return currentFiberId;
|
|
411
462
|
};
|
|
412
463
|
|
|
413
|
-
const
|
|
464
|
+
export const getFiberById = (fiberId: number): Fiber | null => {
|
|
465
|
+
const fiber = fiberByIdMap.get(fiberId)?.deref();
|
|
466
|
+
if (!fiber) {
|
|
467
|
+
fiberByIdMap.delete(fiberId);
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
return getLatestFiber(fiber);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const releaseFiberId = (fiber: Fiber): void => {
|
|
474
|
+
const relatedFibers = fiber.alternate ? [fiber, fiber.alternate] : [fiber];
|
|
475
|
+
const fiberIds = new Set<number>();
|
|
476
|
+
|
|
477
|
+
for (const relatedFiber of relatedFibers) {
|
|
478
|
+
const fiberId = fiberIdMap.get(relatedFiber);
|
|
479
|
+
if (fiberId !== undefined) fiberIds.add(fiberId);
|
|
480
|
+
fiberIdMap.delete(relatedFiber);
|
|
481
|
+
fiberIdFinalizationRegistry?.unregister(relatedFiber);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
for (const fiberId of fiberIds) {
|
|
485
|
+
const assignedFiber = fiberByIdMap.get(fiberId)?.deref();
|
|
486
|
+
if (!assignedFiber || relatedFibers.includes(assignedFiber)) {
|
|
487
|
+
fiberByIdMap.delete(fiberId);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const getRenderedChild = (fiber: Fiber, skipPrimaryWrapper: boolean): Fiber | null => {
|
|
493
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
494
|
+
if (fiber.tag !== workTags.SuspenseComponent) return fiber.child;
|
|
495
|
+
if (fiber.memoizedState !== null) return fiber.child?.sibling?.child ?? null;
|
|
496
|
+
return skipPrimaryWrapper && workTags.OffscreenComponent !== -1
|
|
497
|
+
? (fiber.child?.child ?? null)
|
|
498
|
+
: fiber.child;
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const mountFiberTree = (
|
|
414
502
|
onRender: RenderHandler,
|
|
415
503
|
firstChild: Fiber,
|
|
416
504
|
traverseSiblings: boolean,
|
|
417
505
|
): void => {
|
|
506
|
+
const pendingSiblings: Fiber[] = [];
|
|
418
507
|
let fiber: Fiber | null = firstChild;
|
|
419
|
-
|
|
420
|
-
while (fiber !== null) {
|
|
508
|
+
while (fiber) {
|
|
421
509
|
getFiberId(fiber);
|
|
422
|
-
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent) {
|
|
428
|
-
const isTimedOut = fiber.memoizedState !== null;
|
|
429
|
-
if (isTimedOut) {
|
|
430
|
-
// Special case: if Suspense mounts in a timed-out state,
|
|
431
|
-
// get the fallback child from the inner fragment and mount
|
|
432
|
-
// it as if it was our own child. Updates handle this too.
|
|
433
|
-
const primaryChildFragment = fiber.child;
|
|
434
|
-
const fallbackChildFragment = primaryChildFragment ? primaryChildFragment.sibling : null;
|
|
435
|
-
if (fallbackChildFragment) {
|
|
436
|
-
const fallbackChild = fallbackChildFragment.child;
|
|
437
|
-
if (fallbackChild !== null) {
|
|
438
|
-
mountFiberRecursively(onRender, fallbackChild, false);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
} else {
|
|
442
|
-
const primaryChild = fiber.child?.child ?? null;
|
|
443
|
-
if (primaryChild !== null) {
|
|
444
|
-
mountFiberRecursively(onRender, primaryChild, false);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
} else if (fiber.child !== null) {
|
|
448
|
-
mountFiberRecursively(onRender, fiber.child, true);
|
|
449
|
-
}
|
|
450
|
-
fiber = traverseSiblings ? fiber.sibling : null;
|
|
510
|
+
if (!shouldFilterFiber(fiber) && didFiberRender(fiber)) onRender(fiber, "mount");
|
|
511
|
+
if (traverseSiblings && fiber.sibling) pendingSiblings.push(fiber.sibling);
|
|
512
|
+
fiber = getRenderedChild(fiber, true) ?? pendingSiblings.pop() ?? null;
|
|
513
|
+
traverseSiblings = true;
|
|
451
514
|
}
|
|
452
515
|
};
|
|
453
516
|
|
|
454
|
-
|
|
517
|
+
interface FiberUpdate {
|
|
518
|
+
fiber: Fiber;
|
|
519
|
+
previousFiber: Fiber | null;
|
|
520
|
+
traverseSiblings: boolean;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const updateFiberTree = (
|
|
455
524
|
onRender: RenderHandler,
|
|
456
525
|
nextFiber: Fiber,
|
|
457
526
|
prevFiber: Fiber | null,
|
|
458
527
|
): void => {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
// The easiest fix is to strip out the intermediate Fragment fibers,
|
|
476
|
-
// so the Elements panel and Profiler don't need to special case them.
|
|
477
|
-
// Suspense components only have a non-null memoizedState if they're timed-out.
|
|
478
|
-
const prevDidTimeout = isSuspense && prevFiber.memoizedState !== null;
|
|
479
|
-
const nextDidTimeOut = isSuspense && nextFiber.memoizedState !== null;
|
|
480
|
-
|
|
481
|
-
// The logic below is inspired by the code paths in updateSuspenseComponent()
|
|
482
|
-
// inside ReactFiberBeginWork in the React source code.
|
|
483
|
-
if (prevDidTimeout && nextDidTimeOut) {
|
|
484
|
-
// Fallback -> Fallback:
|
|
485
|
-
// 1. Reconcile fallback set.
|
|
486
|
-
const nextFallbackChildSet = nextFiber.child?.sibling ?? null;
|
|
487
|
-
// Note: We can't use nextFiber.child.sibling.alternate
|
|
488
|
-
// because the set is special and alternate may not exist.
|
|
489
|
-
const prevFallbackChildSet = prevFiber.child?.sibling ?? null;
|
|
490
|
-
|
|
491
|
-
if (nextFallbackChildSet !== null && prevFallbackChildSet !== null) {
|
|
492
|
-
updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet);
|
|
493
|
-
}
|
|
494
|
-
} else if (prevDidTimeout && !nextDidTimeOut) {
|
|
495
|
-
// Fallback -> Primary:
|
|
496
|
-
// 1. Unmount fallback set
|
|
497
|
-
// Note: don't emulate fallback unmount because React actually did it.
|
|
498
|
-
// 2. Mount primary set
|
|
499
|
-
const nextPrimaryChildSet = nextFiber.child;
|
|
500
|
-
|
|
501
|
-
if (nextPrimaryChildSet !== null) {
|
|
502
|
-
mountFiberRecursively(onRender, nextPrimaryChildSet, true);
|
|
528
|
+
if (!prevFiber) {
|
|
529
|
+
getFiberId(nextFiber);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const pendingUpdates: FiberUpdate[] = [
|
|
533
|
+
{ fiber: nextFiber, previousFiber: prevFiber, traverseSiblings: false },
|
|
534
|
+
];
|
|
535
|
+
let update: FiberUpdate | undefined;
|
|
536
|
+
while ((update = pendingUpdates.pop())) {
|
|
537
|
+
const { fiber, previousFiber, traverseSiblings } = update;
|
|
538
|
+
if (traverseSiblings && fiber.sibling) {
|
|
539
|
+
pendingUpdates.push({
|
|
540
|
+
fiber: fiber.sibling,
|
|
541
|
+
previousFiber: fiber.sibling.alternate,
|
|
542
|
+
traverseSiblings: true,
|
|
543
|
+
});
|
|
503
544
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
// This is not a real unmount, so it won't get reported by React.
|
|
508
|
-
// We need to manually walk the previous tree and record unmounts.
|
|
509
|
-
unmountFiberChildrenRecursively(onRender, prevFiber);
|
|
510
|
-
|
|
511
|
-
// 2. Mount fallback set
|
|
512
|
-
const nextFallbackChildSet = nextFiber.child?.sibling ?? null;
|
|
513
|
-
|
|
514
|
-
if (nextFallbackChildSet !== null) {
|
|
515
|
-
mountFiberRecursively(onRender, nextFallbackChildSet, true);
|
|
545
|
+
if (!previousFiber) {
|
|
546
|
+
mountFiberTree(onRender, fiber, false);
|
|
547
|
+
continue;
|
|
516
548
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
} else {
|
|
533
|
-
mountFiberRecursively(onRender, nextChild, false);
|
|
549
|
+
getFiberId(fiber);
|
|
550
|
+
getFiberId(previousFiber);
|
|
551
|
+
if (!shouldFilterFiber(fiber) && didFiberRender(fiber)) onRender(fiber, "update");
|
|
552
|
+
const isSuspense = fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent;
|
|
553
|
+
const wasTimedOut = isSuspense && previousFiber.memoizedState !== null;
|
|
554
|
+
const isTimedOut = isSuspense && fiber.memoizedState !== null;
|
|
555
|
+
if (wasTimedOut && isTimedOut) {
|
|
556
|
+
const nextFallback = fiber.child?.sibling;
|
|
557
|
+
const previousFallback = previousFiber.child?.sibling;
|
|
558
|
+
if (nextFallback) {
|
|
559
|
+
pendingUpdates.push({
|
|
560
|
+
fiber: nextFallback,
|
|
561
|
+
previousFiber: previousFallback ?? null,
|
|
562
|
+
traverseSiblings: false,
|
|
563
|
+
});
|
|
534
564
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
565
|
+
} else if (wasTimedOut && !isTimedOut) {
|
|
566
|
+
if (fiber.child) mountFiberTree(onRender, fiber.child, true);
|
|
567
|
+
} else if (!wasTimedOut && isTimedOut) {
|
|
568
|
+
unmountFiberChildren(onRender, previousFiber);
|
|
569
|
+
const fallback = fiber.child?.sibling;
|
|
570
|
+
if (fallback) mountFiberTree(onRender, fallback, true);
|
|
571
|
+
} else if (fiber.child && fiber.child !== previousFiber.child) {
|
|
572
|
+
pendingUpdates.push({
|
|
573
|
+
fiber: fiber.child,
|
|
574
|
+
previousFiber: fiber.child.alternate,
|
|
575
|
+
traverseSiblings: true,
|
|
576
|
+
});
|
|
538
577
|
}
|
|
539
578
|
}
|
|
540
579
|
};
|
|
@@ -547,30 +586,18 @@ const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
|
547
586
|
}
|
|
548
587
|
};
|
|
549
588
|
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
if (isTimedOutSuspense) {
|
|
557
|
-
// If it's showing fallback tree, let's traverse it instead.
|
|
558
|
-
const primaryChildFragment = fiber.child;
|
|
559
|
-
const fallbackChildFragment = primaryChildFragment?.sibling ?? null;
|
|
560
|
-
|
|
561
|
-
// Skip over to the real Fiber child.
|
|
562
|
-
child = fallbackChildFragment?.child ?? null;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
while (child !== null) {
|
|
566
|
-
// Record simulated unmounts children-first.
|
|
567
|
-
// We skip nodes without return because those are real unmounts.
|
|
589
|
+
const unmountFiberChildren = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
590
|
+
const pendingSiblings: Fiber[] = [];
|
|
591
|
+
let child = getRenderedChild(fiber, false);
|
|
592
|
+
while (child) {
|
|
593
|
+
if (child.sibling) pendingSiblings.push(child.sibling);
|
|
568
594
|
if (child.return !== null) {
|
|
569
595
|
unmountFiber(onRender, child);
|
|
570
|
-
|
|
596
|
+
child = getRenderedChild(child, false);
|
|
597
|
+
} else {
|
|
598
|
+
child = null;
|
|
571
599
|
}
|
|
572
|
-
|
|
573
|
-
child = child.sibling;
|
|
600
|
+
child ??= pendingSiblings.pop() ?? null;
|
|
574
601
|
}
|
|
575
602
|
};
|
|
576
603
|
|
|
@@ -602,39 +629,40 @@ const isRootFiberMounted = (fiber: Fiber): boolean => {
|
|
|
602
629
|
*/
|
|
603
630
|
export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: RenderHandler): void => {
|
|
604
631
|
const fiber = "current" in root ? root.current : root;
|
|
632
|
+
const rootKey = "current" in root || !isFiberRoot(root.stateNode) ? root : root.stateNode;
|
|
605
633
|
|
|
606
|
-
let rootInstance = rootInstanceMap.get(
|
|
634
|
+
let rootInstance = rootInstanceMap.get(rootKey);
|
|
607
635
|
|
|
608
636
|
if (!rootInstance) {
|
|
609
637
|
rootInstance = { prevFiber: null };
|
|
610
|
-
rootInstanceMap.set(
|
|
638
|
+
rootInstanceMap.set(rootKey, rootInstance);
|
|
611
639
|
}
|
|
612
640
|
|
|
613
641
|
const { prevFiber } = rootInstance;
|
|
642
|
+
rootInstance.prevFiber = fiber;
|
|
614
643
|
if (!fiber) {
|
|
615
644
|
if (prevFiber) {
|
|
616
645
|
unmountFiber(onRender, prevFiber);
|
|
617
646
|
}
|
|
618
647
|
} else if (prevFiber !== null) {
|
|
619
|
-
const wasMounted = isRootFiberMounted(prevFiber);
|
|
648
|
+
const wasMounted = isRootFiberMounted(fiber.alternate ?? prevFiber);
|
|
620
649
|
const isMounted = isRootFiberMounted(fiber);
|
|
621
650
|
|
|
622
651
|
if (!wasMounted && isMounted) {
|
|
623
|
-
|
|
652
|
+
mountFiberTree(onRender, fiber, false);
|
|
624
653
|
} else if (wasMounted && isMounted) {
|
|
625
|
-
|
|
654
|
+
updateFiberTree(onRender, fiber, fiber.alternate);
|
|
626
655
|
} else if (wasMounted && !isMounted) {
|
|
627
656
|
unmountFiber(onRender, fiber);
|
|
628
657
|
}
|
|
629
658
|
} else {
|
|
630
|
-
|
|
659
|
+
mountFiberTree(onRender, fiber, true);
|
|
631
660
|
}
|
|
632
|
-
|
|
633
|
-
rootInstance.prevFiber = fiber;
|
|
634
661
|
};
|
|
635
662
|
|
|
636
663
|
export interface InstrumentationOptions {
|
|
637
664
|
name?: string;
|
|
665
|
+
target?: ReactDevToolsTarget;
|
|
638
666
|
onActive?: () => unknown;
|
|
639
667
|
onCommitFiberRoot?: (
|
|
640
668
|
rendererID: number,
|
|
@@ -649,6 +677,7 @@ export interface InstrumentationOptions {
|
|
|
649
677
|
|
|
650
678
|
interface InstrumentationSubscription {
|
|
651
679
|
options: InstrumentationOptions;
|
|
680
|
+
target: ReactDevToolsTarget;
|
|
652
681
|
}
|
|
653
682
|
|
|
654
683
|
const instrumentationSubscriptions = new Set<InstrumentationSubscription>();
|
|
@@ -661,6 +690,7 @@ interface HookDispatchers {
|
|
|
661
690
|
}
|
|
662
691
|
|
|
663
692
|
const hookDispatchers = new WeakMap<ReactDevToolsGlobalHook, Partial<HookDispatchers>>();
|
|
693
|
+
const hookTargets = new WeakMap<ReactDevToolsGlobalHook, ReactDevToolsTarget>();
|
|
664
694
|
let didSubscribeToHookReplacements = false;
|
|
665
695
|
|
|
666
696
|
// each hook event is dispatched from a single re-installable wrapper. If
|
|
@@ -683,28 +713,32 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
683
713
|
priority,
|
|
684
714
|
didError,
|
|
685
715
|
) => {
|
|
716
|
+
const isCurrentDispatcher =
|
|
717
|
+
hookDispatchers.get(rdtHook)?.onCommitFiberRoot === dispatchCommitFiberRoot;
|
|
686
718
|
if (prevOnCommitFiberRoot) {
|
|
687
|
-
prevOnCommitFiberRoot
|
|
719
|
+
callListener(prevOnCommitFiberRoot, rdtHook, rendererID, root, priority, didError);
|
|
688
720
|
}
|
|
689
|
-
if (
|
|
721
|
+
if (!isCurrentDispatcher) return;
|
|
690
722
|
setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
|
|
691
723
|
// Custom renderers and test harnesses commit roots without a memoizedState;
|
|
692
724
|
// those must stay tracked, so only explicit unmount evidence removes a root.
|
|
693
|
-
|
|
694
|
-
const isUnmounting =
|
|
695
|
-
rootMemoizedState === null ||
|
|
696
|
-
(rootMemoizedState !== undefined &&
|
|
697
|
-
(rootMemoizedState.element === null || rootMemoizedState.element === undefined));
|
|
698
|
-
if (isUnmounting) {
|
|
725
|
+
if (isFiberRootUnmounted(root)) {
|
|
699
726
|
_fiberRoots.delete(root);
|
|
700
727
|
rootRendererIds.delete(root);
|
|
728
|
+
rootHooks.delete(root);
|
|
701
729
|
} else {
|
|
702
730
|
_fiberRoots.add(root);
|
|
703
731
|
rootRendererIds.set(root, rendererID);
|
|
732
|
+
rootHooks.set(root, rdtHook);
|
|
704
733
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
734
|
+
const subscriptionSnapshot = [...instrumentationSubscriptions];
|
|
735
|
+
for (const subscription of subscriptionSnapshot) {
|
|
736
|
+
const { options, target } = subscription;
|
|
737
|
+
if (instrumentationSubscriptions.has(subscription) && target === hookTargets.get(rdtHook)) {
|
|
738
|
+
callListener(
|
|
739
|
+
() => options.onCommitFiberRoot?.(rendererID, root, priority, didError),
|
|
740
|
+
undefined,
|
|
741
|
+
);
|
|
708
742
|
}
|
|
709
743
|
}
|
|
710
744
|
};
|
|
@@ -721,17 +755,29 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
721
755
|
rendererID,
|
|
722
756
|
fiber,
|
|
723
757
|
) => {
|
|
758
|
+
const isCurrentDispatcher =
|
|
759
|
+
hookDispatchers.get(rdtHook)?.onCommitFiberUnmount === dispatchCommitFiberUnmount;
|
|
724
760
|
setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
for (const { options } of instrumentationSubscriptions) {
|
|
732
|
-
if (options.onCommitFiberUnmount) {
|
|
733
|
-
options.onCommitFiberUnmount(rendererID, fiber);
|
|
761
|
+
const previousUnmountingFiber = currentUnmountingFiber;
|
|
762
|
+
currentUnmountingFiber = fiber;
|
|
763
|
+
try {
|
|
764
|
+
if (prevOnCommitFiberUnmount) {
|
|
765
|
+
callListener(prevOnCommitFiberUnmount, rdtHook, rendererID, fiber);
|
|
734
766
|
}
|
|
767
|
+
if (!isCurrentDispatcher) return;
|
|
768
|
+
const subscriptionSnapshot = [...instrumentationSubscriptions];
|
|
769
|
+
for (const subscription of subscriptionSnapshot) {
|
|
770
|
+
const { options, target } = subscription;
|
|
771
|
+
if (
|
|
772
|
+
instrumentationSubscriptions.has(subscription) &&
|
|
773
|
+
target === hookTargets.get(rdtHook)
|
|
774
|
+
) {
|
|
775
|
+
callListener(() => options.onCommitFiberUnmount?.(rendererID, fiber), undefined);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
} finally {
|
|
779
|
+
currentUnmountingFiber = previousUnmountingFiber;
|
|
780
|
+
if (isCurrentDispatcher) releaseFiberId(fiber);
|
|
735
781
|
}
|
|
736
782
|
};
|
|
737
783
|
dispatchers.onCommitFiberUnmount = dispatchCommitFiberUnmount;
|
|
@@ -747,15 +793,17 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
747
793
|
rendererID,
|
|
748
794
|
root,
|
|
749
795
|
) => {
|
|
796
|
+
const isCurrentDispatcher =
|
|
797
|
+
hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot === dispatchPostCommitFiberRoot;
|
|
750
798
|
if (prevOnPostCommitFiberRoot) {
|
|
751
|
-
prevOnPostCommitFiberRoot
|
|
799
|
+
callListener(prevOnPostCommitFiberRoot, rdtHook, rendererID, root);
|
|
752
800
|
}
|
|
753
|
-
if (
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
if (
|
|
758
|
-
options.onPostCommitFiberRoot(rendererID, root);
|
|
801
|
+
if (!isCurrentDispatcher) return;
|
|
802
|
+
const subscriptionSnapshot = [...instrumentationSubscriptions];
|
|
803
|
+
for (const subscription of subscriptionSnapshot) {
|
|
804
|
+
const { options, target } = subscription;
|
|
805
|
+
if (instrumentationSubscriptions.has(subscription) && target === hookTargets.get(rdtHook)) {
|
|
806
|
+
callListener(() => options.onPostCommitFiberRoot?.(rendererID, root), undefined);
|
|
759
807
|
}
|
|
760
808
|
}
|
|
761
809
|
};
|
|
@@ -773,13 +821,17 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
773
821
|
root,
|
|
774
822
|
children,
|
|
775
823
|
) => {
|
|
824
|
+
const isCurrentDispatcher =
|
|
825
|
+
hookDispatchers.get(rdtHook)?.onScheduleFiberRoot === dispatchScheduleFiberRoot;
|
|
776
826
|
if (prevOnScheduleFiberRoot) {
|
|
777
|
-
prevOnScheduleFiberRoot
|
|
827
|
+
callListener(prevOnScheduleFiberRoot, rdtHook, rendererID, root, children);
|
|
778
828
|
}
|
|
779
|
-
if (
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
829
|
+
if (!isCurrentDispatcher) return;
|
|
830
|
+
const subscriptionSnapshot = [...instrumentationSubscriptions];
|
|
831
|
+
for (const subscription of subscriptionSnapshot) {
|
|
832
|
+
const { options, target } = subscription;
|
|
833
|
+
if (instrumentationSubscriptions.has(subscription) && target === hookTargets.get(rdtHook)) {
|
|
834
|
+
callListener(() => options.onScheduleFiberRoot?.(rendererID, root, children), undefined);
|
|
783
835
|
}
|
|
784
836
|
}
|
|
785
837
|
};
|
|
@@ -788,11 +840,23 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
788
840
|
}
|
|
789
841
|
};
|
|
790
842
|
|
|
791
|
-
const
|
|
843
|
+
const handleHookReplacement = (
|
|
844
|
+
rdtHook: ReactDevToolsGlobalHook,
|
|
845
|
+
target: ReactDevToolsTarget,
|
|
846
|
+
): void => {
|
|
847
|
+
hookTargets.set(rdtHook, target);
|
|
848
|
+
setHookEventDispatchers(rdtHook);
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
const wireHookEventDispatchers = (
|
|
852
|
+
rdtHook: ReactDevToolsGlobalHook,
|
|
853
|
+
target: ReactDevToolsTarget = globalThis,
|
|
854
|
+
): void => {
|
|
792
855
|
if (!didSubscribeToHookReplacements) {
|
|
793
|
-
onRDTHookReplace(
|
|
856
|
+
onRDTHookReplace(handleHookReplacement);
|
|
794
857
|
didSubscribeToHookReplacements = true;
|
|
795
858
|
}
|
|
859
|
+
hookTargets.set(rdtHook, target);
|
|
796
860
|
setHookEventDispatchers(rdtHook);
|
|
797
861
|
};
|
|
798
862
|
|
|
@@ -822,15 +886,21 @@ try {
|
|
|
822
886
|
* unsubscribe();
|
|
823
887
|
*/
|
|
824
888
|
export const instrument = (options: InstrumentationOptions): Unsubscribe => {
|
|
825
|
-
const
|
|
889
|
+
const target = options.target ?? globalThis;
|
|
890
|
+
const activeListener = options.onActive;
|
|
891
|
+
const onActive = activeListener ? () => activeListener() : undefined;
|
|
892
|
+
const existingHook = target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
893
|
+
if (existingHook) wireHookEventDispatchers(existingHook, target);
|
|
894
|
+
const rdtHook = getRDTHook(undefined, target);
|
|
826
895
|
rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
|
|
827
896
|
|
|
828
|
-
wireHookEventDispatchers(rdtHook);
|
|
829
|
-
const subscription: InstrumentationSubscription = { options };
|
|
897
|
+
wireHookEventDispatchers(rdtHook, target);
|
|
898
|
+
const subscription: InstrumentationSubscription = { options, target };
|
|
830
899
|
instrumentationSubscriptions.add(subscription);
|
|
900
|
+
if (onActive) getRDTHook(onActive, target);
|
|
831
901
|
|
|
832
902
|
return createUnsubscribe(() => {
|
|
833
|
-
if (
|
|
903
|
+
if (onActive) removeActiveListener(onActive, target);
|
|
834
904
|
instrumentationSubscriptions.delete(subscription);
|
|
835
905
|
});
|
|
836
906
|
};
|
|
@@ -874,9 +944,12 @@ const getPublicHostInstance = (stateNode: unknown): unknown => {
|
|
|
874
944
|
return typeof nativeTag === "number" ? nativeTag : stateNode;
|
|
875
945
|
};
|
|
876
946
|
|
|
877
|
-
export const getFiber = <
|
|
878
|
-
|
|
879
|
-
|
|
947
|
+
export const getFiber = <HostInstance>(
|
|
948
|
+
hostInstance: HostInstance,
|
|
949
|
+
target: ReactDevToolsTarget = globalThis,
|
|
950
|
+
): Fiber | null => {
|
|
951
|
+
const rdtHook = target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
952
|
+
if (rdtHook) {
|
|
880
953
|
for (const renderer of rdtHook.renderers.values()) {
|
|
881
954
|
try {
|
|
882
955
|
const fiber = renderer.findFiberByHostInstance?.(hostInstance);
|
|
@@ -899,19 +972,31 @@ export const getFiber = <T>(hostInstance: T): Fiber | null => {
|
|
|
899
972
|
|
|
900
973
|
for (const key of Object.keys(hostInstance)) {
|
|
901
974
|
if (isFiberPropertyKey(key)) {
|
|
902
|
-
knownFiberPropertyKeys.add(key);
|
|
903
975
|
const fiber = Reflect.get(hostInstance, key);
|
|
904
|
-
if (isFiber(fiber))
|
|
976
|
+
if (isFiber(fiber)) {
|
|
977
|
+
knownFiberPropertyKeys.add(key);
|
|
978
|
+
return fiber;
|
|
979
|
+
}
|
|
905
980
|
}
|
|
906
981
|
}
|
|
907
982
|
}
|
|
908
983
|
|
|
909
984
|
if (
|
|
910
985
|
hostInstance !== null &&
|
|
911
|
-
hostInstance !== undefined &&
|
|
912
986
|
(typeof hostInstance === "object" || typeof hostInstance === "number")
|
|
913
987
|
) {
|
|
988
|
+
const targetFiberRoots = new Set<FiberRoot>();
|
|
989
|
+
if (rdtHook?.getFiberRoots) {
|
|
990
|
+
for (const rendererId of rdtHook.renderers.keys()) {
|
|
991
|
+
for (const fiberRoot of rdtHook.getFiberRoots(rendererId)) {
|
|
992
|
+
targetFiberRoots.add(fiberRoot);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
914
996
|
for (const fiberRoot of _fiberRoots) {
|
|
997
|
+
if (rootHooks.get(fiberRoot) === rdtHook) targetFiberRoots.add(fiberRoot);
|
|
998
|
+
}
|
|
999
|
+
for (const fiberRoot of targetFiberRoots) {
|
|
915
1000
|
const fiber = traverseFiber(
|
|
916
1001
|
fiberRoot.current,
|
|
917
1002
|
(candidateFiber) =>
|
|
@@ -928,16 +1013,16 @@ export const getFiberFromHostInstance = getFiber;
|
|
|
928
1013
|
|
|
929
1014
|
export {
|
|
930
1015
|
BIPPY_INSTRUMENTATION_STRING,
|
|
931
|
-
_onActiveListeners,
|
|
932
1016
|
_renderers,
|
|
933
1017
|
getRDTHook,
|
|
934
1018
|
hasRDTHook,
|
|
935
1019
|
installRDTHook,
|
|
1020
|
+
isFiberRootUnmounted,
|
|
936
1021
|
isReactRefresh,
|
|
937
1022
|
isRealReactDevtools,
|
|
938
1023
|
onRendererInject,
|
|
939
1024
|
patchRDTHook,
|
|
940
1025
|
version,
|
|
941
1026
|
} from "./rdt-hook.js";
|
|
942
|
-
export type { Unsubscribe } from "./rdt-hook.js";
|
|
1027
|
+
export type { ReactDevToolsTarget, Unsubscribe } from "./rdt-hook.js";
|
|
943
1028
|
export * from "./react-internals/index.js";
|