bippy 0.7.3-dev.049a43d → 0.7.3-dev.0ac832d
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 +1 -8
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +10 -7
- 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.js +12 -13
- package/package.json +3 -31
- package/src/call-listener.ts +13 -0
- package/src/core.ts +154 -203
- package/src/rdt-hook.ts +7 -4
- package/src/react-internals/current-fiber.ts +84 -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-stack.ts +111 -63
- package/src/source/symbolication.ts +15 -4
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const callListener = <Arguments extends unknown[]>(
|
|
2
|
+
listener: (...listenerArguments: Arguments) => unknown,
|
|
3
|
+
receiver: unknown,
|
|
4
|
+
...listenerArguments: Arguments
|
|
5
|
+
): void => {
|
|
6
|
+
try {
|
|
7
|
+
listener.apply(receiver, listenerArguments);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
try {
|
|
10
|
+
console.error("Bippy instrumentation encountered an error:", error);
|
|
11
|
+
} catch {}
|
|
12
|
+
}
|
|
13
|
+
};
|
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
|
|
|
@@ -200,41 +202,57 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
200
202
|
}
|
|
201
203
|
};
|
|
202
204
|
|
|
205
|
+
interface TraverseFiber {
|
|
206
|
+
(
|
|
207
|
+
fiber: Fiber | null,
|
|
208
|
+
selector: (node: Fiber) => boolean | void,
|
|
209
|
+
ascending?: boolean,
|
|
210
|
+
): Fiber | null;
|
|
211
|
+
(
|
|
212
|
+
fiber: Fiber | null,
|
|
213
|
+
selector: (node: Fiber) => Promise<boolean | void>,
|
|
214
|
+
ascending?: boolean,
|
|
215
|
+
): Promise<Fiber | null>;
|
|
216
|
+
(
|
|
217
|
+
fiber: Fiber | null,
|
|
218
|
+
selector: FiberSelector,
|
|
219
|
+
ascending?: boolean,
|
|
220
|
+
): Fiber | null | Promise<Fiber | null>;
|
|
221
|
+
}
|
|
222
|
+
|
|
203
223
|
/**
|
|
204
224
|
* Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
|
|
205
225
|
*/
|
|
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(
|
|
226
|
+
export const traverseFiber = ((
|
|
222
227
|
fiber: Fiber | null,
|
|
223
228
|
selector: FiberSelector,
|
|
224
229
|
ascending = false,
|
|
225
|
-
): Fiber | null | Promise<Fiber | null> {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
230
|
+
): Fiber | null | Promise<Fiber | null> => {
|
|
231
|
+
const pendingSiblings: Fiber[] = [];
|
|
232
|
+
const getNextFiber = (currentFiber: Fiber): Fiber | null => {
|
|
233
|
+
if (ascending) return currentFiber.return;
|
|
234
|
+
if (currentFiber !== fiber && currentFiber.sibling) {
|
|
235
|
+
pendingSiblings.push(currentFiber.sibling);
|
|
236
|
+
}
|
|
237
|
+
return currentFiber.child ?? pendingSiblings.pop() ?? null;
|
|
238
|
+
};
|
|
239
|
+
const visit = (firstFiber: Fiber | null): Fiber | null | Promise<Fiber | null> => {
|
|
240
|
+
let currentFiber = firstFiber;
|
|
241
|
+
while (currentFiber) {
|
|
242
|
+
const selectedFiber = currentFiber;
|
|
243
|
+
const selection = selector(selectedFiber);
|
|
244
|
+
if (isPromiseLike<boolean | void>(selection)) {
|
|
245
|
+
return Promise.resolve(selection).then((didSelectFiber) =>
|
|
246
|
+
didSelectFiber === true ? selectedFiber : visit(getNextFiber(selectedFiber)),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if (selection === true) return selectedFiber;
|
|
250
|
+
currentFiber = getNextFiber(selectedFiber);
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
};
|
|
254
|
+
return visit(fiber);
|
|
255
|
+
}) as TraverseFiber;
|
|
238
256
|
|
|
239
257
|
const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
|
|
240
258
|
(typeof value === "object" || typeof value === "function") &&
|
|
@@ -242,32 +260,6 @@ const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
|
|
|
242
260
|
"then" in value &&
|
|
243
261
|
typeof value.then === "function";
|
|
244
262
|
|
|
245
|
-
const traverseFiberChildren = (
|
|
246
|
-
fiber: Fiber,
|
|
247
|
-
selector: FiberSelector,
|
|
248
|
-
ascending: boolean,
|
|
249
|
-
): Fiber | null | Promise<Fiber | null> => {
|
|
250
|
-
const firstChild = ascending ? fiber.return : fiber.child;
|
|
251
|
-
return traverseFiberSiblings(firstChild, selector, ascending);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const traverseFiberSiblings = (
|
|
255
|
-
fiber: Fiber | null,
|
|
256
|
-
selector: FiberSelector,
|
|
257
|
-
ascending: boolean,
|
|
258
|
-
): Fiber | null | Promise<Fiber | null> => {
|
|
259
|
-
if (!fiber) return null;
|
|
260
|
-
|
|
261
|
-
const nextSibling = ascending ? null : fiber.sibling;
|
|
262
|
-
const match = traverseFiber(fiber, selector, ascending);
|
|
263
|
-
if (isPromiseLike<Fiber | null>(match)) {
|
|
264
|
-
return Promise.resolve(match).then(
|
|
265
|
-
(resolvedMatch) => resolvedMatch ?? traverseFiberSiblings(nextSibling, selector, ascending),
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
return match ?? traverseFiberSiblings(nextSibling, selector, ascending);
|
|
269
|
-
};
|
|
270
|
-
|
|
271
263
|
/**
|
|
272
264
|
* Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
|
|
273
265
|
*/
|
|
@@ -281,7 +273,14 @@ export const hasMemoCache = (fiber: Fiber): boolean => {
|
|
|
281
273
|
export const getType = (type: unknown): null | React.ComponentType<unknown> => {
|
|
282
274
|
if (isComponentType(type)) return type;
|
|
283
275
|
if (typeof type !== "object" || type === null) return null;
|
|
284
|
-
|
|
276
|
+
const visitedTypes = new Set<object>();
|
|
277
|
+
let currentType: unknown = type;
|
|
278
|
+
while (typeof currentType === "object" && currentType !== null) {
|
|
279
|
+
if (visitedTypes.has(currentType)) return null;
|
|
280
|
+
visitedTypes.add(currentType);
|
|
281
|
+
currentType = Reflect.get(currentType, "type") ?? Reflect.get(currentType, "render");
|
|
282
|
+
}
|
|
283
|
+
return isComponentType(currentType) ? currentType : null;
|
|
285
284
|
};
|
|
286
285
|
|
|
287
286
|
/**
|
|
@@ -331,6 +330,8 @@ const rootHooks = new WeakMap<FiberRoot, ReactDevToolsGlobalHook>();
|
|
|
331
330
|
export const getLatestFiber = (fiber: Fiber): Fiber => {
|
|
332
331
|
const alternate = fiber.alternate;
|
|
333
332
|
if (!alternate) return fiber;
|
|
333
|
+
const currentFiber = getCurrentFiberFromRoot(fiber);
|
|
334
|
+
if (currentFiber) return currentFiber;
|
|
334
335
|
|
|
335
336
|
let rootFiber = fiber;
|
|
336
337
|
while (rootFiber.return) {
|
|
@@ -458,131 +459,91 @@ const releaseFiberId = (fiber: Fiber): void => {
|
|
|
458
459
|
}
|
|
459
460
|
};
|
|
460
461
|
|
|
461
|
-
const
|
|
462
|
+
const getRenderedChild = (fiber: Fiber, skipPrimaryWrapper: boolean): Fiber | null => {
|
|
463
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
464
|
+
if (fiber.tag !== workTags.SuspenseComponent) return fiber.child;
|
|
465
|
+
if (fiber.memoizedState !== null) return fiber.child?.sibling?.child ?? null;
|
|
466
|
+
return skipPrimaryWrapper && workTags.OffscreenComponent !== -1
|
|
467
|
+
? (fiber.child?.child ?? null)
|
|
468
|
+
: fiber.child;
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const mountFiberTree = (
|
|
462
472
|
onRender: RenderHandler,
|
|
463
473
|
firstChild: Fiber,
|
|
464
474
|
traverseSiblings: boolean,
|
|
465
475
|
): void => {
|
|
476
|
+
const pendingSiblings: Fiber[] = [];
|
|
466
477
|
let fiber: Fiber | null = firstChild;
|
|
467
|
-
|
|
468
|
-
while (fiber !== null) {
|
|
478
|
+
while (fiber) {
|
|
469
479
|
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;
|
|
480
|
+
if (!shouldFilterFiber(fiber) && didFiberRender(fiber)) onRender(fiber, "mount");
|
|
481
|
+
if (traverseSiblings && fiber.sibling) pendingSiblings.push(fiber.sibling);
|
|
482
|
+
fiber = getRenderedChild(fiber, true) ?? pendingSiblings.pop() ?? null;
|
|
483
|
+
traverseSiblings = true;
|
|
499
484
|
}
|
|
500
485
|
};
|
|
501
486
|
|
|
502
|
-
|
|
487
|
+
interface FiberUpdate {
|
|
488
|
+
fiber: Fiber;
|
|
489
|
+
previousFiber: Fiber | null;
|
|
490
|
+
traverseSiblings: boolean;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const updateFiberTree = (
|
|
503
494
|
onRender: RenderHandler,
|
|
504
495
|
nextFiber: Fiber,
|
|
505
496
|
prevFiber: Fiber | null,
|
|
506
497
|
): 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);
|
|
498
|
+
if (!prevFiber) {
|
|
499
|
+
getFiberId(nextFiber);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const pendingUpdates: FiberUpdate[] = [
|
|
503
|
+
{ fiber: nextFiber, previousFiber: prevFiber, traverseSiblings: false },
|
|
504
|
+
];
|
|
505
|
+
let update: FiberUpdate | undefined;
|
|
506
|
+
while ((update = pendingUpdates.pop())) {
|
|
507
|
+
const { fiber, previousFiber, traverseSiblings } = update;
|
|
508
|
+
if (traverseSiblings && fiber.sibling) {
|
|
509
|
+
pendingUpdates.push({
|
|
510
|
+
fiber: fiber.sibling,
|
|
511
|
+
previousFiber: fiber.sibling.alternate,
|
|
512
|
+
traverseSiblings: true,
|
|
513
|
+
});
|
|
551
514
|
}
|
|
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);
|
|
515
|
+
if (!previousFiber) {
|
|
516
|
+
mountFiberTree(onRender, fiber, false);
|
|
517
|
+
continue;
|
|
564
518
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
} else {
|
|
581
|
-
mountFiberRecursively(onRender, nextChild, false);
|
|
519
|
+
getFiberId(fiber);
|
|
520
|
+
getFiberId(previousFiber);
|
|
521
|
+
if (!shouldFilterFiber(fiber) && didFiberRender(fiber)) onRender(fiber, "update");
|
|
522
|
+
const isSuspense = fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent;
|
|
523
|
+
const wasTimedOut = isSuspense && previousFiber.memoizedState !== null;
|
|
524
|
+
const isTimedOut = isSuspense && fiber.memoizedState !== null;
|
|
525
|
+
if (wasTimedOut && isTimedOut) {
|
|
526
|
+
const nextFallback = fiber.child?.sibling;
|
|
527
|
+
const previousFallback = previousFiber.child?.sibling;
|
|
528
|
+
if (nextFallback && previousFallback) {
|
|
529
|
+
pendingUpdates.push({
|
|
530
|
+
fiber: nextFallback,
|
|
531
|
+
previousFiber: previousFallback,
|
|
532
|
+
traverseSiblings: false,
|
|
533
|
+
});
|
|
582
534
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
535
|
+
} else if (wasTimedOut && !isTimedOut) {
|
|
536
|
+
if (fiber.child) mountFiberTree(onRender, fiber.child, true);
|
|
537
|
+
} else if (!wasTimedOut && isTimedOut) {
|
|
538
|
+
unmountFiberChildren(onRender, previousFiber);
|
|
539
|
+
const fallback = fiber.child?.sibling;
|
|
540
|
+
if (fallback) mountFiberTree(onRender, fallback, true);
|
|
541
|
+
} else if (fiber.child && fiber.child !== previousFiber.child) {
|
|
542
|
+
pendingUpdates.push({
|
|
543
|
+
fiber: fiber.child,
|
|
544
|
+
previousFiber: fiber.child.alternate,
|
|
545
|
+
traverseSiblings: true,
|
|
546
|
+
});
|
|
586
547
|
}
|
|
587
548
|
}
|
|
588
549
|
};
|
|
@@ -595,30 +556,18 @@ const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
|
595
556
|
}
|
|
596
557
|
};
|
|
597
558
|
|
|
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.
|
|
559
|
+
const unmountFiberChildren = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
560
|
+
const pendingSiblings: Fiber[] = [];
|
|
561
|
+
let child = getRenderedChild(fiber, false);
|
|
562
|
+
while (child) {
|
|
563
|
+
if (child.sibling) pendingSiblings.push(child.sibling);
|
|
616
564
|
if (child.return !== null) {
|
|
617
565
|
unmountFiber(onRender, child);
|
|
618
|
-
|
|
566
|
+
child = getRenderedChild(child, false);
|
|
567
|
+
} else {
|
|
568
|
+
child = null;
|
|
619
569
|
}
|
|
620
|
-
|
|
621
|
-
child = child.sibling;
|
|
570
|
+
child ??= pendingSiblings.pop() ?? null;
|
|
622
571
|
}
|
|
623
572
|
};
|
|
624
573
|
|
|
@@ -668,14 +617,14 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
|
|
|
668
617
|
const isMounted = isRootFiberMounted(fiber);
|
|
669
618
|
|
|
670
619
|
if (!wasMounted && isMounted) {
|
|
671
|
-
|
|
620
|
+
mountFiberTree(onRender, fiber, false);
|
|
672
621
|
} else if (wasMounted && isMounted) {
|
|
673
|
-
|
|
622
|
+
updateFiberTree(onRender, fiber, fiber.alternate);
|
|
674
623
|
} else if (wasMounted && !isMounted) {
|
|
675
624
|
unmountFiber(onRender, fiber);
|
|
676
625
|
}
|
|
677
626
|
} else {
|
|
678
|
-
|
|
627
|
+
mountFiberTree(onRender, fiber, true);
|
|
679
628
|
}
|
|
680
629
|
|
|
681
630
|
rootInstance.prevFiber = fiber;
|
|
@@ -735,7 +684,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
735
684
|
didError,
|
|
736
685
|
) => {
|
|
737
686
|
if (prevOnCommitFiberRoot) {
|
|
738
|
-
prevOnCommitFiberRoot
|
|
687
|
+
callListener(prevOnCommitFiberRoot, rdtHook, rendererID, root, priority, didError);
|
|
739
688
|
}
|
|
740
689
|
if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
|
|
741
690
|
setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
|
|
@@ -752,7 +701,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
752
701
|
}
|
|
753
702
|
for (const { options, target } of instrumentationSubscriptions) {
|
|
754
703
|
if (target === hookTargets.get(rdtHook) && options.onCommitFiberRoot) {
|
|
755
|
-
options.onCommitFiberRoot
|
|
704
|
+
callListener(options.onCommitFiberRoot, options, rendererID, root, priority, didError);
|
|
756
705
|
}
|
|
757
706
|
}
|
|
758
707
|
};
|
|
@@ -771,7 +720,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
771
720
|
) => {
|
|
772
721
|
setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
|
|
773
722
|
if (prevOnCommitFiberUnmount) {
|
|
774
|
-
prevOnCommitFiberUnmount
|
|
723
|
+
callListener(prevOnCommitFiberUnmount, rdtHook, rendererID, fiber);
|
|
775
724
|
}
|
|
776
725
|
if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
|
|
777
726
|
return;
|
|
@@ -779,7 +728,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
779
728
|
try {
|
|
780
729
|
for (const { options, target } of instrumentationSubscriptions) {
|
|
781
730
|
if (target === hookTargets.get(rdtHook) && options.onCommitFiberUnmount) {
|
|
782
|
-
options.onCommitFiberUnmount
|
|
731
|
+
callListener(options.onCommitFiberUnmount, options, rendererID, fiber);
|
|
783
732
|
}
|
|
784
733
|
}
|
|
785
734
|
} finally {
|
|
@@ -800,14 +749,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
800
749
|
root,
|
|
801
750
|
) => {
|
|
802
751
|
if (prevOnPostCommitFiberRoot) {
|
|
803
|
-
prevOnPostCommitFiberRoot
|
|
752
|
+
callListener(prevOnPostCommitFiberRoot, rdtHook, rendererID, root);
|
|
804
753
|
}
|
|
805
754
|
if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
|
|
806
755
|
return;
|
|
807
756
|
}
|
|
808
757
|
for (const { options, target } of instrumentationSubscriptions) {
|
|
809
758
|
if (target === hookTargets.get(rdtHook) && options.onPostCommitFiberRoot) {
|
|
810
|
-
options.onPostCommitFiberRoot
|
|
759
|
+
callListener(options.onPostCommitFiberRoot, options, rendererID, root);
|
|
811
760
|
}
|
|
812
761
|
}
|
|
813
762
|
};
|
|
@@ -826,12 +775,12 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
|
826
775
|
children,
|
|
827
776
|
) => {
|
|
828
777
|
if (prevOnScheduleFiberRoot) {
|
|
829
|
-
prevOnScheduleFiberRoot
|
|
778
|
+
callListener(prevOnScheduleFiberRoot, rdtHook, rendererID, root, children);
|
|
830
779
|
}
|
|
831
780
|
if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
|
|
832
781
|
for (const { options, target } of instrumentationSubscriptions) {
|
|
833
782
|
if (target === hookTargets.get(rdtHook) && options.onScheduleFiberRoot) {
|
|
834
|
-
options.onScheduleFiberRoot
|
|
783
|
+
callListener(options.onScheduleFiberRoot, options, rendererID, root, children);
|
|
835
784
|
}
|
|
836
785
|
}
|
|
837
786
|
};
|
|
@@ -967,9 +916,11 @@ export const getFiber = <HostInstance>(
|
|
|
967
916
|
|
|
968
917
|
for (const key of Object.keys(hostInstance)) {
|
|
969
918
|
if (isFiberPropertyKey(key)) {
|
|
970
|
-
knownFiberPropertyKeys.add(key);
|
|
971
919
|
const fiber = Reflect.get(hostInstance, key);
|
|
972
|
-
if (isFiber(fiber))
|
|
920
|
+
if (isFiber(fiber)) {
|
|
921
|
+
knownFiberPropertyKeys.add(key);
|
|
922
|
+
return fiber;
|
|
923
|
+
}
|
|
973
924
|
}
|
|
974
925
|
}
|
|
975
926
|
}
|
package/src/rdt-hook.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// This module must load before React so renderers can inject into the hook.
|
|
2
2
|
|
|
3
|
+
import { callListener } from "./call-listener.js";
|
|
3
4
|
import type { FiberRoot, ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
|
|
4
5
|
|
|
5
6
|
export interface Unsubscribe extends Disposable {
|
|
@@ -49,6 +50,8 @@ export const isFiberRootUnmounted = (fiberRoot: FiberRoot): boolean => {
|
|
|
49
50
|
};
|
|
50
51
|
|
|
51
52
|
const checkDCE = (functionToCheck: unknown): void => {
|
|
53
|
+
// HACK: Node loads React's unbundled entrypoints, where development guards are intentionally not eliminated.
|
|
54
|
+
if (typeof process !== "undefined" && process.versions?.node) return;
|
|
52
55
|
try {
|
|
53
56
|
const code = Function.prototype.toString.call(functionToCheck);
|
|
54
57
|
if (code.indexOf("^_^") > -1) {
|
|
@@ -115,7 +118,7 @@ export const removeActiveListener = (
|
|
|
115
118
|
|
|
116
119
|
const notifyActiveListeners = (target: ReactDevToolsTarget): void => {
|
|
117
120
|
for (const listener of _onActiveListeners) {
|
|
118
|
-
if (activeListenerTargets.get(listener)?.has(target)) listener
|
|
121
|
+
if (activeListenerTargets.get(listener)?.has(target)) callListener(listener, undefined);
|
|
119
122
|
}
|
|
120
123
|
};
|
|
121
124
|
|
|
@@ -124,7 +127,7 @@ const notifyRendererInjectListeners = (
|
|
|
124
127
|
renderer: ReactRenderer,
|
|
125
128
|
): void => {
|
|
126
129
|
for (const subscription of rendererInjectSubscriptions) {
|
|
127
|
-
if (subscription.target === target) subscription.listener
|
|
130
|
+
if (subscription.target === target) callListener(subscription.listener, subscription, renderer);
|
|
128
131
|
}
|
|
129
132
|
};
|
|
130
133
|
|
|
@@ -133,7 +136,7 @@ const notifyRDTHookReplaceListeners = (
|
|
|
133
136
|
target: ReactDevToolsTarget,
|
|
134
137
|
): void => {
|
|
135
138
|
for (const listener of rdtHookReplaceListeners) {
|
|
136
|
-
listener
|
|
139
|
+
callListener(listener, undefined, rdtHook, target);
|
|
137
140
|
}
|
|
138
141
|
};
|
|
139
142
|
|
|
@@ -324,7 +327,7 @@ export const patchRDTHook = (
|
|
|
324
327
|
};
|
|
325
328
|
}
|
|
326
329
|
if (!didNotifyActiveListeners && (renderers.size || rdtHook._instrumentationIsActive)) {
|
|
327
|
-
onActive
|
|
330
|
+
if (onActive) callListener(onActive, undefined);
|
|
328
331
|
}
|
|
329
332
|
};
|
|
330
333
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Adapted from ReactFiberTreeReflection.js. Copyright (c) Meta Platforms, Inc. and affiliates. MIT licensed.
|
|
2
|
+
import type { Fiber } from "./types.js";
|
|
3
|
+
import { getReactWorkTagsForFiber } from "./index.js";
|
|
4
|
+
|
|
5
|
+
export const getCurrentFiberFromRoot = (fiber: Fiber): Fiber | null => {
|
|
6
|
+
const alternate = fiber.alternate;
|
|
7
|
+
if (!alternate) return null;
|
|
8
|
+
let firstBranch = fiber;
|
|
9
|
+
let secondBranch = alternate;
|
|
10
|
+
|
|
11
|
+
while (firstBranch.return) {
|
|
12
|
+
const firstParent = firstBranch.return;
|
|
13
|
+
const secondParent = firstParent.alternate;
|
|
14
|
+
if (!secondParent) {
|
|
15
|
+
const nextParent = firstParent.return;
|
|
16
|
+
if (!nextParent) return null;
|
|
17
|
+
firstBranch = secondBranch = nextParent;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (firstParent.child === secondParent.child) {
|
|
22
|
+
let child = firstParent.child;
|
|
23
|
+
while (child) {
|
|
24
|
+
if (child === firstBranch || child === secondBranch) {
|
|
25
|
+
let root = firstParent;
|
|
26
|
+
while (root.return) root = root.return;
|
|
27
|
+
if (root.tag !== getReactWorkTagsForFiber(root).HostRoot) return null;
|
|
28
|
+
return child === firstBranch ? fiber : alternate;
|
|
29
|
+
}
|
|
30
|
+
child = child.sibling;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (firstBranch.return !== secondBranch.return) {
|
|
36
|
+
firstBranch = firstParent;
|
|
37
|
+
secondBranch = secondParent;
|
|
38
|
+
} else {
|
|
39
|
+
let didFindChild = false;
|
|
40
|
+
let child = firstParent.child;
|
|
41
|
+
while (child) {
|
|
42
|
+
if (child === firstBranch) {
|
|
43
|
+
firstBranch = firstParent;
|
|
44
|
+
secondBranch = secondParent;
|
|
45
|
+
didFindChild = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (child === secondBranch) {
|
|
49
|
+
firstBranch = secondParent;
|
|
50
|
+
secondBranch = firstParent;
|
|
51
|
+
didFindChild = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
child = child.sibling;
|
|
55
|
+
}
|
|
56
|
+
if (!didFindChild) {
|
|
57
|
+
child = secondParent.child;
|
|
58
|
+
while (child) {
|
|
59
|
+
if (child === firstBranch) {
|
|
60
|
+
firstBranch = secondParent;
|
|
61
|
+
secondBranch = firstParent;
|
|
62
|
+
didFindChild = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
if (child === secondBranch) {
|
|
66
|
+
firstBranch = firstParent;
|
|
67
|
+
secondBranch = secondParent;
|
|
68
|
+
didFindChild = true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
child = child.sibling;
|
|
72
|
+
}
|
|
73
|
+
if (!didFindChild) return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (firstBranch.alternate !== secondBranch) return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (firstBranch.tag !== getReactWorkTagsForFiber(firstBranch).HostRoot) return null;
|
|
80
|
+
const root = firstBranch.stateNode;
|
|
81
|
+
if (typeof root !== "object" || root === null || !("current" in root)) return null;
|
|
82
|
+
if (root.current === firstBranch) return fiber;
|
|
83
|
+
return root.current === secondBranch ? alternate : null;
|
|
84
|
+
};
|