bippy 0.6.1-dev.fe75557 → 0.7.0-dev.07e6032
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 +223 -461
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- 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 +154 -3
- package/dist/index.d.ts +154 -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 -27
- package/src/core.ts +367 -685
- package/src/errors.ts +34 -0
- package/src/index.ts +1 -0
- package/src/install-hook-only.ts +6 -2
- package/src/rdt-hook.ts +177 -156
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +73 -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 +121 -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/core.d.cts +0 -214
- package/dist/core.d.ts +0 -214
- package/dist/core2.d.cts +0 -3
- package/dist/core2.d.ts +0 -3
- 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
|
|
3
|
-
|
|
1
|
+
// React must remain a type-only import because this module loads immediately after the DevTools hook.
|
|
4
2
|
import type * as React from "react";
|
|
5
3
|
|
|
6
4
|
import type {
|
|
7
|
-
ContextDependency,
|
|
8
5
|
Fiber,
|
|
9
6
|
FiberRoot,
|
|
10
|
-
|
|
7
|
+
HostFiber,
|
|
11
8
|
ReactDevToolsGlobalHook,
|
|
12
9
|
ReactRenderer,
|
|
13
|
-
} from "./
|
|
10
|
+
} from "./react-internals/index.js";
|
|
14
11
|
|
|
15
12
|
import {
|
|
16
13
|
_onActiveListeners,
|
|
17
14
|
BIPPY_INSTRUMENTATION_STRING,
|
|
15
|
+
createUnsubscribe,
|
|
18
16
|
getRDTHook,
|
|
19
17
|
hasRDTHook,
|
|
20
18
|
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
|
-
export const
|
|
73
|
+
export const isFiber = (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
|
+
isFiber(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,128 +110,36 @@ 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;
|
|
130
123
|
}
|
|
131
124
|
};
|
|
132
125
|
|
|
133
|
-
/**
|
|
134
|
-
* Returns `true` if the object is a {@link Fiber}
|
|
135
|
-
*/
|
|
136
|
-
export const isFiber = (maybeFiber: unknown): maybeFiber is Fiber => {
|
|
137
|
-
if (!maybeFiber || typeof maybeFiber !== "object") return false;
|
|
138
|
-
// this is a fast check. pendingProps will ALWAYS exist in fiber
|
|
139
|
-
// `containerInfo` is in FiberRootNode, not FiberNode
|
|
140
|
-
return "pendingProps" in maybeFiber && !("containerInfo" in maybeFiber);
|
|
141
|
-
};
|
|
142
|
-
|
|
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
126
|
/**
|
|
228
127
|
* 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
128
|
*/
|
|
230
129
|
export const didFiberRender = (fiber: Fiber): boolean => {
|
|
231
130
|
const nextProps = fiber.memoizedProps;
|
|
232
131
|
const prevProps = fiber.alternate?.memoizedProps || {};
|
|
233
|
-
const flags = fiber.flags ??
|
|
132
|
+
const flags = fiber.flags ?? fiber.effectTag ?? 0;
|
|
133
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
234
134
|
|
|
235
135
|
switch (fiber.tag) {
|
|
236
|
-
case
|
|
237
|
-
case
|
|
238
|
-
case
|
|
239
|
-
case
|
|
240
|
-
case
|
|
241
|
-
case
|
|
242
|
-
return (flags & PerformedWork) === PerformedWork;
|
|
136
|
+
case workTags.ClassComponent:
|
|
137
|
+
case workTags.ContextConsumer:
|
|
138
|
+
case workTags.ForwardRef:
|
|
139
|
+
case workTags.FunctionComponent:
|
|
140
|
+
case workTags.MemoComponent:
|
|
141
|
+
case workTags.SimpleMemoComponent: {
|
|
142
|
+
return (flags & ReactFiberFlags.PerformedWork) === ReactFiberFlags.PerformedWork;
|
|
243
143
|
}
|
|
244
144
|
default:
|
|
245
145
|
// Host nodes (DOM, root, etc.)
|
|
@@ -257,57 +157,18 @@ export const didFiberRender = (fiber: Fiber): boolean => {
|
|
|
257
157
|
*/
|
|
258
158
|
export const didFiberCommit = (fiber: Fiber): boolean => {
|
|
259
159
|
return Boolean(
|
|
260
|
-
(fiber.flags & (MutationMask | Cloned)) !== 0 ||
|
|
261
|
-
(fiber.subtreeFlags & (MutationMask | Cloned)) !== 0,
|
|
160
|
+
(fiber.flags & (MutationMask | ReactFiberFlags.Cloned)) !== 0 ||
|
|
161
|
+
(fiber.subtreeFlags & (MutationMask | ReactFiberFlags.Cloned)) !== 0,
|
|
262
162
|
);
|
|
263
163
|
};
|
|
264
164
|
|
|
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
165
|
/**
|
|
306
166
|
* Returns `true` if the {@link Fiber} should be filtered out during reconciliation.
|
|
307
167
|
*/
|
|
308
168
|
const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
169
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
309
170
|
switch (fiber.tag) {
|
|
310
|
-
case
|
|
171
|
+
case workTags.DehydratedSuspenseComponent:
|
|
311
172
|
// TODO: ideally we would show dehydrated Suspense immediately.
|
|
312
173
|
// However, it has some special behavior (like disconnecting
|
|
313
174
|
// an alternate and turning into real Suspense) which breaks DevTools.
|
|
@@ -315,13 +176,13 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
315
176
|
// https://github.com/bvaughn/react-devtools-experimental/issues/197
|
|
316
177
|
return true;
|
|
317
178
|
|
|
318
|
-
case
|
|
319
|
-
case
|
|
320
|
-
case
|
|
321
|
-
case
|
|
179
|
+
case workTags.Fragment:
|
|
180
|
+
case workTags.HostText:
|
|
181
|
+
case workTags.LegacyHiddenComponent:
|
|
182
|
+
case workTags.OffscreenComponent:
|
|
322
183
|
return true;
|
|
323
184
|
|
|
324
|
-
case
|
|
185
|
+
case workTags.HostRoot:
|
|
325
186
|
// It is never valid to filter the root element.
|
|
326
187
|
return false;
|
|
327
188
|
|
|
@@ -331,15 +192,15 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
331
192
|
|
|
332
193
|
if (typeof symbolOrNumber === "symbol") {
|
|
333
194
|
return (
|
|
334
|
-
symbolOrNumber.description === CONCURRENT_MODE_SYMBOL_DESCRIPTION ||
|
|
335
|
-
symbolOrNumber.description === DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION
|
|
195
|
+
symbolOrNumber.description === ReactSymbols.CONCURRENT_MODE_SYMBOL_DESCRIPTION ||
|
|
196
|
+
symbolOrNumber.description === ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION
|
|
336
197
|
);
|
|
337
198
|
}
|
|
338
199
|
|
|
339
200
|
switch (symbolOrNumber) {
|
|
340
|
-
case CONCURRENT_MODE_NUMBER:
|
|
341
|
-
case CONCURRENT_MODE_SYMBOL_STRING:
|
|
342
|
-
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
|
|
201
|
+
case ReactSymbols.CONCURRENT_MODE_NUMBER:
|
|
202
|
+
case ReactSymbols.CONCURRENT_MODE_SYMBOL_STRING:
|
|
203
|
+
case ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
|
|
343
204
|
return true;
|
|
344
205
|
|
|
345
206
|
default:
|
|
@@ -349,47 +210,6 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
|
|
|
349
210
|
}
|
|
350
211
|
};
|
|
351
212
|
|
|
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
213
|
/**
|
|
394
214
|
* Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
|
|
395
215
|
*/
|
|
@@ -405,144 +225,86 @@ export function traverseFiber(
|
|
|
405
225
|
): Promise<Fiber | null>;
|
|
406
226
|
export function traverseFiber(
|
|
407
227
|
fiber: Fiber | null,
|
|
408
|
-
selector:
|
|
228
|
+
selector: FiberSelector,
|
|
229
|
+
ascending?: boolean,
|
|
230
|
+
): Fiber | null | Promise<Fiber | null>;
|
|
231
|
+
export function traverseFiber(
|
|
232
|
+
fiber: Fiber | null,
|
|
233
|
+
selector: FiberSelector,
|
|
409
234
|
ascending = false,
|
|
410
235
|
): Fiber | null | Promise<Fiber | null> {
|
|
411
236
|
if (!fiber) return null;
|
|
412
237
|
|
|
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
|
-
})();
|
|
238
|
+
const selection = selector(fiber);
|
|
239
|
+
if (isPromiseLike<boolean | void>(selection)) {
|
|
240
|
+
return Promise.resolve(selection).then((didSelectFiber) =>
|
|
241
|
+
didSelectFiber === true ? fiber : traverseFiberChildren(fiber, selector, ascending),
|
|
242
|
+
);
|
|
430
243
|
}
|
|
244
|
+
if (selection === true) return fiber;
|
|
431
245
|
|
|
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;
|
|
246
|
+
return traverseFiberChildren(fiber, selector, ascending);
|
|
441
247
|
}
|
|
442
248
|
|
|
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;
|
|
249
|
+
const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
|
|
250
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
251
|
+
value !== null &&
|
|
252
|
+
"then" in value &&
|
|
253
|
+
typeof value.then === "function";
|
|
455
254
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
255
|
+
const traverseFiberChildren = (
|
|
256
|
+
fiber: Fiber,
|
|
257
|
+
selector: FiberSelector,
|
|
258
|
+
ascending: boolean,
|
|
259
|
+
): Fiber | null | Promise<Fiber | null> => {
|
|
260
|
+
const firstChild = ascending ? fiber.return : fiber.child;
|
|
261
|
+
return traverseFiberSiblings(firstChild, selector, ascending);
|
|
459
262
|
};
|
|
460
263
|
|
|
461
|
-
const
|
|
264
|
+
const traverseFiberSiblings = (
|
|
462
265
|
fiber: Fiber | null,
|
|
463
|
-
selector:
|
|
464
|
-
ascending
|
|
465
|
-
): Promise<Fiber | null> => {
|
|
266
|
+
selector: FiberSelector,
|
|
267
|
+
ascending: boolean,
|
|
268
|
+
): Fiber | null | Promise<Fiber | null> => {
|
|
466
269
|
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
|
-
|
|
474
|
-
child = ascending ? null : child.sibling;
|
|
475
|
-
}
|
|
476
|
-
return null;
|
|
477
|
-
};
|
|
478
270
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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;
|
|
271
|
+
const nextSibling = ascending ? null : fiber.sibling;
|
|
272
|
+
const match = traverseFiber(fiber, selector, ascending);
|
|
273
|
+
if (isPromiseLike<Fiber | null>(match)) {
|
|
274
|
+
return Promise.resolve(match).then(
|
|
275
|
+
(resolvedMatch) => resolvedMatch ?? traverseFiberSiblings(nextSibling, selector, ascending),
|
|
276
|
+
);
|
|
496
277
|
}
|
|
497
|
-
return
|
|
278
|
+
return match ?? traverseFiberSiblings(nextSibling, selector, ascending);
|
|
498
279
|
};
|
|
499
280
|
|
|
500
281
|
/**
|
|
501
282
|
* Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
|
|
502
283
|
*/
|
|
503
284
|
export const hasMemoCache = (fiber: Fiber): boolean => {
|
|
504
|
-
return Boolean(
|
|
285
|
+
return Boolean(fiber.updateQueue?.memoCache);
|
|
505
286
|
};
|
|
506
287
|
|
|
507
|
-
type FiberType =
|
|
508
|
-
| React.ComponentType<unknown>
|
|
509
|
-
| React.ForwardRefExoticComponent<unknown>
|
|
510
|
-
| React.MemoExoticComponent<React.ComponentType<unknown>>;
|
|
511
|
-
|
|
512
288
|
/**
|
|
513
289
|
* Returns the type (e.g. component definition) of the {@link Fiber}
|
|
514
290
|
*/
|
|
515
291
|
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;
|
|
292
|
+
if (isComponentType(type)) return type;
|
|
293
|
+
if (typeof type !== "object" || type === null) return null;
|
|
294
|
+
return getType(Reflect.get(type, "type") ?? Reflect.get(type, "render"));
|
|
528
295
|
};
|
|
529
296
|
|
|
530
297
|
/**
|
|
531
298
|
* Returns the display name of the {@link Fiber} type.
|
|
532
299
|
*/
|
|
533
300
|
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;
|
|
301
|
+
if (typeof type === "string") return type;
|
|
302
|
+
if ((typeof type !== "function" && typeof type !== "object") || type === null) return null;
|
|
303
|
+
const name = getTypeName(type);
|
|
542
304
|
if (name) return name;
|
|
543
|
-
const unwrappedType = getType(
|
|
305
|
+
const unwrappedType = getType(type);
|
|
544
306
|
if (!unwrappedType) return null;
|
|
545
|
-
return unwrappedType
|
|
307
|
+
return getTypeName(unwrappedType);
|
|
546
308
|
};
|
|
547
309
|
|
|
548
310
|
/**
|
|
@@ -550,7 +312,10 @@ export const getDisplayName = (type: unknown): null | string => {
|
|
|
550
312
|
*/
|
|
551
313
|
export const detectReactBuildType = (renderer: ReactRenderer): "development" | "production" => {
|
|
552
314
|
try {
|
|
553
|
-
if (
|
|
315
|
+
if (
|
|
316
|
+
typeof renderer.version === "string" &&
|
|
317
|
+
renderer.bundleType === ReactBuildType.Development
|
|
318
|
+
) {
|
|
554
319
|
return "development";
|
|
555
320
|
}
|
|
556
321
|
} catch {}
|
|
@@ -570,6 +335,7 @@ export const isInstrumentationActive = (): boolean => {
|
|
|
570
335
|
};
|
|
571
336
|
|
|
572
337
|
export const _fiberRoots = new Set<FiberRoot>();
|
|
338
|
+
const rootRendererIds = new WeakMap<FiberRoot, number>();
|
|
573
339
|
|
|
574
340
|
/**
|
|
575
341
|
* Returns the latest fiber (since it may be double-buffered).
|
|
@@ -577,42 +343,71 @@ export const _fiberRoots = new Set<FiberRoot>();
|
|
|
577
343
|
export const getLatestFiber = (fiber: Fiber): Fiber => {
|
|
578
344
|
const alternate = fiber.alternate;
|
|
579
345
|
if (!alternate) return fiber;
|
|
346
|
+
|
|
347
|
+
let rootFiber = fiber;
|
|
348
|
+
while (rootFiber.return) {
|
|
349
|
+
rootFiber = rootFiber.return;
|
|
350
|
+
}
|
|
351
|
+
if (isFiberRoot(rootFiber.stateNode)) {
|
|
352
|
+
const latestFiber = traverseFiber(rootFiber.stateNode.current, (innerFiber) => {
|
|
353
|
+
if (innerFiber === fiber || innerFiber === alternate) return true;
|
|
354
|
+
});
|
|
355
|
+
if (latestFiber) return latestFiber;
|
|
356
|
+
}
|
|
357
|
+
|
|
580
358
|
if (alternate.actualStartTime && fiber.actualStartTime) {
|
|
581
359
|
return alternate.actualStartTime > fiber.actualStartTime ? alternate : fiber;
|
|
582
360
|
}
|
|
583
361
|
for (const root of _fiberRoots) {
|
|
584
362
|
const latestFiber = traverseFiber(root.current, (innerFiber) => {
|
|
585
|
-
if (innerFiber === fiber) return true;
|
|
363
|
+
if (innerFiber === fiber || innerFiber === alternate) return true;
|
|
586
364
|
});
|
|
587
365
|
if (latestFiber) return latestFiber;
|
|
588
366
|
}
|
|
589
367
|
return fiber;
|
|
590
368
|
};
|
|
591
369
|
|
|
592
|
-
|
|
370
|
+
/**
|
|
371
|
+
* Returns the renderer that owns the {@link Fiber}.
|
|
372
|
+
*/
|
|
373
|
+
export const getRenderer = (fiber: Fiber): ReactRenderer | null => {
|
|
374
|
+
if (!hasRDTHook()) return null;
|
|
375
|
+
|
|
376
|
+
let rootFiber = fiber;
|
|
377
|
+
while (rootFiber.return) {
|
|
378
|
+
rootFiber = rootFiber.return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const fiberRoot = rootFiber.stateNode;
|
|
382
|
+
if (!isFiberRoot(fiberRoot)) return null;
|
|
383
|
+
|
|
384
|
+
const rendererID = rootRendererIds.get(fiberRoot);
|
|
385
|
+
if (rendererID === undefined) return null;
|
|
386
|
+
return getRDTHook().renderers.get(rendererID) ?? null;
|
|
387
|
+
};
|
|
593
388
|
|
|
594
389
|
export type RenderPhase = "mount" | "unmount" | "update";
|
|
595
390
|
|
|
596
|
-
let
|
|
391
|
+
let nextFiberId = 0;
|
|
597
392
|
const fiberIdMap = new WeakMap<Fiber, number>();
|
|
598
393
|
|
|
599
|
-
export const setFiberId = (fiber: Fiber,
|
|
600
|
-
fiberIdMap.set(fiber,
|
|
394
|
+
export const setFiberId = (fiber: Fiber, fiberId: number = nextFiberId++): void => {
|
|
395
|
+
fiberIdMap.set(fiber, fiberId);
|
|
396
|
+
if (Number.isSafeInteger(fiberId) && fiberId >= nextFiberId) {
|
|
397
|
+
nextFiberId = fiberId + 1;
|
|
398
|
+
}
|
|
601
399
|
};
|
|
602
400
|
|
|
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
401
|
export const getFiberId = (fiber: Fiber): number => {
|
|
607
|
-
let
|
|
608
|
-
if (
|
|
609
|
-
|
|
402
|
+
let currentFiberId = fiberIdMap.get(fiber);
|
|
403
|
+
if (currentFiberId === undefined && fiber.alternate) {
|
|
404
|
+
currentFiberId = fiberIdMap.get(fiber.alternate);
|
|
610
405
|
}
|
|
611
|
-
if (
|
|
612
|
-
|
|
613
|
-
setFiberId(fiber,
|
|
406
|
+
if (currentFiberId === undefined) {
|
|
407
|
+
currentFiberId = nextFiberId++;
|
|
408
|
+
setFiberId(fiber, currentFiberId);
|
|
614
409
|
}
|
|
615
|
-
return
|
|
410
|
+
return currentFiberId;
|
|
616
411
|
};
|
|
617
412
|
|
|
618
413
|
const mountFiberRecursively = (
|
|
@@ -622,16 +417,14 @@ const mountFiberRecursively = (
|
|
|
622
417
|
): void => {
|
|
623
418
|
let fiber: Fiber | null = firstChild;
|
|
624
419
|
|
|
625
|
-
while (fiber
|
|
626
|
-
|
|
627
|
-
getFiberId(fiber);
|
|
628
|
-
}
|
|
420
|
+
while (fiber !== null) {
|
|
421
|
+
getFiberId(fiber);
|
|
629
422
|
const shouldIncludeInTree = !shouldFilterFiber(fiber);
|
|
630
423
|
if (shouldIncludeInTree && didFiberRender(fiber)) {
|
|
631
424
|
onRender(fiber, "mount");
|
|
632
425
|
}
|
|
633
426
|
|
|
634
|
-
if (fiber.tag ===
|
|
427
|
+
if (fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent) {
|
|
635
428
|
const isTimedOut = fiber.memoizedState !== null;
|
|
636
429
|
if (isTimedOut) {
|
|
637
430
|
// Special case: if Suspense mounts in a timed-out state,
|
|
@@ -646,18 +439,12 @@ const mountFiberRecursively = (
|
|
|
646
439
|
}
|
|
647
440
|
}
|
|
648
441
|
} 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
|
-
}
|
|
442
|
+
const primaryChild = fiber.child?.child ?? null;
|
|
656
443
|
if (primaryChild !== null) {
|
|
657
444
|
mountFiberRecursively(onRender, primaryChild, false);
|
|
658
445
|
}
|
|
659
446
|
}
|
|
660
|
-
} else if (fiber.child
|
|
447
|
+
} else if (fiber.child !== null) {
|
|
661
448
|
mountFiberRecursively(onRender, fiber.child, true);
|
|
662
449
|
}
|
|
663
450
|
fiber = traverseSiblings ? fiber.sibling : null;
|
|
@@ -667,18 +454,13 @@ const mountFiberRecursively = (
|
|
|
667
454
|
const updateFiberRecursively = (
|
|
668
455
|
onRender: RenderHandler,
|
|
669
456
|
nextFiber: Fiber,
|
|
670
|
-
prevFiber: Fiber,
|
|
671
|
-
parentFiber: Fiber | null,
|
|
457
|
+
prevFiber: Fiber | null,
|
|
672
458
|
): void => {
|
|
673
|
-
|
|
674
|
-
getFiberId(nextFiber);
|
|
675
|
-
}
|
|
459
|
+
getFiberId(nextFiber);
|
|
676
460
|
if (!prevFiber) return;
|
|
677
|
-
|
|
678
|
-
getFiberId(prevFiber);
|
|
679
|
-
}
|
|
461
|
+
getFiberId(prevFiber);
|
|
680
462
|
|
|
681
|
-
const isSuspense = nextFiber.tag ===
|
|
463
|
+
const isSuspense = nextFiber.tag === getReactWorkTagsForFiber(nextFiber).SuspenseComponent;
|
|
682
464
|
|
|
683
465
|
const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
|
|
684
466
|
if (shouldIncludeInTree && didFiberRender(nextFiber)) {
|
|
@@ -707,7 +489,7 @@ const updateFiberRecursively = (
|
|
|
707
489
|
const prevFallbackChildSet = prevFiber.child?.sibling ?? null;
|
|
708
490
|
|
|
709
491
|
if (nextFallbackChildSet !== null && prevFallbackChildSet !== null) {
|
|
710
|
-
updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet
|
|
492
|
+
updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet);
|
|
711
493
|
}
|
|
712
494
|
} else if (prevDidTimeout && !nextDidTimeOut) {
|
|
713
495
|
// Fallback -> Primary:
|
|
@@ -746,14 +528,7 @@ const updateFiberRecursively = (
|
|
|
746
528
|
// Schedule updates and mounts depending on whether alternates exist.
|
|
747
529
|
// We don't track deletions here because they are reported separately.
|
|
748
530
|
if (nextChild.alternate) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
updateFiberRecursively(
|
|
752
|
-
onRender,
|
|
753
|
-
nextChild,
|
|
754
|
-
prevChild,
|
|
755
|
-
shouldIncludeInTree ? nextFiber : parentFiber,
|
|
756
|
-
);
|
|
531
|
+
updateFiberRecursively(onRender, nextChild, nextChild.alternate);
|
|
757
532
|
} else {
|
|
758
533
|
mountFiberRecursively(onRender, nextChild, false);
|
|
759
534
|
}
|
|
@@ -765,7 +540,7 @@ const updateFiberRecursively = (
|
|
|
765
540
|
};
|
|
766
541
|
|
|
767
542
|
const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
768
|
-
const isRoot = fiber.tag ===
|
|
543
|
+
const isRoot = fiber.tag === getReactWorkTagsForFiber(fiber).HostRoot;
|
|
769
544
|
|
|
770
545
|
if (isRoot || !shouldFilterFiber(fiber)) {
|
|
771
546
|
onRender(fiber, "unmount");
|
|
@@ -774,7 +549,8 @@ const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
|
774
549
|
|
|
775
550
|
const unmountFiberChildrenRecursively = (onRender: RenderHandler, fiber: Fiber): void => {
|
|
776
551
|
// We might meet a nested Suspense on our way.
|
|
777
|
-
const isTimedOutSuspense =
|
|
552
|
+
const isTimedOutSuspense =
|
|
553
|
+
fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent && fiber.memoizedState !== null;
|
|
778
554
|
let child = fiber.child;
|
|
779
555
|
|
|
780
556
|
if (isTimedOutSuspense) {
|
|
@@ -798,15 +574,25 @@ const unmountFiberChildrenRecursively = (onRender: RenderHandler, fiber: Fiber):
|
|
|
798
574
|
}
|
|
799
575
|
};
|
|
800
576
|
|
|
801
|
-
let commitId = 0;
|
|
802
577
|
const rootInstanceMap = new WeakMap<
|
|
803
|
-
FiberRoot,
|
|
578
|
+
Fiber | FiberRoot,
|
|
804
579
|
{
|
|
805
|
-
id: number;
|
|
806
580
|
prevFiber: Fiber | null;
|
|
807
581
|
}
|
|
808
582
|
>();
|
|
809
583
|
|
|
584
|
+
// Roots from custom renderers may not have a memoizedState at all.
|
|
585
|
+
const isRootFiberMounted = (fiber: Fiber): boolean => {
|
|
586
|
+
const rootState = fiber.memoizedState;
|
|
587
|
+
if (rootState === null || rootState === undefined) return false;
|
|
588
|
+
return (
|
|
589
|
+
rootState.element !== null &&
|
|
590
|
+
rootState.element !== undefined &&
|
|
591
|
+
// A dehydrated root is not considered mounted
|
|
592
|
+
rootState.isDehydrated !== true
|
|
593
|
+
);
|
|
594
|
+
};
|
|
595
|
+
|
|
810
596
|
/**
|
|
811
597
|
* Creates a fiber visitor function. Must pass a fiber root and a render handler.
|
|
812
598
|
* @example
|
|
@@ -814,37 +600,29 @@ const rootInstanceMap = new WeakMap<
|
|
|
814
600
|
* console.log(phase)
|
|
815
601
|
* })
|
|
816
602
|
*/
|
|
817
|
-
export const traverseRenderedFibers = (root: FiberRoot, onRender: RenderHandler): void => {
|
|
603
|
+
export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: RenderHandler): void => {
|
|
818
604
|
const fiber = "current" in root ? root.current : root;
|
|
819
605
|
|
|
820
606
|
let rootInstance = rootInstanceMap.get(root);
|
|
821
607
|
|
|
822
608
|
if (!rootInstance) {
|
|
823
|
-
rootInstance = {
|
|
609
|
+
rootInstance = { prevFiber: null };
|
|
824
610
|
rootInstanceMap.set(root, rootInstance);
|
|
825
611
|
}
|
|
826
612
|
|
|
827
613
|
const { prevFiber } = rootInstance;
|
|
828
|
-
// if fiberRoot don't have current instance, means it's been unmounted
|
|
829
614
|
if (!fiber) {
|
|
830
|
-
|
|
615
|
+
if (prevFiber) {
|
|
616
|
+
unmountFiber(onRender, prevFiber);
|
|
617
|
+
}
|
|
831
618
|
} else if (prevFiber !== null) {
|
|
832
|
-
const wasMounted =
|
|
833
|
-
|
|
834
|
-
prevFiber.memoizedState != null &&
|
|
835
|
-
prevFiber.memoizedState.element != null &&
|
|
836
|
-
// A dehydrated root is not considered mounted
|
|
837
|
-
prevFiber.memoizedState.isDehydrated !== true;
|
|
838
|
-
const isMounted =
|
|
839
|
-
fiber.memoizedState != null &&
|
|
840
|
-
fiber.memoizedState.element != null &&
|
|
841
|
-
// A dehydrated root is not considered mounted
|
|
842
|
-
fiber.memoizedState.isDehydrated !== true;
|
|
619
|
+
const wasMounted = isRootFiberMounted(prevFiber);
|
|
620
|
+
const isMounted = isRootFiberMounted(fiber);
|
|
843
621
|
|
|
844
622
|
if (!wasMounted && isMounted) {
|
|
845
623
|
mountFiberRecursively(onRender, fiber, false);
|
|
846
624
|
} else if (wasMounted && isMounted) {
|
|
847
|
-
updateFiberRecursively(onRender, fiber, fiber.alternate
|
|
625
|
+
updateFiberRecursively(onRender, fiber, fiber.alternate);
|
|
848
626
|
} else if (wasMounted && !isMounted) {
|
|
849
627
|
unmountFiber(onRender, fiber);
|
|
850
628
|
}
|
|
@@ -855,191 +633,25 @@ export const traverseRenderedFibers = (root: FiberRoot, onRender: RenderHandler)
|
|
|
855
633
|
rootInstance.prevFiber = fiber;
|
|
856
634
|
};
|
|
857
635
|
|
|
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
636
|
export interface InstrumentationOptions {
|
|
1023
637
|
name?: string;
|
|
1024
638
|
onActive?: () => unknown;
|
|
1025
|
-
onCommitFiberRoot?: (
|
|
639
|
+
onCommitFiberRoot?: (
|
|
640
|
+
rendererID: number,
|
|
641
|
+
root: FiberRoot,
|
|
642
|
+
priority: number | void,
|
|
643
|
+
didError?: boolean,
|
|
644
|
+
) => unknown;
|
|
1026
645
|
onCommitFiberUnmount?: (rendererID: number, fiber: Fiber) => unknown;
|
|
1027
646
|
onPostCommitFiberRoot?: (rendererID: number, root: FiberRoot) => unknown;
|
|
1028
647
|
onScheduleFiberRoot?: (rendererID: number, root: FiberRoot, children: React.ReactNode) => unknown;
|
|
1029
648
|
}
|
|
1030
649
|
|
|
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
|
-
>();
|
|
650
|
+
interface InstrumentationSubscription {
|
|
651
|
+
options: InstrumentationOptions;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const instrumentationSubscriptions = new Set<InstrumentationSubscription>();
|
|
1043
655
|
|
|
1044
656
|
interface HookDispatchers {
|
|
1045
657
|
onCommitFiberRoot: ReactDevToolsGlobalHook["onCommitFiberRoot"];
|
|
@@ -1049,15 +661,14 @@ interface HookDispatchers {
|
|
|
1049
661
|
}
|
|
1050
662
|
|
|
1051
663
|
const hookDispatchers = new WeakMap<ReactDevToolsGlobalHook, Partial<HookDispatchers>>();
|
|
1052
|
-
|
|
1053
|
-
const rootRendererIds = new WeakMap<FiberRoot, number>();
|
|
664
|
+
let didSubscribeToHookReplacements = false;
|
|
1054
665
|
|
|
1055
666
|
// each hook event is dispatched from a single re-installable wrapper. If
|
|
1056
667
|
// something overwrites the hook method (devtools, direct assignment), the
|
|
1057
668
|
// next instrument() call installs a fresh wrapper over it; a superseded
|
|
1058
669
|
// wrapper still forwards the previous chain but skips the listeners so
|
|
1059
670
|
// they never fire twice.
|
|
1060
|
-
const
|
|
671
|
+
const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
1061
672
|
const dispatchers = hookDispatchers.get(rdtHook) ?? {};
|
|
1062
673
|
hookDispatchers.set(rdtHook, dispatchers);
|
|
1063
674
|
|
|
@@ -1070,13 +681,31 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1070
681
|
rendererID,
|
|
1071
682
|
root,
|
|
1072
683
|
priority,
|
|
684
|
+
didError,
|
|
1073
685
|
) => {
|
|
1074
|
-
prevOnCommitFiberRoot
|
|
686
|
+
if (prevOnCommitFiberRoot) {
|
|
687
|
+
prevOnCommitFiberRoot.call(rdtHook, rendererID, root, priority, didError);
|
|
688
|
+
}
|
|
1075
689
|
if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
690
|
+
setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
|
|
691
|
+
// Custom renderers and test harnesses commit roots without a memoizedState;
|
|
692
|
+
// those must stay tracked, so only explicit unmount evidence removes a root.
|
|
693
|
+
const rootMemoizedState = root.current.memoizedState;
|
|
694
|
+
const isUnmounting =
|
|
695
|
+
rootMemoizedState === null ||
|
|
696
|
+
(rootMemoizedState !== undefined &&
|
|
697
|
+
(rootMemoizedState.element === null || rootMemoizedState.element === undefined));
|
|
698
|
+
if (isUnmounting) {
|
|
699
|
+
_fiberRoots.delete(root);
|
|
700
|
+
rootRendererIds.delete(root);
|
|
701
|
+
} else {
|
|
702
|
+
_fiberRoots.add(root);
|
|
703
|
+
rootRendererIds.set(root, rendererID);
|
|
704
|
+
}
|
|
705
|
+
for (const { options } of instrumentationSubscriptions) {
|
|
706
|
+
if (options.onCommitFiberRoot) {
|
|
707
|
+
options.onCommitFiberRoot(rendererID, root, priority, didError);
|
|
708
|
+
}
|
|
1080
709
|
}
|
|
1081
710
|
};
|
|
1082
711
|
dispatchers.onCommitFiberRoot = dispatchCommitFiberRoot;
|
|
@@ -1092,12 +721,17 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1092
721
|
rendererID,
|
|
1093
722
|
fiber,
|
|
1094
723
|
) => {
|
|
1095
|
-
|
|
724
|
+
setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
|
|
725
|
+
if (prevOnCommitFiberUnmount) {
|
|
726
|
+
prevOnCommitFiberUnmount.call(rdtHook, rendererID, fiber);
|
|
727
|
+
}
|
|
1096
728
|
if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
|
|
1097
729
|
return;
|
|
1098
730
|
}
|
|
1099
|
-
for (const
|
|
1100
|
-
|
|
731
|
+
for (const { options } of instrumentationSubscriptions) {
|
|
732
|
+
if (options.onCommitFiberUnmount) {
|
|
733
|
+
options.onCommitFiberUnmount(rendererID, fiber);
|
|
734
|
+
}
|
|
1101
735
|
}
|
|
1102
736
|
};
|
|
1103
737
|
dispatchers.onCommitFiberUnmount = dispatchCommitFiberUnmount;
|
|
@@ -1113,12 +747,16 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1113
747
|
rendererID,
|
|
1114
748
|
root,
|
|
1115
749
|
) => {
|
|
1116
|
-
prevOnPostCommitFiberRoot
|
|
750
|
+
if (prevOnPostCommitFiberRoot) {
|
|
751
|
+
prevOnPostCommitFiberRoot.call(rdtHook, rendererID, root);
|
|
752
|
+
}
|
|
1117
753
|
if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
|
|
1118
754
|
return;
|
|
1119
755
|
}
|
|
1120
|
-
for (const
|
|
1121
|
-
|
|
756
|
+
for (const { options } of instrumentationSubscriptions) {
|
|
757
|
+
if (options.onPostCommitFiberRoot) {
|
|
758
|
+
options.onPostCommitFiberRoot(rendererID, root);
|
|
759
|
+
}
|
|
1122
760
|
}
|
|
1123
761
|
};
|
|
1124
762
|
dispatchers.onPostCommitFiberRoot = dispatchPostCommitFiberRoot;
|
|
@@ -1135,10 +773,14 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1135
773
|
root,
|
|
1136
774
|
children,
|
|
1137
775
|
) => {
|
|
1138
|
-
prevOnScheduleFiberRoot
|
|
776
|
+
if (prevOnScheduleFiberRoot) {
|
|
777
|
+
prevOnScheduleFiberRoot.call(rdtHook, rendererID, root, children);
|
|
778
|
+
}
|
|
1139
779
|
if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
|
|
1140
|
-
for (const
|
|
1141
|
-
|
|
780
|
+
for (const { options } of instrumentationSubscriptions) {
|
|
781
|
+
if (options.onScheduleFiberRoot) {
|
|
782
|
+
options.onScheduleFiberRoot(rendererID, root, children);
|
|
783
|
+
}
|
|
1142
784
|
}
|
|
1143
785
|
};
|
|
1144
786
|
dispatchers.onScheduleFiberRoot = dispatchScheduleFiberRoot;
|
|
@@ -1146,13 +788,28 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1146
788
|
}
|
|
1147
789
|
};
|
|
1148
790
|
|
|
791
|
+
const wireHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
792
|
+
if (!didSubscribeToHookReplacements) {
|
|
793
|
+
onRDTHookReplace(setHookEventDispatchers);
|
|
794
|
+
didSubscribeToHookReplacements = true;
|
|
795
|
+
}
|
|
796
|
+
setHookEventDispatchers(rdtHook);
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// Importing bippy must never crash module evaluation, even when a foreign
|
|
800
|
+
// hook is frozen or otherwise rejects patching.
|
|
801
|
+
try {
|
|
802
|
+
if (hasRDTHook()) {
|
|
803
|
+
wireHookEventDispatchers(getRDTHook());
|
|
804
|
+
}
|
|
805
|
+
} catch {}
|
|
806
|
+
|
|
1149
807
|
/**
|
|
1150
808
|
* Instruments the DevTools hook. Each hook event is patched once and
|
|
1151
809
|
* dispatches to a set of listeners, so multiple `instrument` calls compose
|
|
1152
810
|
* without stacking patches. Returns an unsubscribe function that removes
|
|
1153
811
|
* exactly the handlers this call registered.
|
|
1154
|
-
* The
|
|
1155
|
-
* bippy subscriptions through `using`.
|
|
812
|
+
* The unsubscribe function can also be disposed through `using`.
|
|
1156
813
|
* @example
|
|
1157
814
|
* const unsubscribe = instrument({
|
|
1158
815
|
* onActive() {
|
|
@@ -1166,35 +823,19 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
|
|
|
1166
823
|
*/
|
|
1167
824
|
export const instrument = (options: InstrumentationOptions): Unsubscribe => {
|
|
1168
825
|
const rdtHook = getRDTHook(options.onActive);
|
|
1169
|
-
|
|
1170
826
|
rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
|
|
1171
827
|
|
|
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);
|
|
828
|
+
wireHookEventDispatchers(rdtHook);
|
|
829
|
+
const subscription: InstrumentationSubscription = { options };
|
|
830
|
+
instrumentationSubscriptions.add(subscription);
|
|
831
|
+
|
|
832
|
+
return createUnsubscribe(() => {
|
|
833
|
+
if (options.onActive) _onActiveListeners.delete(options.onActive);
|
|
834
|
+
instrumentationSubscriptions.delete(subscription);
|
|
1192
835
|
});
|
|
1193
836
|
};
|
|
1194
837
|
|
|
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.
|
|
838
|
+
// React uses per-renderer suffixes for host-instance Fiber keys, so discovered keys are cached.
|
|
1198
839
|
const knownFiberPropertyKeys = new Set<string>();
|
|
1199
840
|
|
|
1200
841
|
const isFiberPropertyKey = (key: string): boolean =>
|
|
@@ -1202,7 +843,38 @@ const isFiberPropertyKey = (key: string): boolean =>
|
|
|
1202
843
|
key.startsWith("__reactInternalInstance$") ||
|
|
1203
844
|
key.startsWith("__reactFiber");
|
|
1204
845
|
|
|
1205
|
-
|
|
846
|
+
const getLegacyRootFiber = (hostInstance: object): Fiber | null => {
|
|
847
|
+
const reactRootContainer = Reflect.get(hostInstance, "_reactRootContainer");
|
|
848
|
+
if (typeof reactRootContainer !== "object" || reactRootContainer === null) return null;
|
|
849
|
+
const internalRoot = Reflect.get(reactRootContainer, "_internalRoot");
|
|
850
|
+
if (typeof internalRoot !== "object" || internalRoot === null) return null;
|
|
851
|
+
const current = Reflect.get(internalRoot, "current");
|
|
852
|
+
if (typeof current !== "object" || current === null) return null;
|
|
853
|
+
const child = Reflect.get(current, "child");
|
|
854
|
+
return isFiber(child) ? child : null;
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
const getInternalInstanceHandle = (hostInstance: object): Fiber | null => {
|
|
858
|
+
const internalInstanceHandle =
|
|
859
|
+
Reflect.get(hostInstance, "__internalInstanceHandle") ??
|
|
860
|
+
Reflect.get(hostInstance, "_internalInstanceHandle");
|
|
861
|
+
return isFiber(internalInstanceHandle) ? internalInstanceHandle : null;
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
const getPublicHostInstance = (stateNode: unknown): unknown => {
|
|
865
|
+
if (typeof stateNode !== "object" || stateNode === null) return stateNode;
|
|
866
|
+
const canonical = Reflect.get(stateNode, "canonical");
|
|
867
|
+
if (typeof canonical === "object" && canonical !== null) {
|
|
868
|
+
const publicInstance = Reflect.get(canonical, "publicInstance");
|
|
869
|
+
if (typeof publicInstance === "object" && publicInstance !== null) {
|
|
870
|
+
return publicInstance;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const nativeTag = Reflect.get(stateNode, "_nativeTag");
|
|
874
|
+
return typeof nativeTag === "number" ? nativeTag : stateNode;
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
export const getFiber = <T>(hostInstance: T): Fiber | null => {
|
|
1206
878
|
const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
1207
879
|
if (rdtHook?.renderers) {
|
|
1208
880
|
for (const renderer of rdtHook.renderers.values()) {
|
|
@@ -1213,42 +885,38 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
|
|
|
1213
885
|
}
|
|
1214
886
|
}
|
|
1215
887
|
|
|
1216
|
-
if (typeof hostInstance === "object" && hostInstance
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
return (hostInstance._reactRootContainer as any)?._internalRoot?.current?.child;
|
|
1220
|
-
}
|
|
888
|
+
if (typeof hostInstance === "object" && hostInstance !== null) {
|
|
889
|
+
const legacyRootFiber = getLegacyRootFiber(hostInstance);
|
|
890
|
+
if (legacyRootFiber) return legacyRootFiber;
|
|
1221
891
|
|
|
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
|
-
}
|
|
892
|
+
const internalInstanceHandle = getInternalInstanceHandle(hostInstance);
|
|
893
|
+
if (internalInstanceHandle) return internalInstanceHandle;
|
|
1233
894
|
|
|
1234
|
-
const hostInstanceRecord = hostInstance as Record<string, unknown>;
|
|
1235
895
|
for (const knownKey of knownFiberPropertyKeys) {
|
|
1236
|
-
const fiber =
|
|
1237
|
-
if (fiber) return fiber
|
|
896
|
+
const fiber = Reflect.get(hostInstance, knownKey);
|
|
897
|
+
if (isFiber(fiber)) return fiber;
|
|
1238
898
|
}
|
|
1239
899
|
|
|
1240
|
-
for (const key of Object.keys(
|
|
900
|
+
for (const key of Object.keys(hostInstance)) {
|
|
1241
901
|
if (isFiberPropertyKey(key)) {
|
|
1242
902
|
knownFiberPropertyKeys.add(key);
|
|
1243
|
-
|
|
903
|
+
const fiber = Reflect.get(hostInstance, key);
|
|
904
|
+
if (isFiber(fiber)) return fiber;
|
|
1244
905
|
}
|
|
1245
906
|
}
|
|
907
|
+
}
|
|
1246
908
|
|
|
909
|
+
if (
|
|
910
|
+
hostInstance !== null &&
|
|
911
|
+
hostInstance !== undefined &&
|
|
912
|
+
(typeof hostInstance === "object" || typeof hostInstance === "number")
|
|
913
|
+
) {
|
|
1247
914
|
for (const fiberRoot of _fiberRoots) {
|
|
1248
|
-
if (getRootRenderer(fiberRoot.current)?.findFiberByHostInstance) continue;
|
|
1249
915
|
const fiber = traverseFiber(
|
|
1250
916
|
fiberRoot.current,
|
|
1251
|
-
(candidateFiber) =>
|
|
917
|
+
(candidateFiber) =>
|
|
918
|
+
isHostFiber(candidateFiber) &&
|
|
919
|
+
getPublicHostInstance(candidateFiber.stateNode) === hostInstance,
|
|
1252
920
|
);
|
|
1253
921
|
if (fiber) return fiber;
|
|
1254
922
|
}
|
|
@@ -1256,6 +924,20 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
|
|
|
1256
924
|
return null;
|
|
1257
925
|
};
|
|
1258
926
|
|
|
1259
|
-
export
|
|
1260
|
-
|
|
1261
|
-
export
|
|
927
|
+
export const getFiberFromHostInstance = getFiber;
|
|
928
|
+
|
|
929
|
+
export {
|
|
930
|
+
BIPPY_INSTRUMENTATION_STRING,
|
|
931
|
+
_onActiveListeners,
|
|
932
|
+
_renderers,
|
|
933
|
+
getRDTHook,
|
|
934
|
+
hasRDTHook,
|
|
935
|
+
installRDTHook,
|
|
936
|
+
isReactRefresh,
|
|
937
|
+
isRealReactDevtools,
|
|
938
|
+
onRendererInject,
|
|
939
|
+
patchRDTHook,
|
|
940
|
+
version,
|
|
941
|
+
} from "./rdt-hook.js";
|
|
942
|
+
export type { Unsubscribe } from "./rdt-hook.js";
|
|
943
|
+
export * from "./react-internals/index.js";
|