bippy 0.7.2 → 0.7.3-dev.04de4f7
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/README.md +12 -79
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +59 -9
- package/dist/errors.d.ts +59 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +34 -58
- package/dist/index.d.ts +36 -60
- 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 +47 -18
- package/dist/source.d.ts +47 -18
- package/dist/source.js +12 -12
- package/package.json +7 -35
- package/src/call-listener.ts +13 -0
- package/src/core.ts +367 -282
- package/src/rdt-hook.ts +159 -87
- package/src/react-internals/current-fiber.ts +84 -0
- package/src/react-internals/generated/react-work-tags.ts +3 -0
- package/src/react-internals/index.ts +38 -11
- package/src/react-internals/types.ts +33 -6
- package/src/react.ts +141 -55
- package/src/source/constants.ts +11 -9
- package/src/source/get-display-name-from-source.ts +10 -3
- package/src/source/get-source.ts +42 -157
- package/src/source/index.ts +17 -3
- package/src/source/inspect-hooks.ts +198 -49
- package/src/source/normalize-file-name.ts +416 -0
- 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 +131 -70
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,21 @@ 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
|
+
interface RDTHookReplaceSubscription {
|
|
28
|
+
listener: RDTHookReplaceListener;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ReactDevToolsTarget {
|
|
32
|
+
__REACT_DEVTOOLS_GLOBAL_HOOK__?: ReactDevToolsGlobalHook;
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
export const version = process.env.VERSION;
|
|
@@ -25,10 +39,23 @@ const objectDefineProperty = Object.defineProperty;
|
|
|
25
39
|
|
|
26
40
|
const noOp = (): void => {};
|
|
27
41
|
|
|
42
|
+
const getTargetHook = (target: ReactDevToolsTarget): ReactDevToolsGlobalHook | undefined =>
|
|
43
|
+
target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
44
|
+
|
|
28
45
|
export const createUnsubscribe = (unsubscribe: () => void): Unsubscribe =>
|
|
29
46
|
Object.assign(unsubscribe, { [Symbol.dispose]: unsubscribe });
|
|
30
47
|
|
|
48
|
+
export const isFiberRootUnmounted = (fiberRoot: FiberRoot): boolean => {
|
|
49
|
+
const rootState = fiberRoot.current.memoizedState;
|
|
50
|
+
return (
|
|
51
|
+
rootState === null ||
|
|
52
|
+
(rootState !== undefined && (rootState.element === null || rootState.element === undefined))
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
31
56
|
const checkDCE = (functionToCheck: unknown): void => {
|
|
57
|
+
// HACK: Node loads React's unbundled entrypoints, where development guards are intentionally not eliminated.
|
|
58
|
+
if (typeof process !== "undefined" && process.versions?.node) return;
|
|
32
59
|
try {
|
|
33
60
|
const code = Function.prototype.toString.call(functionToCheck);
|
|
34
61
|
if (code.indexOf("^_^") > -1) {
|
|
@@ -47,7 +74,7 @@ const checkDCE = (functionToCheck: unknown): void => {
|
|
|
47
74
|
export const isRealReactDevtools = (
|
|
48
75
|
rdtHook: ReactDevToolsGlobalHook | undefined | null = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__,
|
|
49
76
|
): boolean => {
|
|
50
|
-
return Boolean(rdtHook && "getFiberRoots" in rdtHook);
|
|
77
|
+
return Boolean(rdtHook && "getFiberRoots" in rdtHook && rdtHook._isBippyHook !== true);
|
|
51
78
|
};
|
|
52
79
|
|
|
53
80
|
// HACK: react-refresh's inject wrapper is the only stock hook whose parameter is named
|
|
@@ -69,65 +96,90 @@ export const _onActiveListeners = new Set<ActiveListener>();
|
|
|
69
96
|
|
|
70
97
|
export const _renderers = new Set<ReactRenderer>();
|
|
71
98
|
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
>();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
const activeListenerTargets = new WeakMap<ActiveListener, Set<ReactDevToolsTarget>>();
|
|
100
|
+
const rendererInjectSubscriptions = new Set<RendererInjectSubscription>();
|
|
101
|
+
const rdtHookReplaceSubscriptions = new Set<RDTHookReplaceSubscription>();
|
|
102
|
+
const notifiedRenderersByTarget = new WeakMap<ReactDevToolsTarget, WeakSet<ReactRenderer>>();
|
|
103
|
+
|
|
104
|
+
const addActiveListener = (listener: ActiveListener, target: ReactDevToolsTarget): void => {
|
|
105
|
+
_onActiveListeners.add(listener);
|
|
106
|
+
const targets = activeListenerTargets.get(listener) ?? new Set<ReactDevToolsTarget>();
|
|
107
|
+
targets.add(target);
|
|
108
|
+
activeListenerTargets.set(listener, targets);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const removeActiveListener = (
|
|
112
|
+
listener: ActiveListener,
|
|
113
|
+
target: ReactDevToolsTarget,
|
|
114
|
+
): void => {
|
|
115
|
+
const targets = activeListenerTargets.get(listener);
|
|
116
|
+
targets?.delete(target);
|
|
117
|
+
if (!targets || targets.size === 0) {
|
|
118
|
+
activeListenerTargets.delete(listener);
|
|
119
|
+
_onActiveListeners.delete(listener);
|
|
84
120
|
}
|
|
85
121
|
};
|
|
86
122
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
123
|
+
const notifyActiveListeners = (
|
|
124
|
+
target: ReactDevToolsTarget,
|
|
125
|
+
pendingListeners = [..._onActiveListeners],
|
|
126
|
+
): void => {
|
|
127
|
+
for (const listener of pendingListeners) {
|
|
128
|
+
if (activeListenerTargets.get(listener)?.has(target)) callListener(listener, undefined);
|
|
90
129
|
}
|
|
91
130
|
};
|
|
92
131
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
132
|
+
const notifyRendererInjectListeners = (
|
|
133
|
+
target: ReactDevToolsTarget,
|
|
134
|
+
renderer: ReactRenderer,
|
|
135
|
+
): void => {
|
|
136
|
+
const subscriptionSnapshot = [...rendererInjectSubscriptions];
|
|
137
|
+
for (const subscription of subscriptionSnapshot) {
|
|
138
|
+
if (rendererInjectSubscriptions.has(subscription) && subscription.target === target) {
|
|
139
|
+
callListener(subscription.listener, subscription, renderer);
|
|
140
|
+
}
|
|
96
141
|
}
|
|
97
142
|
};
|
|
98
143
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
144
|
+
const notifyRDTHookReplaceListeners = (
|
|
145
|
+
rdtHook: ReactDevToolsGlobalHook,
|
|
146
|
+
target: ReactDevToolsTarget,
|
|
147
|
+
): void => {
|
|
148
|
+
const subscriptionSnapshot = [...rdtHookReplaceSubscriptions];
|
|
149
|
+
for (const subscription of subscriptionSnapshot) {
|
|
150
|
+
if (rdtHookReplaceSubscriptions.has(subscription)) {
|
|
151
|
+
callListener(subscription.listener, undefined, rdtHook, target);
|
|
107
152
|
}
|
|
108
|
-
|
|
109
|
-
};
|
|
110
|
-
rdtHook.inject = nextInject;
|
|
111
|
-
notifyingInjectByHook.set(rdtHook, nextInject);
|
|
153
|
+
}
|
|
112
154
|
};
|
|
113
155
|
|
|
114
|
-
export const onRendererInject = (
|
|
115
|
-
|
|
116
|
-
|
|
156
|
+
export const onRendererInject = (
|
|
157
|
+
listener: RendererInjectListener,
|
|
158
|
+
target: ReactDevToolsTarget = globalThis,
|
|
159
|
+
): Unsubscribe => {
|
|
160
|
+
getRDTHook(undefined, target);
|
|
161
|
+
const subscription = { listener, target };
|
|
162
|
+
rendererInjectSubscriptions.add(subscription);
|
|
117
163
|
return createUnsubscribe(() => {
|
|
118
|
-
|
|
164
|
+
rendererInjectSubscriptions.delete(subscription);
|
|
119
165
|
});
|
|
120
166
|
};
|
|
121
167
|
|
|
122
168
|
export const onRDTHookReplace = (listener: RDTHookReplaceListener): Unsubscribe => {
|
|
123
|
-
|
|
169
|
+
const subscription = { listener };
|
|
170
|
+
rdtHookReplaceSubscriptions.add(subscription);
|
|
124
171
|
return createUnsubscribe(() => {
|
|
125
|
-
|
|
172
|
+
rdtHookReplaceSubscriptions.delete(subscription);
|
|
126
173
|
});
|
|
127
174
|
};
|
|
128
175
|
|
|
176
|
+
// A Map built in another realm (an iframe target) fails instanceof, so replacing it
|
|
177
|
+
// would discard that realm's renderer registry. Object.prototype.toString is realm-agnostic.
|
|
178
|
+
export const isRendererMap = (value: unknown): value is Map<number, ReactRenderer> =>
|
|
179
|
+
Object.prototype.toString.call(value) === "[object Map]";
|
|
180
|
+
|
|
129
181
|
const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRenderer> => {
|
|
130
|
-
if (!(rdtHook.renderers
|
|
182
|
+
if (!isRendererMap(rdtHook.renderers)) {
|
|
131
183
|
rdtHook.renderers = new Map();
|
|
132
184
|
}
|
|
133
185
|
return rdtHook.renderers;
|
|
@@ -135,6 +187,7 @@ const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRend
|
|
|
135
187
|
|
|
136
188
|
const trackInjectedRenderer = (
|
|
137
189
|
rdtHook: ReactDevToolsGlobalHook,
|
|
190
|
+
target: ReactDevToolsTarget,
|
|
138
191
|
renderers: Map<number, ReactRenderer>,
|
|
139
192
|
rendererId: number,
|
|
140
193
|
renderer: ReactRenderer,
|
|
@@ -143,28 +196,47 @@ const trackInjectedRenderer = (
|
|
|
143
196
|
_renderers.add(renderer);
|
|
144
197
|
if (!rdtHook._instrumentationIsActive) {
|
|
145
198
|
rdtHook._instrumentationIsActive = true;
|
|
146
|
-
notifyActiveListeners();
|
|
199
|
+
notifyActiveListeners(target);
|
|
147
200
|
}
|
|
201
|
+
const notifiedRenderers = notifiedRenderersByTarget.get(target) ?? new WeakSet<ReactRenderer>();
|
|
202
|
+
notifiedRenderersByTarget.set(target, notifiedRenderers);
|
|
203
|
+
if (notifiedRenderers.has(renderer)) return;
|
|
204
|
+
notifiedRenderers.add(renderer);
|
|
205
|
+
notifyRendererInjectListeners(target, renderer);
|
|
148
206
|
};
|
|
149
207
|
|
|
150
|
-
export const installRDTHook = (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
208
|
+
export const installRDTHook = (
|
|
209
|
+
onActive?: ActiveListener,
|
|
210
|
+
target: ReactDevToolsTarget = globalThis,
|
|
211
|
+
): ReactDevToolsGlobalHook => {
|
|
212
|
+
if (onActive) addActiveListener(onActive, target);
|
|
154
213
|
const renderers = new Map<number, ReactRenderer>();
|
|
214
|
+
const fiberRoots = new Map<number, Set<FiberRoot>>();
|
|
215
|
+
// Callers register roots by mutating the returned set, so it must be memoized.
|
|
216
|
+
const getRendererFiberRoots = (rendererId: number): Set<FiberRoot> => {
|
|
217
|
+
const rendererRoots = fiberRoots.get(rendererId) ?? new Set<FiberRoot>();
|
|
218
|
+
fiberRoots.set(rendererId, rendererRoots);
|
|
219
|
+
return rendererRoots;
|
|
220
|
+
};
|
|
155
221
|
let rendererIdCounter = 0;
|
|
156
222
|
let rdtHook: ReactDevToolsGlobalHook = {
|
|
157
223
|
_instrumentationIsActive: false,
|
|
158
224
|
_instrumentationSource: BIPPY_INSTRUMENTATION_STRING,
|
|
225
|
+
_isBippyHook: true,
|
|
159
226
|
checkDCE,
|
|
227
|
+
getFiberRoots: getRendererFiberRoots,
|
|
160
228
|
hasUnsupportedRendererAttached: false,
|
|
161
229
|
inject: (renderer) => {
|
|
162
230
|
const nextRendererId = ++rendererIdCounter;
|
|
163
|
-
trackInjectedRenderer(rdtHook, renderers, nextRendererId, renderer);
|
|
231
|
+
trackInjectedRenderer(rdtHook, target, renderers, nextRendererId, renderer);
|
|
164
232
|
return nextRendererId;
|
|
165
233
|
},
|
|
166
234
|
on: noOp,
|
|
167
|
-
onCommitFiberRoot:
|
|
235
|
+
onCommitFiberRoot: (rendererId, fiberRoot) => {
|
|
236
|
+
const rendererRoots = getRendererFiberRoots(rendererId);
|
|
237
|
+
if (isFiberRootUnmounted(fiberRoot)) rendererRoots.delete(fiberRoot);
|
|
238
|
+
else rendererRoots.add(fiberRoot);
|
|
239
|
+
},
|
|
168
240
|
onCommitFiberUnmount: noOp,
|
|
169
241
|
onPostCommitFiberRoot: noOp,
|
|
170
242
|
renderers,
|
|
@@ -172,13 +244,11 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
|
|
|
172
244
|
supportsFlight: true,
|
|
173
245
|
};
|
|
174
246
|
try {
|
|
175
|
-
objectDefineProperty(
|
|
247
|
+
objectDefineProperty(target, "__REACT_DEVTOOLS_GLOBAL_HOOK__", {
|
|
176
248
|
configurable: true,
|
|
177
249
|
enumerable: true,
|
|
178
|
-
get()
|
|
179
|
-
|
|
180
|
-
},
|
|
181
|
-
set(newHook) {
|
|
250
|
+
get: () => rdtHook,
|
|
251
|
+
set: (newHook: ReactDevToolsGlobalHook | undefined) => {
|
|
182
252
|
if (newHook && typeof newHook === "object") {
|
|
183
253
|
const ourRenderers = rdtHook.renderers;
|
|
184
254
|
rdtHook = newHook;
|
|
@@ -187,14 +257,15 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
|
|
|
187
257
|
_renderers.add(renderer);
|
|
188
258
|
nextRenderers.set(rendererId, renderer);
|
|
189
259
|
});
|
|
190
|
-
if (ourRenderers.size > 0 ||
|
|
191
|
-
patchRDTHook(
|
|
260
|
+
if (ourRenderers.size > 0 || rdtHookReplaceSubscriptions.size > 0) {
|
|
261
|
+
patchRDTHook(undefined, target, () => notifyRDTHookReplaceListeners(newHook, target));
|
|
262
|
+
} else {
|
|
263
|
+
notifyRDTHookReplaceListeners(newHook, target);
|
|
192
264
|
}
|
|
193
|
-
notifyRDTHookReplaceListeners(rdtHook);
|
|
194
265
|
}
|
|
195
266
|
},
|
|
196
267
|
});
|
|
197
|
-
if (typeof window !== "undefined") {
|
|
268
|
+
if (target === globalThis && typeof window !== "undefined") {
|
|
198
269
|
// HACK: The DevTools extension uses hasOwnProperty to decide whether it may replace an earlier hook.
|
|
199
270
|
const originalWindowHasOwnProperty = window.hasOwnProperty.bind(window);
|
|
200
271
|
const originalHasOwnPropertyDescriptor = Object.getOwnPropertyDescriptor(
|
|
@@ -224,17 +295,19 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
|
|
|
224
295
|
});
|
|
225
296
|
}
|
|
226
297
|
} catch {
|
|
227
|
-
patchRDTHook(onActive);
|
|
298
|
+
patchRDTHook(onActive, target);
|
|
228
299
|
}
|
|
229
300
|
return rdtHook;
|
|
230
301
|
};
|
|
231
302
|
|
|
232
|
-
export const patchRDTHook = (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
303
|
+
export const patchRDTHook = (
|
|
304
|
+
onActive?: ActiveListener,
|
|
305
|
+
target: ReactDevToolsTarget = globalThis,
|
|
306
|
+
onReady?: () => void,
|
|
307
|
+
): void => {
|
|
308
|
+
if (onActive) addActiveListener(onActive, target);
|
|
309
|
+
let shouldNotifyActiveListeners = false;
|
|
310
|
+
const rdtHook = getTargetHook(target);
|
|
238
311
|
if (!rdtHook) return;
|
|
239
312
|
const renderers = getRendererMap(rdtHook);
|
|
240
313
|
if (!rdtHook._instrumentationSource) {
|
|
@@ -249,43 +322,42 @@ export const patchRDTHook = (onActive?: ActiveListener): void => {
|
|
|
249
322
|
if (!isReactDevtools) {
|
|
250
323
|
rdtHook.on = noOp;
|
|
251
324
|
}
|
|
252
|
-
|
|
253
|
-
renderers.forEach((renderer) => _renderers.add(renderer));
|
|
254
|
-
rdtHook._instrumentationIsActive = true;
|
|
255
|
-
notifyActiveListeners();
|
|
256
|
-
didNotifyActiveListeners = true;
|
|
257
|
-
} else if (!isReactDevtools && isReactRefresh(rdtHook)) {
|
|
258
|
-
// HACK: react-refresh's stub inject never records renderers, so a React app
|
|
259
|
-
// that injected before bippy loaded is undetectable through the renderers map.
|
|
260
|
-
// A react-refresh hook implies a dev renderer, so activate immediately.
|
|
261
|
-
rdtHook._instrumentationIsActive = true;
|
|
262
|
-
notifyActiveListeners();
|
|
263
|
-
didNotifyActiveListeners = true;
|
|
264
|
-
}
|
|
325
|
+
const shouldActivate = renderers.size > 0 || (!isReactDevtools && isReactRefresh(rdtHook));
|
|
265
326
|
const previousInject = rdtHook.inject;
|
|
266
327
|
rdtHook.inject = (renderer) => {
|
|
267
328
|
const rendererId = previousInject.call(rdtHook, renderer);
|
|
268
|
-
trackInjectedRenderer(rdtHook, renderers, rendererId, renderer);
|
|
329
|
+
trackInjectedRenderer(rdtHook, target, renderers, rendererId, renderer);
|
|
269
330
|
return rendererId;
|
|
270
331
|
};
|
|
332
|
+
if (shouldActivate) {
|
|
333
|
+
renderers.forEach((renderer) => _renderers.add(renderer));
|
|
334
|
+
rdtHook._instrumentationIsActive = true;
|
|
335
|
+
shouldNotifyActiveListeners = true;
|
|
336
|
+
}
|
|
271
337
|
}
|
|
272
|
-
|
|
273
|
-
|
|
338
|
+
const pendingListeners = shouldNotifyActiveListeners ? [..._onActiveListeners] : [];
|
|
339
|
+
onReady?.();
|
|
340
|
+
if (shouldNotifyActiveListeners) {
|
|
341
|
+
notifyActiveListeners(target, pendingListeners);
|
|
342
|
+
} else if (onActive && (renderers.size || rdtHook._instrumentationIsActive)) {
|
|
343
|
+
callListener(onActive, undefined);
|
|
274
344
|
}
|
|
275
345
|
};
|
|
276
346
|
|
|
277
|
-
export const hasRDTHook = (): boolean =>
|
|
278
|
-
|
|
279
|
-
};
|
|
347
|
+
export const hasRDTHook = (target: ReactDevToolsTarget = globalThis): boolean =>
|
|
348
|
+
Object.hasOwn(target, "__REACT_DEVTOOLS_GLOBAL_HOOK__");
|
|
280
349
|
|
|
281
350
|
/**
|
|
282
351
|
* Returns the current React DevTools global hook.
|
|
283
352
|
*/
|
|
284
|
-
export const getRDTHook = (
|
|
285
|
-
|
|
286
|
-
|
|
353
|
+
export const getRDTHook = (
|
|
354
|
+
onActive?: ActiveListener,
|
|
355
|
+
target: ReactDevToolsTarget = globalThis,
|
|
356
|
+
): ReactDevToolsGlobalHook => {
|
|
357
|
+
if (!hasRDTHook(target)) {
|
|
358
|
+
return installRDTHook(onActive, target);
|
|
287
359
|
}
|
|
288
360
|
|
|
289
|
-
patchRDTHook(onActive);
|
|
290
|
-
return
|
|
361
|
+
patchRDTHook(onActive, target);
|
|
362
|
+
return getTargetHook(target) ?? installRDTHook(onActive, target);
|
|
291
363
|
};
|
|
@@ -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
|
+
};
|
|
@@ -25,6 +25,9 @@ export const ReactSymbols = {
|
|
|
25
25
|
DEPRECATED_ASYNC_MODE_SYMBOL_STRING: "Symbol(react.async_mode)",
|
|
26
26
|
ELEMENT_SYMBOL_STRING: "Symbol(react.transitional.element)",
|
|
27
27
|
LEGACY_ELEMENT_SYMBOL_STRING: "Symbol(react.element)",
|
|
28
|
+
STRICT_MODE_NUMBER: 60108,
|
|
29
|
+
STRICT_MODE_SYMBOL_DESCRIPTION: "react.strict_mode",
|
|
30
|
+
STRICT_MODE_SYMBOL_STRING: "Symbol(react.strict_mode)",
|
|
28
31
|
} as const;
|
|
29
32
|
|
|
30
33
|
export interface ReactWorkTagMap {
|
|
@@ -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,
|