7
* @flow
8
*/
9
10
+import type {ReactComponentInfo} from 'shared/ReactTypes';
11
+
12
import {
13
ComponentFilterDisplayName,
14
ComponentFilterElementType,
133
import type {Source} from 'react-devtools-shared/src/shared/types';
134
import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack';
135
136
+// Kinds
137
+const FIBER_INSTANCE = 0;
138
+// const VIRTUAL_INSTANCE = 1;
139
+
140
+// Flags
141
+const FORCE_SUSPENSE_FALLBACK = /* */ 0b001;
142
+const FORCE_ERROR = /* */ 0b010;
143
+const FORCE_ERROR_RESET = /* */ 0b100;
144
+
145
+// This type represents a stateful instance of a Client Component i.e. a Fiber pair.
146
+// These instances also let us track stateful DevTools meta data like id and warnings.
147
+type FiberInstance = {
148
+ kind: 0,
149
+ id: number,
150
+ parent: null | DevToolsInstance, // virtual parent
151
+ flags: number, // Force Error/Suspense
152
+ componentStack: null | string,
153
+ errors: null | Map<string, number>, // error messages and count
154
+ warnings: null | Map<string, number>, // warning messages and count
155
+ data: Fiber, // one of a Fiber pair
156
+};
157
+
158
+function createFiberInstance(fiber: Fiber): FiberInstance {
159
+ return {
160
+ kind: 0,
161
+ id: getUID(),
162
+ parent: null,
163
+ flags: 0,
164
+ componentStack: null,
165
+ errors: null,
166
+ warnings: null,
167
+ data: fiber,
168
+ };
169
+}
170
+
171
+// This type represents a stateful instance of a Server Component or a Component
172
+// that gets optimized away - e.g. call-through without creating a Fiber.
173
+// It's basically a virtual Fiber. This is not a semantic concept in React.
174
+// It only exists as a virtual concept to let the same Element in the DevTools
175
+// persist. To be selectable separately from all ReactComponentInfo and overtime.
176
+type VirtualInstance = {
177
+ kind: 1,
178
+ id: number,
179
+ parent: null | DevToolsInstance, // virtual parent
180
+ flags: number,
181
+ componentStack: null | string,
182
+ // Errors and Warnings happen per ReactComponentInfo which can appear in
183
+ // multiple places but we track them per stateful VirtualInstance so
184
+ // that old errors/warnings don't disappear when the instance is refreshed.
185
+ errors: null | Map<string, number>, // error messages and count
186
+ warnings: null | Map<string, number>, // warning messages and count
187
+ // The latest info for this instance. This can be updated over time and the
188
+ // same info can appear in more than once ServerComponentInstance.
189
+ data: ReactComponentInfo,
190
+};
191
+
192
+type DevToolsInstance = FiberInstance | VirtualInstance;
193
+
194
type getDisplayNameForFiberType = (fiber: Fiber) => string | null;
195
type getTypeSymbolType = (type: any) => symbol | number;
196
689
// We track both Fibers to support Fast Refresh,
690
// which may forcefully replace one of the pair as part of hot reloading.
691
// In that case it's still important to be able to locate the previous ID during subsequent renders.
632
-const fiberToIDMap: Map<Fiber, number> = new Map();
692
+const fiberToFiberInstanceMap: Map<Fiber, FiberInstance> = new Map();
693
694
// Map of id to one (arbitrary) Fiber in a pair.
695
// This Map is used to e.g. get the display name for a Fiber or schedule an update,
696
// operations that should be the same whether the current and work-in-progress Fiber is used.
637
-const idToArbitraryFiberMap: Map<number, Fiber> = new Map();
638
-
639
-const fiberToComponentStackMap: WeakMap<Fiber, string> = new WeakMap();
697
+const idToDevToolsInstanceMap: Map<number, DevToolsInstance> = new Map();
698
699
export function attach(
700
hook: DevToolsHook,
808
}
809
810
// Tracks Fibers with recently changed number of error/warning messages.
753
- // These collections store the Fiber rather than the ID,
754
- // in order to avoid generating an ID for Fibers that never get mounted
811
+ // These collections store the Fiber rather than the DevToolsInstance,
812
+ // in order to avoid generating an DevToolsInstance for Fibers that never get mounted
813
// (due to e.g. Suspense or error boundaries).
814
// onErrorOrWarning() adds Fibers and recordPendingErrorsAndWarnings() later clears them.
815
const fibersWithChangedErrorOrWarningCounts: Set<Fiber> = new Set();
816
const pendingFiberToErrorsMap: Map<Fiber, Map<string, number>> = new Map();
817
const pendingFiberToWarningsMap: Map<Fiber, Map<string, number>> = new Map();
818
761
- // Mapping of fiber IDs to error/warning messages and counts.
762
- const fiberIDToErrorsMap: Map<number, Map<string, number>> = new Map();
763
- const fiberIDToWarningsMap: Map<number, Map<string, number>> = new Map();
764
-
819
function clearErrorsAndWarnings() {
820
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
767
- for (const id of fiberIDToErrorsMap.keys()) {
768
- const fiber = idToArbitraryFiberMap.get(id);
769
- if (fiber != null) {
770
- fibersWithChangedErrorOrWarningCounts.add(fiber);
771
- updateMostRecentlyInspectedElementIfNecessary(id);
772
- }
773
- }
774
-
775
- // eslint-disable-next-line no-for-of-loops/no-for-of-loops
776
- for (const id of fiberIDToWarningsMap.keys()) {
777
- const fiber = idToArbitraryFiberMap.get(id);
778
- if (fiber != null) {
779
- fibersWithChangedErrorOrWarningCounts.add(fiber);
780
- updateMostRecentlyInspectedElementIfNecessary(id);
821
+ for (const devtoolsInstance of idToDevToolsInstanceMap.values()) {
822
+ devtoolsInstance.errors = null;
823
+ devtoolsInstance.warnings = null;
824
+ if (devtoolsInstance.kind === FIBER_INSTANCE) {
825
+ fibersWithChangedErrorOrWarningCounts.add(devtoolsInstance.data);
826
+ } else {
827
+ // TODO: Handle VirtualInstance.
828
}
829
+ updateMostRecentlyInspectedElementIfNecessary(devtoolsInstance.id);
830
}
783
-
784
- fiberIDToErrorsMap.clear();
785
- fiberIDToWarningsMap.clear();
786
-
831
flushPendingEvents();
832
}
833
834
function clearMessageCountHelper(
791
- fiberID: number,
835
+ instanceID: number,
836
pendingFiberToMessageCountMap: Map<Fiber, Map<string, number>>,
793
- fiberIDToMessageCountMap: Map<number, Map<string, number>>,
837
+ forError: boolean,
838
) {
795
- const fiber = idToArbitraryFiberMap.get(fiberID);
796
- if (fiber != null) {
797
- // Throw out any pending changes.
798
- pendingFiberToErrorsMap.delete(fiber);
799
-
800
- if (fiberIDToMessageCountMap.has(fiberID)) {
801
- fiberIDToMessageCountMap.delete(fiberID);
839
+ const devtoolsInstance = idToDevToolsInstanceMap.get(instanceID);
840
+ if (devtoolsInstance !== undefined) {
841
+ let changed = false;
842
+ if (forError) {
843
+ if (
844
+ devtoolsInstance.errors !== null &&
845
+ devtoolsInstance.errors.size > 0
846
+ ) {
847
+ changed = true;
848
+ }
849
+ devtoolsInstance.errors = null;
850
+ } else {
851
+ if (
852
+ devtoolsInstance.warnings !== null &&
853
+ devtoolsInstance.warnings.size > 0
854
+ ) {
855
+ changed = true;
856
+ }
857
+ devtoolsInstance.warnings = null;
858
+ }
859
+ if (devtoolsInstance.kind === FIBER_INSTANCE) {
860
+ const fiber = devtoolsInstance.data;
861
+ // Throw out any pending changes.
862
+ pendingFiberToErrorsMap.delete(fiber);
863
803
- // If previous flushed counts have changed, schedule an update too.
804
- fibersWithChangedErrorOrWarningCounts.add(fiber);
805
- flushPendingEvents();
864
+ if (changed) {
865
+ // If previous flushed counts have changed, schedule an update too.
866
+ fibersWithChangedErrorOrWarningCounts.add(fiber);
867
+ flushPendingEvents();
868
807
- updateMostRecentlyInspectedElementIfNecessary(fiberID);
869
+ updateMostRecentlyInspectedElementIfNecessary(instanceID);
870
+ } else {
871
+ fibersWithChangedErrorOrWarningCounts.delete(fiber);
872
+ }
873
} else {
809
- fibersWithChangedErrorOrWarningCounts.delete(fiber);
874
+ // TODO: Handle VirtualInstance.
875
}
876
}
877
}
878
814
- function clearErrorsForElementID(fiberID: number) {
815
- clearMessageCountHelper(
816
- fiberID,
817
- pendingFiberToErrorsMap,
818
- fiberIDToErrorsMap,
819
- );
879
+ function clearErrorsForElementID(instanceID: number) {
880
+ clearMessageCountHelper(instanceID, pendingFiberToErrorsMap, true);
881
}
882
822
- function clearWarningsForElementID(fiberID: number) {
823
- clearMessageCountHelper(
824
- fiberID,
825
- pendingFiberToWarningsMap,
826
- fiberIDToWarningsMap,
827
- );
883
+ function clearWarningsForElementID(instanceID: number) {
884
+ clearMessageCountHelper(instanceID, pendingFiberToWarningsMap, false);
885
}
886
887
function updateMostRecentlyInspectedElementIfNecessary(
902
args: $ReadOnlyArray<any>,
903
): void {
904
if (type === 'error') {
848
- const maybeID = getFiberIDUnsafe(fiber);
905
+ let fiberInstance = fiberToFiberInstanceMap.get(fiber);
906
+ if (fiberInstance === undefined && fiber.alternate !== null) {
907
+ fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate);
908
+ }
909
// if this is an error simulated by us to trigger error boundary, ignore
850
- if (maybeID != null && forceErrorForFiberIDs.get(maybeID) === true) {
910
+ if (fiberInstance !== undefined && fiberInstance.flags & FORCE_ERROR) {
911
return;
912
}
913
}
1246
// Returns the unique ID for a Fiber or generates and caches a new one if the Fiber hasn't been seen before.
1247
// Once this method has been called for a Fiber, untrackFiberID() should always be called later to avoid leaking.
1248
function getOrGenerateFiberID(fiber: Fiber): number {
1189
- let id = null;
1190
- if (fiberToIDMap.has(fiber)) {
1191
- id = fiberToIDMap.get(fiber);
1192
- } else {
1249
+ let fiberInstance = fiberToFiberInstanceMap.get(fiber);
1250
+ if (fiberInstance === undefined) {
1251
const {alternate} = fiber;
1194
- if (alternate !== null && fiberToIDMap.has(alternate)) {
1195
- id = fiberToIDMap.get(alternate);
1252
+ if (alternate !== null) {
1253
+ fiberInstance = fiberToFiberInstanceMap.get(alternate);
1254
+ if (fiberInstance !== undefined) {
1255
+ // We found the other pair, so we need to make sure we track the other side.
1256
+ fiberToFiberInstanceMap.set(fiber, fiberInstance);
1257
+ }
1258
}
1259
}
1260
1261
let didGenerateID = false;
1200
- if (id === null) {
1262
+ if (fiberInstance === undefined) {
1263
didGenerateID = true;
1202
- id = getUID();
1203
- }
1204
-
1205
- // This refinement is for Flow purposes only.
1206
- const refinedID = ((id: any): number);
1207
-
1208
- // Make sure we're tracking this Fiber
1209
- // e.g. if it just mounted or an error was logged during initial render.
1210
- if (!fiberToIDMap.has(fiber)) {
1211
- fiberToIDMap.set(fiber, refinedID);
1212
- idToArbitraryFiberMap.set(refinedID, fiber);
1213
- }
1214
-
1215
- // Also make sure we're tracking its alternate,
1216
- // e.g. in case this is the first update after mount.
1217
- const {alternate} = fiber;
1218
- if (alternate !== null) {
1219
- if (!fiberToIDMap.has(alternate)) {
1220
- fiberToIDMap.set(alternate, refinedID);
1221
- }
1264
+ fiberInstance = createFiberInstance(fiber);
1265
+ fiberToFiberInstanceMap.set(fiber, fiberInstance);
1266
+ idToDevToolsInstanceMap.set(fiberInstance.id, fiberInstance);
1267
}
1268
1269
if (__DEBUG__) {
1277
}
1278
}
1279
1235
- return refinedID;
1280
+ return fiberInstance.id;
1281
}
1282
1283
// Returns an ID if one has already been generated for the Fiber or throws.
1294
// Returns an ID if one has already been generated for the Fiber or null if one has not been generated.
1295
// Use this method while e.g. logging to avoid over-retaining Fibers.
1296
function getFiberIDUnsafe(fiber: Fiber): number | null {
1252
- if (fiberToIDMap.has(fiber)) {
1253
- return ((fiberToIDMap.get(fiber): any): number);
1297
+ const fiberInstance = fiberToFiberInstanceMap.get(fiber);
1298
+ if (fiberInstance !== undefined) {
1299
+ return fiberInstance.id;
1300
} else {
1301
const {alternate} = fiber;
1256
- if (alternate !== null && fiberToIDMap.has(alternate)) {
1257
- return ((fiberToIDMap.get(alternate): any): number);
1302
+ if (alternate !== null) {
1303
+ const alternateInstance = fiberToFiberInstanceMap.get(alternate);
1304
+ if (alternateInstance !== undefined) {
1305
+ return alternateInstance.id;
1306
+ }
1307
}
1308
}
1309
return null;
1355
}
1356
1357
untrackFibersSet.forEach(fiber => {
1309
- const fiberID = getFiberIDUnsafe(fiber);
1310
- if (fiberID !== null) {
1311
- idToArbitraryFiberMap.delete(fiberID);
1358
+ const fiberInstance = fiberToFiberInstanceMap.get(fiber);
1359
+ if (fiberInstance !== undefined) {
1360
+ idToDevToolsInstanceMap.delete(fiberInstance.id);
1361
1362
// Also clear any errors/warnings associated with this fiber.
1314
- clearErrorsForElementID(fiberID);
1315
- clearWarningsForElementID(fiberID);
1363
+ clearErrorsForElementID(fiberInstance.id);
1364
+ clearWarningsForElementID(fiberInstance.id);
1365
+ if (fiberInstance.flags & FORCE_ERROR) {
1366
+ fiberInstance.flags &= ~FORCE_ERROR;
1367
+ forceErrorCount--;
1368
+ if (forceErrorCount === 0 && setErrorHandler != null) {
1369
+ setErrorHandler(shouldErrorFiberAlwaysNull);
1370
+ }
1371
+ }
1372
+ if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) {
1373
+ fiberInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
1374
+ forceFallbackCount--;
1375
+ if (forceFallbackCount === 0 && setSuspenseHandler != null) {
1376
+ setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
1377
+ }
1378
+ }
1379
}
1380
1318
- fiberToIDMap.delete(fiber);
1319
- fiberToComponentStackMap.delete(fiber);
1381
+ fiberToFiberInstanceMap.delete(fiber);
1382
1383
const {alternate} = fiber;
1384
if (alternate !== null) {
1323
- fiberToIDMap.delete(alternate);
1324
- fiberToComponentStackMap.delete(alternate);
1325
- }
1326
-
1327
- if (forceErrorForFiberIDs.has(fiberID)) {
1328
- forceErrorForFiberIDs.delete(fiberID);
1329
- if (forceErrorForFiberIDs.size === 0 && setErrorHandler != null) {
1330
- setErrorHandler(shouldErrorFiberAlwaysNull);
1331
- }
1385
+ fiberToFiberInstanceMap.delete(alternate);
1386
}
1387
});
1388
untrackFibersSet.clear();
1792
1793
function reevaluateErrorsAndWarnings() {
1794
fibersWithChangedErrorOrWarningCounts.clear();
1741
- fiberIDToErrorsMap.forEach((countMap, fiberID) => {
1742
- const fiber = idToArbitraryFiberMap.get(fiberID);
1743
- if (fiber != null) {
1744
- fibersWithChangedErrorOrWarningCounts.add(fiber);
1745
- }
1746
- });
1747
- fiberIDToWarningsMap.forEach((countMap, fiberID) => {
1748
- const fiber = idToArbitraryFiberMap.get(fiberID);
1749
- if (fiber != null) {
1750
- fibersWithChangedErrorOrWarningCounts.add(fiber);
1795
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
1796
+ for (const devtoolsInstance of idToDevToolsInstanceMap.values()) {
1797
+ if (devtoolsInstance.kind === FIBER_INSTANCE) {
1798
+ fibersWithChangedErrorOrWarningCounts.add(devtoolsInstance.data);
1799
+ } else {
1800
+ // TODO: Handle VirtualInstance.
1801
}
1752
- });
1802
+ }
1803
recordPendingErrorsAndWarnings();
1804
}
1805
1807
fiber: Fiber,
1808
fiberID: number,
1809
pendingFiberToMessageCountMap: Map<Fiber, Map<string, number>>,
1760
- fiberIDToMessageCountMap: Map<number, Map<string, number>>,
1810
+ forError: boolean,
1811
): number {
1812
let newCount = 0;
1813
1764
- let messageCountMap = fiberIDToMessageCountMap.get(fiberID);
1814
+ const devtoolsInstance = idToDevToolsInstanceMap.get(fiberID);
1815
+
1816
+ if (devtoolsInstance === undefined) {
1817
+ return 0;
1818
+ }
1819
+
1820
+ let messageCountMap = forError
1821
+ ? devtoolsInstance.errors
1822
+ : devtoolsInstance.warnings;
1823
1824
const pendingMessageCountMap = pendingFiberToMessageCountMap.get(fiber);
1825
if (pendingMessageCountMap != null) {
1768
- if (messageCountMap == null) {
1826
+ if (messageCountMap === null) {
1827
messageCountMap = pendingMessageCountMap;
1770
-
1771
- fiberIDToMessageCountMap.set(fiberID, pendingMessageCountMap);
1828
+ if (forError) {
1829
+ devtoolsInstance.errors = pendingMessageCountMap;
1830
+ } else {
1831
+ devtoolsInstance.warnings = pendingMessageCountMap;
1832
+ }
1833
} else {
1834
// This Flow refinement should not be necessary and yet...
1835
const refinedMessageCountMap = ((messageCountMap: any): Map<
1869
fiber,
1870
fiberID,
1871
pendingFiberToErrorsMap,
1811
- fiberIDToErrorsMap,
1872
+ true,
1873
);
1874
const warningCount = mergeMapsAndGetCountHelper(
1875
fiber,
1876
fiberID,
1877
pendingFiberToWarningsMap,
1817
- fiberIDToWarningsMap,
1878
+ false,
1879
);
1880
1881
pushOperation(TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS);
2891
2892
function findAllCurrentHostFibers(id: number): $ReadOnlyArray<Fiber> {
2893
const fibers = [];
2833
- const fiber = findCurrentFiberUsingSlowPathById(id);
2894
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
2895
+ if (devtoolsInstance === undefined) {
2896
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
2897
+ return fibers;
2898
+ }
2899
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
2900
+ // TODO: Handle VirtualInstance.
2901
+ return fibers;
2902
+ }
2903
+ const fiber =
2904
+ findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
2905
if (!fiber) {
2906
return fibers;
2907
}
2935
2936
function findHostInstancesForElementID(id: number) {
2937
try {
2867
- const fiber = findCurrentFiberUsingSlowPathById(id);
2938
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
2939
+ if (devtoolsInstance === undefined) {
2940
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
2941
+ return null;
2942
+ }
2943
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
2944
+ // TODO: Handle VirtualInstance.
2945
+ return null;
2946
+ }
2947
+ const fiber =
2948
+ findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
2949
if (fiber === null) {
2950
return null;
2951
}
2959
}
2960
2961
function getDisplayNameForElementID(id: number): null | string {
2881
- const fiber = idToArbitraryFiberMap.get(id);
2882
- return fiber != null ? getDisplayNameForFiber(fiber) : null;
2962
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
2963
+ if (devtoolsInstance === undefined) {
2964
+ return null;
2965
+ }
2966
+ if (devtoolsInstance.kind === FIBER_INSTANCE) {
2967
+ return getDisplayNameForFiber(devtoolsInstance.data);
2968
+ } else {
2969
+ return devtoolsInstance.data.name || '';
2970
+ }
2971
}
2972
2973
function getNearestMountedHostInstance(
3048
// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberTreeReflection.js
3049
// It would be nice if we updated React to inject this function directly (vs just indirectly via findDOMNode).
3050
// BEGIN copied code
2963
- function findCurrentFiberUsingSlowPathById(id: number): Fiber | null {
2964
- const fiber = idToArbitraryFiberMap.get(id);
2965
- if (fiber == null) {
2966
- console.warn(`Could not find Fiber with id "${id}"`);
2967
- return null;
2968
- }
2969
-
3051
+ function findCurrentFiberUsingSlowPathByFiberInstance(
3052
+ fiberInstance: FiberInstance,
3053
+ ): Fiber | null {
3054
+ const fiber = fiberInstance.data;
3055
const alternate = fiber.alternate;
3056
if (!alternate) {
3057
// If there is no alternate, then we only need to check if it is mounted.
3211
}
3212
3213
function prepareViewElementSource(id: number): void {
3129
- const fiber = idToArbitraryFiberMap.get(id);
3130
- if (fiber == null) {
3131
- console.warn(`Could not find Fiber with id "${id}"`);
3214
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
3215
+ if (devtoolsInstance === undefined) {
3216
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
3217
+ return;
3218
+ }
3219
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
3220
+ // TODO: Handle VirtualInstance.
3221
return;
3222
}
3223
+ const fiber = devtoolsInstance.data;
3224
3225
const {elementType, tag, type} = fiber;
3226
3258
}
3259
3260
function getOwnersList(id: number): Array<SerializedElement> | null {
3171
- const fiber = findCurrentFiberUsingSlowPathById(id);
3261
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
3262
+ if (devtoolsInstance === undefined) {
3263
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
3264
+ return null;
3265
+ }
3266
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
3267
+ // TODO: Handle VirtualInstance.
3268
+ return null;
3269
+ }
3270
+ const fiber =
3271
+ findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
3272
if (fiber == null) {
3273
return null;
3274
}
3297
let instance = null;
3298
let style = null;
3299
3200
- const fiber = findCurrentFiberUsingSlowPathById(id);
3300
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
3301
+ if (devtoolsInstance === undefined) {
3302
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
3303
+ return {instance, style};
3304
+ }
3305
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
3306
+ // TODO: Handle VirtualInstance.
3307
+ return {instance, style};
3308
+ }
3309
+
3310
+ const fiber =
3311
+ findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
3312
if (fiber !== null) {
3313
instance = fiber.stateNode;
3314
3349
}
3350
3351
function inspectElementRaw(id: number): InspectedElement | null {
3241
- const fiber = findCurrentFiberUsingSlowPathById(id);
3352
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
3353
+ if (devtoolsInstance === undefined) {
3354
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
3355
+ return null;
3356
+ }
3357
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
3358
+ // TODO: Handle VirtualInstance.
3359
+ return null;
3360
+ }
3361
+ const fiber =
3362
+ findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
3363
if (fiber == null) {
3364
return null;
3365
}
3548
rootType = fiberRoot._debugRootType;
3549
}
3550
3430
- const errors = fiberIDToErrorsMap.get(id) || new Map();
3431
- const warnings = fiberIDToWarningsMap.get(id) || new Map();
3432
-
3551
let isErrored = false;
3552
let targetErrorBoundaryID;
3553
if (isErrorBoundary(fiber)) {
3562
const DidCapture = 0b000000000000000000010000000;
3563
isErrored =
3564
(fiber.flags & DidCapture) !== 0 ||
3447
- forceErrorForFiberIDs.get(id) === true;
3565
+ (devtoolsInstance.flags & FORCE_ERROR) !== 0;
3566
targetErrorBoundaryID = isErrored ? id : getNearestErrorBoundaryID(fiber);
3567
} else {
3568
targetErrorBoundaryID = getNearestErrorBoundaryID(fiber);
3611
(!isTimedOutSuspense ||
3612
// If it's showing fallback because we previously forced it to,
3613
// allow toggling it back to remove the fallback override.
3496
- forceFallbackForSuspenseIDs.has(id)),
3614
+ (devtoolsInstance.flags & FORCE_SUSPENSE_FALLBACK) !== 0),
3615
3616
// Can view component source location.
3617
canViewSource,
3631
hooks,
3632
props: memoizedProps,
3633
state: showState ? memoizedState : null,
3516
- errors: Array.from(errors.entries()),
3517
- warnings: Array.from(warnings.entries()),
3634
+ errors:
3635
+ devtoolsInstance.errors === null
3636
+ ? []
3637
+ : Array.from(devtoolsInstance.errors.entries()),
3638
+ warnings:
3639
+ devtoolsInstance.warnings === null
3640
+ ? []
3641
+ : Array.from(devtoolsInstance.warnings.entries()),
3642
3643
// List of owners
3644
owners,
3736
function updateSelectedElement(inspectedElement: InspectedElement): void {
3737
const {hooks, id, props} = inspectedElement;
3738
3615
- const fiber = idToArbitraryFiberMap.get(id);
3616
- if (fiber == null) {
3617
- console.warn(`Could not find Fiber with id "${id}"`);
3739
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
3740
+ if (devtoolsInstance === undefined) {
3741
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
3742
+ return;
3743
+ }
3744
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
3745
+ // TODO: Handle VirtualInstance.
3746
return;
3747
}
3748
3749
+ const fiber = devtoolsInstance.data;
3750
const {elementType, stateNode, tag, type} = fiber;
3751
3752
switch (tag) {
3880
// Log error & cause for user to debug
3881
console.error(message + '\n\n', error);
3882
if (error.cause != null) {
3754
- const fiber = findCurrentFiberUsingSlowPathById(id);
3755
- const componentName =
3756
- fiber != null ? getDisplayNameForFiber(fiber) : null;
3883
+ const componentName = getDisplayNameForElementID(id);
3884
console.error(
3885
'React DevTools encountered an error while trying to inspect hooks. ' +
3886
'This is most likely caused by an error in current inspected component' +
3982
? mostRecentlyInspectedElement
3983
: inspectElementRaw(id);
3984
if (result === null) {
3858
- console.warn(`Could not find Fiber with id "${id}"`);
3985
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
3986
return;
3987
}
3988
4023
hookID: ?number,
4024
path: Array<string | number>,
4025
): void {
3899
- const fiber = findCurrentFiberUsingSlowPathById(id);
4026
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4027
+ if (devtoolsInstance === undefined) {
4028
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
4029
+ return;
4030
+ }
4031
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
4032
+ // TODO: Handle VirtualInstance.
4033
+ return;
4034
+ }
4035
+ const fiber =
4036
+ findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
4037
if (fiber !== null) {
4038
const instance = fiber.stateNode;
4039
4089
oldPath: Array<string | number>,
4090
newPath: Array<string | number>,
4091
): void {
3955
- const fiber = findCurrentFiberUsingSlowPathById(id);
4092
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4093
+ if (devtoolsInstance === undefined) {
4094
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
4095
+ return;
4096
+ }
4097
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
4098
+ // TODO: Handle VirtualInstance.
4099
+ return;
4100
+ }
4101
+ const fiber =
4102
+ findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
4103
if (fiber !== null) {
4104
const instance = fiber.stateNode;
4105
4165
path: Array<string | number>,
4166
value: any,
4167
): void {
4021
- const fiber = findCurrentFiberUsingSlowPathById(id);
4168
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4169
+ if (devtoolsInstance === undefined) {
4170
+ console.warn(`Could not find DevToolsInstance with id "${id}"`);
4171
+ return;
4172
+ }
4173
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
4174
+ // TODO: Handle VirtualInstance.
4175
+ return;
4176
+ }
4177
+ const fiber =
4178
+ findCurrentFiberUsingSlowPathByFiberInstance(devtoolsInstance);
4179
if (fiber !== null) {
4180
const instance = fiber.stateNode;
4181
4438
return null;
4439
}
4440
4284
- // Map of id and its force error status: true (error), false (toggled off),
4285
- // null (do nothing)
4286
- const forceErrorForFiberIDs = new Map<number | null, $FlowFixMe>();
4441
+ let forceErrorCount = 0;
4442
4288
- function shouldErrorFiberAccordingToMap(fiber: any) {
4443
+ function shouldErrorFiberAccordingToMap(fiber: any): null | boolean {
4444
if (typeof setErrorHandler !== 'function') {
4445
throw new Error(
4446
'Expected overrideError() to not get called for earlier React versions.',
4447
);
4448
}
4449
4295
- const id = getFiberIDUnsafe(fiber);
4296
- if (id === null) {
4450
+ let fiberInstance = fiberToFiberInstanceMap.get(fiber);
4451
+ if (fiberInstance === undefined && fiber.alternate !== null) {
4452
+ fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate);
4453
+ }
4454
+ if (fiberInstance === undefined) {
4455
return null;
4456
}
4457
4300
- let status = null;
4301
- if (forceErrorForFiberIDs.has(id)) {
4302
- status = forceErrorForFiberIDs.get(id);
4303
- if (status === false) {
4304
- // TRICKY overrideError adds entries to this Map,
4305
- // so ideally it would be the method that clears them too,
4306
- // but that would break the functionality of the feature,
4307
- // since DevTools needs to tell React to act differently than it normally would
4308
- // (don't just re-render the failed boundary, but reset its errored state too).
4309
- // So we can only clear it after telling React to reset the state.
4310
- // Technically this is premature and we should schedule it for later,
4311
- // since the render could always fail without committing the updated error boundary,
4312
- // but since this is a DEV-only feature, the simplicity is worth the trade off.
4313
- forceErrorForFiberIDs.delete(id);
4314
-
4315
- if (forceErrorForFiberIDs.size === 0) {
4316
- // Last override is gone. Switch React back to fast path.
4317
- setErrorHandler(shouldErrorFiberAlwaysNull);
4318
- }
4458
+ if (fiberInstance.flags & FORCE_ERROR_RESET) {
4459
+ // TRICKY overrideError adds entries to this Map,
4460
+ // so ideally it would be the method that clears them too,
4461
+ // but that would break the functionality of the feature,
4462
+ // since DevTools needs to tell React to act differently than it normally would
4463
+ // (don't just re-render the failed boundary, but reset its errored state too).
4464
+ // So we can only clear it after telling React to reset the state.
4465
+ // Technically this is premature and we should schedule it for later,
4466
+ // since the render could always fail without committing the updated error boundary,
4467
+ // but since this is a DEV-only feature, the simplicity is worth the trade off.
4468
+ forceErrorCount--;
4469
+ fiberInstance.flags &= ~FORCE_ERROR_RESET;
4470
+ if (forceErrorCount === 0) {
4471
+ // Last override is gone. Switch React back to fast path.
4472
+ setErrorHandler(shouldErrorFiberAlwaysNull);
4473
}
4474
+ return false;
4475
+ } else if (fiberInstance.flags & FORCE_ERROR) {
4476
+ return true;
4477
+ } else {
4478
+ return null;
4479
}
4321
- return status;
4480
}
4481
4482
function overrideError(id: number, forceError: boolean) {
4489
);
4490
}
4491
4334
- forceErrorForFiberIDs.set(id, forceError);
4335
-
4336
- if (forceErrorForFiberIDs.size === 1) {
4337
- // First override is added. Switch React to slower path.
4338
- setErrorHandler(shouldErrorFiberAccordingToMap);
4492
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4493
+ if (devtoolsInstance === undefined) {
4494
+ return;
4495
}
4496
+ if ((devtoolsInstance.flags & (FORCE_ERROR | FORCE_ERROR_RESET)) === 0) {
4497
+ forceErrorCount++;
4498
+ if (forceErrorCount === 1) {
4499
+ // First override is added. Switch React to slower path.
4500
+ setErrorHandler(shouldErrorFiberAccordingToMap);
4501
+ }
4502
+ }
4503
+ devtoolsInstance.flags &= forceError ? ~FORCE_ERROR_RESET : ~FORCE_ERROR;
4504
+ devtoolsInstance.flags |= forceError ? FORCE_ERROR : FORCE_ERROR_RESET;
4505
4341
- const fiber = idToArbitraryFiberMap.get(id);
4342
- if (fiber != null) {
4506
+ if (devtoolsInstance.kind === FIBER_INSTANCE) {
4507
+ const fiber = devtoolsInstance.data;
4508
scheduleUpdate(fiber);
4509
+ } else {
4510
+ // TODO: Handle VirtualInstance.
4511
}
4512
}
4513
4515
return false;
4516
}
4517
4351
- const forceFallbackForSuspenseIDs = new Set<number>();
4518
+ let forceFallbackCount = 0;
4519
4520
function shouldSuspendFiberAccordingToSet(fiber: any) {
4354
- const maybeID = getFiberIDUnsafe(((fiber: any): Fiber));
4355
- return maybeID !== null && forceFallbackForSuspenseIDs.has(maybeID);
4521
+ let fiberInstance = fiberToFiberInstanceMap.get(fiber);
4522
+ if (fiberInstance === undefined && fiber.alternate !== null) {
4523
+ fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate);
4524
+ }
4525
+ return (
4526
+ fiberInstance !== undefined &&
4527
+ (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) !== 0
4528
+ );
4529
}
4530
4531
function overrideSuspense(id: number, forceFallback: boolean) {
4537
'Expected overrideSuspense() to not get called for earlier React versions.',
4538
);
4539
}
4540
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4541
+ if (devtoolsInstance === undefined) {
4542
+ return;
4543
+ }
4544
+
4545
if (forceFallback) {
4368
- forceFallbackForSuspenseIDs.add(id);
4369
- if (forceFallbackForSuspenseIDs.size === 1) {
4370
- // First override is added. Switch React to slower path.
4371
- setSuspenseHandler(shouldSuspendFiberAccordingToSet);
4546
+ if ((devtoolsInstance.flags & FORCE_SUSPENSE_FALLBACK) === 0) {
4547
+ devtoolsInstance.flags |= FORCE_SUSPENSE_FALLBACK;
4548
+ forceFallbackCount++;
4549
+ if (forceFallbackCount === 1) {
4550
+ // First override is added. Switch React to slower path.
4551
+ setSuspenseHandler(shouldSuspendFiberAccordingToSet);
4552
+ }
4553
}
4554
} else {
4374
- forceFallbackForSuspenseIDs.delete(id);
4375
- if (forceFallbackForSuspenseIDs.size === 0) {
4376
- // Last override is gone. Switch React back to fast path.
4377
- setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
4555
+ if ((devtoolsInstance.flags & FORCE_SUSPENSE_FALLBACK) !== 0) {
4556
+ devtoolsInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
4557
+ forceFallbackCount--;
4558
+ if (forceFallbackCount === 0) {
4559
+ // Last override is gone. Switch React back to fast path.
4560
+ setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
4561
+ }
4562
}
4563
}
4380
- const fiber = idToArbitraryFiberMap.get(id);
4381
- if (fiber != null) {
4564
+
4565
+ if (devtoolsInstance.kind === FIBER_INSTANCE) {
4566
+ const fiber = devtoolsInstance.data;
4567
scheduleUpdate(fiber);
4568
+ } else {
4569
+ // TODO: Handle VirtualInstance.
4570
}
4571
}
4572
4744
// The return path will contain Fibers that are "invisible" to the store
4745
// because their keys and indexes are important to restoring the selection.
4746
function getPathForElement(id: number): Array<PathFrame> | null {
4560
- let fiber: ?Fiber = idToArbitraryFiberMap.get(id);
4561
- if (fiber == null) {
4747
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4748
+ if (devtoolsInstance === undefined) {
4749
+ return null;
4750
+ }
4751
+ if (devtoolsInstance.kind !== FIBER_INSTANCE) {
4752
+ // TODO: Handle VirtualInstance.
4753
return null;
4754
}
4755
+
4756
+ let fiber: null | Fiber = devtoolsInstance.data;
4757
const keyPath = [];
4758
while (fiber !== null) {
4759
// $FlowFixMe[incompatible-call] found when upgrading Flow
4816
}
4817
4818
function hasElementWithId(id: number): boolean {
4626
- return idToArbitraryFiberMap.has(id);
4819
+ return idToDevToolsInstanceMap.has(id);
4820
}
4821
4822
function getComponentStackForFiber(fiber: Fiber): string | null {
4630
- let componentStack = fiberToComponentStackMap.get(fiber);
4631
- if (componentStack == null) {
4632
- const dispatcherRef = getDispatcherRef(renderer);
4633
- if (dispatcherRef == null) {
4634
- return null;
4635
- }
4636
-
4637
- componentStack = getStackByFiberInDevAndProd(
4638
- ReactTypeOfWork,
4639
- fiber,
4640
- dispatcherRef,
4641
- );
4642
- fiberToComponentStackMap.set(fiber, componentStack);
4823
+ // TODO: This should really just take an DevToolsInstance directly.
4824
+ let fiberInstance = fiberToFiberInstanceMap.get(fiber);
4825
+ if (fiberInstance === undefined && fiber.alternate !== null) {
4826
+ fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate);
4827
+ }
4828
+ if (fiberInstance === undefined) {
4829
+ // We're no longer tracking this instance.
4830
+ return null;
4831
+ }
4832
+ if (fiberInstance.componentStack !== null) {
4833
+ // Cached entry.
4834
+ return fiberInstance.componentStack;
4835
+ }
4836
+ const dispatcherRef = getDispatcherRef(renderer);
4837
+ if (dispatcherRef == null) {
4838
+ return null;
4839
}
4840
4645
- return componentStack;
4841
+ return (fiberInstance.componentStack = getStackByFiberInDevAndProd(
4842
+ ReactTypeOfWork,
4843
+ fiber,
4844
+ dispatcherRef,
4845
+ ));
4846
}
4847
4848
function getSourceForFiber(fiber: Fiber): Source | null {