bippy 0.6.1-dev.61475ed → 0.6.1-dev.879f777
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 -1
- package/README.md +177 -500
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +45 -105
- package/dist/core.d.ts +45 -105
- package/dist/core.js +1 -1
- package/dist/core2.cjs +9 -0
- package/dist/core2.d.cts +11 -3
- package/dist/core2.d.ts +11 -3
- package/dist/core2.js +9 -0
- package/dist/errors.d.cts +410 -0
- package/dist/errors.d.ts +410 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.d.cts +9 -1
- package/dist/install-hook-only.d.ts +9 -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 +14 -5
- package/dist/source.d.cts +86 -69
- package/dist/source.d.ts +86 -69
- package/dist/source.js +14 -5
- package/package.json +26 -17
- package/src/core.ts +340 -673
- package/src/errors.ts +34 -0
- package/src/index.ts +1 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +160 -161
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +67 -0
- package/src/react-internals/semver.ts +80 -0
- package/src/react-internals/types.ts +186 -0
- package/src/react.ts +76 -0
- package/src/source/constants.ts +1 -1
- package/src/source/error-stack.ts +11 -0
- package/src/source/get-display-name-from-source.ts +44 -40
- package/src/source/get-source.ts +43 -26
- package/src/source/index.ts +11 -1
- package/src/source/inspect-hooks.ts +220 -207
- package/src/source/owner-stack.ts +119 -174
- package/src/source/parse-debug-stack.ts +4 -4
- package/src/source/parse-hook-names.ts +14 -53
- package/src/source/parse-stack.ts +14 -31
- package/src/source/renderer-dispatchers.ts +30 -0
- package/src/source/symbolication.ts +709 -120
- package/dist/get-source.cjs +0 -19
- package/dist/get-source.js +0 -19
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
- package/dist/react-refresh.cjs +0 -9
- package/dist/react-refresh.d.cts +0 -66
- package/dist/react-refresh.d.ts +0 -66
- package/dist/react-refresh.js +0 -9
- package/dist/unsubscribe.d.cts +0 -298
- package/dist/unsubscribe.d.ts +0 -298
- package/src/react-refresh/constants.ts +0 -9
- package/src/react-refresh/detect-hmr-transport.ts +0 -33
- package/src/react-refresh/index.ts +0 -173
- package/src/react-refresh/metro-hmr-transport.ts +0 -188
- package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
- package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
- package/src/react-refresh/types.ts +0 -7
- package/src/react-refresh/vite-hmr-transport.ts +0 -116
- package/src/types.ts +0 -438
- package/src/unsubscribe.ts +0 -17
package/src/core.ts
CHANGED
|
@@ -1,71 +1,59 @@
|
|
|
1
|
-
//
|
|
2
|
-
// since it will be executed before the react devtools hook is created
|
|
1
|
+
// React must remain a type-only import because this module loads immediately after the DevTools hook.
|
|
3
2
|
|
|
4
3
|
import type * as React from "react";
|
|
5
4
|
|
|
6
5
|
import type {
|
|
7
|
-
ContextDependency,
|
|
8
6
|
Fiber,
|
|
9
7
|
FiberRoot,
|
|
10
|
-
|
|
8
|
+
HostFiber,
|
|
11
9
|
ReactDevToolsGlobalHook,
|
|
12
10
|
ReactRenderer,
|
|
13
|
-
} from "./
|
|
11
|
+
} from "./react-internals/index.js";
|
|
14
12
|
|
|
15
13
|
import {
|
|
16
14
|
_onActiveListeners,
|
|
17
15
|
BIPPY_INSTRUMENTATION_STRING,
|
|
16
|
+
createUnsubscribe,
|
|
18
17
|
getRDTHook,
|
|
19
18
|
hasRDTHook,
|
|
20
|
-
isReactRefresh,
|
|
21
19
|
isRealReactDevtools,
|
|
22
|
-
|
|
20
|
+
onRDTHookReplace,
|
|
23
21
|
} from "./rdt-hook.js";
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const Placement = 0b10;
|
|
60
|
-
const Hydrating = 0b1000000000000;
|
|
61
|
-
const Update = 0b100;
|
|
62
|
-
const Cloned = 0b1000;
|
|
63
|
-
const ChildDeletion = 0b10000;
|
|
64
|
-
const ContentReset = 0b100000;
|
|
65
|
-
const Snapshot = 0b10000000000;
|
|
66
|
-
const Visibility = 0b10000000000000;
|
|
67
|
-
const MutationMask =
|
|
68
|
-
Placement | Update | ChildDeletion | ContentReset | Hydrating | Visibility | Snapshot;
|
|
22
|
+
import type { Unsubscribe } from "./rdt-hook.js";
|
|
23
|
+
import {
|
|
24
|
+
getReactWorkTagsForFiber,
|
|
25
|
+
MutationMask,
|
|
26
|
+
ReactBuildType,
|
|
27
|
+
ReactFiberFlags,
|
|
28
|
+
ReactSymbols,
|
|
29
|
+
setReactWorkTagsForFiber,
|
|
30
|
+
} from "./react-internals/index.js";
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
BippyError,
|
|
34
|
+
BippyHookInspectionError,
|
|
35
|
+
BippyHookRenderError,
|
|
36
|
+
BippySourceMapError,
|
|
37
|
+
BippyUnsupportedHookError,
|
|
38
|
+
} from "./errors.js";
|
|
39
|
+
|
|
40
|
+
const isComponentType = (value: unknown): value is React.ComponentType<unknown> =>
|
|
41
|
+
typeof value === "function";
|
|
42
|
+
|
|
43
|
+
const getTypeName = (value: object): string | null => {
|
|
44
|
+
const displayName = "displayName" in value ? value.displayName : null;
|
|
45
|
+
if (typeof displayName === "string" && displayName) return displayName;
|
|
46
|
+
const name = "name" in value ? value.name : null;
|
|
47
|
+
return typeof name === "string" && name ? name : null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
interface FiberSelector {
|
|
51
|
+
(node: Fiber): boolean | Promise<boolean | void> | void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RenderHandler {
|
|
55
|
+
(fiber: Fiber, phase: RenderPhase): unknown;
|
|
56
|
+
}
|
|
69
57
|
|
|
70
58
|
/**
|
|
71
59
|
* Returns `true` if object is a React Element.
|
|
@@ -74,19 +62,17 @@ const MutationMask =
|
|
|
74
62
|
*/
|
|
75
63
|
export const isValidElement = (element: unknown): element is React.ReactElement =>
|
|
76
64
|
typeof element === "object" &&
|
|
77
|
-
element
|
|
65
|
+
element !== null &&
|
|
78
66
|
"$$typeof" in element &&
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
String(element.$$typeof),
|
|
82
|
-
);
|
|
67
|
+
(String(element.$$typeof) === ReactSymbols.LEGACY_ELEMENT_SYMBOL_STRING ||
|
|
68
|
+
String(element.$$typeof) === ReactSymbols.ELEMENT_SYMBOL_STRING);
|
|
83
69
|
|
|
84
70
|
/**
|
|
85
71
|
* Returns `true` if object is a React Fiber.
|
|
86
72
|
*/
|
|
87
73
|
export const isValidFiber = (fiber: unknown): fiber is Fiber =>
|
|
88
74
|
typeof fiber === "object" &&
|
|
89
|
-
fiber
|
|
75
|
+
fiber !== null &&
|
|
90
76
|
"tag" in fiber &&
|
|
91
77
|
"stateNode" in fiber &&
|
|
92
78
|
"return" in fiber &&
|
|
@@ -94,21 +80,27 @@ export const isValidFiber = (fiber: unknown): fiber is Fiber =>
|
|
|
94
80
|
"sibling" in fiber &&
|
|
95
81
|
"flags" in fiber;
|
|
96
82
|
|
|
83
|
+
const isFiberRoot = (fiberRoot: unknown): fiberRoot is FiberRoot =>
|
|
84
|
+
typeof fiberRoot === "object" &&
|
|
85
|
+
fiberRoot !== null &&
|
|
86
|
+
"current" in fiberRoot &&
|
|
87
|
+
isValidFiber(fiberRoot.current);
|
|
88
|
+
|
|
97
89
|
/**
|
|
98
90
|
* Returns `true` if fiber is a host fiber. Host fibers are DOM nodes in react-dom, `View` in react-native, etc.
|
|
99
91
|
*
|
|
100
92
|
* @see https://reactnative.dev/architecture/glossary#host-view-tree-and-host-view
|
|
101
93
|
*/
|
|
102
|
-
export const isHostFiber = (fiber: Fiber):
|
|
94
|
+
export const isHostFiber = (fiber: Fiber): fiber is HostFiber => {
|
|
95
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
103
96
|
switch (fiber.tag) {
|
|
104
|
-
case
|
|
105
|
-
|
|
106
|
-
case
|
|
107
|
-
|
|
108
|
-
case HostSingletonTag:
|
|
97
|
+
case workTags.HostComponent:
|
|
98
|
+
case workTags.HostText:
|
|
99
|
+
case workTags.HostHoistable:
|
|
100
|
+
case workTags.HostSingleton:
|
|
109
101
|
return true;
|
|
110
102
|
default:
|
|
111
|
-
return
|
|
103
|
+
return false;
|
|
112
104
|
}
|
|
113
105
|
};
|
|
114
106
|
|
|
@@ -118,12 +110,13 @@ export const isHostFiber = (fiber: Fiber): boolean => {
|
|
|
118
110
|
* @see https://reactnative.dev/architecture/glossary#react-composite-components
|
|
119
111
|
*/
|
|
120
112
|
export const isCompositeFiber = (fiber: Fiber): boolean => {
|
|
113
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
121
114
|
switch (fiber.tag) {
|
|
122
|
-
case
|
|
123
|
-
case
|
|
124
|
-
case
|
|
125
|
-
case
|
|
126
|
-
case
|
|
115
|
+
case workTags.ClassComponent:
|
|
116
|
+
case workTags.ForwardRef:
|
|
117
|
+
case workTags.FunctionComponent:
|
|
118
|
+
case workTags.MemoComponent:
|
|
119
|
+
case workTags.SimpleMemoComponent:
|
|
127
120
|
return true;
|
|
128
121
|
default:
|
|
129
122
|
return false;
|
|
@@ -140,106 +133,23 @@ export const isFiber = (maybeFiber: unknown): maybeFiber is Fiber => {
|
|
|
140
133
|
return "pendingProps" in maybeFiber && !("containerInfo" in maybeFiber);
|
|
141
134
|
};
|
|
142
135
|
|
|
143
|
-
/**
|
|
144
|
-
* Traverses up or down a {@link Fiber}'s contexts, return `true` to stop and select the current and previous context value.
|
|
145
|
-
*/
|
|
146
|
-
export const traverseContexts = (
|
|
147
|
-
fiber: Fiber,
|
|
148
|
-
selector: (
|
|
149
|
-
nextValue: ContextDependency<unknown> | null | undefined,
|
|
150
|
-
prevValue: ContextDependency<unknown> | null | undefined,
|
|
151
|
-
) => boolean | void,
|
|
152
|
-
): boolean => {
|
|
153
|
-
try {
|
|
154
|
-
const nextDependencies = fiber.dependencies;
|
|
155
|
-
const prevDependencies = fiber.alternate?.dependencies;
|
|
156
|
-
|
|
157
|
-
if (!nextDependencies || !prevDependencies) return false;
|
|
158
|
-
if (
|
|
159
|
-
typeof nextDependencies !== "object" ||
|
|
160
|
-
!("firstContext" in nextDependencies) ||
|
|
161
|
-
typeof prevDependencies !== "object" ||
|
|
162
|
-
!("firstContext" in prevDependencies)
|
|
163
|
-
) {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
let nextContext: ContextDependency<unknown> | null | undefined = nextDependencies.firstContext;
|
|
167
|
-
let prevContext: ContextDependency<unknown> | null | undefined = prevDependencies.firstContext;
|
|
168
|
-
while (
|
|
169
|
-
(nextContext && typeof nextContext === "object" && "memoizedValue" in nextContext) ||
|
|
170
|
-
(prevContext && typeof prevContext === "object" && "memoizedValue" in prevContext)
|
|
171
|
-
) {
|
|
172
|
-
if (selector(nextContext, prevContext) === true) return true;
|
|
173
|
-
|
|
174
|
-
nextContext = nextContext?.next;
|
|
175
|
-
prevContext = prevContext?.next;
|
|
176
|
-
}
|
|
177
|
-
} catch {}
|
|
178
|
-
return false;
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Traverses up or down a {@link Fiber}'s states, return `true` to stop and select the current and previous state value. This stores both state values and effects.
|
|
183
|
-
*/
|
|
184
|
-
export const traverseState = (
|
|
185
|
-
fiber: Fiber,
|
|
186
|
-
selector: (
|
|
187
|
-
nextValue: MemoizedState | null | undefined,
|
|
188
|
-
prevValue: MemoizedState | null | undefined,
|
|
189
|
-
) => boolean | void,
|
|
190
|
-
): boolean => {
|
|
191
|
-
try {
|
|
192
|
-
let nextState: MemoizedState | null | undefined = fiber.memoizedState;
|
|
193
|
-
let prevState: MemoizedState | null | undefined = fiber.alternate?.memoizedState;
|
|
194
|
-
|
|
195
|
-
while (nextState || prevState) {
|
|
196
|
-
if (selector(nextState, prevState) === true) return true;
|
|
197
|
-
|
|
198
|
-
nextState = nextState?.next;
|
|
199
|
-
prevState = prevState?.next;
|
|
200
|
-
}
|
|
201
|
-
} catch {}
|
|
202
|
-
return false;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Traverses up or down a {@link Fiber}'s props, return `true` to stop and select the current and previous props value.
|
|
207
|
-
*/
|
|
208
|
-
export const traverseProps = (
|
|
209
|
-
fiber: Fiber,
|
|
210
|
-
selector: (propName: string, nextValue: unknown, prevValue: unknown) => boolean | void,
|
|
211
|
-
): boolean => {
|
|
212
|
-
try {
|
|
213
|
-
const nextProps = fiber.memoizedProps;
|
|
214
|
-
const prevProps = fiber.alternate?.memoizedProps || {};
|
|
215
|
-
|
|
216
|
-
for (const propName of Object.keys(nextProps)) {
|
|
217
|
-
if (selector(propName, nextProps[propName], prevProps[propName]) === true) return true;
|
|
218
|
-
}
|
|
219
|
-
for (const propName of Object.keys(prevProps)) {
|
|
220
|
-
if (propName in nextProps) continue;
|
|
221
|
-
if (selector(propName, nextProps[propName], prevProps[propName]) === true) return true;
|
|
222
|
-
}
|
|
223
|
-
} catch {}
|
|
224
|
-
return false;
|
|
225
|
-
};
|
|
226
|
-
|
|
227
136
|
/**
|
|
228
137
|
* Returns `true` if the {@link Fiber} has rendered. Note that this does not mean the fiber has rendered in the current commit, just that it has rendered in the past.
|
|
229
138
|
*/
|
|
230
139
|
export const didFiberRender = (fiber: Fiber): boolean => {
|
|
231
140
|
const nextProps = fiber.memoizedProps;
|
|
232
141
|
const prevProps = fiber.alternate?.memoizedProps || {};
|
|
233
|
-
const flags = fiber.flags ??
|
|
142
|
+
const flags = fiber.flags ?? fiber.effectTag ?? 0;
|
|
143
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
234
144
|
|
|
235
145
|
switch (fiber.tag) {
|
|
236
|
-
case
|
|
237
|
-
case
|
|
238
|
-
case
|
|
239
|
-
case
|
|
240
|
-
case
|
|
241
|
-
case
|
|
242
|
-
return (flags & PerformedWork) === PerformedWork;
|
|
146
|
+
case workTags.ClassComponent:
|
|
147
|
+
case workTags.ContextConsumer:
|
|
148
|
+
case workTags.ForwardRef:
|
|
149
|
+
case workTags.FunctionComponent:
|
|
150
|
+
case workTags.MemoComponent:
|
|
151
|
+
case workTags.SimpleMemoComponent: {
|
|
152
|
+
return (flags & ReactFiberFlags.PerformedWork) === ReactFiberFlags.PerformedWork;
|
|
243
153
|
}
|
|
244
154
|
default:
|
|
245
155
|
// Host nodes (DOM, root, etc.)
|
|
@@ -257,57 +167,18 @@ export const didFiberRender = (fiber: Fiber): boolean => {
|
|
|
257
167
|
*/
|
|
258
168
|
export const didFiberCommit = (fiber: Fiber): boolean => {
|
|
259
169
|
return Boolean(
|
|
260
|
-
(fiber.flags & (MutationMask | Cloned)) !== 0 ||
|
|
261
|
-
(fiber.subtreeFlags & (MutationMask | Cloned)) !== 0,
|
|
170
|
+
(fiber.flags & (MutationMask | ReactFiberFlags.Cloned)) !== 0 ||
|
|
171
|
+
(fiber.subtreeFlags & (MutationMask | ReactFiberFlags.Cloned)) !== 0,
|
|
262
172
|
);
|
|
263
173
|
};
|
|
264
174
|
|
|
265
|
-
/**
|
|
266
|
-
* Returns all host {@link Fiber}s that have committed and rendered.
|
|
267
|
-
*/
|
|
268
|
-
export const getMutatedHostFibers = (fiber: Fiber): Fiber[] => {
|
|
269
|
-
const mutations: Fiber[] = [];
|
|
270
|
-
const stack: Fiber[] = [fiber];
|
|
271
|
-
|
|
272
|
-
while (stack.length) {
|
|
273
|
-
const node = stack.pop();
|
|
274
|
-
if (!node) continue;
|
|
275
|
-
|
|
276
|
-
if (isHostFiber(node) && didFiberCommit(node) && didFiberRender(node)) {
|
|
277
|
-
mutations.push(node);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (node.child) stack.push(node.child);
|
|
281
|
-
if (node.sibling) stack.push(node.sibling);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return mutations;
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Returns the stack of {@link Fiber}s from the current fiber to the root fiber.
|
|
289
|
-
*
|
|
290
|
-
* @example
|
|
291
|
-
* ```ts
|
|
292
|
-
* [fiber, fiber.return, fiber.return.return, ...]
|
|
293
|
-
* ```
|
|
294
|
-
*/
|
|
295
|
-
export const getFiberStack = (fiber: Fiber): Fiber[] => {
|
|
296
|
-
const stack: Fiber[] = [];
|
|
297
|
-
let currentFiber = fiber;
|
|
298
|
-
while (currentFiber.return) {
|
|
299
|
-
stack.push(currentFiber);
|
|
300
|
-
currentFiber = currentFiber.return as Fiber;
|
|
301
|
-
}
|
|
302
|
-
return stack;
|
|
303
|
-
};
|
|
304
|
-
|
|
305
175
|
/**
|
|
306
176
|
* Returns `true` if the {@link Fiber} should be filtered out during reconciliation.
|
|
307
177
|
*/
|
|
308
178
|
const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
179
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
309
180
|
switch (fiber.tag) {
|
|
310
|
-
case
|
|
181
|
+
case workTags.DehydratedSuspenseComponent:
|
|
311
182
|
// TODO: ideally we would show dehydrated Suspense immediately.
|
|
312
183
|
// However, it has some special behavior (like disconnecting
|
|
313
184
|
// an alternate and turning into real Suspense) which breaks DevTools.
|
|
@@ -315,13 +186,13 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
315
186
|
// https://github.com/bvaughn/react-devtools-experimental/issues/197
|
|
316
187
|
return true;
|
|
317
188
|
|
|
318
|
-
case
|
|
319
|
-
case
|
|
320
|
-
case
|
|
321
|
-
case
|
|
189
|
+
case workTags.Fragment:
|
|
190
|
+
case workTags.HostText:
|
|
191
|
+
case workTags.LegacyHiddenComponent:
|
|
192
|
+
case workTags.OffscreenComponent:
|
|
322
193
|
return true;
|
|
323
194
|
|
|
324
|
-
case
|
|
195
|
+
case workTags.HostRoot:
|
|
325
196
|
// It is never valid to filter the root element.
|
|
326
197
|
return false;
|
|
327
198
|
|
|
@@ -331,15 +202,15 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
331
202
|
|
|
332
203
|
if (typeof symbolOrNumber === "symbol") {
|
|
333
204
|
return (
|
|
334
|
-
symbolOrNumber.description === CONCURRENT_MODE_SYMBOL_DESCRIPTION ||
|
|
335
|
-
symbolOrNumber.description === DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION
|
|
205
|
+
symbolOrNumber.description === ReactSymbols.CONCURRENT_MODE_SYMBOL_DESCRIPTION ||
|
|
206
|
+
symbolOrNumber.description === ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION
|
|
336
207
|
);
|
|
337
208
|
}
|
|
338
209
|
|
|
339
210
|
switch (symbolOrNumber) {
|
|
340
|
-
case CONCURRENT_MODE_NUMBER:
|
|
341
|
-
case CONCURRENT_MODE_SYMBOL_STRING:
|
|
342
|
-
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
|
|
211
|
+
case ReactSymbols.CONCURRENT_MODE_NUMBER:
|
|
212
|
+
case ReactSymbols.CONCURRENT_MODE_SYMBOL_STRING:
|
|
213
|
+
case ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
|
|
343
214
|
return true;
|
|
344
215
|
|
|
345
216
|
default:
|
|
@@ -349,47 +220,6 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
349
220
|
}
|
|
350
221
|
};
|
|
351
222
|
|
|
352
|
-
/**
|
|
353
|
-
* Returns the nearest host {@link Fiber} to the current {@link Fiber}.
|
|
354
|
-
*/
|
|
355
|
-
export const getNearestHostFiber = (fiber: Fiber, ascending = false): Fiber | null => {
|
|
356
|
-
let hostFiber = traverseFiber(fiber, isHostFiber, ascending);
|
|
357
|
-
if (!hostFiber) {
|
|
358
|
-
hostFiber = traverseFiber(fiber, isHostFiber, !ascending);
|
|
359
|
-
}
|
|
360
|
-
return hostFiber;
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Returns all host {@link Fiber}s in the tree that are associated with the current {@link Fiber}.
|
|
365
|
-
*/
|
|
366
|
-
export const getNearestHostFibers = (fiber: Fiber): Fiber[] => {
|
|
367
|
-
const hostFibers: Fiber[] = [];
|
|
368
|
-
const stack: Fiber[] = [];
|
|
369
|
-
|
|
370
|
-
if (isHostFiber(fiber)) {
|
|
371
|
-
hostFibers.push(fiber);
|
|
372
|
-
} else if (fiber.child) {
|
|
373
|
-
stack.push(fiber.child);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
while (stack.length) {
|
|
377
|
-
const currentNode = stack.pop();
|
|
378
|
-
if (!currentNode) break;
|
|
379
|
-
if (isHostFiber(currentNode)) {
|
|
380
|
-
hostFibers.push(currentNode);
|
|
381
|
-
} else if (currentNode.child) {
|
|
382
|
-
stack.push(currentNode.child);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (currentNode.sibling) {
|
|
386
|
-
stack.push(currentNode.sibling);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
return hostFibers;
|
|
391
|
-
};
|
|
392
|
-
|
|
393
223
|
/**
|
|
394
224
|
* Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
|
|
395
225
|
*/
|
|
@@ -405,144 +235,86 @@ export function traverseFiber(
|
|
|
405
235
|
): Promise<Fiber | null>;
|
|
406
236
|
export function traverseFiber(
|
|
407
237
|
fiber: Fiber | null,
|
|
408
|
-
selector:
|
|
238
|
+
selector: FiberSelector,
|
|
239
|
+
ascending?: boolean,
|
|
240
|
+
): Fiber | null | Promise<Fiber | null>;
|
|
241
|
+
export function traverseFiber(
|
|
242
|
+
fiber: Fiber | null,
|
|
243
|
+
selector: FiberSelector,
|
|
409
244
|
ascending = false,
|
|
410
245
|
): Fiber | null | Promise<Fiber | null> {
|
|
411
246
|
if (!fiber) return null;
|
|
412
247
|
|
|
413
|
-
const
|
|
414
|
-
if (
|
|
415
|
-
return (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
let child = ascending ? fiber.return : fiber.child;
|
|
419
|
-
while (child) {
|
|
420
|
-
const match = await traverseFiberAsync(
|
|
421
|
-
child,
|
|
422
|
-
selector as (node: Fiber) => Promise<boolean | void>,
|
|
423
|
-
ascending,
|
|
424
|
-
);
|
|
425
|
-
if (match) return match;
|
|
426
|
-
child = ascending ? null : child.sibling;
|
|
427
|
-
}
|
|
428
|
-
return null;
|
|
429
|
-
})();
|
|
248
|
+
const selection = selector(fiber);
|
|
249
|
+
if (isPromiseLike<boolean | void>(selection)) {
|
|
250
|
+
return Promise.resolve(selection).then((didSelectFiber) =>
|
|
251
|
+
didSelectFiber === true ? fiber : traverseFiberChildren(fiber, selector, ascending),
|
|
252
|
+
);
|
|
430
253
|
}
|
|
254
|
+
if (selection === true) return fiber;
|
|
431
255
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
let child = ascending ? fiber.return : fiber.child;
|
|
435
|
-
while (child) {
|
|
436
|
-
const match = traverseFiberSync(child, selector as (node: Fiber) => boolean | void, ascending);
|
|
437
|
-
if (match) return match;
|
|
438
|
-
child = ascending ? null : child.sibling;
|
|
439
|
-
}
|
|
440
|
-
return null;
|
|
256
|
+
return traverseFiberChildren(fiber, selector, ascending);
|
|
441
257
|
}
|
|
442
258
|
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (!fiber) return null;
|
|
449
|
-
if (selector(fiber) === true) return fiber;
|
|
450
|
-
|
|
451
|
-
let child = ascending ? fiber.return : fiber.child;
|
|
452
|
-
while (child) {
|
|
453
|
-
const match = traverseFiberSync(child, selector, ascending);
|
|
454
|
-
if (match) return match;
|
|
259
|
+
const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
|
|
260
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
261
|
+
value !== null &&
|
|
262
|
+
"then" in value &&
|
|
263
|
+
typeof value.then === "function";
|
|
455
264
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
265
|
+
const traverseFiberChildren = (
|
|
266
|
+
fiber: Fiber,
|
|
267
|
+
selector: FiberSelector,
|
|
268
|
+
ascending: boolean,
|
|
269
|
+
): Fiber | null | Promise<Fiber | null> => {
|
|
270
|
+
const firstChild = ascending ? fiber.return : fiber.child;
|
|
271
|
+
return traverseFiberSiblings(firstChild, selector, ascending);
|
|
459
272
|
};
|
|
460
273
|
|
|
461
|
-
const
|
|
274
|
+
const traverseFiberSiblings = (
|
|
462
275
|
fiber: Fiber | null,
|
|
463
|
-
selector:
|
|
464
|
-
ascending
|
|
465
|
-
): Promise<Fiber | null> => {
|
|
276
|
+
selector: FiberSelector,
|
|
277
|
+
ascending: boolean,
|
|
278
|
+
): Fiber | null | Promise<Fiber | null> => {
|
|
466
279
|
if (!fiber) return null;
|
|
467
|
-
if ((await selector(fiber)) === true) return fiber;
|
|
468
|
-
|
|
469
|
-
let child = ascending ? fiber.return : fiber.child;
|
|
470
|
-
while (child) {
|
|
471
|
-
const match = await traverseFiberAsync(child, selector, ascending);
|
|
472
|
-
if (match) return match;
|
|
473
280
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
* Returns the timings of the {@link Fiber}.
|
|
481
|
-
*
|
|
482
|
-
* @example
|
|
483
|
-
* ```ts
|
|
484
|
-
* const { selfTime, totalTime } = getTimings(fiber);
|
|
485
|
-
* console.log(selfTime, totalTime);
|
|
486
|
-
* ```
|
|
487
|
-
*/
|
|
488
|
-
export const getTimings = (fiber?: Fiber | null): { selfTime: number; totalTime: number } => {
|
|
489
|
-
const totalTime = fiber?.actualDuration ?? 0;
|
|
490
|
-
let selfTime = totalTime;
|
|
491
|
-
// TODO: calculate a DOM time, which is just host component summed up
|
|
492
|
-
let child = fiber?.child ?? null;
|
|
493
|
-
while (totalTime > 0 && child != null) {
|
|
494
|
-
selfTime -= child.actualDuration ?? 0;
|
|
495
|
-
child = child.sibling;
|
|
281
|
+
const nextSibling = ascending ? null : fiber.sibling;
|
|
282
|
+
const match = traverseFiber(fiber, selector, ascending);
|
|
283
|
+
if (isPromiseLike<Fiber | null>(match)) {
|
|
284
|
+
return Promise.resolve(match).then(
|
|
285
|
+
(resolvedMatch) => resolvedMatch ?? traverseFiberSiblings(nextSibling, selector, ascending),
|
|
286
|
+
);
|
|
496
287
|
}
|
|
497
|
-
return
|
|
288
|
+
return match ?? traverseFiberSiblings(nextSibling, selector, ascending);
|
|
498
289
|
};
|
|
499
290
|
|
|
500
291
|
/**
|
|
501
292
|
* Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
|
|
502
293
|
*/
|
|
503
294
|
export const hasMemoCache = (fiber: Fiber): boolean => {
|
|
504
|
-
return Boolean(
|
|
295
|
+
return Boolean(fiber.updateQueue?.memoCache);
|
|
505
296
|
};
|
|
506
297
|
|
|
507
|
-
type FiberType =
|
|
508
|
-
| React.ComponentType<unknown>
|
|
509
|
-
| React.ForwardRefExoticComponent<unknown>
|
|
510
|
-
| React.MemoExoticComponent<React.ComponentType<unknown>>;
|
|
511
|
-
|
|
512
298
|
/**
|
|
513
299
|
* Returns the type (e.g. component definition) of the {@link Fiber}
|
|
514
300
|
*/
|
|
515
301
|
export const getType = (type: unknown): null | React.ComponentType<unknown> => {
|
|
516
|
-
|
|
517
|
-
if (typeof
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
if (typeof currentType === "object" && currentType) {
|
|
521
|
-
// memo / forwardRef case
|
|
522
|
-
return getType(
|
|
523
|
-
(currentType as React.MemoExoticComponent<React.ComponentType<unknown>>).type ||
|
|
524
|
-
(currentType as { render: React.ComponentType<unknown> }).render,
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
return null;
|
|
302
|
+
if (isComponentType(type)) return type;
|
|
303
|
+
if (typeof type !== "object" || type === null) return null;
|
|
304
|
+
return getType(Reflect.get(type, "type") ?? Reflect.get(type, "render"));
|
|
528
305
|
};
|
|
529
306
|
|
|
530
307
|
/**
|
|
531
308
|
* Returns the display name of the {@link Fiber} type.
|
|
532
309
|
*/
|
|
533
310
|
export const getDisplayName = (type: unknown): null | string => {
|
|
534
|
-
|
|
535
|
-
if (typeof
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
if (typeof currentType !== "function" && !(typeof currentType === "object" && currentType)) {
|
|
539
|
-
return null;
|
|
540
|
-
}
|
|
541
|
-
const name = currentType.displayName || currentType.name || null;
|
|
311
|
+
if (typeof type === "string") return type;
|
|
312
|
+
if ((typeof type !== "function" && typeof type !== "object") || type === null) return null;
|
|
313
|
+
const name = getTypeName(type);
|
|
542
314
|
if (name) return name;
|
|
543
|
-
const unwrappedType = getType(
|
|
315
|
+
const unwrappedType = getType(type);
|
|
544
316
|
if (!unwrappedType) return null;
|
|
545
|
-
return unwrappedType
|
|
317
|
+
return getTypeName(unwrappedType);
|
|
546
318
|
};
|
|
547
319
|
|
|
548
320
|
/**
|
|
@@ -550,7 +322,10 @@ export const getDisplayName = (type: unknown): null | string => {
|
|
|
550
322
|
*/
|
|
551
323
|
export const detectReactBuildType = (renderer: ReactRenderer): "development" | "production" => {
|
|
552
324
|
try {
|
|
553
|
-
if (
|
|
325
|
+
if (
|
|
326
|
+
typeof renderer.version === "string" &&
|
|
327
|
+
renderer.bundleType === ReactBuildType.Development
|
|
328
|
+
) {
|
|
554
329
|
return "development";
|
|
555
330
|
}
|
|
556
331
|
} catch {}
|
|
@@ -562,14 +337,11 @@ export const detectReactBuildType = (renderer: ReactRenderer): "development" | "
|
|
|
562
337
|
*/
|
|
563
338
|
export const isInstrumentationActive = (): boolean => {
|
|
564
339
|
const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
565
|
-
return (
|
|
566
|
-
Boolean(rdtHook?._instrumentationIsActive) ||
|
|
567
|
-
isRealReactDevtools(rdtHook) ||
|
|
568
|
-
isReactRefresh(rdtHook)
|
|
569
|
-
);
|
|
340
|
+
return Boolean(rdtHook?._instrumentationIsActive) || isRealReactDevtools(rdtHook);
|
|
570
341
|
};
|
|
571
342
|
|
|
572
343
|
export const _fiberRoots = new Set<FiberRoot>();
|
|
344
|
+
const rootRendererIds = new WeakMap<FiberRoot, number>();
|
|
573
345
|
|
|
574
346
|
/**
|
|
575
347
|
* Returns the latest fiber (since it may be double-buffered).
|
|
@@ -582,37 +354,54 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
|
|
|
582
354
|
}
|
|
583
355
|
for (const root of _fiberRoots) {
|
|
584
356
|
const latestFiber = traverseFiber(root.current, (innerFiber) => {
|
|
585
|
-
if (innerFiber === fiber) return true;
|
|
357
|
+
if (innerFiber === fiber || innerFiber === alternate) return true;
|
|
586
358
|
});
|
|
587
359
|
if (latestFiber) return latestFiber;
|
|
588
360
|
}
|
|
589
361
|
return fiber;
|
|
590
362
|
};
|
|
591
363
|
|
|
592
|
-
|
|
364
|
+
/**
|
|
365
|
+
* Returns the renderer that owns the {@link Fiber}.
|
|
366
|
+
*/
|
|
367
|
+
export const getRenderer = (fiber: Fiber): ReactRenderer | null => {
|
|
368
|
+
if (!hasRDTHook()) return null;
|
|
369
|
+
|
|
370
|
+
let rootFiber = fiber;
|
|
371
|
+
while (rootFiber.return) {
|
|
372
|
+
rootFiber = rootFiber.return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const fiberRoot = rootFiber.stateNode;
|
|
376
|
+
if (!isFiberRoot(fiberRoot)) return null;
|
|
377
|
+
|
|
378
|
+
const rendererID = rootRendererIds.get(fiberRoot);
|
|
379
|
+
if (rendererID === undefined) return null;
|
|
380
|
+
return getRDTHook().renderers.get(rendererID) ?? null;
|
|
381
|
+
};
|
|
593
382
|
|
|
594
383
|
export type RenderPhase = "mount" | "unmount" | "update";
|
|
595
384
|
|
|
596
|
-
let
|
|
385
|
+
let nextFiberId = 0;
|
|
597
386
|
const fiberIdMap = new WeakMap<Fiber, number>();
|
|
598
387
|
|
|
599
|
-
export const setFiberId = (fiber: Fiber,
|
|
600
|
-
fiberIdMap.set(fiber,
|
|
388
|
+
export const setFiberId = (fiber: Fiber, fiberId: number = nextFiberId++): void => {
|
|
389
|
+
fiberIdMap.set(fiber, fiberId);
|
|
390
|
+
if (Number.isSafeInteger(fiberId) && fiberId >= nextFiberId) {
|
|
391
|
+
nextFiberId = fiberId + 1;
|
|
392
|
+
}
|
|
601
393
|
};
|
|
602
394
|
|
|
603
|
-
// react fibers are double buffered, so the alternate fiber may
|
|
604
|
-
// be switched to the current fiber and vice versa.
|
|
605
|
-
// fiber === fiber.alternate.alternate
|
|
606
395
|
export const getFiberId = (fiber: Fiber): number => {
|
|
607
|
-
let
|
|
608
|
-
if (
|
|
609
|
-
|
|
396
|
+
let currentFiberId = fiberIdMap.get(fiber);
|
|
397
|
+
if (currentFiberId === undefined && fiber.alternate) {
|
|
398
|
+
currentFiberId = fiberIdMap.get(fiber.alternate);
|
|
610
399
|
}
|
|
611
|
-
if (
|
|
612
|
-
|
|
613
|
-
setFiberId(fiber,
|
|
400
|
+
if (currentFiberId === undefined) {
|
|
401
|
+
currentFiberId = nextFiberId++;
|
|
402
|
+
setFiberId(fiber, currentFiberId);
|
|
614
403
|
}
|
|
615
|
-
return
|
|
404
|
+
return currentFiberId;
|
|
616
405
|
};
|
|
617
406
|
|
|
618
407
|
const mountFiberRecursively = (
|
|
@@ -622,16 +411,14 @@ const mountFiberRecursively = (
|
|
|
622
411
|
): void => {
|
|
623
412
|
let fiber: Fiber | null = firstChild;
|
|
624
413
|
|
|
625
|
-
while (fiber
|
|
626
|
-
|
|
627
|
-
getFiberId(fiber);
|
|
628
|
-
}
|
|
414
|
+
while (fiber !== null) {
|
|
415
|
+
getFiberId(fiber);
|
|
629
416
|
const shouldIncludeInTree = !shouldFilterFiber(fiber);
|
|
630
417
|
if (shouldIncludeInTree && didFiberRender(fiber)) {
|
|
631
418
|
onRender(fiber, "mount");
|
|
632
419
|
}
|
|
633
420
|
|
|
634
|
-
if (fiber.tag ===
|
|
421
|
+
if (fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent) {
|
|
635
422
|
const isTimedOut = fiber.memoizedState !== null;
|
|
636
423
|
if (isTimedOut) {
|
|
637
424
|
// Special case: if Suspense mounts in a timed-out state,
|
|
@@ -646,18 +433,12 @@ const mountFiberRecursively = (
|
|
|
646
433
|
}
|
|
647
434
|
}
|
|
648
435
|
} else {
|
|
649
|
-
|
|
650
|
-
const areSuspenseChildrenConditionallyWrapped = (OffscreenComponentTag as number) === -1;
|
|
651
|
-
if (areSuspenseChildrenConditionallyWrapped) {
|
|
652
|
-
primaryChild = fiber.child;
|
|
653
|
-
} else if (fiber.child !== null) {
|
|
654
|
-
primaryChild = fiber.child.child;
|
|
655
|
-
}
|
|
436
|
+
const primaryChild = fiber.child?.child ?? null;
|
|
656
437
|
if (primaryChild !== null) {
|
|
657
438
|
mountFiberRecursively(onRender, primaryChild, false);
|
|
658
439
|
}
|
|
659
440
|
}
|
|
660
|
-
} else if (fiber.child
|
|
441
|
+
} else if (fiber.child !== null) {
|
|
661
442
|
mountFiberRecursively(onRender, fiber.child, true);
|
|
662
443
|
}
|
|
663
444
|
fiber = traverseSiblings ? fiber.sibling : null;
|
|
@@ -667,18 +448,13 @@ const mountFiberRecursively = (
|
|
|
667
448
|
const updateFiberRecursively = (
|
|
668
449
|
onRender: RenderHandler,
|
|
669
450
|
nextFiber: Fiber,
|
|
670
|
-
prevFiber: Fiber,
|
|
671
|
-
parentFiber: Fiber | null,
|
|
451
|
+
prevFiber: Fiber | null,
|
|
672
452
|
): void => {
|
|
673
|
-
|
|
674
|
-
getFiberId(nextFiber);
|
|
675
|
-
}
|
|
453
|
+
getFiberId(nextFiber);
|
|
676
454
|
if (!prevFiber) return;
|
|
677
|
-
|
|
678
|
-
getFiberId(prevFiber);
|
|
679
|
-
}
|
|
455
|
+
getFiberId(prevFiber);
|
|
680
456
|
|
|
681
|
-
const isSuspense = nextFiber.tag ===
|
|
457
|
+
const isSuspense = nextFiber.tag === getReactWorkTagsForFiber(nextFiber).SuspenseComponent;
|
|
682
458
|
|
|
683
459
|
const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
|
|
684
460
|
if (shouldIncludeInTree && didFiberRender(nextFiber)) {
|
|
@@ -707,7 +483,7 @@ const updateFiberRecursively = (
|
|
|
707
483
|
const prevFallbackChildSet = prevFiber.child?.sibling ?? null;
|
|
708
484
|
|
|
709
485
|
if (nextFallbackChildSet !== null && prevFallbackChildSet !== null) {
|
|
710
|
-
updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet
|
|
486
|
+
updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet);
|
|
711
487
|
}
|
|
712
488
|
} else if (prevDidTimeout && !nextDidTimeOut) {
|
|
713
489
|
// Fallback -> Primary:
|
|
@@ -746,14 +522,7 @@ const updateFiberRecursively = (
|
|
|
746
522
|
// Schedule updates and mounts depending on whether alternates exist.
|
|
747
523
|
// We don't track deletions here because they are reported separately.
|
|
748
524
|
if (nextChild.alternate) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
updateFiberRecursively(
|
|
752
|
-
onRender,
|
|
753
|
-
nextChild,
|
|
754
|
-
prevChild,
|
|
755
|
-
shouldIncludeInTree ? nextFiber : parentFiber,
|
|
756
|
-
);
|
|
525
|
+
updateFiberRecursively(onRender, nextChild, nextChild.alternate);
|
|
757
526
|
} else {
|
|
758
527
|
mountFiberRecursively(onRender, nextChild, false);
|
|
759
528
|
}
|
|
@@ -765,7 +534,7 @@ const updateFiberRecursively = (
|
|
|
765
534
|
};
|
|
766
535
|
|
|
767
536
|
const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
768
|
-
const isRoot = fiber.tag ===
|
|
537
|
+
const isRoot = fiber.tag === getReactWorkTagsForFiber(fiber).HostRoot;
|
|
769
538
|
|
|
770
539
|
if (isRoot || !shouldFilterFiber(fiber)) {
|
|
771
540
|
onRender(fiber, "unmount");
|
|
@@ -774,7 +543,8 @@ const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
|
774
543
|
|
|
775
544
|
const unmountFiberChildrenRecursively = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
776
545
|
// We might meet a nested Suspense on our way.
|
|
777
|
-
const isTimedOutSuspense =
|
|
546
|
+
const isTimedOutSuspense =
|
|
547
|
+
fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent && fiber.memoizedState !== null;
|
|
778
548
|
let child = fiber.child;
|
|
779
549
|
|
|
780
550
|
if (isTimedOutSuspense) {
|
|
@@ -798,11 +568,9 @@ const unmountFiberChildrenRecursively = (onRender: RenderHandler, fiber: Fiber):
|
|
|
798
568
|
}
|
|
799
569
|
};
|
|
800
570
|
|
|
801
|
-
let commitId = 0;
|
|
802
571
|
const rootInstanceMap = new WeakMap<
|
|
803
|
-
FiberRoot,
|
|
572
|
+
Fiber | FiberRoot,
|
|
804
573
|
{
|
|
805
|
-
id: number;
|
|
806
574
|
prevFiber: Fiber | null;
|
|
807
575
|
}
|
|
808
576
|
>();
|
|
@@ -814,37 +582,39 @@ const rootInstanceMap = new WeakMap<
|
|
|
814
582
|
* console.log(phase)
|
|
815
583
|
* })
|
|
816
584
|
*/
|
|
817
|
-
export const traverseRenderedFibers = (root: FiberRoot, onRender: RenderHandler): void => {
|
|
585
|
+
export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: RenderHandler): void => {
|
|
818
586
|
const fiber = "current" in root ? root.current : root;
|
|
819
587
|
|
|
820
588
|
let rootInstance = rootInstanceMap.get(root);
|
|
821
589
|
|
|
822
590
|
if (!rootInstance) {
|
|
823
|
-
rootInstance = {
|
|
591
|
+
rootInstance = { prevFiber: null };
|
|
824
592
|
rootInstanceMap.set(root, rootInstance);
|
|
825
593
|
}
|
|
826
594
|
|
|
827
595
|
const { prevFiber } = rootInstance;
|
|
828
|
-
// if fiberRoot don't have current instance, means it's been unmounted
|
|
829
596
|
if (!fiber) {
|
|
830
|
-
|
|
597
|
+
if (prevFiber) {
|
|
598
|
+
unmountFiber(onRender, prevFiber);
|
|
599
|
+
}
|
|
831
600
|
} else if (prevFiber !== null) {
|
|
832
601
|
const wasMounted =
|
|
833
|
-
prevFiber &&
|
|
834
|
-
prevFiber.memoizedState
|
|
835
|
-
prevFiber.memoizedState.element
|
|
602
|
+
prevFiber.memoizedState !== null &&
|
|
603
|
+
prevFiber.memoizedState.element !== null &&
|
|
604
|
+
prevFiber.memoizedState.element !== undefined &&
|
|
836
605
|
// A dehydrated root is not considered mounted
|
|
837
606
|
prevFiber.memoizedState.isDehydrated !== true;
|
|
838
607
|
const isMounted =
|
|
839
|
-
fiber.memoizedState
|
|
840
|
-
fiber.memoizedState.element
|
|
608
|
+
fiber.memoizedState !== null &&
|
|
609
|
+
fiber.memoizedState.element !== null &&
|
|
610
|
+
fiber.memoizedState.element !== undefined &&
|
|
841
611
|
// A dehydrated root is not considered mounted
|
|
842
612
|
fiber.memoizedState.isDehydrated !== true;
|
|
843
613
|
|
|
844
614
|
if (!wasMounted && isMounted) {
|
|
845
615
|
mountFiberRecursively(onRender, fiber, false);
|
|
846
616
|
} else if (wasMounted && isMounted) {
|
|
847
|
-
updateFiberRecursively(onRender, fiber, fiber.alternate
|
|
617
|
+
updateFiberRecursively(onRender, fiber, fiber.alternate);
|
|
848
618
|
} else if (wasMounted && !isMounted) {
|
|
849
619
|
unmountFiber(onRender, fiber);
|
|
850
620
|
}
|
|
@@ -855,191 +625,25 @@ export const traverseRenderedFibers = (root: FiberRoot, onRender: RenderHandler)
|
|
|
855
625
|
rootInstance.prevFiber = fiber;
|
|
856
626
|
};
|
|
857
627
|
|
|
858
|
-
const overrideRenderers = new Set<ReactRenderer>();
|
|
859
|
-
let areOverrideRenderersWired = false;
|
|
860
|
-
|
|
861
|
-
const wireOverrideRenderers = (): void => {
|
|
862
|
-
if (areOverrideRenderersWired) return;
|
|
863
|
-
if (!hasRDTHook()) return;
|
|
864
|
-
const rdtHook = getRDTHook();
|
|
865
|
-
if (!rdtHook?.renderers) return;
|
|
866
|
-
areOverrideRenderersWired = true;
|
|
867
|
-
|
|
868
|
-
ensureHookDispatchesToListeners(rdtHook);
|
|
869
|
-
for (const renderer of rdtHook.renderers.values()) {
|
|
870
|
-
overrideRenderers.add(renderer);
|
|
871
|
-
}
|
|
872
|
-
onRendererInject((renderer) => {
|
|
873
|
-
overrideRenderers.add(renderer);
|
|
874
|
-
});
|
|
875
|
-
};
|
|
876
|
-
|
|
877
|
-
const getRootRenderer = (fiber: Fiber): ReactRenderer | null => {
|
|
878
|
-
if (!hasRDTHook()) return null;
|
|
879
|
-
let hostRootFiber = fiber;
|
|
880
|
-
while (hostRootFiber.return) {
|
|
881
|
-
hostRootFiber = hostRootFiber.return;
|
|
882
|
-
}
|
|
883
|
-
const rendererId = rootRendererIds.get(hostRootFiber.stateNode);
|
|
884
|
-
if (rendererId === undefined) return null;
|
|
885
|
-
return getRDTHook().renderers?.get(rendererId) ?? null;
|
|
886
|
-
};
|
|
887
|
-
|
|
888
|
-
// dispatch to the renderer that owns the fiber's root when known; renderers
|
|
889
|
-
// that never committed through bippy's hook patch fall back to "try all",
|
|
890
|
-
// which matches the pre-ownership behavior for single-renderer apps
|
|
891
|
-
const resolveOverrideRenderers = (fiber: Fiber): ReactRenderer[] => {
|
|
892
|
-
wireOverrideRenderers();
|
|
893
|
-
const rootRenderer = getRootRenderer(fiber);
|
|
894
|
-
if (rootRenderer) return [rootRenderer];
|
|
895
|
-
return Array.from(overrideRenderers);
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
const applyPropsOverride = (
|
|
899
|
-
renderers: ReactRenderer[],
|
|
900
|
-
fiber: Fiber,
|
|
901
|
-
path: string[],
|
|
902
|
-
value: unknown,
|
|
903
|
-
): void => {
|
|
904
|
-
for (const renderer of renderers) {
|
|
905
|
-
try {
|
|
906
|
-
renderer.overrideProps?.(fiber, path, value);
|
|
907
|
-
} catch {}
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
|
|
911
|
-
const getHookStateDispatch = (
|
|
912
|
-
fiber: Fiber,
|
|
913
|
-
hookIndex: number,
|
|
914
|
-
): ((value: unknown) => void) | null => {
|
|
915
|
-
let hookState = fiber.memoizedState;
|
|
916
|
-
for (let i = 0; i < hookIndex; i++) {
|
|
917
|
-
if (!hookState?.next) return null;
|
|
918
|
-
hookState = hookState.next;
|
|
919
|
-
}
|
|
920
|
-
const queue = hookState?.queue;
|
|
921
|
-
if (isPOJO(queue) && typeof queue.dispatch === "function") {
|
|
922
|
-
return queue.dispatch as (value: unknown) => void;
|
|
923
|
-
}
|
|
924
|
-
return null;
|
|
925
|
-
};
|
|
926
|
-
|
|
927
|
-
const findContextProviderFiber = (fiber: Fiber, contextType: unknown): Fiber | null => {
|
|
928
|
-
let currentFiber: Fiber | null = fiber;
|
|
929
|
-
while (currentFiber) {
|
|
930
|
-
const fiberType = currentFiber.type;
|
|
931
|
-
if (fiberType === contextType || fiberType?.Provider === contextType) {
|
|
932
|
-
return currentFiber;
|
|
933
|
-
}
|
|
934
|
-
currentFiber = currentFiber.return;
|
|
935
|
-
}
|
|
936
|
-
return null;
|
|
937
|
-
};
|
|
938
|
-
|
|
939
|
-
const isPOJO = (maybePOJO: unknown): maybePOJO is Record<string, unknown> => {
|
|
940
|
-
return (
|
|
941
|
-
Object.prototype.toString.call(maybePOJO) === "[object Object]" &&
|
|
942
|
-
(Object.getPrototypeOf(maybePOJO) === Object.prototype ||
|
|
943
|
-
Object.getPrototypeOf(maybePOJO) === null)
|
|
944
|
-
);
|
|
945
|
-
};
|
|
946
|
-
|
|
947
|
-
const buildPathsFromValue = (
|
|
948
|
-
maybePOJO: Record<string, unknown>,
|
|
949
|
-
basePath: string[] = [],
|
|
950
|
-
): Array<{ path: string[]; value: unknown }> => {
|
|
951
|
-
if (!isPOJO(maybePOJO)) {
|
|
952
|
-
return [{ path: basePath, value: maybePOJO }];
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
const paths: Array<{ path: string[]; value: unknown }> = [];
|
|
956
|
-
|
|
957
|
-
for (const key in maybePOJO) {
|
|
958
|
-
const value = maybePOJO[key];
|
|
959
|
-
const path = basePath.concat(key);
|
|
960
|
-
|
|
961
|
-
if (isPOJO(value)) {
|
|
962
|
-
paths.push(...buildPathsFromValue(value, path));
|
|
963
|
-
} else {
|
|
964
|
-
paths.push({ path, value });
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
return paths;
|
|
969
|
-
};
|
|
970
|
-
|
|
971
|
-
const buildValueWrites = (partialValue: unknown): Array<{ path: string[]; value: unknown }> =>
|
|
972
|
-
isPOJO(partialValue) ? buildPathsFromValue(partialValue) : [{ path: [], value: partialValue }];
|
|
973
|
-
|
|
974
|
-
export const overrideProps = (fiber: Fiber, partialValue: Record<string, unknown>) => {
|
|
975
|
-
const renderers = resolveOverrideRenderers(fiber);
|
|
976
|
-
for (const { path, value } of buildValueWrites(partialValue)) {
|
|
977
|
-
applyPropsOverride(renderers, fiber, path, value);
|
|
978
|
-
}
|
|
979
|
-
};
|
|
980
|
-
|
|
981
|
-
export const overrideHookState = (fiber: Fiber, id: number, partialValue: unknown) => {
|
|
982
|
-
const renderers = resolveOverrideRenderers(fiber).filter((renderer) =>
|
|
983
|
-
Boolean(renderer.overrideHookState),
|
|
984
|
-
);
|
|
985
|
-
const writes = buildValueWrites(partialValue);
|
|
986
|
-
|
|
987
|
-
if (renderers.length > 0) {
|
|
988
|
-
const hookId = String(id);
|
|
989
|
-
for (const renderer of renderers) {
|
|
990
|
-
for (const { path, value } of writes) {
|
|
991
|
-
try {
|
|
992
|
-
renderer.overrideHookState?.(fiber, hookId, path, value);
|
|
993
|
-
} catch {}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
// production renderers don't expose overrideHookState; dispatching through
|
|
1000
|
-
// the hook's own queue still works there, but only for whole-value writes
|
|
1001
|
-
// (a path write through dispatch would replace the entire hook state)
|
|
1002
|
-
if (isPOJO(partialValue)) return;
|
|
1003
|
-
const dispatch = getHookStateDispatch(fiber, id);
|
|
1004
|
-
if (!dispatch) return;
|
|
1005
|
-
try {
|
|
1006
|
-
dispatch(partialValue);
|
|
1007
|
-
} catch {}
|
|
1008
|
-
};
|
|
1009
|
-
|
|
1010
|
-
export const overrideContext = (fiber: Fiber, contextType: unknown, partialValue: unknown) => {
|
|
1011
|
-
const providerFiber = findContextProviderFiber(fiber, contextType);
|
|
1012
|
-
if (!providerFiber) return;
|
|
1013
|
-
const renderers = resolveOverrideRenderers(providerFiber);
|
|
1014
|
-
for (const { path, value } of buildValueWrites(partialValue)) {
|
|
1015
|
-
applyPropsOverride(renderers, providerFiber, ["value", ...path], value);
|
|
1016
|
-
if (providerFiber.alternate) {
|
|
1017
|
-
applyPropsOverride(renderers, providerFiber.alternate, ["value", ...path], value);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
};
|
|
1021
|
-
|
|
1022
628
|
export interface InstrumentationOptions {
|
|
1023
629
|
name?: string;
|
|
1024
630
|
onActive?: () => unknown;
|
|
1025
|
-
onCommitFiberRoot?: (
|
|
631
|
+
onCommitFiberRoot?: (
|
|
632
|
+
rendererID: number,
|
|
633
|
+
root: FiberRoot,
|
|
634
|
+
priority: number | void,
|
|
635
|
+
didError?: boolean,
|
|
636
|
+
) => unknown;
|
|
1026
637
|
onCommitFiberUnmount?: (rendererID: number, fiber: Fiber) => unknown;
|
|
1027
638
|
onPostCommitFiberRoot?: (rendererID: number, root: FiberRoot) => unknown;
|
|
1028
639
|
onScheduleFiberRoot?: (rendererID: number, root: FiberRoot, children: React.ReactNode) => unknown;
|
|
1029
640
|
}
|
|
1030
641
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
>();
|
|
1037
|
-
const postCommitFiberRootListeners = new Set<
|
|
1038
|
-
NonNullable<InstrumentationOptions["onPostCommitFiberRoot"]>
|
|
1039
|
-
>();
|
|
1040
|
-
const scheduleFiberRootListeners = new Set<
|
|
1041
|
-
NonNullable<InstrumentationOptions["onScheduleFiberRoot"]>
|
|
1042
|
-
>();
|
|
642
|
+
interface InstrumentationSubscription {
|
|
643
|
+
options: InstrumentationOptions;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const instrumentationSubscriptions = new Set<InstrumentationSubscription>();
|
|
1043
647
|
|
|
1044
648
|
interface HookDispatchers {
|
|
1045
649
|
onCommitFiberRoot: ReactDevToolsGlobalHook["onCommitFiberRoot"];
|
|
@@ -1049,15 +653,14 @@ interface HookDispatchers {
|
|
|
1049
653
|
}
|
|
1050
654
|
|
|
1051
655
|
const hookDispatchers = new WeakMap<ReactDevToolsGlobalHook, Partial<HookDispatchers>>();
|
|
1052
|
-
|
|
1053
|
-
const rootRendererIds = new WeakMap<FiberRoot, number>();
|
|
656
|
+
let didSubscribeToHookReplacements = false;
|
|
1054
657
|
|
|
1055
658
|
// each hook event is dispatched from a single re-installable wrapper. If
|
|
1056
659
|
// something overwrites the hook method (devtools, direct assignment), the
|
|
1057
660
|
// next instrument() call installs a fresh wrapper over it; a superseded
|
|
1058
661
|
// wrapper still forwards the previous chain but skips the listeners so
|
|
1059
662
|
// they never fire twice.
|
|
1060
|
-
const
|
|
663
|
+
const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
1061
664
|
const dispatchers = hookDispatchers.get(rdtHook) ?? {};
|
|
1062
665
|
hookDispatchers.set(rdtHook, dispatchers);
|
|
1063
666
|
|
|
@@ -1070,13 +673,29 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1070
673
|
rendererID,
|
|
1071
674
|
root,
|
|
1072
675
|
priority,
|
|
676
|
+
didError,
|
|
1073
677
|
) => {
|
|
1074
|
-
prevOnCommitFiberRoot
|
|
678
|
+
if (prevOnCommitFiberRoot) {
|
|
679
|
+
prevOnCommitFiberRoot.call(rdtHook, rendererID, root, priority, didError);
|
|
680
|
+
}
|
|
1075
681
|
if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
682
|
+
setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
|
|
683
|
+
const rootMemoizedState = root.current.memoizedState;
|
|
684
|
+
const isUnmounting =
|
|
685
|
+
rootMemoizedState === null ||
|
|
686
|
+
rootMemoizedState.element === null ||
|
|
687
|
+
rootMemoizedState.element === undefined;
|
|
688
|
+
if (isUnmounting) {
|
|
689
|
+
_fiberRoots.delete(root);
|
|
690
|
+
rootRendererIds.delete(root);
|
|
691
|
+
} else {
|
|
692
|
+
_fiberRoots.add(root);
|
|
693
|
+
rootRendererIds.set(root, rendererID);
|
|
694
|
+
}
|
|
695
|
+
for (const { options } of instrumentationSubscriptions) {
|
|
696
|
+
if (options.onCommitFiberRoot) {
|
|
697
|
+
options.onCommitFiberRoot(rendererID, root, priority, didError);
|
|
698
|
+
}
|
|
1080
699
|
}
|
|
1081
700
|
};
|
|
1082
701
|
dispatchers.onCommitFiberRoot = dispatchCommitFiberRoot;
|
|
@@ -1092,12 +711,17 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1092
711
|
rendererID,
|
|
1093
712
|
fiber,
|
|
1094
713
|
) => {
|
|
1095
|
-
|
|
714
|
+
setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
|
|
715
|
+
if (prevOnCommitFiberUnmount) {
|
|
716
|
+
prevOnCommitFiberUnmount.call(rdtHook, rendererID, fiber);
|
|
717
|
+
}
|
|
1096
718
|
if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
|
|
1097
719
|
return;
|
|
1098
720
|
}
|
|
1099
|
-
for (const
|
|
1100
|
-
|
|
721
|
+
for (const { options } of instrumentationSubscriptions) {
|
|
722
|
+
if (options.onCommitFiberUnmount) {
|
|
723
|
+
options.onCommitFiberUnmount(rendererID, fiber);
|
|
724
|
+
}
|
|
1101
725
|
}
|
|
1102
726
|
};
|
|
1103
727
|
dispatchers.onCommitFiberUnmount = dispatchCommitFiberUnmount;
|
|
@@ -1113,12 +737,16 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1113
737
|
rendererID,
|
|
1114
738
|
root,
|
|
1115
739
|
) => {
|
|
1116
|
-
prevOnPostCommitFiberRoot
|
|
740
|
+
if (prevOnPostCommitFiberRoot) {
|
|
741
|
+
prevOnPostCommitFiberRoot.call(rdtHook, rendererID, root);
|
|
742
|
+
}
|
|
1117
743
|
if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
|
|
1118
744
|
return;
|
|
1119
745
|
}
|
|
1120
|
-
for (const
|
|
1121
|
-
|
|
746
|
+
for (const { options } of instrumentationSubscriptions) {
|
|
747
|
+
if (options.onPostCommitFiberRoot) {
|
|
748
|
+
options.onPostCommitFiberRoot(rendererID, root);
|
|
749
|
+
}
|
|
1122
750
|
}
|
|
1123
751
|
};
|
|
1124
752
|
dispatchers.onPostCommitFiberRoot = dispatchPostCommitFiberRoot;
|
|
@@ -1135,10 +763,14 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1135
763
|
root,
|
|
1136
764
|
children,
|
|
1137
765
|
) => {
|
|
1138
|
-
prevOnScheduleFiberRoot
|
|
766
|
+
if (prevOnScheduleFiberRoot) {
|
|
767
|
+
prevOnScheduleFiberRoot.call(rdtHook, rendererID, root, children);
|
|
768
|
+
}
|
|
1139
769
|
if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
|
|
1140
|
-
for (const
|
|
1141
|
-
|
|
770
|
+
for (const { options } of instrumentationSubscriptions) {
|
|
771
|
+
if (options.onScheduleFiberRoot) {
|
|
772
|
+
options.onScheduleFiberRoot(rendererID, root, children);
|
|
773
|
+
}
|
|
1142
774
|
}
|
|
1143
775
|
};
|
|
1144
776
|
dispatchers.onScheduleFiberRoot = dispatchScheduleFiberRoot;
|
|
@@ -1146,13 +778,24 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1146
778
|
}
|
|
1147
779
|
};
|
|
1148
780
|
|
|
781
|
+
const wireHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
782
|
+
if (!didSubscribeToHookReplacements) {
|
|
783
|
+
onRDTHookReplace(setHookEventDispatchers);
|
|
784
|
+
didSubscribeToHookReplacements = true;
|
|
785
|
+
}
|
|
786
|
+
setHookEventDispatchers(rdtHook);
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
if (hasRDTHook()) {
|
|
790
|
+
wireHookEventDispatchers(getRDTHook());
|
|
791
|
+
}
|
|
792
|
+
|
|
1149
793
|
/**
|
|
1150
794
|
* Instruments the DevTools hook. Each hook event is patched once and
|
|
1151
795
|
* dispatches to a set of listeners, so multiple `instrument` calls compose
|
|
1152
796
|
* without stacking patches. Returns an unsubscribe function that removes
|
|
1153
797
|
* exactly the handlers this call registered.
|
|
1154
|
-
* The
|
|
1155
|
-
* bippy subscriptions through `using`.
|
|
798
|
+
* The unsubscribe function can also be disposed through `using`.
|
|
1156
799
|
* @example
|
|
1157
800
|
* const unsubscribe = instrument({
|
|
1158
801
|
* onActive() {
|
|
@@ -1166,35 +809,19 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1166
809
|
*/
|
|
1167
810
|
export const instrument = (options: InstrumentationOptions): Unsubscribe => {
|
|
1168
811
|
const rdtHook = getRDTHook(options.onActive);
|
|
1169
|
-
|
|
1170
812
|
rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
|
|
1171
813
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
onScheduleFiberRoot,
|
|
1180
|
-
} = options;
|
|
1181
|
-
if (onCommitFiberRoot) commitFiberRootListeners.add(onCommitFiberRoot);
|
|
1182
|
-
if (onCommitFiberUnmount) commitFiberUnmountListeners.add(onCommitFiberUnmount);
|
|
1183
|
-
if (onPostCommitFiberRoot) postCommitFiberRootListeners.add(onPostCommitFiberRoot);
|
|
1184
|
-
if (onScheduleFiberRoot) scheduleFiberRootListeners.add(onScheduleFiberRoot);
|
|
1185
|
-
|
|
1186
|
-
return toUnsubscribe(() => {
|
|
1187
|
-
if (onActive) _onActiveListeners.delete(onActive);
|
|
1188
|
-
if (onCommitFiberRoot) commitFiberRootListeners.delete(onCommitFiberRoot);
|
|
1189
|
-
if (onCommitFiberUnmount) commitFiberUnmountListeners.delete(onCommitFiberUnmount);
|
|
1190
|
-
if (onPostCommitFiberRoot) postCommitFiberRootListeners.delete(onPostCommitFiberRoot);
|
|
1191
|
-
if (onScheduleFiberRoot) scheduleFiberRootListeners.delete(onScheduleFiberRoot);
|
|
814
|
+
wireHookEventDispatchers(rdtHook);
|
|
815
|
+
const subscription: InstrumentationSubscription = { options };
|
|
816
|
+
instrumentationSubscriptions.add(subscription);
|
|
817
|
+
|
|
818
|
+
return createUnsubscribe(() => {
|
|
819
|
+
if (options.onActive) _onActiveListeners.delete(options.onActive);
|
|
820
|
+
instrumentationSubscriptions.delete(subscription);
|
|
1192
821
|
});
|
|
1193
822
|
};
|
|
1194
823
|
|
|
1195
|
-
// React
|
|
1196
|
-
// caching discovered keys makes repeat lookups a single property read instead of a
|
|
1197
|
-
// scan over every own key of the element.
|
|
824
|
+
// React uses per-renderer suffixes for host-instance Fiber keys, so discovered keys are cached.
|
|
1198
825
|
const knownFiberPropertyKeys = new Set<string>();
|
|
1199
826
|
|
|
1200
827
|
const isFiberPropertyKey = (key: string): boolean =>
|
|
@@ -1202,7 +829,38 @@ const isFiberPropertyKey = (key: string): boolean =>
|
|
|
1202
829
|
key.startsWith("__reactInternalInstance$") ||
|
|
1203
830
|
key.startsWith("__reactFiber");
|
|
1204
831
|
|
|
1205
|
-
|
|
832
|
+
const getLegacyRootFiber = (hostInstance: object): Fiber | null => {
|
|
833
|
+
const reactRootContainer = Reflect.get(hostInstance, "_reactRootContainer");
|
|
834
|
+
if (typeof reactRootContainer !== "object" || reactRootContainer === null) return null;
|
|
835
|
+
const internalRoot = Reflect.get(reactRootContainer, "_internalRoot");
|
|
836
|
+
if (typeof internalRoot !== "object" || internalRoot === null) return null;
|
|
837
|
+
const current = Reflect.get(internalRoot, "current");
|
|
838
|
+
if (typeof current !== "object" || current === null) return null;
|
|
839
|
+
const child = Reflect.get(current, "child");
|
|
840
|
+
return isValidFiber(child) ? child : null;
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
const getInternalInstanceHandle = (hostInstance: object): Fiber | null => {
|
|
844
|
+
const internalInstanceHandle =
|
|
845
|
+
Reflect.get(hostInstance, "__internalInstanceHandle") ??
|
|
846
|
+
Reflect.get(hostInstance, "_internalInstanceHandle");
|
|
847
|
+
return isValidFiber(internalInstanceHandle) ? internalInstanceHandle : null;
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const getPublicHostInstance = (stateNode: unknown): unknown => {
|
|
851
|
+
if (typeof stateNode !== "object" || stateNode === null) return stateNode;
|
|
852
|
+
const canonical = Reflect.get(stateNode, "canonical");
|
|
853
|
+
if (typeof canonical === "object" && canonical !== null) {
|
|
854
|
+
const publicInstance = Reflect.get(canonical, "publicInstance");
|
|
855
|
+
if (typeof publicInstance === "object" && publicInstance !== null) {
|
|
856
|
+
return publicInstance;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
const nativeTag = Reflect.get(stateNode, "_nativeTag");
|
|
860
|
+
return typeof nativeTag === "number" ? nativeTag : stateNode;
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
export const getFiber = <T>(hostInstance: T): Fiber | null => {
|
|
1206
864
|
const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
1207
865
|
if (rdtHook?.renderers) {
|
|
1208
866
|
for (const renderer of rdtHook.renderers.values()) {
|
|
@@ -1213,42 +871,38 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
|
|
|
1213
871
|
}
|
|
1214
872
|
}
|
|
1215
873
|
|
|
1216
|
-
if (typeof hostInstance === "object" && hostInstance
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
return (hostInstance._reactRootContainer as any)?._internalRoot?.current?.child;
|
|
1220
|
-
}
|
|
874
|
+
if (typeof hostInstance === "object" && hostInstance !== null) {
|
|
875
|
+
const legacyRootFiber = getLegacyRootFiber(hostInstance);
|
|
876
|
+
if (legacyRootFiber) return legacyRootFiber;
|
|
1221
877
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
// handle" instead of a __reactFiber$-prefixed key
|
|
1225
|
-
const fabricInstanceHandle =
|
|
1226
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1227
|
-
(hostInstance as any).__internalInstanceHandle ??
|
|
1228
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1229
|
-
(hostInstance as any)._internalInstanceHandle;
|
|
1230
|
-
if (isFiber(fabricInstanceHandle)) {
|
|
1231
|
-
return fabricInstanceHandle;
|
|
1232
|
-
}
|
|
878
|
+
const internalInstanceHandle = getInternalInstanceHandle(hostInstance);
|
|
879
|
+
if (internalInstanceHandle) return internalInstanceHandle;
|
|
1233
880
|
|
|
1234
|
-
const hostInstanceRecord = hostInstance as Record<string, unknown>;
|
|
1235
881
|
for (const knownKey of knownFiberPropertyKeys) {
|
|
1236
|
-
const fiber =
|
|
1237
|
-
if (fiber) return fiber
|
|
882
|
+
const fiber = Reflect.get(hostInstance, knownKey);
|
|
883
|
+
if (isValidFiber(fiber)) return fiber;
|
|
1238
884
|
}
|
|
1239
885
|
|
|
1240
|
-
for (const key of Object.keys(
|
|
886
|
+
for (const key of Object.keys(hostInstance)) {
|
|
1241
887
|
if (isFiberPropertyKey(key)) {
|
|
1242
888
|
knownFiberPropertyKeys.add(key);
|
|
1243
|
-
|
|
889
|
+
const fiber = Reflect.get(hostInstance, key);
|
|
890
|
+
if (isValidFiber(fiber)) return fiber;
|
|
1244
891
|
}
|
|
1245
892
|
}
|
|
893
|
+
}
|
|
1246
894
|
|
|
895
|
+
if (
|
|
896
|
+
hostInstance !== null &&
|
|
897
|
+
hostInstance !== undefined &&
|
|
898
|
+
(typeof hostInstance === "object" || typeof hostInstance === "number")
|
|
899
|
+
) {
|
|
1247
900
|
for (const fiberRoot of _fiberRoots) {
|
|
1248
|
-
if (getRootRenderer(fiberRoot.current)?.findFiberByHostInstance) continue;
|
|
1249
901
|
const fiber = traverseFiber(
|
|
1250
902
|
fiberRoot.current,
|
|
1251
|
-
(candidateFiber) =>
|
|
903
|
+
(candidateFiber) =>
|
|
904
|
+
isHostFiber(candidateFiber) &&
|
|
905
|
+
getPublicHostInstance(candidateFiber.stateNode) === hostInstance,
|
|
1252
906
|
);
|
|
1253
907
|
if (fiber) return fiber;
|
|
1254
908
|
}
|
|
@@ -1256,6 +910,19 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
|
|
|
1256
910
|
return null;
|
|
1257
911
|
};
|
|
1258
912
|
|
|
1259
|
-
export
|
|
1260
|
-
|
|
1261
|
-
export
|
|
913
|
+
export const getFiberFromHostInstance = getFiber;
|
|
914
|
+
|
|
915
|
+
export {
|
|
916
|
+
BIPPY_INSTRUMENTATION_STRING,
|
|
917
|
+
_onActiveListeners,
|
|
918
|
+
_renderers,
|
|
919
|
+
getRDTHook,
|
|
920
|
+
hasRDTHook,
|
|
921
|
+
installRDTHook,
|
|
922
|
+
isRealReactDevtools,
|
|
923
|
+
onRendererInject,
|
|
924
|
+
patchRDTHook,
|
|
925
|
+
version,
|
|
926
|
+
} from "./rdt-hook.js";
|
|
927
|
+
export type { Unsubscribe } from "./rdt-hook.js";
|
|
928
|
+
export * from "./react-internals/index.js";
|