Skip to content

Commit de1a0b2

Browse files
committed
[DevTools] Activity slices in Suspense tab
1 parent 40c7a7f commit de1a0b2

File tree

13 files changed

+479
-41
lines changed

13 files changed

+479
-41
lines changed

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
ComponentFilterHOC,
2727
ComponentFilterLocation,
2828
ComponentFilterEnvironmentName,
29+
ComponentFilterActivitySlice,
2930
ElementTypeClass,
3031
ElementTypeContext,
3132
ElementTypeFunction,
@@ -1433,16 +1434,24 @@ export function attach(
14331434
const hideElementsWithPaths: Set<RegExp> = new Set();
14341435
const hideElementsWithTypes: Set<ElementType> = new Set();
14351436
const hideElementsWithEnvs: Set<string> = new Set();
1437+
let activitySliceID: null | FiberInstance['id'] = null;
1438+
let activitySlice: null | Fiber = null;
1439+
let isInActivitySlice: boolean = true;
14361440
14371441
// Highlight updates
14381442
let traceUpdatesEnabled: boolean = false;
14391443
const traceUpdatesForNodes: Set<HostInstance> = new Set();
14401444
1441-
function applyComponentFilters(componentFilters: Array<ComponentFilter>) {
1445+
function applyComponentFilters(
1446+
componentFilters: Array<ComponentFilter>,
1447+
nextActivitySlice: null | Fiber = null,
1448+
) {
14421449
hideElementsWithTypes.clear();
14431450
hideElementsWithDisplayNames.clear();
14441451
hideElementsWithPaths.clear();
14451452
hideElementsWithEnvs.clear();
1453+
activitySliceID = null;
1454+
activitySlice = null;
14461455
14471456
componentFilters.forEach(componentFilter => {
14481457
if (!componentFilter.isEnabled) {
@@ -1471,6 +1480,16 @@ export function attach(
14711480
case ComponentFilterEnvironmentName:
14721481
hideElementsWithEnvs.add(componentFilter.value);
14731482
break;
1483+
case ComponentFilterActivitySlice:
1484+
activitySlice = nextActivitySlice;
1485+
if (activitySlice === null) {
1486+
// We're not filtering by activity slice after all. Consider everything
1487+
// to be in the slice.
1488+
isInActivitySlice = true;
1489+
} else {
1490+
isInActivitySlice = false;
1491+
}
1492+
break;
14741493
default:
14751494
console.warn(
14761495
`Invalid component filter type "${componentFilter.type}"`,
@@ -1510,6 +1529,20 @@ export function attach(
15101529
throw Error('Cannot modify filter preferences while profiling');
15111530
}
15121531
1532+
// The ID will be based on the old tree. We need to find the Fiber based on
1533+
// that ID before we unmount everything. We set the activity slice ID once
1534+
// we mount it again.
1535+
let nextActivitySlice: null | Fiber = null;
1536+
for (let i = 0; i < componentFilters.length; i++) {
1537+
const filter = componentFilters[i];
1538+
if (filter.type === ComponentFilterActivitySlice && filter.isEnabled) {
1539+
const instance = idToDevToolsInstanceMap.get(filter.activityID);
1540+
if (instance !== undefined && instance.kind === FIBER_INSTANCE) {
1541+
nextActivitySlice = instance.data;
1542+
}
1543+
}
1544+
}
1545+
15131546
// Recursively unmount all roots.
15141547
hook.getFiberRoots(rendererID).forEach(root => {
15151548
const rootInstance = rootToFiberInstanceMap.get(root);
@@ -1525,7 +1558,7 @@ export function attach(
15251558
currentRoot = (null: any);
15261559
});
15271560
1528-
applyComponentFilters(componentFilters);
1561+
applyComponentFilters(componentFilters, nextActivitySlice);
15291562
15301563
// Reset pseudo counters so that new path selections will be persisted.
15311564
rootDisplayNameCounter.clear();
@@ -1614,6 +1647,11 @@ export function attach(
16141647
function shouldFilterFiber(fiber: Fiber): boolean {
16151648
const {tag, type, key} = fiber;
16161649
1650+
// It is never valid to filter the root element.
1651+
if (tag !== HostRoot && !isInActivitySlice) {
1652+
return true;
1653+
}
1654+
16171655
switch (tag) {
16181656
case DehydratedSuspenseComponent:
16191657
// TODO: ideally we would show dehydrated Suspense immediately.
@@ -3920,11 +3958,21 @@ export function attach(
39203958
fiber: Fiber,
39213959
traceNearestHostComponentUpdate: boolean,
39223960
): void {
3961+
const isActivitySliceEntry =
3962+
activitySlice !== null &&
3963+
(fiber === activitySlice || fiber.alternate === activitySlice);
3964+
if (isActivitySliceEntry) {
3965+
isInActivitySlice = true;
3966+
}
3967+
39233968
const shouldIncludeInTree = !shouldFilterFiber(fiber);
39243969
let newInstance = null;
39253970
let newSuspenseNode = null;
39263971
if (shouldIncludeInTree) {
39273972
newInstance = recordMount(fiber, reconcilingParent);
3973+
if (isActivitySliceEntry) {
3974+
activitySliceID = newInstance.id;
3975+
}
39283976
if (fiber.tag === SuspenseComponent || fiber.tag === HostRoot) {
39293977
newSuspenseNode = createSuspenseNode(newInstance);
39303978
// Measure this Suspense node. In general we shouldn't do this until we have
@@ -4040,6 +4088,7 @@ export function attach(
40404088
const stashedSuspenseParent = reconcilingParentSuspenseNode;
40414089
const stashedSuspensePrevious = previouslyReconciledSiblingSuspenseNode;
40424090
const stashedSuspenseRemaining = remainingReconcilingChildrenSuspenseNodes;
4091+
const stashedIsInActivitySlice = isInActivitySlice;
40434092
if (newInstance !== null) {
40444093
// Push a new DevTools instance parent while reconciling this subtree.
40454094
reconcilingParent = newInstance;
@@ -4053,6 +4102,13 @@ export function attach(
40534102
remainingReconcilingChildrenSuspenseNodes = null;
40544103
shouldPopSuspenseNode = true;
40554104
}
4105+
if (
4106+
!isActivitySliceEntry &&
4107+
activitySlice !== null &&
4108+
fiber.tag === ActivityComponent
4109+
) {
4110+
isInActivitySlice = false;
4111+
}
40564112
try {
40574113
if (traceUpdatesEnabled) {
40584114
if (traceNearestHostComponentUpdate) {
@@ -4180,6 +4236,7 @@ export function attach(
41804236
}
41814237
}
41824238
} finally {
4239+
isInActivitySlice = stashedIsInActivitySlice;
41834240
if (newInstance !== null) {
41844241
reconcilingParent = stashedParent;
41854242
previouslyReconciledSibling = stashedPrevious;
@@ -4211,6 +4268,7 @@ export function attach(
42114268
const stashedSuspenseParent = reconcilingParentSuspenseNode;
42124269
const stashedSuspensePrevious = previouslyReconciledSiblingSuspenseNode;
42134270
const stashedSuspenseRemaining = remainingReconcilingChildrenSuspenseNodes;
4271+
const stashedIsInActivitySlice = isInActivitySlice;
42144272
const previousSuspendedBy = instance.suspendedBy;
42154273
// Push a new DevTools instance parent while reconciling this subtree.
42164274
reconcilingParent = instance;
@@ -4229,6 +4287,18 @@ export function attach(
42294287
shouldPopSuspenseNode = true;
42304288
}
42314289
4290+
if (activitySlice !== null) {
4291+
if (instance.id === activitySliceID) {
4292+
isInActivitySlice = true;
4293+
} else if (
4294+
instance.kind === FIBER_INSTANCE &&
4295+
instance.data !== null &&
4296+
instance.data.tag === ActivityComponent
4297+
) {
4298+
isInActivitySlice = false;
4299+
}
4300+
}
4301+
42324302
try {
42334303
// Unmount the remaining set.
42344304
if (
@@ -4279,6 +4349,7 @@ export function attach(
42794349
previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
42804350
remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
42814351
}
4352+
isInActivitySlice = stashedIsInActivitySlice;
42824353
}
42834354
if (instance.kind === FIBER_INSTANCE) {
42844355
recordUnmount(instance);
@@ -4959,6 +5030,7 @@ export function attach(
49595030
const stashedSuspenseParent = reconcilingParentSuspenseNode;
49605031
const stashedSuspensePrevious = previouslyReconciledSiblingSuspenseNode;
49615032
const stashedSuspenseRemaining = remainingReconcilingChildrenSuspenseNodes;
5033+
const stashedIsInActivitySlice = isInActivitySlice;
49625034
let updateFlags = NoUpdate;
49635035
let shouldMeasureSuspenseNode = false;
49645036
let shouldPopSuspenseNode = false;
@@ -4998,6 +5070,15 @@ export function attach(
49985070
shouldMeasureSuspenseNode = true;
49995071
shouldPopSuspenseNode = true;
50005072
}
5073+
5074+
if (activitySlice !== null) {
5075+
if (fiberInstance.id === activitySliceID) {
5076+
isInActivitySlice = true;
5077+
} else if (nextFiber.tag === ActivityComponent) {
5078+
// Reached the next Activity so we're exiting the slice.
5079+
isInActivitySlice = false;
5080+
}
5081+
}
50015082
}
50025083
try {
50035084
trackDebugInfoFromLazyType(nextFiber);
@@ -5422,6 +5503,7 @@ export function attach(
54225503
previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
54235504
remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
54245505
}
5506+
isInActivitySlice = stashedIsInActivitySlice;
54255507
}
54265508
}
54275509
}

packages/react-devtools-shared/src/devtools/store.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
SUSPENSE_TREE_OPERATION_RESIZE,
2828
SUSPENSE_TREE_OPERATION_SUSPENDERS,
2929
} from '../constants';
30-
import {ElementTypeRoot} from '../frontend/types';
30+
import {ElementTypeActivity, ElementTypeRoot} from '../frontend/types';
3131
import {
3232
getSavedComponentFilters,
3333
setSavedComponentFilters,

packages/react-devtools-shared/src/devtools/views/Components/Element.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import * as React from 'react';
1111
import {Fragment, useContext, useMemo, useState} from 'react';
1212
import Store from 'react-devtools-shared/src/devtools/store';
13+
import {ElementTypeActivity} from 'react-devtools-shared/src/frontend/types';
1314
import ButtonIcon from '../ButtonIcon';
1415
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
1516
import {StoreContext} from '../context';
@@ -25,6 +26,7 @@ import styles from './Element.css';
2526
import Icon from '../Icon';
2627
import {useChangeOwnerAction} from './OwnersListContext';
2728
import Tooltip from './reach-ui/tooltip';
29+
import {useChangeActivitySliceAction} from '../SuspenseTab/ActivityList';
2830

2931
type Props = {
3032
data: ItemData,
@@ -66,6 +68,8 @@ export default function Element({data, index, style}: Props): React.Node {
6668

6769
const changeOwnerAction = useChangeOwnerAction();
6870

71+
const [, changeActivitySliceAction] = useChangeActivitySliceAction();
72+
6973
// Handle elements that are removed from the tree while an async render is in progress.
7074
if (element == null) {
7175
console.warn(`<Element> Could not find element at index ${index}`);
@@ -75,8 +79,10 @@ export default function Element({data, index, style}: Props): React.Node {
7579
}
7680

7781
const handleDoubleClick = () => {
78-
if (id !== null) {
79-
changeOwnerAction(id);
82+
if (element.type === ElementTypeActivity) {
83+
changeActivitySliceAction(element.id);
84+
} else {
85+
changeOwnerAction(element.id);
8086
}
8187
};
8288

packages/react-devtools-shared/src/devtools/views/Components/Tree.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ import ButtonIcon from '../ButtonIcon';
3737
import Button from '../Button';
3838
import {logEvent} from 'react-devtools-shared/src/Logger';
3939
import {useExtensionComponentsPanelVisibility} from 'react-devtools-shared/src/frontend/hooks/useExtensionComponentsPanelVisibility';
40+
import {ElementTypeActivity} from 'react-devtools-shared/src/frontend/types';
4041
import {useChangeOwnerAction} from './OwnersListContext';
42+
import {useChangeActivitySliceAction} from '../SuspenseTab/ActivityList';
4143

4244
// Indent for each node at level N, compared to node at level N - 1.
4345
const INDENTATION_SIZE = 10;
@@ -302,14 +304,23 @@ export default function Tree(): React.Node {
302304
const handleBlur = useCallback(() => setTreeFocused(false), []);
303305
const handleFocus = useCallback(() => setTreeFocused(true), []);
304306

307+
const [, changeActivitySliceAction] = useChangeActivitySliceAction();
305308
const changeOwnerAction = useChangeOwnerAction();
306309
const handleKeyPress = useCallback(
307310
(event: $FlowFixMe) => {
308311
switch (event.key) {
309312
case 'Enter':
310313
case ' ':
311314
if (inspectedElementID !== null) {
312-
changeOwnerAction(inspectedElementID);
315+
const inspectedElement = store.getElementByID(inspectedElementID);
316+
if (
317+
inspectedElement !== null &&
318+
inspectedElement.type === ElementTypeActivity
319+
) {
320+
changeActivitySliceAction(inspectedElementID);
321+
} else {
322+
changeOwnerAction(inspectedElementID);
323+
}
313324
}
314325
break;
315326
default:

packages/react-devtools-shared/src/devtools/views/Settings/ComponentsSettings.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
ComponentFilterHOC,
3030
ComponentFilterLocation,
3131
ComponentFilterEnvironmentName,
32+
ComponentFilterActivitySlice,
3233
ElementTypeClass,
3334
ElementTypeContext,
3435
ElementTypeFunction,
@@ -171,6 +172,8 @@ export default function ComponentsSettings({
171172
isValid: true,
172173
value: 'Client',
173174
};
175+
} else if (type === ComponentFilterActivitySlice) {
176+
// TODO: Allow changing type
174177
}
175178
}
176179
return cloned;
@@ -371,6 +374,9 @@ export default function ComponentsSettings({
371374
: styles.InvalidRegExp
372375
}
373376
isChecked={componentFilter.isEnabled}
377+
isDisabled={
378+
componentFilter.type === ComponentFilterActivitySlice
379+
}
374380
onChange={isEnabled =>
375381
toggleFilterIsEnabled(componentFilter, isEnabled)
376382
}
@@ -392,6 +398,9 @@ export default function ComponentsSettings({
392398
</td>
393399
<td className={styles.TableCell}>
394400
<select
401+
disabled={
402+
componentFilter.type === ComponentFilterActivitySlice
403+
}
395404
value={componentFilter.type}
396405
onChange={({currentTarget}) =>
397406
changeFilterType(
@@ -413,6 +422,7 @@ export default function ComponentsSettings({
413422
environment
414423
</option>
415424
)}
425+
<option value={ComponentFilterActivitySlice}>activity</option>
416426
</select>
417427
</td>
418428
<td className={styles.TableCell}>
@@ -487,6 +497,9 @@ export default function ComponentsSettings({
487497
))}
488498
</select>
489499
)}
500+
{componentFilter.type === ComponentFilterActivitySlice && (
501+
<span>Activity Slice</span>
502+
)}
490503
</td>
491504
<td className={styles.TableCell}>
492505
<Button
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
.ActivityList {
2+
cursor: default;
3+
list-style-type: none;
4+
margin: 0;
5+
padding: 0;
6+
}
7+
8+
.ActivityList[data-pending-activity-slice-selection="true"] {
9+
cursor: wait;
10+
}
11+
12+
.ActivityList:focus {
13+
outline: none;
14+
}
15+
16+
.ActivityListItem {
17+
color: var(--color-component-name);
18+
padding: 0 0.25rem;
19+
user-select: none;
20+
}
21+
22+
.ActivityListItem:hover {
23+
background-color: var(--color-background-hover);
24+
}
25+
26+
.ActivityListItem[aria-selected="true"] {
27+
background-color: var(--color-background-inactive);
28+
}
29+
30+
.ActivityList:focus .ActivityListItem[aria-selected="true"] {
31+
background-color: var(--color-background-selected);
32+
color: var(--color-text-selected);
33+
34+
/* Invert colors */
35+
--color-component-name: var(--color-component-name-inverted);
36+
--color-text: var(--color-text-selected);
37+
--color-component-badge-background: var(
38+
--color-component-badge-background-inverted
39+
);
40+
--color-forget-badge-background: var(--color-forget-badge-background-inverted);
41+
--color-component-badge-count: var(--color-component-badge-count-inverted);
42+
--color-attribute-name: var(--color-attribute-name-inverted);
43+
--color-attribute-value: var(--color-attribute-value-inverted);
44+
--color-expand-collapse-toggle: var(--color-component-name-inverted);
45+
}

0 commit comments

Comments
 (0)