bippy 0.7.3-dev.8ba0705 → 0.7.3-dev.c0e872e
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 +12 -11
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +4 -1
- package/dist/errors.d.ts +4 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +32 -29
- package/dist/index.d.ts +34 -31
- package/dist/index.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +11 -12
- package/dist/source.d.cts +22 -22
- package/dist/source.d.ts +22 -22
- package/dist/source.js +12 -13
- package/package.json +6 -34
- package/src/call-listener.ts +13 -0
- package/src/core.ts +248 -242
- package/src/rdt-hook.ts +45 -29
- 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.ts +141 -56
- package/src/source/inspect-hooks.ts +29 -21
- package/src/source/parse-debug-stack.ts +1 -1
- package/src/source/parse-stack.ts +111 -63
- package/src/source/symbolication.ts +15 -4
package/src/rdt-hook.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// This module must load before React so renderers can inject into the hook.
|
|
2
2
|
|
|
3
|
+
import { callListener } from "./call-listener.js";
|
|
3
4
|
import type { FiberRoot, ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
|
|
4
5
|
|
|
5
6
|
export interface Unsubscribe extends Disposable {
|
|
@@ -23,6 +24,10 @@ interface RDTHookReplaceListener {
|
|
|
23
24
|
(rdtHook: ReactDevToolsGlobalHook, target: ReactDevToolsTarget): void;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
interface RDTHookReplaceSubscription {
|
|
28
|
+
listener: RDTHookReplaceListener;
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
export interface ReactDevToolsTarget {
|
|
27
32
|
__REACT_DEVTOOLS_GLOBAL_HOOK__?: ReactDevToolsGlobalHook;
|
|
28
33
|
}
|
|
@@ -49,6 +54,8 @@ export const isFiberRootUnmounted = (fiberRoot: FiberRoot): boolean => {
|
|
|
49
54
|
};
|
|
50
55
|
|
|
51
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;
|
|
52
59
|
try {
|
|
53
60
|
const code = Function.prototype.toString.call(functionToCheck);
|
|
54
61
|
if (code.indexOf("^_^") > -1) {
|
|
@@ -91,7 +98,7 @@ export const _renderers = new Set<ReactRenderer>();
|
|
|
91
98
|
|
|
92
99
|
const activeListenerTargets = new WeakMap<ActiveListener, Set<ReactDevToolsTarget>>();
|
|
93
100
|
const rendererInjectSubscriptions = new Set<RendererInjectSubscription>();
|
|
94
|
-
const
|
|
101
|
+
const rdtHookReplaceSubscriptions = new Set<RDTHookReplaceSubscription>();
|
|
95
102
|
const notifiedRenderersByTarget = new WeakMap<ReactDevToolsTarget, WeakSet<ReactRenderer>>();
|
|
96
103
|
|
|
97
104
|
const addActiveListener = (listener: ActiveListener, target: ReactDevToolsTarget): void => {
|
|
@@ -113,9 +120,12 @@ export const removeActiveListener = (
|
|
|
113
120
|
}
|
|
114
121
|
};
|
|
115
122
|
|
|
116
|
-
const notifyActiveListeners = (
|
|
117
|
-
|
|
118
|
-
|
|
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);
|
|
119
129
|
}
|
|
120
130
|
};
|
|
121
131
|
|
|
@@ -123,8 +133,11 @@ const notifyRendererInjectListeners = (
|
|
|
123
133
|
target: ReactDevToolsTarget,
|
|
124
134
|
renderer: ReactRenderer,
|
|
125
135
|
): void => {
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
}
|
|
128
141
|
}
|
|
129
142
|
};
|
|
130
143
|
|
|
@@ -132,8 +145,11 @@ const notifyRDTHookReplaceListeners = (
|
|
|
132
145
|
rdtHook: ReactDevToolsGlobalHook,
|
|
133
146
|
target: ReactDevToolsTarget,
|
|
134
147
|
): void => {
|
|
135
|
-
|
|
136
|
-
|
|
148
|
+
const subscriptionSnapshot = [...rdtHookReplaceSubscriptions];
|
|
149
|
+
for (const subscription of subscriptionSnapshot) {
|
|
150
|
+
if (rdtHookReplaceSubscriptions.has(subscription)) {
|
|
151
|
+
callListener(subscription.listener, undefined, rdtHook, target);
|
|
152
|
+
}
|
|
137
153
|
}
|
|
138
154
|
};
|
|
139
155
|
|
|
@@ -150,9 +166,10 @@ export const onRendererInject = (
|
|
|
150
166
|
};
|
|
151
167
|
|
|
152
168
|
export const onRDTHookReplace = (listener: RDTHookReplaceListener): Unsubscribe => {
|
|
153
|
-
|
|
169
|
+
const subscription = { listener };
|
|
170
|
+
rdtHookReplaceSubscriptions.add(subscription);
|
|
154
171
|
return createUnsubscribe(() => {
|
|
155
|
-
|
|
172
|
+
rdtHookReplaceSubscriptions.delete(subscription);
|
|
156
173
|
});
|
|
157
174
|
};
|
|
158
175
|
|
|
@@ -240,10 +257,11 @@ export const installRDTHook = (
|
|
|
240
257
|
_renderers.add(renderer);
|
|
241
258
|
nextRenderers.set(rendererId, renderer);
|
|
242
259
|
});
|
|
243
|
-
if (ourRenderers.size > 0 ||
|
|
244
|
-
patchRDTHook(
|
|
260
|
+
if (ourRenderers.size > 0 || rdtHookReplaceSubscriptions.size > 0) {
|
|
261
|
+
patchRDTHook(undefined, target, () => notifyRDTHookReplaceListeners(newHook, target));
|
|
262
|
+
} else {
|
|
263
|
+
notifyRDTHookReplaceListeners(newHook, target);
|
|
245
264
|
}
|
|
246
|
-
notifyRDTHookReplaceListeners(rdtHook, target);
|
|
247
265
|
}
|
|
248
266
|
},
|
|
249
267
|
});
|
|
@@ -285,9 +303,10 @@ export const installRDTHook = (
|
|
|
285
303
|
export const patchRDTHook = (
|
|
286
304
|
onActive?: ActiveListener,
|
|
287
305
|
target: ReactDevToolsTarget = globalThis,
|
|
306
|
+
onReady?: () => void,
|
|
288
307
|
): void => {
|
|
289
308
|
if (onActive) addActiveListener(onActive, target);
|
|
290
|
-
let
|
|
309
|
+
let shouldNotifyActiveListeners = false;
|
|
291
310
|
const rdtHook = getTargetHook(target);
|
|
292
311
|
if (!rdtHook) return;
|
|
293
312
|
const renderers = getRendererMap(rdtHook);
|
|
@@ -303,28 +322,25 @@ export const patchRDTHook = (
|
|
|
303
322
|
if (!isReactDevtools) {
|
|
304
323
|
rdtHook.on = noOp;
|
|
305
324
|
}
|
|
306
|
-
|
|
307
|
-
renderers.forEach((renderer) => _renderers.add(renderer));
|
|
308
|
-
rdtHook._instrumentationIsActive = true;
|
|
309
|
-
notifyActiveListeners(target);
|
|
310
|
-
didNotifyActiveListeners = true;
|
|
311
|
-
} else if (!isReactDevtools && isReactRefresh(rdtHook)) {
|
|
312
|
-
// HACK: react-refresh's stub inject never records renderers, so a React app
|
|
313
|
-
// that injected before bippy loaded is undetectable through the renderers map.
|
|
314
|
-
// A react-refresh hook implies a dev renderer, so activate immediately.
|
|
315
|
-
rdtHook._instrumentationIsActive = true;
|
|
316
|
-
notifyActiveListeners(target);
|
|
317
|
-
didNotifyActiveListeners = true;
|
|
318
|
-
}
|
|
325
|
+
const shouldActivate = renderers.size > 0 || (!isReactDevtools && isReactRefresh(rdtHook));
|
|
319
326
|
const previousInject = rdtHook.inject;
|
|
320
327
|
rdtHook.inject = (renderer) => {
|
|
321
328
|
const rendererId = previousInject.call(rdtHook, renderer);
|
|
322
329
|
trackInjectedRenderer(rdtHook, target, renderers, rendererId, renderer);
|
|
323
330
|
return rendererId;
|
|
324
331
|
};
|
|
332
|
+
if (shouldActivate) {
|
|
333
|
+
renderers.forEach((renderer) => _renderers.add(renderer));
|
|
334
|
+
rdtHook._instrumentationIsActive = true;
|
|
335
|
+
shouldNotifyActiveListeners = true;
|
|
336
|
+
}
|
|
325
337
|
}
|
|
326
|
-
|
|
327
|
-
|
|
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);
|
|
328
344
|
}
|
|
329
345
|
};
|
|
330
346
|
|
|
@@ -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;
|
package/src/react.ts
CHANGED
|
@@ -1,77 +1,162 @@
|
|
|
1
1
|
import "./install-hook-only.js";
|
|
2
|
-
import
|
|
3
|
-
import { isFiber } from "./core.js";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { isFiber, traverseFiber } from "./core.js";
|
|
4
|
+
import { _renderers } from "./rdt-hook.js";
|
|
5
|
+
import { getCurrentFiberFromRoot } from "./react-internals/current-fiber.js";
|
|
4
6
|
import type { Fiber } from "./react-internals/index.js";
|
|
5
7
|
|
|
6
8
|
export type { Fiber } from "./react-internals/index.js";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const subscribeToEmptyStore = (): (() => void) => unsubscribeFromEmptyStore;
|
|
12
|
-
const useSyncExternalStore: unknown = Reflect.get(React, "useSyncExternalStore");
|
|
10
|
+
interface RenderMarker {
|
|
11
|
+
(state: undefined): undefined;
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
interface ReducerQueue {
|
|
15
|
+
lastRenderedReducer: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface FiberCapture {
|
|
19
|
+
fiber: Fiber;
|
|
20
|
+
queue: ReducerQueue | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const isReducerQueue = (value: unknown): value is ReducerQueue =>
|
|
24
|
+
typeof value === "object" && value !== null && "lastRenderedReducer" in value;
|
|
25
|
+
|
|
26
|
+
const hasRenderMarker = (fiber: Fiber, renderMarker: RenderMarker): boolean => {
|
|
27
|
+
for (let hook = fiber.memoizedState; hook; hook = hook.next) {
|
|
28
|
+
if (Array.isArray(hook.memoizedState) && hook.memoizedState[0] === renderMarker) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getReducerQueue = (fiber: Fiber, renderMarker: RenderMarker): ReducerQueue | null => {
|
|
34
|
+
for (let hook = fiber.memoizedState; hook; hook = hook.next) {
|
|
35
|
+
if (isReducerQueue(hook.queue) && hook.queue.lastRenderedReducer === renderMarker)
|
|
36
|
+
return hook.queue;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getDevToolsFiber = (renderMarker: RenderMarker): Fiber | null => {
|
|
42
|
+
for (const renderer of _renderers) {
|
|
43
|
+
try {
|
|
44
|
+
const fiber = renderer.getCurrentFiber?.();
|
|
45
|
+
if (fiber && isFiber(fiber) && hasRenderMarker(fiber, renderMarker)) return fiber;
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const captureReducerFiber = (
|
|
52
|
+
renderMarker: RenderMarker,
|
|
53
|
+
shouldCapture: boolean,
|
|
54
|
+
): FiberCapture | null => {
|
|
55
|
+
let originalBind: typeof Function.prototype.bind | undefined;
|
|
56
|
+
if (shouldCapture) {
|
|
57
|
+
try {
|
|
58
|
+
originalBind = Function.prototype.bind;
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
if (!originalBind) {
|
|
62
|
+
React.useReducer(renderMarker, undefined);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let capture: FiberCapture | null = null;
|
|
67
|
+
let isCapturing = true;
|
|
68
|
+
// HACK: Production React binds a new reducer's Fiber and queue; validate the private reducer marker, not argument positions.
|
|
18
69
|
const bindProxy = new Proxy(originalBind, {
|
|
19
|
-
apply: (bind,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
70
|
+
apply: (bind, callback, boundArguments) => {
|
|
71
|
+
if (isCapturing && !capture) {
|
|
72
|
+
try {
|
|
73
|
+
const queue = boundArguments.find(
|
|
74
|
+
(argument): argument is ReducerQueue =>
|
|
75
|
+
isReducerQueue(argument) && argument.lastRenderedReducer === renderMarker,
|
|
76
|
+
);
|
|
77
|
+
if (queue) {
|
|
78
|
+
const fiber = boundArguments.find(
|
|
79
|
+
(argument): argument is Fiber => isFiber(argument) && isFiber(argument.return),
|
|
80
|
+
);
|
|
81
|
+
if (fiber) capture = { fiber, queue };
|
|
82
|
+
}
|
|
83
|
+
} catch {}
|
|
23
84
|
}
|
|
24
|
-
return Reflect.apply(bind,
|
|
85
|
+
return Reflect.apply(bind, callback, boundArguments);
|
|
25
86
|
},
|
|
26
87
|
});
|
|
27
|
-
Reflect.set(Function.prototype, "bind", bindProxy);
|
|
28
88
|
|
|
29
89
|
try {
|
|
30
|
-
|
|
90
|
+
try {
|
|
91
|
+
Reflect.set(Function.prototype, "bind", bindProxy);
|
|
92
|
+
} catch {}
|
|
93
|
+
React.useReducer(renderMarker, undefined);
|
|
94
|
+
return capture;
|
|
31
95
|
} finally {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
96
|
+
isCapturing = false;
|
|
97
|
+
capture = null;
|
|
98
|
+
try {
|
|
99
|
+
if (Function.prototype.bind === bindProxy) {
|
|
100
|
+
Reflect.set(Function.prototype, "bind", originalBind);
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
35
103
|
}
|
|
36
|
-
|
|
37
|
-
return capturedFiber;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const useExternalStoreCapture = (): void => {
|
|
41
|
-
if (typeof useSyncExternalStore !== "function") return;
|
|
42
|
-
Reflect.apply(useSyncExternalStore, React, [
|
|
43
|
-
subscribeToEmptyStore,
|
|
44
|
-
readEmptySnapshot,
|
|
45
|
-
readEmptySnapshot,
|
|
46
|
-
]);
|
|
47
104
|
};
|
|
48
105
|
|
|
49
|
-
|
|
50
|
-
|
|
106
|
+
// HACK: React <16.13 attaches hooks after render; the shared reducer queue validates the root-walk fallback.
|
|
107
|
+
const getRenderingFiberFromRoot = (
|
|
108
|
+
{ fiber, queue }: FiberCapture,
|
|
109
|
+
renderMarker: RenderMarker,
|
|
110
|
+
): Fiber | null => {
|
|
111
|
+
if (queue?.lastRenderedReducer !== renderMarker) return null;
|
|
112
|
+
let rootFiber = fiber;
|
|
113
|
+
while (rootFiber.return) rootFiber = rootFiber.return;
|
|
114
|
+
const root = rootFiber.stateNode;
|
|
115
|
+
if (typeof root !== "object" || root === null || !("current" in root) || !isFiber(root.current)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const renderingRoot = root.current.alternate;
|
|
119
|
+
const renderingFiber = getCurrentFiberFromRoot(fiber)?.alternate;
|
|
120
|
+
if (renderingFiber) {
|
|
121
|
+
let ancestor = renderingFiber;
|
|
122
|
+
while (ancestor.return) ancestor = ancestor.return;
|
|
123
|
+
if (ancestor === renderingRoot) return renderingFiber;
|
|
124
|
+
}
|
|
125
|
+
return traverseFiber(renderingRoot, (candidate) => {
|
|
126
|
+
if (candidate !== fiber && candidate !== fiber.alternate) return false;
|
|
127
|
+
let parent = candidate;
|
|
128
|
+
while (parent.return) parent = parent.return;
|
|
129
|
+
return parent === renderingRoot;
|
|
130
|
+
});
|
|
51
131
|
};
|
|
52
132
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
133
|
+
export const useFiber = (): Fiber | undefined => {
|
|
134
|
+
"use no memo";
|
|
135
|
+
const fiberRef = React.useRef<FiberCapture | null>(null);
|
|
136
|
+
const renderMarker: RenderMarker = (state) => state;
|
|
137
|
+
React.useMemo(() => renderMarker, [renderMarker]);
|
|
138
|
+
const devToolsFiber = fiberRef.current ? null : getDevToolsFiber(renderMarker);
|
|
139
|
+
const capture = captureReducerFiber(renderMarker, !fiberRef.current && !devToolsFiber);
|
|
140
|
+
if (devToolsFiber) {
|
|
141
|
+
fiberRef.current = {
|
|
142
|
+
fiber: devToolsFiber,
|
|
143
|
+
queue: getReducerQueue(devToolsFiber, renderMarker),
|
|
144
|
+
};
|
|
145
|
+
return devToolsFiber;
|
|
146
|
+
}
|
|
147
|
+
const knownCapture = capture ?? fiberRef.current;
|
|
148
|
+
if (!knownCapture) return undefined;
|
|
149
|
+
const knownFiber = knownCapture.fiber;
|
|
60
150
|
const fiber =
|
|
61
|
-
|
|
62
|
-
(
|
|
63
|
-
?
|
|
64
|
-
:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}, [fiber]);
|
|
72
|
-
|
|
151
|
+
capture?.fiber ??
|
|
152
|
+
(hasRenderMarker(knownFiber, renderMarker)
|
|
153
|
+
? knownFiber
|
|
154
|
+
: knownFiber.alternate && hasRenderMarker(knownFiber.alternate, renderMarker)
|
|
155
|
+
? knownFiber.alternate
|
|
156
|
+
: getRenderingFiberFromRoot(knownCapture, renderMarker));
|
|
157
|
+
if (fiber) {
|
|
158
|
+
knownCapture.fiber = fiber;
|
|
159
|
+
fiberRef.current = knownCapture;
|
|
160
|
+
}
|
|
73
161
|
return fiber ?? undefined;
|
|
74
162
|
};
|
|
75
|
-
|
|
76
|
-
export const useFiber =
|
|
77
|
-
typeof useSyncExternalStore === "function" ? useFiberWithExternalStore : useFiberWithReducer;
|