From c92803edc8a3c3137759b392da0ff851acb6ea6a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 5 Apr 2021 16:57:36 -0400 Subject: [PATCH] Show which Fibers scheduled an update in DevTools This builds on top of a recently-added React feature, enableUpdaterTracking. --- .../ownersListContext-test.js.snap | 12 + .../__snapshots__/profilingCache-test.js.snap | 435 +++++++++++++++++- .../src/__tests__/profilingUtils-test.js | 2 +- .../src/backend/legacy/renderer.js | 5 +- .../src/backend/renderer.js | 46 +- .../src/backend/types.js | 10 +- .../react-devtools-shared/src/constants.js | 2 +- .../views/Components/InspectedElementView.js | 4 +- .../views/Components/OwnersListContext.js | 14 +- .../devtools/views/Components/OwnersStack.js | 16 +- .../src/devtools/views/Components/types.js | 7 +- .../devtools/views/Profiler/Schedulers.css | 25 + .../src/devtools/views/Profiler/Schedulers.js | 55 +++ .../views/Profiler/SidebarCommitInfo.css | 5 + .../views/Profiler/SidebarCommitInfo.js | 21 +- .../src/devtools/views/Profiler/types.js | 7 +- .../src/devtools/views/Profiler/utils.js | 26 +- 17 files changed, 637 insertions(+), 55 deletions(-) create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/Schedulers.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/Schedulers.js diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/ownersListContext-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/ownersListContext-test.js.snap index d5e244adf062f..a92d85a6e624b 100644 --- a/packages/react-devtools-shared/src/__tests__/__snapshots__/ownersListContext-test.js.snap +++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/ownersListContext-test.js.snap @@ -13,18 +13,21 @@ Array [ "displayName": "Grandparent", "hocDisplayNames": null, "id": 2, + "key": null, "type": 5, }, Object { "displayName": "Parent", "hocDisplayNames": null, "id": 3, + "key": null, "type": 5, }, Object { "displayName": "Child", "hocDisplayNames": null, "id": 4, + "key": null, "type": 5, }, ] @@ -44,18 +47,21 @@ Array [ "displayName": "Grandparent", "hocDisplayNames": null, "id": 2, + "key": null, "type": 5, }, Object { "displayName": "Parent", "hocDisplayNames": null, "id": 3, + "key": null, "type": 5, }, Object { "displayName": "Child", "hocDisplayNames": null, "id": 4, + "key": null, "type": 5, }, ] @@ -67,12 +73,14 @@ Array [ "displayName": "Grandparent", "hocDisplayNames": null, "id": 2, + "key": null, "type": 5, }, Object { "displayName": "Parent", "hocDisplayNames": null, "id": 3, + "key": null, "type": 5, }, ] @@ -84,6 +92,7 @@ Array [ "displayName": "Grandparent", "hocDisplayNames": null, "id": 2, + "key": null, "type": 5, }, Object { @@ -92,6 +101,7 @@ Array [ "Memo", ], "id": 3, + "key": null, "type": 8, }, Object { @@ -100,6 +110,7 @@ Array [ "ForwardRef", ], "id": 4, + "key": null, "type": 6, }, ] @@ -116,6 +127,7 @@ Array [ "displayName": "Grandparent", "hocDisplayNames": null, "id": 2, + "key": null, "type": 5, }, ] diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap index c9e7f9770159f..9b94c71e6ed12 100644 --- a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap +++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap @@ -43,6 +43,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 16, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -82,6 +91,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 15, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -110,6 +128,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 18, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -165,6 +192,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 12, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -222,6 +258,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 25, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -261,6 +306,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 35, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -291,6 +345,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 45, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -392,6 +455,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 12, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -488,6 +560,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 25, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -548,6 +629,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 35, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -590,6 +680,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 45, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Parent", @@ -736,7 +835,7 @@ Object { "snapshots": Array [], }, ], - "version": 4, + "version": 5, } `; @@ -855,6 +954,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 11, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -933,6 +1041,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 22, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -1029,6 +1146,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 35, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Parent", @@ -1170,7 +1296,7 @@ Object { "snapshots": Array [], }, ], - "version": 4, + "version": 5, } `; @@ -1230,6 +1356,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 13, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Map { @@ -1266,6 +1401,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 34, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Map { @@ -1293,6 +1437,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 44, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Parent", @@ -1470,6 +1623,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 24, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 11, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Parent", @@ -1554,6 +1716,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 34, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 6, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Parent", @@ -1723,6 +1894,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 13, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -1783,6 +1963,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 34, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -1825,6 +2014,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 44, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Parent", @@ -2062,6 +2260,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 24, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 11, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Parent", @@ -2143,6 +2350,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 34, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 6, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Parent", @@ -2235,7 +2451,7 @@ Object { ], }, ], - "version": 4, + "version": 5, } `; @@ -2258,6 +2474,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Suspense", @@ -2327,6 +2552,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -2359,6 +2593,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -2385,6 +2628,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Component", + "hocDisplayNames": null, + "id": 3, + "key": null, + "type": 5, + }, + ], } `; @@ -2411,6 +2663,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Component", + "hocDisplayNames": null, + "id": 3, + "key": null, + "type": 5, + }, + ], } `; @@ -2441,6 +2702,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -2496,6 +2766,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -2546,6 +2825,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -2578,6 +2866,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Component", + "hocDisplayNames": null, + "id": 3, + "key": null, + "type": 5, + }, + ], }, Object { "changeDescriptions": Array [ @@ -2610,6 +2907,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Component", + "hocDisplayNames": null, + "id": 3, + "key": null, + "type": 5, + }, + ], }, Object { "changeDescriptions": Array [ @@ -2658,6 +2964,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Component", @@ -2747,7 +3062,7 @@ Object { "snapshots": Array [], }, ], - "version": 4, + "version": 5, } `; @@ -2814,6 +3129,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -2886,6 +3210,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "LegacyContextProvider", + "hocDisplayNames": null, + "id": 2, + "key": null, + "type": 1, + }, + ], } `; @@ -2954,6 +3287,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -3023,6 +3365,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -3091,6 +3442,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], } `; @@ -3218,6 +3578,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -3338,6 +3707,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "LegacyContextProvider", + "hocDisplayNames": null, + "id": 2, + "key": null, + "type": 1, + }, + ], }, Object { "changeDescriptions": Array [ @@ -3460,6 +3838,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -3583,6 +3970,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -3705,6 +4101,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 0, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "LegacyContextProvider", @@ -3917,7 +4322,7 @@ Object { "snapshots": Array [], }, ], - "version": 4, + "version": 5, } `; @@ -4020,6 +4425,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Normal", "timestamp": 11, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, Object { "changeDescriptions": Array [ @@ -4100,6 +4514,15 @@ Object { "passiveEffectDuration": null, "priorityLevel": "Immediate", "timestamp": 22, + "updaters": Array [ + Object { + "displayName": "Anonymous", + "hocDisplayNames": null, + "id": 1, + "key": null, + "type": 11, + }, + ], }, ], "displayName": "Parent", @@ -4237,6 +4660,6 @@ Object { "snapshots": Array [], }, ], - "version": 4, + "version": 5, } `; diff --git a/packages/react-devtools-shared/src/__tests__/profilingUtils-test.js b/packages/react-devtools-shared/src/__tests__/profilingUtils-test.js index 5cf07cf472eeb..2c9268da2adbf 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingUtils-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingUtils-test.js @@ -22,6 +22,6 @@ describe('profiling utils', () => { dataForRoots: [], }: any), ), - ).toThrow('Unsupported profiler export version "0"'); + ).toThrow('Unsupported profile export version'); }); }); diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index 08cbfe340fb6f..723ff106b3192 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -51,7 +51,7 @@ import type { ComponentFilter, ElementType, } from 'react-devtools-shared/src/types'; -import type {Owner, InspectedElement} from '../types'; +import type {InspectedElement, SerializedElement} from '../types'; export type InternalInstance = Object; type LegacyRenderer = Object; @@ -767,6 +767,7 @@ export function attach( owners.push({ displayName: getData(owner).displayName || 'Unknown', id: getID(owner), + key: element.key, type: getElementType(owner), }); if (owner._currentElement) { @@ -1044,7 +1045,7 @@ export function attach( // Not implemented. } - function getOwnersList(id: number): Array | null { + function getOwnersList(id: number): Array | null { // Not implemented. return null; } diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 050a4e709ab32..dc9e6e2a12da7 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -80,6 +80,7 @@ import { } from './ReactSymbols'; import {format} from './utils'; import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags'; +import is from 'shared/objectIs'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type { @@ -90,13 +91,13 @@ import type { InspectedElementPayload, InstanceAndStyle, NativeType, - Owner, PathFrame, PathMatch, ProfilingDataBackend, ProfilingDataForRootBackend, ReactRenderer, RendererInterface, + SerializedElement, WorkTagMap, } from './types'; import type {Interaction} from 'react-devtools-shared/src/devtools/views/Profiler/types'; @@ -104,7 +105,6 @@ import type { ComponentFilter, ElementType, } from 'react-devtools-shared/src/types'; -import is from 'shared/objectIs'; type getDisplayNameForFiberType = (fiber: Fiber) => string | null; type getTypeSymbolType = (type: any) => Symbol | number; @@ -2186,6 +2186,7 @@ export function attach( ), maxActualDuration: 0, priorityLevel: null, + updaters: getUpdatersList(root), effectDuration, passiveEffectDuration, }; @@ -2198,6 +2199,12 @@ export function attach( } } + function getUpdatersList(root): Array | null { + return root.memoizedUpdaters != null + ? Array.from(root.memoizedUpdaters).map(fiberToSerializedElement) + : null; + } + function handleCommitFiberUnmount(fiber) { // This is not recursive. // We can't traverse fibers after unmounting so instead @@ -2258,6 +2265,7 @@ export function attach( maxActualDuration: 0, priorityLevel: priorityLevel == null ? null : formatPriorityLevel(priorityLevel), + updaters: getUpdatersList(root), effectDuration, passiveEffectDuration, }; @@ -2652,7 +2660,16 @@ export function attach( } } - function getOwnersList(id: number): Array | null { + function fiberToSerializedElement(fiber: Fiber): SerializedElement { + return { + displayName: getDisplayNameForFiber(fiber) || 'Anonymous', + id: getFiberID(getPrimaryFiber(fiber)), + key: fiber.key, + type: getElementTypeForFiber(fiber), + }; + } + + function getOwnersList(id: number): Array | null { const fiber = findCurrentFiberUsingSlowPathById(id); if (fiber == null) { return null; @@ -2660,22 +2677,12 @@ export function attach( const {_debugOwner} = fiber; - const owners = [ - { - displayName: getDisplayNameForFiber(fiber) || 'Anonymous', - id, - type: getElementTypeForFiber(fiber), - }, - ]; + const owners: Array = [fiberToSerializedElement(fiber)]; if (_debugOwner) { let owner = _debugOwner; while (owner !== null) { - owners.unshift({ - displayName: getDisplayNameForFiber(owner) || 'Anonymous', - id: getFiberID(getPrimaryFiber(owner)), - type: getElementTypeForFiber(owner), - }); + owners.unshift(fiberToSerializedElement(owner)); owner = owner._debugOwner || null; } } @@ -2806,11 +2813,7 @@ export function attach( owners = []; let owner = _debugOwner; while (owner !== null) { - owners.push({ - displayName: getDisplayNameForFiber(owner) || 'Anonymous', - id: getFiberID(getPrimaryFiber(owner)), - type: getElementTypeForFiber(owner), - }); + owners.push(fiberToSerializedElement(owner)); owner = owner._debugOwner || null; } } @@ -3395,6 +3398,7 @@ export function attach( maxActualDuration: number, passiveEffectDuration: number | null, priorityLevel: string | null, + updaters: Array | null, |}; type CommitProfilingMetadataMap = Map>; @@ -3453,6 +3457,7 @@ export function attach( passiveEffectDuration, priorityLevel, commitTime, + updaters, } = commitProfilingData; const interactionIDs: Array = []; @@ -3493,6 +3498,7 @@ export function attach( passiveEffectDuration, priorityLevel, timestamp: commitTime, + updaters, }); }); diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index c0ef91021f073..6a07e5b789210 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -168,6 +168,7 @@ export type CommitDataBackend = {| passiveEffectDuration: number | null, priorityLevel: string | null, timestamp: number, + updaters: Array | null, |}; export type ProfilingDataForRootBackend = {| @@ -199,15 +200,16 @@ export type PathMatch = {| isFullMatch: boolean, |}; -export type Owner = {| +export type SerializedElement = {| displayName: string | null, id: number, + key: number | string | null, type: ElementType, |}; export type OwnersList = {| id: number, - owners: Array | null, + owners: Array | null, |}; export type InspectedElement = {| @@ -244,7 +246,7 @@ export type InspectedElement = {| warnings: Array<[string, number]>, // List of owners - owners: Array | null, + owners: Array | null, // Location of component in source code. source: Source | null, @@ -322,7 +324,7 @@ export type RendererInterface = { getDisplayNameForFiberID: GetDisplayNameForFiberID, getInstanceAndStyle(id: number): InstanceAndStyle, getProfilingData(): ProfilingDataBackend, - getOwnersList: (id: number) => Array | null, + getOwnersList: (id: number) => Array | null, getPathForElement: (id: number) => Array | null, handleCommitFiberRoot: (fiber: Object, commitPriority?: number) => void, handleCommitFiberUnmount: (fiber: Object) => void, diff --git a/packages/react-devtools-shared/src/constants.js b/packages/react-devtools-shared/src/constants.js index f8868111e095d..bae23abc01223 100644 --- a/packages/react-devtools-shared/src/constants.js +++ b/packages/react-devtools-shared/src/constants.js @@ -41,7 +41,7 @@ export const LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY = export const LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY = 'React::DevTools::traceUpdatesEnabled'; -export const PROFILER_EXPORT_VERSION = 4; +export const PROFILER_EXPORT_VERSION = 5; export const CHANGE_LOG_URL = 'https://github.com/facebook/react/blob/master/packages/react-devtools/CHANGELOG.md'; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js index 4d8662d80dae5..e7b4165910926 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js @@ -35,7 +35,7 @@ import { import styles from './InspectedElementView.css'; import type {ContextMenuContextType} from '../context'; -import type {Element, InspectedElement, Owner} from './types'; +import type {Element, InspectedElement, SerializedElement} from './types'; import type {ElementType} from 'react-devtools-shared/src/types'; export type CopyPath = (path: Array) => void; @@ -127,7 +127,7 @@ export default function InspectedElementView({
rendered by
{showOwnersList && - ((owners: any): Array).map(owner => ( + ((owners: any): Array).map(owner => ( Array | null; +type Context = (id: number) => Array | null; const OwnersListContext = createContext(((null: any): Context)); OwnersListContext.displayName = 'OwnersListContext'; -type ResolveFn = (ownersList: Array | null) => void; +type ResolveFn = (ownersList: Array | null) => void; type InProgressRequest = {| - promise: Thenable>, + promise: Thenable>, resolveFn: ResolveFn, |}; const inProgressRequests: WeakMap = new WeakMap(); -const resource: Resource> = createResource( +const resource: Resource< + Element, + Element, + Array, +> = createResource( (element: Element) => { const request = inProgressRequests.get(element); if (request != null) { diff --git a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js index 8e106e8f044fa..d7efafee6c522 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js @@ -27,16 +27,16 @@ import {TreeDispatcherContext, TreeStateContext} from './TreeContext'; import {useIsOverflowing} from '../hooks'; import {StoreContext} from '../context'; -import type {Owner} from './types'; +import type {SerializedElement} from './types'; import styles from './OwnersStack.css'; -type SelectOwner = (owner: Owner | null) => void; +type SelectOwner = (owner: SerializedElement | null) => void; type ACTION_UPDATE_OWNER_ID = {| type: 'UPDATE_OWNER_ID', ownerID: number | null, - owners: Array, + owners: Array, |}; type ACTION_UPDATE_SELECTED_INDEX = {| type: 'UPDATE_SELECTED_INDEX', @@ -47,7 +47,7 @@ type Action = ACTION_UPDATE_OWNER_ID | ACTION_UPDATE_SELECTED_INDEX; type State = {| ownerID: number | null, - owners: Array, + owners: Array, selectedIndex: number, |}; @@ -104,7 +104,7 @@ export default function OwnerStack() { const {owners, selectedIndex} = state; const selectOwner = useCallback( - (owner: Owner | null) => { + (owner: SerializedElement | null) => { if (owner !== null) { const index = owners.indexOf(owner); dispatch({ @@ -197,7 +197,7 @@ export default function OwnerStack() { } type ElementsDropdownProps = { - owners: Array, + owners: Array, selectedIndex: number, selectOwner: SelectOwner, ... @@ -245,7 +245,7 @@ function ElementsDropdown({ type ElementViewProps = { isSelected: boolean, - owner: Owner, + owner: SerializedElement, selectOwner: SelectOwner, ... }; @@ -278,7 +278,7 @@ function ElementView({isSelected, owner, selectOwner}: ElementViewProps) { } type BackToOwnerButtonProps = {| - owners: Array, + owners: Array, selectedIndex: number, selectOwner: SelectOwner, |}; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/types.js b/packages/react-devtools-shared/src/devtools/views/Components/types.js index 11eb544c3f789..e09a251392bfe 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/types.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/types.js @@ -44,16 +44,17 @@ export type Element = {| weight: number, |}; -export type Owner = {| +export type SerializedElement = {| displayName: string | null, id: number, + key: number | string | null, hocDisplayNames: Array | null, type: ElementType, |}; export type OwnersList = {| id: number, - owners: Array | null, + owners: Array | null, |}; export type InspectedElementResponseType = @@ -94,7 +95,7 @@ export type InspectedElement = {| warnings: Array<[string, number]>, // List of owners - owners: Array | null, + owners: Array | null, // Location of component in source code. source: Source | null, diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Schedulers.css b/packages/react-devtools-shared/src/devtools/views/Profiler/Schedulers.css new file mode 100644 index 0000000000000..18d7f7f8fad09 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Schedulers.css @@ -0,0 +1,25 @@ +.Updaters { + margin: 0 0 0.5rem; +} + +.NoUpdaters, +.Updater, +.UnmountedUpdater { + display: block; + width: 100%; + text-align: left; + background: none; + border: none; + padding: 0.25rem 0.5rem; + color: var(--color-text); +} +.Updater:focus, +.Updater:hover { + outline: none; + background-color: var(--color-background-hover); +} + +.NoUpdaters, +.UnmountedUpdater { + color: var(--color-dim); +} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Schedulers.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Schedulers.js new file mode 100644 index 0000000000000..52fb5f0c0d23f --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Schedulers.js @@ -0,0 +1,55 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {CommitTree} from './types'; +import type {SerializedElement} from '../Components/types'; + +import * as React from 'react'; +import {useContext} from 'react'; +import {ProfilerContext} from './ProfilerContext'; +import styles from './Schedulers.css'; + +export type Props = {| + commitTree: CommitTree, + updaters: Array, +|}; + +export default function Schedulers({commitTree, updaters}: Props) { + const {selectFiber} = useContext(ProfilerContext); + + const children = + updaters.length > 0 ? ( + updaters.map((serializedElement: SerializedElement) => { + const {displayName, id, key} = serializedElement; + const isVisibleInTree = commitTree.nodes.has(id); + if (isVisibleInTree) { + return ( + + ); + } else { + return ( +
+ {displayName} {key ? `key="${key}"` : ''} +
+ ); + } + }) + ) : ( +
+ (unknown) +
+ ); + + return
{children}
; +} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.css b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.css index 34917846eb329..d82f10b3d73a5 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.css +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.css @@ -30,6 +30,7 @@ .Interactions { margin: 0 0 0.5rem; } +.NoInteractions, .Interaction { display: block; width: 100%; @@ -45,6 +46,10 @@ background-color: var(--color-background-hover); } +.NoInteractions { + color: var(--color-dim); +} + .Label { overflow: hidden; text-overflow: ellipsis; diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.js index ed913984cc7a0..33242470829e7 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.js @@ -10,8 +10,10 @@ import * as React from 'react'; import {Fragment, useContext} from 'react'; import {ProfilerContext} from './ProfilerContext'; +import Schedulers from './Schedulers'; import {formatDuration, formatTime} from './utils'; import {StoreContext} from '../context'; +import {getCommitTree} from './CommitTreeBuilder'; import styles from './SidebarCommitInfo.css'; @@ -39,6 +41,7 @@ export default function SidebarCommitInfo(_: Props) { passiveEffectDuration, priorityLevel, timestamp, + updaters, } = profilerStore.getCommitData(rootID, selectedCommitIndex); const viewInteraction = interactionID => { @@ -49,6 +52,15 @@ export default function SidebarCommitInfo(_: Props) { const hasCommitPhaseDurations = effectDuration !== null || passiveEffectDuration !== null; + const commitTree = + updaters !== null + ? getCommitTree({ + commitIndex: selectedCommitIndex, + profilerStore, + rootID, + }) + : null; + return (
Commit information
@@ -102,11 +114,18 @@ export default function SidebarCommitInfo(_: Props) { )} + {updaters !== null && commitTree !== null && ( +
  • + ? + +
  • + )} +
  • :
    {interactionIDs.length === 0 ? ( -
    None
    +
    (none)
    ) : null} {interactionIDs.map(interactionID => { const interaction = interactions.get(interactionID); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/types.js b/packages/react-devtools-shared/src/devtools/views/Profiler/types.js index 11e99c05b1bec..90c56b3529be4 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/types.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/types.js @@ -8,6 +8,7 @@ */ import type {ElementType} from 'react-devtools-shared/src/types'; +import type {SerializedElement} from '../Components/types'; export type CommitTreeNode = {| id: number, @@ -80,6 +81,9 @@ export type CommitDataFrontend = {| // When did this commit occur (relative to the start of profiling) timestamp: number, + + // Fiber(s) responsible for scheduling this update. + updaters: Array | null, |}; export type ProfilingDataForRootFrontend = {| @@ -131,6 +135,7 @@ export type CommitDataExport = {| passiveEffectDuration: number | null, priorityLevel: string | null, timestamp: number, + updaters: Array | null, |}; export type ProfilingDataForRootExport = {| @@ -148,6 +153,6 @@ export type ProfilingDataForRootExport = {| // Serializable version of ProfilingDataFrontend data. export type ProfilingDataExport = {| - version: 4, + version: 5, dataForRoots: Array, |}; diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js b/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js index c667276cfafac..55f7ecb5c1452 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js @@ -8,6 +8,7 @@ */ import {PROFILER_EXPORT_VERSION} from 'react-devtools-shared/src/constants'; +import {separateDisplayNameAndHOCs} from 'react-devtools-shared/src/utils'; import type {ProfilingDataBackend} from 'react-devtools-shared/src/backend/types'; import type { @@ -84,6 +85,23 @@ export function prepareProfilingDataFrontendFromBackendAndStore( passiveEffectDuration: commitDataBackend.passiveEffectDuration, priorityLevel: commitDataBackend.priorityLevel, timestamp: commitDataBackend.timestamp, + updaters: + commitDataBackend.updaters !== null + ? commitDataBackend.updaters.map(serializedElement => { + const [ + serializedElementDisplayName, + serializedElementHocDisplayNames, + ] = separateDisplayNameAndHOCs( + serializedElement.displayName, + serializedElement.type, + ); + return { + ...serializedElement, + displayName: serializedElementDisplayName, + hocDisplayNames: serializedElementHocDisplayNames, + }; + }) + : null, }), ); @@ -111,7 +129,9 @@ export function prepareProfilingDataFrontendFromExport( const {version} = profilingDataExport; if (version !== PROFILER_EXPORT_VERSION) { - throw Error(`Unsupported profiler export version "${version}"`); + throw Error( + `Unsupported profile export version "${version}". Supported version is "${PROFILER_EXPORT_VERSION}".`, + ); } const dataForRoots: Map = new Map(); @@ -138,6 +158,7 @@ export function prepareProfilingDataFrontendFromExport( passiveEffectDuration, priorityLevel, timestamp, + updaters, }) => ({ changeDescriptions: changeDescriptions != null ? new Map(changeDescriptions) : null, @@ -149,6 +170,7 @@ export function prepareProfilingDataFrontendFromExport( passiveEffectDuration, priorityLevel, timestamp, + updaters, }), ), displayName, @@ -193,6 +215,7 @@ export function prepareProfilingDataExport( passiveEffectDuration, priorityLevel, timestamp, + updaters, }) => ({ changeDescriptions: changeDescriptions != null @@ -206,6 +229,7 @@ export function prepareProfilingDataExport( passiveEffectDuration, priorityLevel, timestamp, + updaters, }), ), displayName,