From 564a2233685d52fceccfd2f4a515a654ec6f9879 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 9 May 2019 11:47:22 -0700 Subject: [PATCH] Fetch owners list from renderer (using suspense) Owners in the list may have been filtered out of the Store, but in the owners list view- it's important to still show them. The frontend cannot do this on its own, so this list needs to come from the renderer interface. --- src/backend/agent.js | 22 +- src/backend/renderer.js | 46 ++-- src/backend/types.js | 6 +- src/devtools/views/Components/Components.js | 29 +-- src/devtools/views/Components/Element.js | 4 +- .../views/Components/OwnersListContext.js | 109 +++++++++ src/devtools/views/Components/OwnersStack.css | 5 + src/devtools/views/Components/OwnersStack.js | 212 +++++++++++++----- .../views/Components/SelectedElement.js | 4 +- src/devtools/views/Components/Tree.css | 11 + src/devtools/views/Components/Tree.js | 20 +- src/devtools/views/Components/TreeContext.js | 113 +++------- src/devtools/views/Components/types.js | 5 + 13 files changed, 404 insertions(+), 182 deletions(-) create mode 100644 src/devtools/views/Components/OwnersListContext.js diff --git a/src/backend/agent.js b/src/backend/agent.js index 1a12eee29a..0c8fd77f09 100644 --- a/src/backend/agent.js +++ b/src/backend/agent.js @@ -16,6 +16,7 @@ import type { RendererID, RendererInterface, } from './types'; +import type { OwnersList } from 'src/devtools/views/Components/types'; import type { Bridge, ComponentFilter } from '../types'; const debug = (methodName, ...args) => { @@ -29,7 +30,7 @@ const debug = (methodName, ...args) => { } }; -type InspectSelectParams = {| +type ElementAndRendererID = {| id: number, rendererID: number, |}; @@ -99,6 +100,7 @@ export default class Agent extends EventEmitter { bridge.addListener('getProfilingStatus', this.getProfilingStatus); bridge.addListener('getProfilingSummary', this.getProfilingSummary); bridge.addListener('highlightElementInDOM', this.highlightElementInDOM); + bridge.addListener('getOwnersList', this.getOwnersList); bridge.addListener('inspectElement', this.inspectElement); bridge.addListener('logElementToConsole', this.logElementToConsole); bridge.addListener('overrideContext', this.overrideContext); @@ -303,7 +305,17 @@ export default class Agent extends EventEmitter { } }; - inspectElement = ({ id, rendererID }: InspectSelectParams) => { + getOwnersList = ({ id, rendererID }: ElementAndRendererID) => { + const renderer = this._rendererInterfaces[rendererID]; + if (renderer == null) { + console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`); + } else { + const owners = renderer.getOwnersList(id); + this._bridge.send('ownersList', ({ id, owners }: OwnersList)); + } + }; + + inspectElement = ({ id, rendererID }: ElementAndRendererID) => { const renderer = this._rendererInterfaces[rendererID]; if (renderer == null) { console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`); @@ -312,7 +324,7 @@ export default class Agent extends EventEmitter { } }; - logElementToConsole = ({ id, rendererID }: InspectSelectParams) => { + logElementToConsole = ({ id, rendererID }: ElementAndRendererID) => { const renderer = this._rendererInterfaces[rendererID]; if (renderer == null) { console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`); @@ -340,7 +352,7 @@ export default class Agent extends EventEmitter { this._bridge.send('screenshotCaptured', { commitIndex, dataURL }); }; - selectElement = ({ id, rendererID }: InspectSelectParams) => { + selectElement = ({ id, rendererID }: ElementAndRendererID) => { const renderer = this._rendererInterfaces[rendererID]; if (renderer == null) { console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`); @@ -506,7 +518,7 @@ export default class Agent extends EventEmitter { } }; - viewElementSource = ({ id, rendererID }: InspectSelectParams) => { + viewElementSource = ({ id, rendererID }: ElementAndRendererID) => { const renderer = this._rendererInterfaces[rendererID]; if (renderer == null) { console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`); diff --git a/src/backend/renderer.js b/src/backend/renderer.js index c05c1dee05..cc49385c8f 100644 --- a/src/backend/renderer.js +++ b/src/backend/renderer.js @@ -50,7 +50,10 @@ import type { ReactRenderer, RendererInterface, } from './types'; -import type { InspectedElement } from 'src/devtools/views/Components/types'; +import type { + InspectedElement, + Owner, +} from 'src/devtools/views/Components/types'; import type { ComponentFilter, ElementType } from 'src/types'; function getInternalReactConstants(version) { @@ -1685,6 +1688,30 @@ export function attach( } } + function getOwnersList(id: number): Array | null { + let fiber = findCurrentFiberUsingSlowPathById(id); + if (fiber == null) { + return null; + } + + const { _debugOwner } = fiber; + + let owners = null; + if (_debugOwner) { + owners = []; + let owner = _debugOwner; + while (owner !== null) { + owners.push({ + displayName: getDisplayNameForFiber(owner) || 'Unknown', + id: getFiberID(getPrimaryFiber(owner)), + }); + owner = owner._debugOwner || null; + } + } + + return owners; + } + function inspectElementRaw(id: number): InspectedElement | null { let fiber = findCurrentFiberUsingSlowPathById(id); if (fiber == null) { @@ -1692,7 +1719,6 @@ export function attach( } const { - _debugOwner, _debugSource, stateNode, memoizedProps, @@ -1764,19 +1790,6 @@ export function attach( context = { value: context }; } - let owners = null; - if (_debugOwner) { - owners = []; - let owner = _debugOwner; - while (owner !== null) { - owners.push({ - displayName: getDisplayNameForFiber(owner) || 'Unknown', - id: getFiberID(getPrimaryFiber(owner)), - }); - owner = owner._debugOwner || null; - } - } - const isTimedOutSuspense = tag === SuspenseComponent && memoizedState !== null; @@ -1812,7 +1825,7 @@ export function attach( state: usesHooks ? null : memoizedState, // List of owners - owners, + owners: getOwnersList(id), // Location of component in source coude. source: _debugSource, @@ -2385,6 +2398,7 @@ export function attach( getFiberCommits, getInteractions, findNativeByFiberID, + getOwnersList, getPathForElement, getProfilingDataForDownload, getProfilingSummary, diff --git a/src/backend/types.js b/src/backend/types.js index 7cd529528b..a9680cd78a 100644 --- a/src/backend/types.js +++ b/src/backend/types.js @@ -1,7 +1,10 @@ // @flow import type { ComponentFilter, ElementType } from 'src/types'; -import type { InspectedElement } from 'src/devtools/views/Components/types'; +import type { + InspectedElement, + Owner, +} from 'src/devtools/views/Components/types'; type BundleType = | 0 // PROD @@ -175,6 +178,7 @@ export type RendererInterface = { ) => number | null, getFiberCommits: (rootID: number, fiberID: number) => FiberCommitsBackend, getInteractions: (rootID: number) => InteractionsBackend, + getOwnersList: (id: number) => Array | null, getProfilingDataForDownload: (rootID: number) => Object, getProfilingSummary: (rootID: number) => ProfilingSummaryBackend, getPathForElement: (id: number) => Array | null, diff --git a/src/devtools/views/Components/Components.js b/src/devtools/views/Components/Components.js index dd4b775431..3ed317a101 100644 --- a/src/devtools/views/Components/Components.js +++ b/src/devtools/views/Components/Components.js @@ -4,6 +4,7 @@ import React, { Suspense } from 'react'; import Tree from './Tree'; import SelectedElement from './SelectedElement'; import { InspectedElementContextController } from './InspectedElementContext'; +import { OwnersListContextController } from './OwnersListContext'; import portaledContent from '../portaledContent'; import { ModalDialog } from '../ModalDialog'; @@ -12,19 +13,21 @@ import styles from './Components.css'; function Components(_: {||}) { // TODO Flex wrappers below should be user resizable. return ( -
-
- -
-
- - }> - - - -
- -
+ + +
+
+ +
+
+ }> + + +
+ +
+
+
); } diff --git a/src/devtools/views/Components/Element.js b/src/devtools/views/Components/Element.js index 322726784d..79b015e85c 100644 --- a/src/devtools/views/Components/Element.js +++ b/src/devtools/views/Components/Element.js @@ -29,7 +29,7 @@ type Props = { export default function ElementView({ data, index, style }: Props) { const store = useContext(StoreContext); - const { ownerFlatTree, ownerStack, selectedElementID } = useContext( + const { ownerFlatTree, ownerID, selectedElementID } = useContext( TreeStateContext ); const dispatch = useContext(TreeDispatcherContext); @@ -168,7 +168,7 @@ export default function ElementView({ data, index, style }: Props) { }} > - {ownerStack.length === 0 ? ( + {ownerID === null ? ( ) : null} diff --git a/src/devtools/views/Components/OwnersListContext.js b/src/devtools/views/Components/OwnersListContext.js new file mode 100644 index 0000000000..3720f3b2cc --- /dev/null +++ b/src/devtools/views/Components/OwnersListContext.js @@ -0,0 +1,109 @@ +// @flow + +import React, { + createContext, + useCallback, + useContext, + useEffect, +} from 'react'; +import { createResource } from '../../cache'; +import { BridgeContext, StoreContext } from '../context'; +import { TreeStateContext } from './TreeContext'; + +import type { + Element, + Owner, + OwnersList, +} from 'src/devtools/views/Components/types'; +import type { Resource, Thenable } from '../../cache'; + +type Context = (id: number) => Array | null; + +const OwnersListContext = createContext(((null: any): Context)); +OwnersListContext.displayName = 'OwnersListContext'; + +type ResolveFn = (ownersList: Array | null) => void; +type InProgressRequest = {| + promise: Thenable>, + resolveFn: ResolveFn, +|}; + +const inProgressRequests: WeakMap = new WeakMap(); +const resource: Resource> = createResource( + (element: Element) => { + let request = inProgressRequests.get(element); + if (request != null) { + return request.promise; + } + + let resolveFn = ((null: any): ResolveFn); + const promise = new Promise(resolve => { + resolveFn = resolve; + }); + + inProgressRequests.set(element, { promise, resolveFn }); + + return promise; + }, + (element: Element) => element, + { useWeakMap: true } +); + +type Props = {| + children: React$Node, +|}; + +function OwnersListContextController({ children }: Props) { + const bridge = useContext(BridgeContext); + const store = useContext(StoreContext); + const { ownerID } = useContext(TreeStateContext); + + const read = useCallback( + (id: number) => { + const element = store.getElementByID(id); + if (element !== null) { + return resource.read(element); + } else { + return null; + } + }, + [store] + ); + + useEffect(() => { + const onOwnersList = (ownersList: OwnersList) => { + const id = ownersList.id; + + const element = store.getElementByID(id); + if (element !== null) { + const request = inProgressRequests.get(element); + if (request != null) { + inProgressRequests.delete(element); + request.resolveFn(ownersList.owners); + } + } + }; + + bridge.addListener('ownersList', onOwnersList); + return () => bridge.removeListener('ownersList', onOwnersList); + }, [bridge, store]); + + // This effect requests an updated owners list any time the selected owner changes + useEffect(() => { + if (ownerID !== null) { + const rendererID = store.getRendererIDForElement(ownerID); + + bridge.send('getOwnersList', { id: ownerID, rendererID }); + } + + return () => {}; + }, [bridge, ownerID, store]); + + return ( + + {children} + + ); +} + +export { OwnersListContext, OwnersListContextController }; diff --git a/src/devtools/views/Components/OwnersStack.css b/src/devtools/views/Components/OwnersStack.css index 1aba50714a..48c9db0fc4 100644 --- a/src/devtools/views/Components/OwnersStack.css +++ b/src/devtools/views/Components/OwnersStack.css @@ -91,3 +91,8 @@ font-family: var(--font-family-monospace); font-size: var(--font-size-monospace-normal); } + +.NotInStore, +.NotInStore:hover { + color: var(--color-dimmest); +} diff --git a/src/devtools/views/Components/OwnersStack.js b/src/devtools/views/Components/OwnersStack.js index 9635045d33..77ec8eed60 100644 --- a/src/devtools/views/Components/OwnersStack.js +++ b/src/devtools/views/Components/OwnersStack.js @@ -4,6 +4,7 @@ import React, { useCallback, useContext, useLayoutEffect, + useReducer, useRef, useState, } from 'react'; @@ -12,22 +13,111 @@ import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button'; import Button from '../Button'; import ButtonIcon from '../ButtonIcon'; import Toggle from '../Toggle'; +import { OwnersListContext } from './OwnersListContext'; import { TreeDispatcherContext, TreeStateContext } from './TreeContext'; -import { StoreContext } from '../context'; import { useIsOverflowing } from '../hooks'; +import { StoreContext } from '../context'; -import type { Element } from './types'; +import type { Owner } from './types'; import styles from './OwnersStack.css'; +type SelectOwner = (owner: Owner | null) => void; + +type ACTION_UPDATE_OWNER_ID = {| + type: 'UPDATE_OWNER_ID', + ownerID: number | null, + owners: Array, +|}; +type ACTION_UPDATE_SELECTED_INDEX = {| + type: 'UPDATE_SELECTED_INDEX', + selectedIndex: number, +|}; + +type Action = ACTION_UPDATE_OWNER_ID | ACTION_UPDATE_SELECTED_INDEX; + +type State = {| + ownerID: number | null, + owners: Array, + selectedIndex: number, +|}; + +function dialogReducer(state, action) { + switch (action.type) { + case 'UPDATE_OWNER_ID': + const selectedIndex = state.owners.findIndex( + owner => owner.id === action.ownerID + ); + return { + ownerID: action.ownerID, + owners: action.owners, + selectedIndex, + }; + case 'UPDATE_SELECTED_INDEX': + return { + ...state, + selectedIndex: action.selectedIndex, + }; + default: + throw new Error(`Invalid action "${action.type}"`); + } +} + export default function OwnerStack() { - const { ownerStack, ownerStackIndex } = useContext(TreeStateContext); - const dispatch = useContext(TreeDispatcherContext); + const read = useContext(OwnersListContext); + const { ownerID } = useContext(TreeStateContext); + const treeDispatch = useContext(TreeDispatcherContext); + + const [state, dispatch] = useReducer(dialogReducer, { + ownerID: null, + owners: [], + selectedIndex: -1, + }); + + // TODO (owners) Explain this and use reducer with ownerID too to avoid inf. loop + if (ownerID === null) { + dispatch({ + type: 'UPDATE_OWNER_ID', + ownerID: null, + owners: [], + }); + } else if (ownerID !== state.ownerID) { + const isInList = state.owners.findIndex(owner => owner.id === ownerID) >= 0; + dispatch({ + type: 'UPDATE_OWNER_ID', + ownerID, + owners: isInList ? state.owners : read(ownerID) || [], + }); + } + + const { owners, selectedIndex } = state; + + const selectOwner = useCallback( + (owner: Owner | null) => { + if (owner !== null) { + const index = owners.indexOf(owner); + dispatch({ + type: 'UPDATE_SELECTED_INDEX', + selectedIndex: index >= 0 ? index : 0, + }); + treeDispatch({ type: 'SELECT_OWNER', payload: owner.id }); + } else { + dispatch({ + type: 'UPDATE_SELECTED_INDEX', + selectedIndex: 0, + }); + treeDispatch({ type: 'RESET_OWNER_STACK' }); + } + }, + [owners, treeDispatch] + ); const [elementsTotalWidth, setElementsTotalWidth] = useState(0); const elementsBarRef = useRef(null); const isOverflowing = useIsOverflowing(elementsBarRef, elementsTotalWidth); + const selectedOwner = owners[selectedIndex]; + useLayoutEffect(() => { // If we're already overflowing, then we don't need to re-measure items. // That's because once the owners stack is open, it can only get larger (by driling in). @@ -37,7 +127,7 @@ export default function OwnerStack() { } let elementsTotalWidth = 0; - for (let i = 0; i < ownerStack.length; i++) { + for (let i = 0; i < owners.length; i++) { const element = elementsBarRef.current.children[i]; const computedStyle = getComputedStyle(element); @@ -48,7 +138,7 @@ export default function OwnerStack() { } setElementsTotalWidth(elementsTotalWidth); - }, [elementsBarRef, isOverflowing, ownerStack.length]); + }, [elementsBarRef, isOverflowing, owners.length]); return (
@@ -56,28 +146,38 @@ export default function OwnerStack() { {isOverflowing && ( - + {selectedOwner != null && ( + + )} )} {!isOverflowing && - ownerStack.map((id, index) => ( - + owners.map((owner, index) => ( + ))}
diff --git a/src/devtools/views/Components/SelectedElement.js b/src/devtools/views/Components/SelectedElement.js index 90da16ee48..4ead00425a 100644 --- a/src/devtools/views/Components/SelectedElement.js +++ b/src/devtools/views/Components/SelectedElement.js @@ -227,7 +227,7 @@ function InspectedElementView({ state, } = inspectedElement; - const { ownerStack } = useContext(TreeStateContext); + const { ownerID } = useContext(TreeStateContext); const bridge = useContext(BridgeContext); const store = useContext(StoreContext); @@ -298,7 +298,7 @@ function InspectedElementView({ overrideValueFn={overrideContextFn} /> - {ownerStack.length === 0 && owners !== null && owners.length > 0 && ( + {ownerID === null && owners !== null && owners.length > 0 && (
rendered by
{owners.map(owner => ( diff --git a/src/devtools/views/Components/Tree.css b/src/devtools/views/Components/Tree.css index 3ce44f0828..3efc1bf863 100644 --- a/src/devtools/views/Components/Tree.css +++ b/src/devtools/views/Components/Tree.css @@ -37,3 +37,14 @@ margin: 0 0.5rem; background-color: var(--color-border); } + +.Loading { + height: 100%; + padding-left: 0.5rem; + display: flex; + align-items: center; + flex: 1; + justify-content: flex-start; + font-size: var(--font-size-sans-large); + color: var(--color-dim); +} diff --git a/src/devtools/views/Components/Tree.js b/src/devtools/views/Components/Tree.js index 8c3c4031f7..4687c79939 100644 --- a/src/devtools/views/Components/Tree.js +++ b/src/devtools/views/Components/Tree.js @@ -1,6 +1,7 @@ // @flow import React, { + Suspense, useState, useCallback, useContext, @@ -38,7 +39,7 @@ export default function Tree(props: Props) { const dispatch = useContext(TreeDispatcherContext); const { numElements, - ownerStack, + ownerID, searchIndex, searchResults, selectedElementID, @@ -277,7 +278,9 @@ export default function Tree(props: Props) {
- {ownerStack.length > 0 ? : } + }> + {ownerID !== null ? : } +
@@ -317,7 +320,7 @@ export default function Tree(props: Props) { } function InnerElementType({ style, ...rest }) { - const { ownerStack } = useContext(TreeStateContext); + const { ownerID } = useContext(TreeStateContext); // The list may need to scroll horizontally due to deeply nested elements. // We don't know the maximum scroll width up front, because we're windowing. @@ -346,10 +349,9 @@ function InnerElementType({ style, ...rest }) { // We shouldn't retain this width across different conceptual trees though, // so when the user opens the "owners tree" view, we should discard the previous width. - const hasOwnerStack = ownerStack.length > 0; - const [prevHasOwnerStack, setPrevHasOwnerStack] = useState(hasOwnerStack); - if (hasOwnerStack !== prevHasOwnerStack) { - setPrevHasOwnerStack(hasOwnerStack); + const [prevOwnerID, setPrevOwnerID] = useState(ownerID); + if (ownerID !== prevOwnerID) { + setPrevOwnerID(ownerID); setMinWidth(null); } @@ -371,3 +373,7 @@ function InnerElementType({ style, ...rest }) { /> ); } + +function Loading() { + return
Loading...
; +} diff --git a/src/devtools/views/Components/TreeContext.js b/src/devtools/views/Components/TreeContext.js index 002f6b619e..b2c9b29ac4 100644 --- a/src/devtools/views/Components/TreeContext.js +++ b/src/devtools/views/Components/TreeContext.js @@ -50,9 +50,8 @@ type StateContext = {| searchText: string, // Owners + ownerID: number | null, ownerFlatTree: Array | null, - ownerStack: Array, - ownerStackIndex: number | null, // Inspection element panel inspectedElementID: number | null, @@ -142,8 +141,7 @@ type State = {| searchText: string, // Owners - ownerStack: Array, - ownerStackIndex: number | null, + ownerID: number | null, ownerFlatTree: Array | null, // Inspection element panel @@ -151,17 +149,12 @@ type State = {| |}; function reduceTreeState(store: Store, state: State, action: Action): State { - let { - numElements, - ownerStack, - selectedElementIndex, - selectedElementID, - } = state; + let { numElements, ownerID, selectedElementIndex, selectedElementID } = state; let lookupIDForIndex = true; // Base tree should ignore selected element changes when the owner's tree is active. - if (ownerStack.length === 0) { + if (ownerID === null) { switch (action.type) { case 'HANDLE_STORE_MUTATION': numElements = store.numElements; @@ -276,7 +269,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State { function reduceSearchState(store: Store, state: State, action: Action): State { let { - ownerStack, + ownerID, searchIndex, searchResults, searchText, @@ -295,7 +288,7 @@ function reduceSearchState(store: Store, state: State, action: Action): State { let didRequestSearch = false; // Search isn't supported when the owner's tree is active. - if (ownerStack.length === 0) { + if (ownerID === null) { switch (action.type) { case 'GO_TO_NEXT_SEARCH_RESULT': if (numPrevSearchResults > 0) { @@ -442,9 +435,8 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { numElements, selectedElementID, selectedElementIndex, + ownerID, ownerFlatTree, - ownerStack, - ownerStackIndex, searchIndex, searchResults, searchText, @@ -454,25 +446,9 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { switch (action.type) { case 'HANDLE_STORE_MUTATION': - if (ownerStack.length > 0) { - let indexOfRemovedItem = -1; - for (let i = 0; i < ownerStack.length; i++) { - if (store.getElementByID(ownerStack[i]) === null) { - indexOfRemovedItem = i; - break; - } - } - - if (indexOfRemovedItem >= 0) { - ownerStack = ownerStack.slice(0, indexOfRemovedItem); - if (ownerStack.length === 0) { - ownerFlatTree = null; - ownerStackIndex = null; - } else { - ownerStackIndex = ownerStack.length - 1; - } - } - if (selectedElementID !== null && ownerFlatTree !== null) { + if (ownerID !== null) { + ownerFlatTree = store.getOwnersListForElement(ownerID); + if (selectedElementID !== null) { // Mutation might have caused the index of this ID to shift. selectedElementIndex = ownerFlatTree.findIndex( element => element.id === selectedElementID @@ -491,13 +467,12 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { } break; case 'RESET_OWNER_STACK': - ownerStack = []; - ownerStackIndex = null; + ownerID = null; + ownerFlatTree = null; selectedElementIndex = selectedElementID !== null ? store.getIndexOfElementID(selectedElementID) : null; - ownerFlatTree = null; break; case 'SELECT_ELEMENT_AT_INDEX': if (ownerFlatTree !== null) { @@ -533,33 +508,12 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { // If the Store doesn't have any owners metadata, don't drill into an empty stack. // This is a confusing user experience. if (store.hasOwnerMetadata) { - const id = (action: ACTION_SELECT_OWNER).payload; - ownerStackIndex = ownerStack.indexOf(id); + ownerID = (action: ACTION_SELECT_OWNER).payload; + ownerFlatTree = store.getOwnersListForElement(ownerID); // Always force reset selection to be the top of the new owner tree. selectedElementIndex = 0; prevSelectedElementIndex = null; - - // If this owner is already in the current stack, just select it. - // Otherwise, create a new stack. - if (ownerStackIndex < 0) { - // Add this new owner, and fill in the owners above it as well. - ownerStack = []; - let currentOwnerID = id; - while (currentOwnerID !== 0) { - ownerStack.unshift(currentOwnerID); - currentOwnerID = ((store.getElementByID( - currentOwnerID - ): any): Element).ownerID; - } - ownerStackIndex = ownerStack.length - 1; - - if (searchText !== '') { - searchIndex = null; - searchResults = []; - searchText = ''; - } - } } break; default: @@ -569,17 +523,12 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { // Changes in the selected owner require re-calculating the owners tree. if ( - ownerStackIndex !== state.ownerStackIndex || - ownerStack !== state.ownerStack || + ownerFlatTree !== state.ownerFlatTree || action.type === 'HANDLE_STORE_MUTATION' ) { - if (ownerStackIndex === null) { - ownerFlatTree = null; + if (ownerFlatTree === null) { numElements = store.numElements; } else { - ownerFlatTree = store.getOwnersListForElement( - ownerStack[ownerStackIndex] - ); numElements = ownerFlatTree.length; } } @@ -588,9 +537,10 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { if (selectedElementIndex !== prevSelectedElementIndex) { if (selectedElementIndex === null) { selectedElementID = null; - } else if (ownerFlatTree !== null) { - selectedElementID = - ownerFlatTree[((selectedElementIndex: any): number)].id; + } else { + if (ownerFlatTree !== null) { + selectedElementID = ownerFlatTree[selectedElementIndex].id; + } } } @@ -605,8 +555,7 @@ function reduceOwnersState(store: Store, state: State, action: Action): State { searchResults, searchText, - ownerStack, - ownerStackIndex, + ownerID, ownerFlatTree, }; } @@ -619,14 +568,19 @@ function reduceSuspenseState( const { type } = action; switch (type) { case 'UPDATE_INSPECTED_ELEMENT_ID': - return { - ...state, - inspectedElementID: state.selectedElementID, - }; + if (state.inspectedElementID !== state.selectedElementID) { + return { + ...state, + inspectedElementID: state.selectedElementID, + }; + } + break; default: - // React can bailout of no-op updates. - return state; + break; } + + // React can bailout of no-op updates. + return state; } type Props = {| children: React$Node |}; @@ -696,8 +650,7 @@ function TreeContextController({ children }: Props) { searchText: '', // Owners - ownerStack: [], - ownerStackIndex: null, + ownerID: null, ownerFlatTree: null, // Inspection element panel diff --git a/src/devtools/views/Components/types.js b/src/devtools/views/Components/types.js index 0849ec019a..0e33349a47 100644 --- a/src/devtools/views/Components/types.js +++ b/src/devtools/views/Components/types.js @@ -35,6 +35,11 @@ export type Owner = {| id: number, |}; +export type OwnersList = {| + id: number, + owners: Array | null, +|}; + export type InspectedElement = {| id: number,