148
const VIRTUAL_INSTANCE = 1;
149
const FILTERED_FIBER_INSTANCE = 2;
150
151
-// Flags
152
-const FORCE_SUSPENSE_FALLBACK = /* */ 0b001;
153
-const FORCE_ERROR = /* */ 0b010;
154
-const FORCE_ERROR_RESET = /* */ 0b100;
155
-
151
// This type represents a stateful instance of a Client Component i.e. a Fiber pair.
152
// These instances also let us track stateful DevTools meta data like id and warnings.
153
type FiberInstance = {
156
parent: null | DevToolsInstance,
157
firstChild: null | DevToolsInstance,
158
nextSibling: null | DevToolsInstance,
164
- flags: number, // Force Error/Suspense
159
source: null | string | Error | Source, // source location of this component function, or owned child stack
160
errors: null | Map<string, number>, // error messages and count
161
warnings: null | Map<string, number>, // warning messages and count
170
parent: null,
171
firstChild: null,
172
nextSibling: null,
179
- flags: 0,
173
source: null,
174
errors: null,
175
warnings: null,
186
parent: null | DevToolsInstance,
187
firstChild: null | DevToolsInstance,
188
nextSibling: null | DevToolsInstance,
196
- flags: number, // Force Error/Suspense
189
source: null | string | Error | Source, // always null here.
190
errors: null, // error messages and count
191
warnings: null, // warning messages and count
201
parent: null,
202
firstChild: null,
203
nextSibling: null,
212
- flags: 0,
204
componentStack: null,
205
errors: null,
206
warnings: null,
220
parent: null | DevToolsInstance,
221
firstChild: null | DevToolsInstance,
222
nextSibling: null | DevToolsInstance,
232
- flags: number,
223
source: null | string | Error | Source, // source location of this server component, or owned child stack
224
// Errors and Warnings happen per ReactComponentInfo which can appear in
225
// multiple places but we track them per stateful VirtualInstance so
241
parent: null,
242
firstChild: null,
243
nextSibling: null,
254
- flags: 0,
244
source: null,
245
errors: null,
246
warnings: null,
1069
args: $ReadOnlyArray<any>,
1070
): void {
1071
if (type === 'error') {
1083
- let fiberInstance = fiberToFiberInstanceMap.get(fiber);
1084
- if (fiberInstance === undefined && fiber.alternate !== null) {
1085
- fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate);
1086
- }
1072
// if this is an error simulated by us to trigger error boundary, ignore
1088
- if (fiberInstance !== undefined && fiberInstance.flags & FORCE_ERROR) {
1073
+ if (
1074
+ forceErrorForFibers.get(fiber) === true ||
1075
+ (fiber.alternate !== null &&
1076
+ forceErrorForFibers.get(fiber.alternate) === true)
1077
+ ) {
1078
return;
1079
}
1080
}
1566
// Removes a Fiber (and its alternate) from the Maps used to track their id.
1567
// This method should always be called when a Fiber is unmounting.
1568
function untrackFiber(nearestInstance: DevToolsInstance, fiber: Fiber) {
1569
+ if (forceErrorForFibers.size > 0) {
1570
+ forceErrorForFibers.delete(fiber);
1571
+ if (fiber.alternate) {
1572
+ forceErrorForFibers.delete(fiber.alternate);
1573
+ }
1574
+ if (forceErrorForFibers.size === 0 && setErrorHandler != null) {
1575
+ setErrorHandler(shouldErrorFiberAlwaysNull);
1576
+ }
1577
+ }
1578
+
1579
+ if (forceFallbackForFibers.size > 0) {
1580
+ forceFallbackForFibers.delete(fiber);
1581
+ if (fiber.alternate) {
1582
+ forceFallbackForFibers.delete(fiber.alternate);
1583
+ }
1584
+ if (forceFallbackForFibers.size === 0 && setSuspenseHandler != null) {
1585
+ setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
1586
+ }
1587
+ }
1588
+
1589
// TODO: Consider using a WeakMap instead. The only thing where that doesn't work
1590
// is React Native Paper which tracks tags but that support is eventually going away
1591
// and can use the old findFiberByHostInstance strategy.
2474
fiberInstance.warnings = null;
2475
}
2476
2468
- if (fiberInstance.flags & FORCE_ERROR) {
2469
- fiberInstance.flags &= ~FORCE_ERROR;
2470
- forceErrorCount--;
2471
- if (forceErrorCount === 0 && setErrorHandler != null) {
2472
- setErrorHandler(shouldErrorFiberAlwaysNull);
2473
- }
2474
- }
2475
- if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) {
2476
- fiberInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
2477
- forceFallbackCount--;
2478
- if (forceFallbackCount === 0 && setSuspenseHandler != null) {
2479
- setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
2480
- }
2481
- }
2482
-
2477
if (fiberToFiberInstanceMap.get(fiber) === fiberInstance) {
2478
fiberToFiberInstanceMap.delete(fiber);
2479
}
4202
}
4203
}
4204
4211
- function getNearestErrorBoundaryID(fiber: Fiber): number | null {
4212
- let parent = fiber.return;
4213
- while (parent !== null) {
4214
- if (isErrorBoundary(parent)) {
4215
- // TODO: If this boundary is filtered it won't have an ID.
4216
- return getFiberIDUnsafe(parent);
4217
- }
4218
- parent = parent.return;
4219
- }
4220
- return null;
4221
- }
4222
-
4205
function inspectElementRaw(id: number): InspectedElement | null {
4206
const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4207
if (devtoolsInstance === undefined) {
4356
const owners: null | Array<SerializedElement> =
4357
getOwnersListFromInstance(fiberInstance);
4358
4377
- const isTimedOutSuspense =
4378
- tag === SuspenseComponent && memoizedState !== null;
4379
-
4359
let hooks = null;
4360
if (usesHooks) {
4361
const originalConsoleMethods: {[string]: $FlowFixMe} = {};
4385
4386
let rootType = null;
4387
let current = fiber;
4388
+ let hasErrorBoundary = false;
4389
+ let hasSuspenseBoundary = false;
4390
while (current.return !== null) {
4391
+ const temp = current;
4392
current = current.return;
4393
+ if (temp.tag === SuspenseComponent) {
4394
+ hasSuspenseBoundary = true;
4395
+ } else if (isErrorBoundary(temp)) {
4396
+ hasErrorBoundary = true;
4397
+ }
4398
}
4399
const fiberRoot = current.stateNode;
4400
if (fiberRoot != null && fiberRoot._debugRootType !== null) {
4401
rootType = fiberRoot._debugRootType;
4402
}
4403
4404
+ const isTimedOutSuspense =
4405
+ tag === SuspenseComponent && memoizedState !== null;
4406
+
4407
let isErrored = false;
4418
- let targetErrorBoundaryID;
4408
if (isErrorBoundary(fiber)) {
4409
// if the current inspected element is an error boundary,
4410
// either that we want to use it to toggle off error state
4417
const DidCapture = 0b000000000000000000010000000;
4418
isErrored =
4419
(fiber.flags & DidCapture) !== 0 ||
4431
- (fiberInstance.flags & FORCE_ERROR) !== 0;
4432
- targetErrorBoundaryID = isErrored
4433
- ? fiberInstance.id
4434
- : getNearestErrorBoundaryID(fiber);
4435
- } else {
4436
- targetErrorBoundaryID = getNearestErrorBoundaryID(fiber);
4420
+ forceErrorForFibers.get(fiber) === true ||
4421
+ (fiber.alternate !== null &&
4422
+ forceErrorForFibers.get(fiber.alternate) === true);
4423
}
4424
4425
const plugins: Plugins = {
4454
canEditFunctionPropsRenamePaths:
4455
typeof overridePropsRenamePath === 'function',
4456
4471
- canToggleError: supportsTogglingError && targetErrorBoundaryID != null,
4457
+ canToggleError: supportsTogglingError && hasErrorBoundary,
4458
// Is this error boundary in error state.
4459
isErrored,
4474
- targetErrorBoundaryID,
4460
4461
canToggleSuspense:
4462
supportsTogglingSuspense &&
4463
+ hasSuspenseBoundary &&
4464
// If it's showing the real content, we can always flip fallback.
4465
(!isTimedOutSuspense ||
4466
// If it's showing fallback because we previously forced it to,
4467
// allow toggling it back to remove the fallback override.
4482
- (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) !== 0),
4468
+ forceFallbackForFibers.has(fiber) ||
4469
+ (fiber.alternate !== null &&
4470
+ forceFallbackForFibers.has(fiber.alternate))),
4471
4472
// Can view component source location.
4473
canViewSource,
4521
getOwnersListFromInstance(virtualInstance);
4522
4523
let rootType = null;
4536
- let targetErrorBoundaryID = null;
4537
- let parent = virtualInstance.parent;
4538
- while (parent !== null) {
4539
- if (parent.kind !== VIRTUAL_INSTANCE) {
4540
- targetErrorBoundaryID = getNearestErrorBoundaryID(parent.data);
4541
- let current = parent.data;
4542
- while (current.return !== null) {
4543
- current = current.return;
4544
- }
4545
- const fiberRoot = current.stateNode;
4546
- if (fiberRoot != null && fiberRoot._debugRootType !== null) {
4547
- rootType = fiberRoot._debugRootType;
4524
+ let hasErrorBoundary = false;
4525
+ let hasSuspenseBoundary = false;
4526
+ const nearestFiber = getNearestFiber(virtualInstance);
4527
+ if (nearestFiber !== null) {
4528
+ let current = nearestFiber;
4529
+ while (current.return !== null) {
4530
+ const temp = current;
4531
+ current = current.return;
4532
+ if (temp.tag === SuspenseComponent) {
4533
+ hasSuspenseBoundary = true;
4534
+ } else if (isErrorBoundary(temp)) {
4535
+ hasErrorBoundary = true;
4536
}
4549
- break;
4537
}
4551
- parent = parent.parent;
4538
+ const fiberRoot = current.stateNode;
4539
+ if (fiberRoot != null && fiberRoot._debugRootType !== null) {
4540
+ rootType = fiberRoot._debugRootType;
4541
+ }
4542
}
4543
4544
const plugins: Plugins = {
4556
canEditFunctionPropsDeletePaths: false,
4557
canEditFunctionPropsRenamePaths: false,
4558
4569
- canToggleError: supportsTogglingError && targetErrorBoundaryID != null,
4559
+ canToggleError: supportsTogglingError && hasErrorBoundary,
4560
isErrored: false,
4571
- targetErrorBoundaryID,
4561
4573
- canToggleSuspense: supportsTogglingSuspense,
4562
+ canToggleSuspense: supportsTogglingSuspense && hasSuspenseBoundary,
4563
4564
// Can view component source location.
4565
canViewSource,
5395
);
5396
}
5397
5398
+ function getNearestFiber(devtoolsInstance: DevToolsInstance): null | Fiber {
5399
+ if (devtoolsInstance.kind === VIRTUAL_INSTANCE) {
5400
+ let inst: DevToolsInstance = devtoolsInstance;
5401
+ while (inst.kind === VIRTUAL_INSTANCE) {
5402
+ // For virtual instances, we search deeper until we find a Fiber instance.
5403
+ // Then we search upwards from that Fiber. That's because Virtual Instances
5404
+ // will always have an Fiber child filtered or not. If we searched its parents
5405
+ // we might skip through a filtered Error Boundary before we hit a FiberInstance.
5406
+ if (inst.firstChild === null) {
5407
+ return null;
5408
+ }
5409
+ inst = inst.firstChild;
5410
+ }
5411
+ return inst.data.return;
5412
+ } else {
5413
+ return devtoolsInstance.data;
5414
+ }
5415
+ }
5416
+
5417
// React will switch between these implementations depending on whether
5418
// we have any manually suspended/errored-out Fibers or not.
5419
function shouldErrorFiberAlwaysNull() {
5420
return null;
5421
}
5422
5415
- let forceErrorCount = 0;
5423
+ // Map of Fiber and its force error status: true (error), false (toggled off)
5424
+ const forceErrorForFibers = new Map<Fiber, boolean>();
5425
5417
- function shouldErrorFiberAccordingToMap(fiber: any): null | boolean {
5426
+ function shouldErrorFiberAccordingToMap(fiber: any): boolean {
5427
if (typeof setErrorHandler !== 'function') {
5428
throw new Error(
5429
'Expected overrideError() to not get called for earlier React versions.',
5430
);
5431
}
5432
5424
- let fiberInstance = fiberToFiberInstanceMap.get(fiber);
5425
- if (fiberInstance === undefined && fiber.alternate !== null) {
5426
- fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate);
5427
- }
5428
- if (fiberInstance === undefined) {
5429
- return null;
5430
- }
5431
-
5432
- if (fiberInstance.flags & FORCE_ERROR_RESET) {
5433
+ let status = forceErrorForFibers.get(fiber);
5434
+ if (status === false) {
5435
// TRICKY overrideError adds entries to this Map,
5436
// so ideally it would be the method that clears them too,
5437
// but that would break the functionality of the feature,
5441
// Technically this is premature and we should schedule it for later,
5442
// since the render could always fail without committing the updated error boundary,
5443
// but since this is a DEV-only feature, the simplicity is worth the trade off.
5442
- forceErrorCount--;
5443
- fiberInstance.flags &= ~FORCE_ERROR_RESET;
5444
- if (forceErrorCount === 0) {
5444
+ forceErrorForFibers.delete(fiber);
5445
+ if (forceErrorForFibers.size === 0) {
5446
// Last override is gone. Switch React back to fast path.
5447
setErrorHandler(shouldErrorFiberAlwaysNull);
5448
}
5449
return false;
5449
- } else if (fiberInstance.flags & FORCE_ERROR) {
5450
- return true;
5451
- } else {
5452
- return null;
5450
}
5451
+ if (status === undefined && fiber.alternate !== null) {
5452
+ status = forceErrorForFibers.get(fiber.alternate);
5453
+ if (status === false) {
5454
+ forceErrorForFibers.delete(fiber.alternate);
5455
+ if (forceErrorForFibers.size === 0) {
5456
+ // Last override is gone. Switch React back to fast path.
5457
+ setErrorHandler(shouldErrorFiberAlwaysNull);
5458
+ }
5459
+ }
5460
+ }
5461
+ if (status === undefined) {
5462
+ return false;
5463
+ }
5464
+ return status;
5465
}
5466
5467
function overrideError(id: number, forceError: boolean) {
5478
if (devtoolsInstance === undefined) {
5479
return;
5480
}
5470
- if ((devtoolsInstance.flags & (FORCE_ERROR | FORCE_ERROR_RESET)) === 0) {
5471
- forceErrorCount++;
5472
- if (forceErrorCount === 1) {
5473
- // First override is added. Switch React to slower path.
5474
- setErrorHandler(shouldErrorFiberAccordingToMap);
5481
+ const nearestFiber = getNearestFiber(devtoolsInstance);
5482
+ if (nearestFiber === null) {
5483
+ return;
5484
+ }
5485
+ let fiber = nearestFiber;
5486
+ while (!isErrorBoundary(fiber)) {
5487
+ if (fiber.return === null) {
5488
+ return;
5489
}
5490
+ fiber = fiber.return;
5491
}
5477
- devtoolsInstance.flags &= forceError ? ~FORCE_ERROR_RESET : ~FORCE_ERROR;
5478
- devtoolsInstance.flags |= forceError ? FORCE_ERROR : FORCE_ERROR_RESET;
5479
-
5480
- if (devtoolsInstance.kind === FIBER_INSTANCE) {
5481
- const fiber = devtoolsInstance.data;
5482
- scheduleUpdate(fiber);
5483
- } else {
5484
- // TODO: Handle VirtualInstance.
5492
+ forceErrorForFibers.set(fiber, forceError);
5493
+ if (fiber.alternate !== null) {
5494
+ // We only need one of the Fibers in the set.
5495
+ forceErrorForFibers.delete(fiber.alternate);
5496
+ }
5497
+ if (forceErrorForFibers.size === 1) {
5498
+ // First override is added. Switch React to slower path.
5499
+ setErrorHandler(shouldErrorFiberAccordingToMap);
5500
}
5501
+ scheduleUpdate(fiber);
5502
}
5503
5504
function shouldSuspendFiberAlwaysFalse() {
5505
return false;
5506
}
5507
5492
- let forceFallbackCount = 0;
5508
+ const forceFallbackForFibers = new Set<Fiber>();
5509
5494
- function shouldSuspendFiberAccordingToSet(fiber: any) {
5495
- let fiberInstance = fiberToFiberInstanceMap.get(fiber);
5496
- if (fiberInstance === undefined && fiber.alternate !== null) {
5497
- fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate);
5498
- }
5510
+ function shouldSuspendFiberAccordingToSet(fiber: Fiber): boolean {
5511
return (
5500
- fiberInstance !== undefined &&
5501
- (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) !== 0
5512
+ forceFallbackForFibers.has(fiber) ||
5513
+ (fiber.alternate !== null && forceFallbackForFibers.has(fiber.alternate))
5514
);
5515
}
5516
5527
if (devtoolsInstance === undefined) {
5528
return;
5529
}
5530
+ const nearestFiber = getNearestFiber(devtoolsInstance);
5531
+ if (nearestFiber === null) {
5532
+ return;
5533
+ }
5534
+ let fiber = nearestFiber;
5535
+ while (fiber.tag !== SuspenseComponent) {
5536
+ if (fiber.return === null) {
5537
+ return;
5538
+ }
5539
+ fiber = fiber.return;
5540
+ }
5541
5542
+ if (fiber.alternate !== null) {
5543
+ // We only need one of the Fibers in the set.
5544
+ forceFallbackForFibers.delete(fiber.alternate);
5545
+ }
5546
if (forceFallback) {
5520
- if ((devtoolsInstance.flags & FORCE_SUSPENSE_FALLBACK) === 0) {
5521
- devtoolsInstance.flags |= FORCE_SUSPENSE_FALLBACK;
5522
- forceFallbackCount++;
5523
- if (forceFallbackCount === 1) {
5524
- // First override is added. Switch React to slower path.
5525
- setSuspenseHandler(shouldSuspendFiberAccordingToSet);
5526
- }
5547
+ forceFallbackForFibers.add(fiber);
5548
+ if (forceFallbackForFibers.size === 1) {
5549
+ // First override is added. Switch React to slower path.
5550
+ setSuspenseHandler(shouldSuspendFiberAccordingToSet);
5551
}
5552
} else {
5529
- if ((devtoolsInstance.flags & FORCE_SUSPENSE_FALLBACK) !== 0) {
5530
- devtoolsInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
5531
- forceFallbackCount--;
5532
- if (forceFallbackCount === 0) {
5533
- // Last override is gone. Switch React back to fast path.
5534
- setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
5535
- }
5553
+ forceFallbackForFibers.delete(fiber);
5554
+ if (forceFallbackForFibers.size === 0) {
5555
+ // Last override is gone. Switch React back to fast path.
5556
+ setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
5557
}
5558
}
5538
-
5539
- if (devtoolsInstance.kind === FIBER_INSTANCE) {
5540
- const fiber = devtoolsInstance.data;
5541
- scheduleUpdate(fiber);
5542
- } else {
5543
- // TODO: Handle VirtualInstance.
5544
- }
5559
+ scheduleUpdate(fiber);
5560
}
5561
5562
// Remember if we're trying to restore the selection after reload.