bippy 0.7.3-dev.8ba0705 → 0.7.3-dev.c0e872e
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/LICENSE +1 -0
- package/README.md +12 -11
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +4 -1
- package/dist/errors.d.ts +4 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +32 -29
- package/dist/index.d.ts +34 -31
- package/dist/index.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +11 -12
- package/dist/source.d.cts +22 -22
- package/dist/source.d.ts +22 -22
- package/dist/source.js +12 -13
- package/package.json +6 -34
- package/src/call-listener.ts +13 -0
- package/src/core.ts +248 -242
- package/src/rdt-hook.ts +45 -29
- 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.ts +141 -56
- package/src/source/inspect-hooks.ts +29 -21
- package/src/source/parse-debug-stack.ts +1 -1
- package/src/source/parse-stack.ts +111 -63
- package/src/source/symbolication.ts +15 -4
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,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
removeActiveListener,
|
|
22
23
|
} from "./rdt-hook.js";
|
|
23
24
|
import type { ReactDevToolsTarget, Unsubscribe } from "./rdt-hook.js";
|
|
25
|
+
import { getCurrentFiberFromRoot } from "./react-internals/current-fiber.js";
|
|
24
26
|
import {
|
|
25
27
|
getReactWorkTagsForFiber,
|
|
26
28
|
ReactBuildType,
|
|
@@ -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
|
|
|
@@ -166,16 +168,20 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
166
168
|
// https://github.com/bvaughn/react-devtools-experimental/issues/197
|
|
167
169
|
return true;
|
|
168
170
|
|
|
169
|
-
case workTags.
|
|
171
|
+
case workTags.HostPortal:
|
|
170
172
|
case workTags.HostText:
|
|
171
173
|
case workTags.LegacyHiddenComponent:
|
|
172
174
|
case workTags.OffscreenComponent:
|
|
175
|
+
case workTags.Throw:
|
|
173
176
|
return true;
|
|
174
177
|
|
|
175
178
|
case workTags.HostRoot:
|
|
176
179
|
// It is never valid to filter the root element.
|
|
177
180
|
return false;
|
|
178
181
|
|
|
182
|
+
case workTags.Fragment:
|
|
183
|
+
return fiber.key === null;
|
|
184
|
+
|
|
179
185
|
default: {
|
|
180
186
|
const symbolOrNumber =
|
|
181
187
|
typeof fiber.type === "object" && fiber.type !== null ? fiber.type.$$typeof : fiber.type;
|
|
@@ -183,7 +189,8 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
183
189
|
if (typeof symbolOrNumber === "symbol") {
|
|
184
190
|
return (
|
|
185
191
|
symbolOrNumber.description === ReactSymbols.CONCURRENT_MODE_SYMBOL_DESCRIPTION ||
|
|
186
|
-
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
|
|
187
194
|
);
|
|
188
195
|
}
|
|
189
196
|
|
|
@@ -191,6 +198,8 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
191
198
|
case ReactSymbols.CONCURRENT_MODE_NUMBER:
|
|
192
199
|
case ReactSymbols.CONCURRENT_MODE_SYMBOL_STRING:
|
|
193
200
|
case ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
|
|
201
|
+
case ReactSymbols.STRICT_MODE_NUMBER:
|
|
202
|
+
case ReactSymbols.STRICT_MODE_SYMBOL_STRING:
|
|
194
203
|
return true;
|
|
195
204
|
|
|
196
205
|
default:
|
|
@@ -200,73 +209,64 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
200
209
|
}
|
|
201
210
|
};
|
|
202
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
|
+
|
|
203
230
|
/**
|
|
204
231
|
* Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
|
|
205
232
|
*/
|
|
206
|
-
export
|
|
207
|
-
fiber: Fiber | null,
|
|
208
|
-
selector: (node: Fiber) => boolean | void,
|
|
209
|
-
ascending?: boolean,
|
|
210
|
-
): Fiber | null;
|
|
211
|
-
export function traverseFiber(
|
|
212
|
-
fiber: Fiber | null,
|
|
213
|
-
selector: (node: Fiber) => Promise<boolean | void>,
|
|
214
|
-
ascending?: boolean,
|
|
215
|
-
): Promise<Fiber | null>;
|
|
216
|
-
export function traverseFiber(
|
|
217
|
-
fiber: Fiber | null,
|
|
218
|
-
selector: FiberSelector,
|
|
219
|
-
ascending?: boolean,
|
|
220
|
-
): Fiber | null | Promise<Fiber | null>;
|
|
221
|
-
export function traverseFiber(
|
|
233
|
+
export const traverseFiber = ((
|
|
222
234
|
fiber: Fiber | null,
|
|
223
235
|
selector: FiberSelector,
|
|
224
236
|
ascending = false,
|
|
225
|
-
): Fiber | null | Promise<Fiber | null> {
|
|
226
|
-
if (!fiber) return null;
|
|
227
|
-
|
|
228
|
-
const selection = selector(fiber);
|
|
229
|
-
if (isPromiseLike<boolean | void>(selection)) {
|
|
230
|
-
return Promise.resolve(selection).then((didSelectFiber) =>
|
|
231
|
-
didSelectFiber === true ? fiber : traverseFiberChildren(fiber, selector, ascending),
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
if (selection === true) return fiber;
|
|
235
|
-
|
|
236
|
-
return traverseFiberChildren(fiber, selector, ascending);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
|
|
240
|
-
(typeof value === "object" || typeof value === "function") &&
|
|
241
|
-
value !== null &&
|
|
242
|
-
"then" in value &&
|
|
243
|
-
typeof value.then === "function";
|
|
244
|
-
|
|
245
|
-
const traverseFiberChildren = (
|
|
246
|
-
fiber: Fiber,
|
|
247
|
-
selector: FiberSelector,
|
|
248
|
-
ascending: boolean,
|
|
249
237
|
): Fiber | null | Promise<Fiber | null> => {
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
): Fiber | null | Promise<Fiber | null> => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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;
|
|
270
270
|
|
|
271
271
|
/**
|
|
272
272
|
* Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
|
|
@@ -281,7 +281,14 @@ export const hasMemoCache = (fiber: Fiber): boolean => {
|
|
|
281
281
|
export const getType = (type: unknown): null | React.ComponentType<unknown> => {
|
|
282
282
|
if (isComponentType(type)) return type;
|
|
283
283
|
if (typeof type !== "object" || type === null) return null;
|
|
284
|
-
|
|
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;
|
|
285
292
|
};
|
|
286
293
|
|
|
287
294
|
/**
|
|
@@ -324,6 +331,7 @@ export const isInstrumentationActive = (target: ReactDevToolsTarget = globalThis
|
|
|
324
331
|
export const _fiberRoots = new Set<FiberRoot>();
|
|
325
332
|
const rootRendererIds = new WeakMap<FiberRoot, number>();
|
|
326
333
|
const rootHooks = new WeakMap<FiberRoot, ReactDevToolsGlobalHook>();
|
|
334
|
+
let currentUnmountingFiber: Fiber | null = null;
|
|
327
335
|
|
|
328
336
|
/**
|
|
329
337
|
* Returns the latest fiber (since it may be double-buffered).
|
|
@@ -331,6 +339,10 @@ const rootHooks = new WeakMap<FiberRoot, ReactDevToolsGlobalHook>();
|
|
|
331
339
|
export const getLatestFiber = (fiber: Fiber): Fiber => {
|
|
332
340
|
const alternate = fiber.alternate;
|
|
333
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;
|
|
334
346
|
|
|
335
347
|
let rootFiber = fiber;
|
|
336
348
|
while (rootFiber.return) {
|
|
@@ -342,6 +354,7 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
|
|
|
342
354
|
});
|
|
343
355
|
if (latestFiber) return latestFiber;
|
|
344
356
|
}
|
|
357
|
+
if (currentFiber) return currentFiber;
|
|
345
358
|
|
|
346
359
|
if (alternate.actualStartTime && fiber.actualStartTime) {
|
|
347
360
|
return alternate.actualStartTime > fiber.actualStartTime ? alternate : fiber;
|
|
@@ -405,14 +418,24 @@ const fiberIdFinalizationRegistry =
|
|
|
405
418
|
const createFiberReference = (fiber: Fiber): FiberReference =>
|
|
406
419
|
typeof WeakRef === "function" ? new WeakRef(fiber) : { deref: () => fiber };
|
|
407
420
|
|
|
408
|
-
|
|
421
|
+
const getNextFiberId = (): number => {
|
|
422
|
+
if (!Number.isSafeInteger(nextFiberId)) throw new RangeError("Fiber ID space exhausted");
|
|
423
|
+
return nextFiberId++;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
export const setFiberId = (fiber: Fiber, fiberId: number = getNextFiberId()): void => {
|
|
409
427
|
const previousFiberId = fiberIdMap.get(fiber);
|
|
410
|
-
if (
|
|
428
|
+
if (
|
|
429
|
+
previousFiberId !== undefined &&
|
|
430
|
+
previousFiberId !== fiberId &&
|
|
431
|
+
fiberByIdMap.get(previousFiberId)?.deref() === fiber
|
|
432
|
+
) {
|
|
411
433
|
fiberByIdMap.delete(previousFiberId);
|
|
412
434
|
}
|
|
413
435
|
fiberIdMap.set(fiber, fiberId);
|
|
414
436
|
fiberByIdMap.set(fiberId, createFiberReference(fiber));
|
|
415
|
-
fiberIdFinalizationRegistry?.
|
|
437
|
+
if (previousFiberId !== undefined) fiberIdFinalizationRegistry?.unregister(fiber);
|
|
438
|
+
fiberIdFinalizationRegistry?.register(fiber, fiberId, fiber);
|
|
416
439
|
if (Number.isSafeInteger(fiberId) && fiberId >= nextFiberId) {
|
|
417
440
|
nextFiberId = fiberId + 1;
|
|
418
441
|
}
|
|
@@ -422,10 +445,17 @@ export const getFiberId = (fiber: Fiber): number => {
|
|
|
422
445
|
let currentFiberId = fiberIdMap.get(fiber);
|
|
423
446
|
if (currentFiberId === undefined && fiber.alternate) {
|
|
424
447
|
currentFiberId = fiberIdMap.get(fiber.alternate);
|
|
425
|
-
if (currentFiberId !== undefined)
|
|
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
|
+
}
|
|
426
456
|
}
|
|
427
457
|
if (currentFiberId === undefined) {
|
|
428
|
-
currentFiberId =
|
|
458
|
+
currentFiberId = getNextFiberId();
|
|
429
459
|
setFiberId(fiber, currentFiberId);
|
|
430
460
|
}
|
|
431
461
|
return currentFiberId;
|
|
@@ -448,6 +478,7 @@ const releaseFiberId = (fiber: Fiber): void => {
|
|
|
448
478
|
const fiberId = fiberIdMap.get(relatedFiber);
|
|
449
479
|
if (fiberId !== undefined) fiberIds.add(fiberId);
|
|
450
480
|
fiberIdMap.delete(relatedFiber);
|
|
481
|
+
fiberIdFinalizationRegistry?.unregister(relatedFiber);
|
|
451
482
|
}
|
|
452
483
|
|
|
453
484
|
for (const fiberId of fiberIds) {
|
|
@@ -458,131 +489,91 @@ const releaseFiberId = (fiber: Fiber): void => {
|
|
|
458
489
|
}
|
|
459
490
|
};
|
|
460
491
|
|
|
461
|
-
const
|
|
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 = (
|
|
462
502
|
onRender: RenderHandler,
|
|
463
503
|
firstChild: Fiber,
|
|
464
504
|
traverseSiblings: boolean,
|
|
465
505
|
): void => {
|
|
506
|
+
const pendingSiblings: Fiber[] = [];
|
|
466
507
|
let fiber: Fiber | null = firstChild;
|
|
467
|
-
|
|
468
|
-
while (fiber !== null) {
|
|
508
|
+
while (fiber) {
|
|
469
509
|
getFiberId(fiber);
|
|
470
|
-
|
|
471
|
-
if (
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent) {
|
|
476
|
-
const isTimedOut = fiber.memoizedState !== null;
|
|
477
|
-
if (isTimedOut) {
|
|
478
|
-
// Special case: if Suspense mounts in a timed-out state,
|
|
479
|
-
// get the fallback child from the inner fragment and mount
|
|
480
|
-
// it as if it was our own child. Updates handle this too.
|
|
481
|
-
const primaryChildFragment = fiber.child;
|
|
482
|
-
const fallbackChildFragment = primaryChildFragment ? primaryChildFragment.sibling : null;
|
|
483
|
-
if (fallbackChildFragment) {
|
|
484
|
-
const fallbackChild = fallbackChildFragment.child;
|
|
485
|
-
if (fallbackChild !== null) {
|
|
486
|
-
mountFiberRecursively(onRender, fallbackChild, false);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
} else {
|
|
490
|
-
const primaryChild = fiber.child?.child ?? null;
|
|
491
|
-
if (primaryChild !== null) {
|
|
492
|
-
mountFiberRecursively(onRender, primaryChild, false);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
} else if (fiber.child !== null) {
|
|
496
|
-
mountFiberRecursively(onRender, fiber.child, true);
|
|
497
|
-
}
|
|
498
|
-
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;
|
|
499
514
|
}
|
|
500
515
|
};
|
|
501
516
|
|
|
502
|
-
|
|
517
|
+
interface FiberUpdate {
|
|
518
|
+
fiber: Fiber;
|
|
519
|
+
previousFiber: Fiber | null;
|
|
520
|
+
traverseSiblings: boolean;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const updateFiberTree = (
|
|
503
524
|
onRender: RenderHandler,
|
|
504
525
|
nextFiber: Fiber,
|
|
505
526
|
prevFiber: Fiber | null,
|
|
506
527
|
): void => {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
// The easiest fix is to strip out the intermediate Fragment fibers,
|
|
524
|
-
// so the Elements panel and Profiler don't need to special case them.
|
|
525
|
-
// Suspense components only have a non-null memoizedState if they're timed-out.
|
|
526
|
-
const prevDidTimeout = isSuspense && prevFiber.memoizedState !== null;
|
|
527
|
-
const nextDidTimeOut = isSuspense && nextFiber.memoizedState !== null;
|
|
528
|
-
|
|
529
|
-
// The logic below is inspired by the code paths in updateSuspenseComponent()
|
|
530
|
-
// inside ReactFiberBeginWork in the React source code.
|
|
531
|
-
if (prevDidTimeout && nextDidTimeOut) {
|
|
532
|
-
// Fallback -> Fallback:
|
|
533
|
-
// 1. Reconcile fallback set.
|
|
534
|
-
const nextFallbackChildSet = nextFiber.child?.sibling ?? null;
|
|
535
|
-
// Note: We can't use nextFiber.child.sibling.alternate
|
|
536
|
-
// because the set is special and alternate may not exist.
|
|
537
|
-
const prevFallbackChildSet = prevFiber.child?.sibling ?? null;
|
|
538
|
-
|
|
539
|
-
if (nextFallbackChildSet !== null && prevFallbackChildSet !== null) {
|
|
540
|
-
updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet);
|
|
541
|
-
}
|
|
542
|
-
} else if (prevDidTimeout && !nextDidTimeOut) {
|
|
543
|
-
// Fallback -> Primary:
|
|
544
|
-
// 1. Unmount fallback set
|
|
545
|
-
// Note: don't emulate fallback unmount because React actually did it.
|
|
546
|
-
// 2. Mount primary set
|
|
547
|
-
const nextPrimaryChildSet = nextFiber.child;
|
|
548
|
-
|
|
549
|
-
if (nextPrimaryChildSet !== null) {
|
|
550
|
-
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
|
+
});
|
|
551
544
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// This is not a real unmount, so it won't get reported by React.
|
|
556
|
-
// We need to manually walk the previous tree and record unmounts.
|
|
557
|
-
unmountFiberChildrenRecursively(onRender, prevFiber);
|
|
558
|
-
|
|
559
|
-
// 2. Mount fallback set
|
|
560
|
-
const nextFallbackChildSet = nextFiber.child?.sibling ?? null;
|
|
561
|
-
|
|
562
|
-
if (nextFallbackChildSet !== null) {
|
|
563
|
-
mountFiberRecursively(onRender, nextFallbackChildSet, true);
|
|
545
|
+
if (!previousFiber) {
|
|
546
|
+
mountFiberTree(onRender, fiber, false);
|
|
547
|
+
continue;
|
|
564
548
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
} else {
|
|
581
|
-
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
|
+
});
|
|
582
564
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
+
});
|
|
586
577
|
}
|
|
587
578
|
}
|
|
588
579
|
};
|
|
@@ -595,30 +586,18 @@ const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
|
595
586
|
}
|
|
596
587
|
};
|
|
597
588
|
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (isTimedOutSuspense) {
|
|
605
|
-
// If it's showing fallback tree, let's traverse it instead.
|
|
606
|
-
const primaryChildFragment = fiber.child;
|
|
607
|
-
const fallbackChildFragment = primaryChildFragment?.sibling ?? null;
|
|
608
|
-
|
|
609
|
-
// Skip over to the real Fiber child.
|
|
610
|
-
child = fallbackChildFragment?.child ?? null;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
while (child !== null) {
|
|
614
|
-
// Record simulated unmounts children-first.
|
|
615
|
-
// 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);
|
|
616
594
|
if (child.return !== null) {
|
|
617
595
|
unmountFiber(onRender, child);
|
|
618
|
-
|
|
596
|
+
child = getRenderedChild(child, false);
|
|
597
|
+
} else {
|
|
598
|
+
child = null;
|
|
619
599
|
}
|
|
620
|
-
|
|
621
|
-
child = child.sibling;
|
|
600
|
+
child ??= pendingSiblings.pop() ?? null;
|
|
622
601
|
}
|
|
623
602
|
};
|
|
624
603
|
|
|
@@ -650,35 +629,35 @@ const isRootFiberMounted = (fiber: Fiber): boolean => {
|
|
|
650
629
|
*/
|
|
651
630
|
export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: RenderHandler): void => {
|
|
652
631
|
const fiber = "current" in root ? root.current : root;
|
|
632
|
+
const rootKey = "current" in root || !isFiberRoot(root.stateNode) ? root : root.stateNode;
|
|
653
633
|
|
|
654
|
-
let rootInstance = rootInstanceMap.get(
|
|
634
|
+
let rootInstance = rootInstanceMap.get(rootKey);
|
|
655
635
|
|
|
656
636
|
if (!rootInstance) {
|
|
657
637
|
rootInstance = { prevFiber: null };
|
|
658
|
-
rootInstanceMap.set(
|
|
638
|
+
rootInstanceMap.set(rootKey, rootInstance);
|
|
659
639
|
}
|
|
660
640
|
|
|
661
641
|
const { prevFiber } = rootInstance;
|
|
642
|
+
rootInstance.prevFiber = fiber;
|
|
662
643
|
if (!fiber) {
|
|
663
644
|
if (prevFiber) {
|
|
664
645
|
unmountFiber(onRender, prevFiber);
|
|
665
646
|
}
|
|
666
647
|
} else if (prevFiber !== null) {
|
|
667
|
-
const wasMounted = isRootFiberMounted(prevFiber);
|
|
648
|
+
const wasMounted = isRootFiberMounted(fiber.alternate ?? prevFiber);
|
|
668
649
|
const isMounted = isRootFiberMounted(fiber);
|
|
669
650
|
|
|
670
651
|
if (!wasMounted && isMounted) {
|
|
671
|
-
|
|
652
|
+
mountFiberTree(onRender, fiber, false);
|
|
672
653
|
} else if (wasMounted && isMounted) {
|
|
673
|
-
|
|
654
|
+
updateFiberTree(onRender, fiber, fiber.alternate);
|
|
674
655
|
} else if (wasMounted && !isMounted) {
|
|
675
656
|
unmountFiber(onRender, fiber);
|
|
676
657
|
}
|
|
677
658
|
} else {
|
|
678
|
-
|
|
659
|
+
mountFiberTree(onRender, fiber, true);
|
|
679
660
|
}
|
|
680
|
-
|
|
681
|
-
rootInstance.prevFiber = fiber;
|
|
682
661
|
};
|
|
683
662
|
|
|
684
663
|
export interface InstrumentationOptions {
|
|
@@ -734,10 +713,12 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
734
713
|
priority,
|
|
735
714
|
didError,
|
|
736
715
|
) => {
|
|
716
|
+
const isCurrentDispatcher =
|
|
717
|
+
hookDispatchers.get(rdtHook)?.onCommitFiberRoot === dispatchCommitFiberRoot;
|
|
737
718
|
if (prevOnCommitFiberRoot) {
|
|
738
|
-
prevOnCommitFiberRoot
|
|
719
|
+
callListener(prevOnCommitFiberRoot, rdtHook, rendererID, root, priority, didError);
|
|
739
720
|
}
|
|
740
|
-
if (
|
|
721
|
+
if (!isCurrentDispatcher) return;
|
|
741
722
|
setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
|
|
742
723
|
// Custom renderers and test harnesses commit roots without a memoizedState;
|
|
743
724
|
// those must stay tracked, so only explicit unmount evidence removes a root.
|
|
@@ -750,9 +731,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
750
731
|
rootRendererIds.set(root, rendererID);
|
|
751
732
|
rootHooks.set(root, rdtHook);
|
|
752
733
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
+
);
|
|
756
742
|
}
|
|
757
743
|
}
|
|
758
744
|
};
|
|
@@ -769,21 +755,29 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
769
755
|
rendererID,
|
|
770
756
|
fiber,
|
|
771
757
|
) => {
|
|
758
|
+
const isCurrentDispatcher =
|
|
759
|
+
hookDispatchers.get(rdtHook)?.onCommitFiberUnmount === dispatchCommitFiberUnmount;
|
|
772
760
|
setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
761
|
+
const previousUnmountingFiber = currentUnmountingFiber;
|
|
762
|
+
currentUnmountingFiber = fiber;
|
|
779
763
|
try {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
764
|
+
if (prevOnCommitFiberUnmount) {
|
|
765
|
+
callListener(prevOnCommitFiberUnmount, rdtHook, rendererID, fiber);
|
|
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);
|
|
783
776
|
}
|
|
784
777
|
}
|
|
785
778
|
} finally {
|
|
786
|
-
|
|
779
|
+
currentUnmountingFiber = previousUnmountingFiber;
|
|
780
|
+
if (isCurrentDispatcher) releaseFiberId(fiber);
|
|
787
781
|
}
|
|
788
782
|
};
|
|
789
783
|
dispatchers.onCommitFiberUnmount = dispatchCommitFiberUnmount;
|
|
@@ -799,15 +793,17 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
799
793
|
rendererID,
|
|
800
794
|
root,
|
|
801
795
|
) => {
|
|
796
|
+
const isCurrentDispatcher =
|
|
797
|
+
hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot === dispatchPostCommitFiberRoot;
|
|
802
798
|
if (prevOnPostCommitFiberRoot) {
|
|
803
|
-
prevOnPostCommitFiberRoot
|
|
799
|
+
callListener(prevOnPostCommitFiberRoot, rdtHook, rendererID, root);
|
|
804
800
|
}
|
|
805
|
-
if (
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
if (target === hookTargets.get(rdtHook)
|
|
810
|
-
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);
|
|
811
807
|
}
|
|
812
808
|
}
|
|
813
809
|
};
|
|
@@ -825,13 +821,17 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
825
821
|
root,
|
|
826
822
|
children,
|
|
827
823
|
) => {
|
|
824
|
+
const isCurrentDispatcher =
|
|
825
|
+
hookDispatchers.get(rdtHook)?.onScheduleFiberRoot === dispatchScheduleFiberRoot;
|
|
828
826
|
if (prevOnScheduleFiberRoot) {
|
|
829
|
-
prevOnScheduleFiberRoot
|
|
827
|
+
callListener(prevOnScheduleFiberRoot, rdtHook, rendererID, root, children);
|
|
830
828
|
}
|
|
831
|
-
if (
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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);
|
|
835
835
|
}
|
|
836
836
|
}
|
|
837
837
|
};
|
|
@@ -887,15 +887,20 @@ try {
|
|
|
887
887
|
*/
|
|
888
888
|
export const instrument = (options: InstrumentationOptions): Unsubscribe => {
|
|
889
889
|
const target = options.target ?? globalThis;
|
|
890
|
-
const
|
|
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);
|
|
891
895
|
rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
|
|
892
896
|
|
|
893
897
|
wireHookEventDispatchers(rdtHook, target);
|
|
894
898
|
const subscription: InstrumentationSubscription = { options, target };
|
|
895
899
|
instrumentationSubscriptions.add(subscription);
|
|
900
|
+
if (onActive) getRDTHook(onActive, target);
|
|
896
901
|
|
|
897
902
|
return createUnsubscribe(() => {
|
|
898
|
-
if (
|
|
903
|
+
if (onActive) removeActiveListener(onActive, target);
|
|
899
904
|
instrumentationSubscriptions.delete(subscription);
|
|
900
905
|
});
|
|
901
906
|
};
|
|
@@ -967,16 +972,17 @@ export const getFiber = <HostInstance>(
|
|
|
967
972
|
|
|
968
973
|
for (const key of Object.keys(hostInstance)) {
|
|
969
974
|
if (isFiberPropertyKey(key)) {
|
|
970
|
-
knownFiberPropertyKeys.add(key);
|
|
971
975
|
const fiber = Reflect.get(hostInstance, key);
|
|
972
|
-
if (isFiber(fiber))
|
|
976
|
+
if (isFiber(fiber)) {
|
|
977
|
+
knownFiberPropertyKeys.add(key);
|
|
978
|
+
return fiber;
|
|
979
|
+
}
|
|
973
980
|
}
|
|
974
981
|
}
|
|
975
982
|
}
|
|
976
983
|
|
|
977
984
|
if (
|
|
978
985
|
hostInstance !== null &&
|
|
979
|
-
hostInstance !== undefined &&
|
|
980
986
|
(typeof hostInstance === "object" || typeof hostInstance === "number")
|
|
981
987
|
) {
|
|
982
988
|
const targetFiberRoots = new Set<FiberRoot>();
|