bippy 0.7.2 → 0.7.3-dev.0ac832d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/README.md +9 -76
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +56 -9
- package/dist/errors.d.ts +56 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -38
- package/dist/index.d.ts +18 -42
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +11 -11
- package/dist/source.d.cts +36 -9
- package/dist/source.d.ts +36 -9
- package/dist/source.js +11 -11
- package/package.json +5 -33
- package/src/call-listener.ts +13 -0
- package/src/core.ts +287 -258
- package/src/rdt-hook.ts +130 -71
- package/src/react-internals/current-fiber.ts +84 -0
- package/src/react-internals/index.ts +38 -11
- package/src/react-internals/types.ts +33 -6
- package/src/react.ts +141 -55
- package/src/source/constants.ts +1 -3
- package/src/source/get-display-name-from-source.ts +10 -3
- package/src/source/get-source.ts +39 -19
- package/src/source/index.ts +17 -3
- package/src/source/inspect-hooks.ts +198 -49
- package/src/source/owner-stack.ts +43 -35
- package/src/source/parse-debug-stack.ts +1 -1
- package/src/source/parse-hook-names.ts +15 -8
- package/src/source/parse-stack.ts +114 -64
- package/src/source/renderer-dispatchers.ts +9 -4
- package/src/source/symbolication.ts +68 -36
package/src/rdt-hook.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// This module must load before React so renderers can inject into the hook.
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { callListener } from "./call-listener.js";
|
|
4
|
+
import type { FiberRoot, ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
|
|
4
5
|
|
|
5
6
|
export interface Unsubscribe extends Disposable {
|
|
6
7
|
(): void;
|
|
@@ -14,8 +15,17 @@ interface RendererInjectListener {
|
|
|
14
15
|
(renderer: ReactRenderer): void;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
interface RendererInjectSubscription {
|
|
19
|
+
listener: RendererInjectListener;
|
|
20
|
+
target: ReactDevToolsTarget;
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
interface RDTHookReplaceListener {
|
|
18
|
-
(rdtHook: ReactDevToolsGlobalHook): void;
|
|
24
|
+
(rdtHook: ReactDevToolsGlobalHook, target: ReactDevToolsTarget): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ReactDevToolsTarget {
|
|
28
|
+
__REACT_DEVTOOLS_GLOBAL_HOOK__?: ReactDevToolsGlobalHook;
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
export const version = process.env.VERSION;
|
|
@@ -25,10 +35,23 @@ const objectDefineProperty = Object.defineProperty;
|
|
|
25
35
|
|
|
26
36
|
const noOp = (): void => {};
|
|
27
37
|
|
|
38
|
+
const getTargetHook = (target: ReactDevToolsTarget): ReactDevToolsGlobalHook | undefined =>
|
|
39
|
+
target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
40
|
+
|
|
28
41
|
export const createUnsubscribe = (unsubscribe: () => void): Unsubscribe =>
|
|
29
42
|
Object.assign(unsubscribe, { [Symbol.dispose]: unsubscribe });
|
|
30
43
|
|
|
44
|
+
export const isFiberRootUnmounted = (fiberRoot: FiberRoot): boolean => {
|
|
45
|
+
const rootState = fiberRoot.current.memoizedState;
|
|
46
|
+
return (
|
|
47
|
+
rootState === null ||
|
|
48
|
+
(rootState !== undefined && (rootState.element === null || rootState.element === undefined))
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
31
52
|
const checkDCE = (functionToCheck: unknown): void => {
|
|
53
|
+
// HACK: Node loads React's unbundled entrypoints, where development guards are intentionally not eliminated.
|
|
54
|
+
if (typeof process !== "undefined" && process.versions?.node) return;
|
|
32
55
|
try {
|
|
33
56
|
const code = Function.prototype.toString.call(functionToCheck);
|
|
34
57
|
if (code.indexOf("^_^") > -1) {
|
|
@@ -47,7 +70,7 @@ const checkDCE = (functionToCheck: unknown): void => {
|
|
|
47
70
|
export const isRealReactDevtools = (
|
|
48
71
|
rdtHook: ReactDevToolsGlobalHook | undefined | null = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__,
|
|
49
72
|
): boolean => {
|
|
50
|
-
return Boolean(rdtHook && "getFiberRoots" in rdtHook);
|
|
73
|
+
return Boolean(rdtHook && "getFiberRoots" in rdtHook && rdtHook._isBippyHook !== true);
|
|
51
74
|
};
|
|
52
75
|
|
|
53
76
|
// HACK: react-refresh's inject wrapper is the only stock hook whose parameter is named
|
|
@@ -69,53 +92,63 @@ export const _onActiveListeners = new Set<ActiveListener>();
|
|
|
69
92
|
|
|
70
93
|
export const _renderers = new Set<ReactRenderer>();
|
|
71
94
|
|
|
72
|
-
const
|
|
95
|
+
const activeListenerTargets = new WeakMap<ActiveListener, Set<ReactDevToolsTarget>>();
|
|
96
|
+
const rendererInjectSubscriptions = new Set<RendererInjectSubscription>();
|
|
73
97
|
const rdtHookReplaceListeners = new Set<RDTHookReplaceListener>();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
const notifiedRenderersByTarget = new WeakMap<ReactDevToolsTarget, WeakSet<ReactRenderer>>();
|
|
99
|
+
|
|
100
|
+
const addActiveListener = (listener: ActiveListener, target: ReactDevToolsTarget): void => {
|
|
101
|
+
_onActiveListeners.add(listener);
|
|
102
|
+
const targets = activeListenerTargets.get(listener) ?? new Set<ReactDevToolsTarget>();
|
|
103
|
+
targets.add(target);
|
|
104
|
+
activeListenerTargets.set(listener, targets);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const removeActiveListener = (
|
|
108
|
+
listener: ActiveListener,
|
|
109
|
+
target: ReactDevToolsTarget,
|
|
110
|
+
): void => {
|
|
111
|
+
const targets = activeListenerTargets.get(listener);
|
|
112
|
+
targets?.delete(target);
|
|
113
|
+
if (!targets || targets.size === 0) {
|
|
114
|
+
activeListenerTargets.delete(listener);
|
|
115
|
+
_onActiveListeners.delete(listener);
|
|
84
116
|
}
|
|
85
117
|
};
|
|
86
118
|
|
|
87
|
-
const
|
|
88
|
-
for (const listener of
|
|
89
|
-
listener(
|
|
119
|
+
const notifyActiveListeners = (target: ReactDevToolsTarget): void => {
|
|
120
|
+
for (const listener of _onActiveListeners) {
|
|
121
|
+
if (activeListenerTargets.get(listener)?.has(target)) callListener(listener, undefined);
|
|
90
122
|
}
|
|
91
123
|
};
|
|
92
124
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
125
|
+
const notifyRendererInjectListeners = (
|
|
126
|
+
target: ReactDevToolsTarget,
|
|
127
|
+
renderer: ReactRenderer,
|
|
128
|
+
): void => {
|
|
129
|
+
for (const subscription of rendererInjectSubscriptions) {
|
|
130
|
+
if (subscription.target === target) callListener(subscription.listener, subscription, renderer);
|
|
96
131
|
}
|
|
97
132
|
};
|
|
98
133
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
notifyRendererInjectListeners(renderer);
|
|
107
|
-
}
|
|
108
|
-
return rendererId;
|
|
109
|
-
};
|
|
110
|
-
rdtHook.inject = nextInject;
|
|
111
|
-
notifyingInjectByHook.set(rdtHook, nextInject);
|
|
134
|
+
const notifyRDTHookReplaceListeners = (
|
|
135
|
+
rdtHook: ReactDevToolsGlobalHook,
|
|
136
|
+
target: ReactDevToolsTarget,
|
|
137
|
+
): void => {
|
|
138
|
+
for (const listener of rdtHookReplaceListeners) {
|
|
139
|
+
callListener(listener, undefined, rdtHook, target);
|
|
140
|
+
}
|
|
112
141
|
};
|
|
113
142
|
|
|
114
|
-
export const onRendererInject = (
|
|
115
|
-
|
|
116
|
-
|
|
143
|
+
export const onRendererInject = (
|
|
144
|
+
listener: RendererInjectListener,
|
|
145
|
+
target: ReactDevToolsTarget = globalThis,
|
|
146
|
+
): Unsubscribe => {
|
|
147
|
+
getRDTHook(undefined, target);
|
|
148
|
+
const subscription = { listener, target };
|
|
149
|
+
rendererInjectSubscriptions.add(subscription);
|
|
117
150
|
return createUnsubscribe(() => {
|
|
118
|
-
|
|
151
|
+
rendererInjectSubscriptions.delete(subscription);
|
|
119
152
|
});
|
|
120
153
|
};
|
|
121
154
|
|
|
@@ -126,8 +159,13 @@ export const onRDTHookReplace = (listener: RDTHookReplaceListener): Unsubscribe
|
|
|
126
159
|
});
|
|
127
160
|
};
|
|
128
161
|
|
|
162
|
+
// A Map built in another realm (an iframe target) fails instanceof, so replacing it
|
|
163
|
+
// would discard that realm's renderer registry. Object.prototype.toString is realm-agnostic.
|
|
164
|
+
export const isRendererMap = (value: unknown): value is Map<number, ReactRenderer> =>
|
|
165
|
+
Object.prototype.toString.call(value) === "[object Map]";
|
|
166
|
+
|
|
129
167
|
const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRenderer> => {
|
|
130
|
-
if (!(rdtHook.renderers
|
|
168
|
+
if (!isRendererMap(rdtHook.renderers)) {
|
|
131
169
|
rdtHook.renderers = new Map();
|
|
132
170
|
}
|
|
133
171
|
return rdtHook.renderers;
|
|
@@ -135,6 +173,7 @@ const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRend
|
|
|
135
173
|
|
|
136
174
|
const trackInjectedRenderer = (
|
|
137
175
|
rdtHook: ReactDevToolsGlobalHook,
|
|
176
|
+
target: ReactDevToolsTarget,
|
|
138
177
|
renderers: Map<number, ReactRenderer>,
|
|
139
178
|
rendererId: number,
|
|
140
179
|
renderer: ReactRenderer,
|
|
@@ -143,28 +182,47 @@ const trackInjectedRenderer = (
|
|
|
143
182
|
_renderers.add(renderer);
|
|
144
183
|
if (!rdtHook._instrumentationIsActive) {
|
|
145
184
|
rdtHook._instrumentationIsActive = true;
|
|
146
|
-
notifyActiveListeners();
|
|
185
|
+
notifyActiveListeners(target);
|
|
147
186
|
}
|
|
187
|
+
const notifiedRenderers = notifiedRenderersByTarget.get(target) ?? new WeakSet<ReactRenderer>();
|
|
188
|
+
notifiedRenderersByTarget.set(target, notifiedRenderers);
|
|
189
|
+
if (notifiedRenderers.has(renderer)) return;
|
|
190
|
+
notifiedRenderers.add(renderer);
|
|
191
|
+
notifyRendererInjectListeners(target, renderer);
|
|
148
192
|
};
|
|
149
193
|
|
|
150
|
-
export const installRDTHook = (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
194
|
+
export const installRDTHook = (
|
|
195
|
+
onActive?: ActiveListener,
|
|
196
|
+
target: ReactDevToolsTarget = globalThis,
|
|
197
|
+
): ReactDevToolsGlobalHook => {
|
|
198
|
+
if (onActive) addActiveListener(onActive, target);
|
|
154
199
|
const renderers = new Map<number, ReactRenderer>();
|
|
200
|
+
const fiberRoots = new Map<number, Set<FiberRoot>>();
|
|
201
|
+
// Callers register roots by mutating the returned set, so it must be memoized.
|
|
202
|
+
const getRendererFiberRoots = (rendererId: number): Set<FiberRoot> => {
|
|
203
|
+
const rendererRoots = fiberRoots.get(rendererId) ?? new Set<FiberRoot>();
|
|
204
|
+
fiberRoots.set(rendererId, rendererRoots);
|
|
205
|
+
return rendererRoots;
|
|
206
|
+
};
|
|
155
207
|
let rendererIdCounter = 0;
|
|
156
208
|
let rdtHook: ReactDevToolsGlobalHook = {
|
|
157
209
|
_instrumentationIsActive: false,
|
|
158
210
|
_instrumentationSource: BIPPY_INSTRUMENTATION_STRING,
|
|
211
|
+
_isBippyHook: true,
|
|
159
212
|
checkDCE,
|
|
213
|
+
getFiberRoots: getRendererFiberRoots,
|
|
160
214
|
hasUnsupportedRendererAttached: false,
|
|
161
215
|
inject: (renderer) => {
|
|
162
216
|
const nextRendererId = ++rendererIdCounter;
|
|
163
|
-
trackInjectedRenderer(rdtHook, renderers, nextRendererId, renderer);
|
|
217
|
+
trackInjectedRenderer(rdtHook, target, renderers, nextRendererId, renderer);
|
|
164
218
|
return nextRendererId;
|
|
165
219
|
},
|
|
166
220
|
on: noOp,
|
|
167
|
-
onCommitFiberRoot:
|
|
221
|
+
onCommitFiberRoot: (rendererId, fiberRoot) => {
|
|
222
|
+
const rendererRoots = getRendererFiberRoots(rendererId);
|
|
223
|
+
if (isFiberRootUnmounted(fiberRoot)) rendererRoots.delete(fiberRoot);
|
|
224
|
+
else rendererRoots.add(fiberRoot);
|
|
225
|
+
},
|
|
168
226
|
onCommitFiberUnmount: noOp,
|
|
169
227
|
onPostCommitFiberRoot: noOp,
|
|
170
228
|
renderers,
|
|
@@ -172,13 +230,11 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
|
|
|
172
230
|
supportsFlight: true,
|
|
173
231
|
};
|
|
174
232
|
try {
|
|
175
|
-
objectDefineProperty(
|
|
233
|
+
objectDefineProperty(target, "__REACT_DEVTOOLS_GLOBAL_HOOK__", {
|
|
176
234
|
configurable: true,
|
|
177
235
|
enumerable: true,
|
|
178
|
-
get()
|
|
179
|
-
|
|
180
|
-
},
|
|
181
|
-
set(newHook) {
|
|
236
|
+
get: () => rdtHook,
|
|
237
|
+
set: (newHook: ReactDevToolsGlobalHook | undefined) => {
|
|
182
238
|
if (newHook && typeof newHook === "object") {
|
|
183
239
|
const ourRenderers = rdtHook.renderers;
|
|
184
240
|
rdtHook = newHook;
|
|
@@ -188,13 +244,13 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
|
|
|
188
244
|
nextRenderers.set(rendererId, renderer);
|
|
189
245
|
});
|
|
190
246
|
if (ourRenderers.size > 0 || rdtHookReplaceListeners.size > 0) {
|
|
191
|
-
patchRDTHook(onActive);
|
|
247
|
+
patchRDTHook(onActive, target);
|
|
192
248
|
}
|
|
193
|
-
notifyRDTHookReplaceListeners(rdtHook);
|
|
249
|
+
notifyRDTHookReplaceListeners(rdtHook, target);
|
|
194
250
|
}
|
|
195
251
|
},
|
|
196
252
|
});
|
|
197
|
-
if (typeof window !== "undefined") {
|
|
253
|
+
if (target === globalThis && typeof window !== "undefined") {
|
|
198
254
|
// HACK: The DevTools extension uses hasOwnProperty to decide whether it may replace an earlier hook.
|
|
199
255
|
const originalWindowHasOwnProperty = window.hasOwnProperty.bind(window);
|
|
200
256
|
const originalHasOwnPropertyDescriptor = Object.getOwnPropertyDescriptor(
|
|
@@ -224,17 +280,18 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
|
|
|
224
280
|
});
|
|
225
281
|
}
|
|
226
282
|
} catch {
|
|
227
|
-
patchRDTHook(onActive);
|
|
283
|
+
patchRDTHook(onActive, target);
|
|
228
284
|
}
|
|
229
285
|
return rdtHook;
|
|
230
286
|
};
|
|
231
287
|
|
|
232
|
-
export const patchRDTHook = (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
288
|
+
export const patchRDTHook = (
|
|
289
|
+
onActive?: ActiveListener,
|
|
290
|
+
target: ReactDevToolsTarget = globalThis,
|
|
291
|
+
): void => {
|
|
292
|
+
if (onActive) addActiveListener(onActive, target);
|
|
236
293
|
let didNotifyActiveListeners = false;
|
|
237
|
-
const rdtHook =
|
|
294
|
+
const rdtHook = getTargetHook(target);
|
|
238
295
|
if (!rdtHook) return;
|
|
239
296
|
const renderers = getRendererMap(rdtHook);
|
|
240
297
|
if (!rdtHook._instrumentationSource) {
|
|
@@ -252,40 +309,42 @@ export const patchRDTHook = (onActive?: ActiveListener): void => {
|
|
|
252
309
|
if (renderers.size) {
|
|
253
310
|
renderers.forEach((renderer) => _renderers.add(renderer));
|
|
254
311
|
rdtHook._instrumentationIsActive = true;
|
|
255
|
-
notifyActiveListeners();
|
|
312
|
+
notifyActiveListeners(target);
|
|
256
313
|
didNotifyActiveListeners = true;
|
|
257
314
|
} else if (!isReactDevtools && isReactRefresh(rdtHook)) {
|
|
258
315
|
// HACK: react-refresh's stub inject never records renderers, so a React app
|
|
259
316
|
// that injected before bippy loaded is undetectable through the renderers map.
|
|
260
317
|
// A react-refresh hook implies a dev renderer, so activate immediately.
|
|
261
318
|
rdtHook._instrumentationIsActive = true;
|
|
262
|
-
notifyActiveListeners();
|
|
319
|
+
notifyActiveListeners(target);
|
|
263
320
|
didNotifyActiveListeners = true;
|
|
264
321
|
}
|
|
265
322
|
const previousInject = rdtHook.inject;
|
|
266
323
|
rdtHook.inject = (renderer) => {
|
|
267
324
|
const rendererId = previousInject.call(rdtHook, renderer);
|
|
268
|
-
trackInjectedRenderer(rdtHook, renderers, rendererId, renderer);
|
|
325
|
+
trackInjectedRenderer(rdtHook, target, renderers, rendererId, renderer);
|
|
269
326
|
return rendererId;
|
|
270
327
|
};
|
|
271
328
|
}
|
|
272
329
|
if (!didNotifyActiveListeners && (renderers.size || rdtHook._instrumentationIsActive)) {
|
|
273
|
-
onActive
|
|
330
|
+
if (onActive) callListener(onActive, undefined);
|
|
274
331
|
}
|
|
275
332
|
};
|
|
276
333
|
|
|
277
|
-
export const hasRDTHook = (): boolean =>
|
|
278
|
-
|
|
279
|
-
};
|
|
334
|
+
export const hasRDTHook = (target: ReactDevToolsTarget = globalThis): boolean =>
|
|
335
|
+
Object.hasOwn(target, "__REACT_DEVTOOLS_GLOBAL_HOOK__");
|
|
280
336
|
|
|
281
337
|
/**
|
|
282
338
|
* Returns the current React DevTools global hook.
|
|
283
339
|
*/
|
|
284
|
-
export const getRDTHook = (
|
|
285
|
-
|
|
286
|
-
|
|
340
|
+
export const getRDTHook = (
|
|
341
|
+
onActive?: ActiveListener,
|
|
342
|
+
target: ReactDevToolsTarget = globalThis,
|
|
343
|
+
): ReactDevToolsGlobalHook => {
|
|
344
|
+
if (!hasRDTHook(target)) {
|
|
345
|
+
return installRDTHook(onActive, target);
|
|
287
346
|
}
|
|
288
347
|
|
|
289
|
-
patchRDTHook(onActive);
|
|
290
|
-
return
|
|
348
|
+
patchRDTHook(onActive, target);
|
|
349
|
+
return getTargetHook(target) ?? installRDTHook(onActive, target);
|
|
291
350
|
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Adapted from ReactFiberTreeReflection.js. Copyright (c) Meta Platforms, Inc. and affiliates. MIT licensed.
|
|
2
|
+
import type { Fiber } from "./types.js";
|
|
3
|
+
import { getReactWorkTagsForFiber } from "./index.js";
|
|
4
|
+
|
|
5
|
+
export const getCurrentFiberFromRoot = (fiber: Fiber): Fiber | null => {
|
|
6
|
+
const alternate = fiber.alternate;
|
|
7
|
+
if (!alternate) return null;
|
|
8
|
+
let firstBranch = fiber;
|
|
9
|
+
let secondBranch = alternate;
|
|
10
|
+
|
|
11
|
+
while (firstBranch.return) {
|
|
12
|
+
const firstParent = firstBranch.return;
|
|
13
|
+
const secondParent = firstParent.alternate;
|
|
14
|
+
if (!secondParent) {
|
|
15
|
+
const nextParent = firstParent.return;
|
|
16
|
+
if (!nextParent) return null;
|
|
17
|
+
firstBranch = secondBranch = nextParent;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (firstParent.child === secondParent.child) {
|
|
22
|
+
let child = firstParent.child;
|
|
23
|
+
while (child) {
|
|
24
|
+
if (child === firstBranch || child === secondBranch) {
|
|
25
|
+
let root = firstParent;
|
|
26
|
+
while (root.return) root = root.return;
|
|
27
|
+
if (root.tag !== getReactWorkTagsForFiber(root).HostRoot) return null;
|
|
28
|
+
return child === firstBranch ? fiber : alternate;
|
|
29
|
+
}
|
|
30
|
+
child = child.sibling;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (firstBranch.return !== secondBranch.return) {
|
|
36
|
+
firstBranch = firstParent;
|
|
37
|
+
secondBranch = secondParent;
|
|
38
|
+
} else {
|
|
39
|
+
let didFindChild = false;
|
|
40
|
+
let child = firstParent.child;
|
|
41
|
+
while (child) {
|
|
42
|
+
if (child === firstBranch) {
|
|
43
|
+
firstBranch = firstParent;
|
|
44
|
+
secondBranch = secondParent;
|
|
45
|
+
didFindChild = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (child === secondBranch) {
|
|
49
|
+
firstBranch = secondParent;
|
|
50
|
+
secondBranch = firstParent;
|
|
51
|
+
didFindChild = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
child = child.sibling;
|
|
55
|
+
}
|
|
56
|
+
if (!didFindChild) {
|
|
57
|
+
child = secondParent.child;
|
|
58
|
+
while (child) {
|
|
59
|
+
if (child === firstBranch) {
|
|
60
|
+
firstBranch = secondParent;
|
|
61
|
+
secondBranch = firstParent;
|
|
62
|
+
didFindChild = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
if (child === secondBranch) {
|
|
66
|
+
firstBranch = firstParent;
|
|
67
|
+
secondBranch = secondParent;
|
|
68
|
+
didFindChild = true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
child = child.sibling;
|
|
72
|
+
}
|
|
73
|
+
if (!didFindChild) return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (firstBranch.alternate !== secondBranch) return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (firstBranch.tag !== getReactWorkTagsForFiber(firstBranch).HostRoot) return null;
|
|
80
|
+
const root = firstBranch.stateNode;
|
|
81
|
+
if (typeof root !== "object" || root === null || !("current" in root)) return null;
|
|
82
|
+
if (root.current === firstBranch) return fiber;
|
|
83
|
+
return root.current === secondBranch ? alternate : null;
|
|
84
|
+
};
|
|
@@ -18,8 +18,24 @@ export type {
|
|
|
18
18
|
} from "./generated/react-work-tags.js";
|
|
19
19
|
export * from "./types.js";
|
|
20
20
|
|
|
21
|
+
interface InheritedWorkTags {
|
|
22
|
+
workTags: Readonly<ReactWorkTagMap>;
|
|
23
|
+
generation: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
const defaultReactWorkTags = getReactWorkTags();
|
|
22
27
|
const fiberReactWorkTags = new WeakMap<Fiber, Readonly<ReactWorkTagMap>>();
|
|
28
|
+
const inheritedFiberWorkTags = new WeakMap<Fiber, InheritedWorkTags>();
|
|
29
|
+
let workTagGeneration = 0;
|
|
30
|
+
|
|
31
|
+
const getCachedWorkTags = (fiber: Fiber): Readonly<ReactWorkTagMap> | undefined => {
|
|
32
|
+
const assignedWorkTags = fiberReactWorkTags.get(fiber);
|
|
33
|
+
if (assignedWorkTags) return assignedWorkTags;
|
|
34
|
+
const inheritedWorkTags = inheritedFiberWorkTags.get(fiber);
|
|
35
|
+
return inheritedWorkTags?.generation === workTagGeneration
|
|
36
|
+
? inheritedWorkTags.workTags
|
|
37
|
+
: undefined;
|
|
38
|
+
};
|
|
23
39
|
|
|
24
40
|
// React's experimental channel historically reported "0.0.0-experimental-<sha>"
|
|
25
41
|
// as the runtime version; those builds use modern work tags, not the 16.x rows
|
|
@@ -39,25 +55,36 @@ export const getReactWorkTagsForRenderer = (
|
|
|
39
55
|
|
|
40
56
|
export const setReactWorkTagsForFiber = (fiber: Fiber, renderer?: ReactRenderer): void => {
|
|
41
57
|
const workTags = getReactWorkTagsForRenderer(renderer);
|
|
58
|
+
if (
|
|
59
|
+
getCachedWorkTags(fiber) !== workTags ||
|
|
60
|
+
(fiber.alternate && getCachedWorkTags(fiber.alternate) !== workTags)
|
|
61
|
+
) {
|
|
62
|
+
workTagGeneration++;
|
|
63
|
+
}
|
|
42
64
|
fiberReactWorkTags.set(fiber, workTags);
|
|
43
65
|
if (fiber.alternate) fiberReactWorkTags.set(fiber.alternate, workTags);
|
|
44
66
|
};
|
|
45
67
|
|
|
46
68
|
export const getReactWorkTagsForFiber = (fiber: Fiber): Readonly<ReactWorkTagMap> => {
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
let workTags = getCachedWorkTags(fiber);
|
|
70
|
+
if (workTags) return workTags;
|
|
71
|
+
const traversedFibers: Fiber[] = [];
|
|
72
|
+
let ancestor = fiber;
|
|
73
|
+
while (!workTags) {
|
|
74
|
+
traversedFibers.push(ancestor);
|
|
75
|
+
const parent = ancestor.return;
|
|
76
|
+
if (!parent) {
|
|
77
|
+
workTags = inheritedFiberWorkTags.get(ancestor)?.workTags ?? defaultReactWorkTags;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
ancestor = parent;
|
|
81
|
+
workTags = getCachedWorkTags(ancestor);
|
|
55
82
|
}
|
|
56
|
-
const
|
|
83
|
+
const inheritedWorkTags = { workTags, generation: workTagGeneration };
|
|
57
84
|
for (const traversedFiber of traversedFibers) {
|
|
58
|
-
|
|
85
|
+
inheritedFiberWorkTags.set(traversedFiber, inheritedWorkTags);
|
|
59
86
|
if (traversedFiber.alternate) {
|
|
60
|
-
|
|
87
|
+
inheritedFiberWorkTags.set(traversedFiber.alternate, inheritedWorkTags);
|
|
61
88
|
}
|
|
62
89
|
}
|
|
63
90
|
return workTags;
|
|
@@ -31,12 +31,30 @@ export interface ServerComponentInfo {
|
|
|
31
31
|
env?: string;
|
|
32
32
|
owner?: Fiber | ServerComponentInfo | null;
|
|
33
33
|
debugStack?: Error | null;
|
|
34
|
+
debugLocation?: Error | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ReactIOInfo {
|
|
38
|
+
name: string;
|
|
39
|
+
start: number;
|
|
40
|
+
end: number;
|
|
41
|
+
byteSize?: number;
|
|
42
|
+
value?: Promise<unknown> | null;
|
|
43
|
+
env?: string;
|
|
44
|
+
owner?: ServerComponentInfo | null;
|
|
45
|
+
stack?: unknown[] | null;
|
|
46
|
+
debugStack?: Error | null;
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
export interface ReactDebugInfo {
|
|
37
|
-
|
|
50
|
+
awaited?: ReactIOInfo;
|
|
51
|
+
debugLocation?: Error | null;
|
|
38
52
|
env?: string;
|
|
39
53
|
name?: string;
|
|
54
|
+
owner?: ServerComponentInfo | null;
|
|
55
|
+
stack?: unknown[] | null;
|
|
56
|
+
debugStack?: Error | null;
|
|
57
|
+
time?: number;
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
export interface ReactMemoCache {
|
|
@@ -71,10 +89,10 @@ export interface Fiber<T = unknown> extends Omit<
|
|
|
71
89
|
| "tag"
|
|
72
90
|
| "updateQueue"
|
|
73
91
|
> {
|
|
74
|
-
_debugInfo?: ReactDebugInfo[];
|
|
92
|
+
_debugInfo?: ReactDebugInfo[] | null;
|
|
75
93
|
_debugOwner?: Fiber | ServerComponentInfo | null;
|
|
76
94
|
_debugSource?: FiberDebugSource | null;
|
|
77
|
-
_debugStack?: Error
|
|
95
|
+
_debugStack?: Error | null;
|
|
78
96
|
alternate: Fiber | null;
|
|
79
97
|
child: Fiber | null;
|
|
80
98
|
deletions: Fiber[] | null;
|
|
@@ -96,6 +114,8 @@ export interface HostFiber<T = unknown> extends Fiber<T> {
|
|
|
96
114
|
|
|
97
115
|
export interface FiberRoot {
|
|
98
116
|
current: Fiber;
|
|
117
|
+
effectDuration?: number;
|
|
118
|
+
passiveEffectDuration?: number;
|
|
99
119
|
}
|
|
100
120
|
|
|
101
121
|
export interface MemoizedState {
|
|
@@ -115,6 +135,7 @@ export interface ReactDevToolsEventHandler {
|
|
|
115
135
|
export interface ReactDevToolsGlobalHook {
|
|
116
136
|
_instrumentationIsActive?: boolean;
|
|
117
137
|
_instrumentationSource?: string;
|
|
138
|
+
_isBippyHook?: boolean;
|
|
118
139
|
checkDCE: (fn: unknown) => void;
|
|
119
140
|
emit?: (event: string, data?: unknown) => void;
|
|
120
141
|
getFiberRoots?: (rendererID: number) => Set<FiberRoot>;
|
|
@@ -149,14 +170,20 @@ export type RendererDispatcherRef = CurrentDispatcherRef | LegacyDispatcherRef;
|
|
|
149
170
|
|
|
150
171
|
export interface ReactRenderer extends Omit<
|
|
151
172
|
ReactReconciler.DevToolsConfig<unknown, unknown, unknown>,
|
|
152
|
-
"findFiberByHostInstance"
|
|
173
|
+
"bundleType" | "findFiberByHostInstance"
|
|
153
174
|
> {
|
|
175
|
+
bundleType: number;
|
|
154
176
|
currentDispatcherRef?: RendererDispatcherRef | null;
|
|
155
177
|
findFiberByHostInstance?: (hostInstance: unknown) => Fiber | null;
|
|
156
178
|
getCurrentFiber?: () => Fiber | null;
|
|
157
179
|
overrideContext?: (fiber: Fiber, contextType: unknown, path: string[], value: unknown) => void;
|
|
158
180
|
|
|
159
|
-
overrideHookState?: (
|
|
181
|
+
overrideHookState?: (
|
|
182
|
+
fiber: Fiber,
|
|
183
|
+
id: number,
|
|
184
|
+
path: Array<number | string>,
|
|
185
|
+
value: unknown,
|
|
186
|
+
) => void;
|
|
160
187
|
overrideHookStateDeletePath?: (fiber: Fiber, id: number, path: Array<number | string>) => void;
|
|
161
188
|
overrideHookStateRenamePath?: (
|
|
162
189
|
fiber: Fiber,
|
|
@@ -164,7 +191,7 @@ export interface ReactRenderer extends Omit<
|
|
|
164
191
|
oldPath: Array<number | string>,
|
|
165
192
|
newPath: Array<number | string>,
|
|
166
193
|
) => void;
|
|
167
|
-
overrideProps?: (fiber: Fiber, path: string
|
|
194
|
+
overrideProps?: (fiber: Fiber, path: Array<number | string>, value: unknown) => void;
|
|
168
195
|
overridePropsDeletePath?: (fiber: Fiber, path: Array<number | string>) => void;
|
|
169
196
|
overridePropsRenamePath?: (
|
|
170
197
|
fiber: Fiber,
|