101
import {enableStyleXFeatures} from 'react-devtools-feature-flags';
102
import is from 'shared/objectIs';
103
import hasOwnProperty from 'shared/hasOwnProperty';
104
+
105
+// $FlowFixMe[method-unbinding]
106
+const toString = Object.prototype.toString;
107
+
108
+function isError(object: mixed) {
109
+ return toString.call(object) === '[object Error]';
110
+}
111
+
112
import {getStyleXData} from '../StyleX/utils';
113
import {createProfilingHooks} from '../profilingHooks';
114
139
Plugins,
140
} from 'react-devtools-shared/src/frontend/types';
141
import type {Source} from 'react-devtools-shared/src/shared/types';
134
-import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack';
142
+import {getSourceLocationByFiber} from './DevToolsFiberComponentStack';
143
+import {formatOwnerStack} from '../shared/DevToolsOwnerStack';
144
145
// Kinds
146
const FIBER_INSTANCE = 0;
161
previousSibling: null | DevToolsInstance, // filtered next sibling, including virtual
162
nextSibling: null | DevToolsInstance, // filtered next sibling, including virtual
163
flags: number, // Force Error/Suspense
155
- componentStack: null | string,
164
+ source: null | string | Error | Source, // source location of this component function, or owned child stack
165
errors: null | Map<string, number>, // error messages and count
166
warnings: null | Map<string, number>, // warning messages and count
167
data: Fiber, // one of a Fiber pair
176
previousSibling: null,
177
nextSibling: null,
178
flags: 0,
170
- componentStack: null,
179
+ source: null,
180
errors: null,
181
warnings: null,
182
data: fiber,
196
previousSibling: null | DevToolsInstance, // filtered next sibling, including virtual
197
nextSibling: null | DevToolsInstance, // filtered next sibling, including virtual
198
flags: number,
190
- componentStack: null | string,
199
+ source: null | string | Error | Source, // source location of this server component, or owned child stack
200
// Errors and Warnings happen per ReactComponentInfo which can appear in
201
// multiple places but we track them per stateful VirtualInstance so
202
// that old errors/warnings don't disappear when the instance is refreshed.
218
previousSibling: null,
219
nextSibling: null,
220
flags: 0,
212
- componentStack: null,
221
+ source: null,
222
errors: null,
223
warnings: null,
224
data: debugEntry,
2163
parentInstance,
2164
debugOwner,
2165
);
2166
+ if (
2167
+ ownerInstance !== null &&
2168
+ debugOwner === fiber._debugOwner &&
2169
+ fiber._debugStack != null &&
2170
+ ownerInstance.source === null
2171
+ ) {
2172
+ // The new Fiber is directly owned by the ownerInstance. Therefore somewhere on
2173
+ // the debugStack will be a stack frame inside the ownerInstance's source.
2174
+ ownerInstance.source = fiber._debugStack;
2175
+ }
2176
const ownerID = ownerInstance === null ? 0 : ownerInstance.id;
2177
const parentID = parentInstance ? parentInstance.id : 0;
2178
2247
// away so maybe it's not so bad.
2248
const debugOwner = getUnfilteredOwner(componentInfo);
2249
const ownerInstance = findNearestOwnerInstance(parentInstance, debugOwner);
2250
+ if (
2251
+ ownerInstance !== null &&
2252
+ debugOwner === componentInfo.owner &&
2253
+ componentInfo.debugStack != null &&
2254
+ ownerInstance.source === null
2255
+ ) {
2256
+ // The new Fiber is directly owned by the ownerInstance. Therefore somewhere on
2257
+ // the debugStack will be a stack frame inside the ownerInstance's source.
2258
+ ownerInstance.source = componentInfo.debugStack;
2259
+ }
2260
const ownerID = ownerInstance === null ? 0 : ownerInstance.id;
2261
const parentID = parentInstance ? parentInstance.id : 0;
2262
4353
4354
let source = null;
4355
if (canViewSource) {
4327
- source = getSourceForFiber(fiber);
4356
+ source = getSourceForFiberInstance(fiberInstance);
4357
}
4358
4359
return {
4427
function inspectVirtualInstanceRaw(
4428
virtualInstance: VirtualInstance,
4429
): InspectedElement | null {
4401
- const canViewSource = false;
4430
+ const canViewSource = true;
4431
+ const source = getSourceForInstance(virtualInstance);
4432
4433
const componentInfo = virtualInstance.data;
4434
const key =
4468
stylex: null,
4469
};
4470
4441
- // TODO: Support getting the source location from the owner stack.
4442
- const source = null;
4443
-
4471
return {
4472
id: virtualInstance.id,
4473
5691
return idToDevToolsInstanceMap.has(id);
5692
}
5693
5667
- function getComponentStackForFiber(fiber: Fiber): string | null {
5668
- // TODO: This should really just take an DevToolsInstance directly.
5669
- let fiberInstance = fiberToFiberInstanceMap.get(fiber);
5670
- if (fiberInstance === undefined && fiber.alternate !== null) {
5671
- fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate);
5672
- }
5673
- if (fiberInstance === undefined) {
5674
- // We're no longer tracking this instance.
5675
- return null;
5676
- }
5677
- if (fiberInstance.componentStack !== null) {
5678
- // Cached entry.
5679
- return fiberInstance.componentStack;
5694
+ function getSourceForFiberInstance(
5695
+ fiberInstance: FiberInstance,
5696
+ ): Source | null {
5697
+ const unresolvedSource = fiberInstance.source;
5698
+ if (
5699
+ unresolvedSource !== null &&
5700
+ typeof unresolvedSource === 'object' &&
5701
+ !isError(unresolvedSource)
5702
+ ) {
5703
+ // $FlowFixMe: isError should have refined it.
5704
+ return unresolvedSource;
5705
}
5706
const dispatcherRef = getDispatcherRef(renderer);
5682
- if (dispatcherRef == null) {
5707
+ const stackFrame =
5708
+ dispatcherRef == null
5709
+ ? null
5710
+ : getSourceLocationByFiber(
5711
+ ReactTypeOfWork,
5712
+ fiberInstance.data,
5713
+ dispatcherRef,
5714
+ );
5715
+ if (stackFrame === null) {
5716
+ // If we don't find a source location by throwing, try to get one
5717
+ // from an owned child if possible. This is the same branch as
5718
+ // for virtual instances.
5719
+ return getSourceForInstance(fiberInstance);
5720
+ }
5721
+ const source = parseSourceFromComponentStack(stackFrame);
5722
+ fiberInstance.source = source;
5723
+ return source;
5724
+ }
5725
+
5726
+ function getSourceForInstance(instance: DevToolsInstance): Source | null {
5727
+ let unresolvedSource = instance.source;
5728
+ if (unresolvedSource === null) {
5729
+ // We don't have any source yet. We can try again later in case an owned child mounts later.
5730
+ // TODO: We won't have any information here if the child is filtered.
5731
return null;
5732
}
5733
5686
- return (fiberInstance.componentStack = getStackByFiberInDevAndProd(
5687
- ReactTypeOfWork,
5688
- fiber,
5689
- dispatcherRef,
5690
- ));
5691
- }
5692
-
5693
- function getSourceForFiber(fiber: Fiber): Source | null {
5694
- const componentStack = getComponentStackForFiber(fiber);
5695
- if (componentStack == null) {
5696
- return null;
5734
+ // If we have the debug stack (the creation stack of the JSX) for any owned child of this
5735
+ // component, then at the bottom of that stack will be a stack frame that is somewhere within
5736
+ // the component's function body. Typically it would be the callsite of the JSX unless there's
5737
+ // any intermediate utility functions. This won't point to the top of the component function
5738
+ // but it's at least somewhere within it.
5739
+ if (isError(unresolvedSource)) {
5740
+ unresolvedSource = formatOwnerStack((unresolvedSource: any));
5741
+ }
5742
+ if (typeof unresolvedSource === 'string') {
5743
+ const idx = unresolvedSource.lastIndexOf('\n');
5744
+ const lastLine =
5745
+ idx === -1 ? unresolvedSource : unresolvedSource.slice(idx + 1);
5746
+ return (instance.source = parseSourceFromComponentStack(lastLine));
5747
}
5748
5699
- return parseSourceFromComponentStack(componentStack);
5749
+ // $FlowFixMe: refined.
5750
+ return unresolvedSource;
5751
}
5752
5753
return {