diff --git a/shells/dev/app/App.css b/shells/dev/app/App.css deleted file mode 100644 index 890641f080..0000000000 --- a/shells/dev/app/App.css +++ /dev/null @@ -1,7 +0,0 @@ -.App { - /* GitHub.com frontend fonts */ - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, - sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; - font-size: 14px; - line-height: 1.5; -} diff --git a/shells/dev/app/App.js b/shells/dev/app/App.js deleted file mode 100644 index 787a5af9fd..0000000000 --- a/shells/dev/app/App.js +++ /dev/null @@ -1,17 +0,0 @@ -// @flow - -import React from 'react'; -import ElementTypes from './ElementTypes'; -import InspectableElements from './InspectableElements'; -import List from './ToDoList'; -import styles from './App.css'; - -export default function App() { - return ( -
- - - -
- ); -} diff --git a/shells/dev/app/InspectableElements/InspectableElements.css b/shells/dev/app/InspectableElements/InspectableElements.css index 6d36bfb51d..b1ebb8ee71 100644 --- a/shells/dev/app/InspectableElements/InspectableElements.css +++ b/shells/dev/app/InspectableElements/InspectableElements.css @@ -1,3 +1,11 @@ +.App { + /* GitHub.com frontend fonts */ + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, + sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; + font-size: 14px; + line-height: 1.5; +} + .Header { font-size: 1.5rem; font-weight: bold; diff --git a/shells/dev/app/InspectableElements/InspectableElements.js b/shells/dev/app/InspectableElements/InspectableElements.js index fa814cceda..b7cbbf2937 100644 --- a/shells/dev/app/InspectableElements/InspectableElements.js +++ b/shells/dev/app/InspectableElements/InspectableElements.js @@ -1,6 +1,6 @@ // @flow -import React, { Fragment } from 'react'; +import React from 'react'; import Contexts from './Contexts'; import CustomHooks from './CustomHooks'; import NestedProps from './NestedProps'; @@ -10,11 +10,11 @@ import styles from './InspectableElements.css'; export default function InspectableElements() { return ( - +
Inspectable elements
- +
); } diff --git a/shells/dev/app/ToDoList/List.css b/shells/dev/app/ToDoList/List.css index febdc684fa..ae39496000 100644 --- a/shells/dev/app/ToDoList/List.css +++ b/shells/dev/app/ToDoList/List.css @@ -1,3 +1,11 @@ +.App { + /* GitHub.com frontend fonts */ + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, + sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; + font-size: 14px; + line-height: 1.5; +} + .Header { font-size: 1.5rem; font-weight: bold; diff --git a/shells/dev/app/ToDoList/List.js b/shells/dev/app/ToDoList/List.js index 2c6e96b524..1ab1cac1f9 100644 --- a/shells/dev/app/ToDoList/List.js +++ b/shells/dev/app/ToDoList/List.js @@ -1,6 +1,6 @@ // @flow -import React, { Fragment, useCallback, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import ListItem from './ListItem'; import styles from './List.css'; @@ -77,7 +77,7 @@ export default function List(props: Props) { ); return ( - +
List
))} - +
); } diff --git a/shells/dev/app/index.js b/shells/dev/app/index.js index af1f5b8227..c04798ac80 100644 --- a/shells/dev/app/index.js +++ b/shells/dev/app/index.js @@ -1,19 +1,33 @@ /** @flow */ +// This test harness mounts each test app as a separate root to test multi-root applications. + import { createElement } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import App from './App'; +import ElementTypes from './ElementTypes'; +import InspectableElements from './InspectableElements'; +import ToDoList from './ToDoList'; -const container = document.createElement('div'); +const containers = []; -((document.body: any): HTMLBodyElement).appendChild(container); +function mountHelper(App) { + const container = document.createElement('div'); + + ((document.body: any): HTMLBodyElement).appendChild(container); + + containers.push(container); -function mountTestApp() { render(createElement(App), container); } +function mountTestApp() { + mountHelper(ToDoList); + mountHelper(InspectableElements); + mountHelper(ElementTypes); +} + function unmountTestApp() { - unmountComponentAtNode(container); + containers.forEach(container => unmountComponentAtNode(container)); } mountTestApp(); diff --git a/src/devtools/store.js b/src/devtools/store.js index 6ef2c16739..59465fd3e4 100644 --- a/src/devtools/store.js +++ b/src/devtools/store.js @@ -53,6 +53,7 @@ export default class Store extends EventEmitter { debug('constructor', 'subscribing to Bridge'); bridge.on('operations', this.onBridgeOperations); + bridge.on('shutdown', this.onBridgeShutdown); } get numElements(): number { @@ -85,7 +86,9 @@ export default class Store extends EventEmitter { for (let i = 0; i < this._roots.length; i++) { rootID = this._roots[i]; root = ((this._idToElement.get(rootID): any): Element); - if (rootWeight + root.weight > index) { + if (root.children.length === 0) { + continue; + } else if (rootWeight + root.weight > index) { break; } else { rootWeight += root.weight; @@ -96,7 +99,7 @@ export default class Store extends EventEmitter { // Skip over the root itself, because roots aren't visible in the Elements tree. const firstChildID = ((root: any): Element).children[0]; let currentElement = ((this._idToElement.get(firstChildID): any): Element); - let currentWeight = 0; + let currentWeight = rootWeight; while (index !== currentWeight) { for (let i = 0; i < currentElement.children.length; i++) { const childID = currentElement.children[i]; @@ -234,22 +237,28 @@ export default class Store extends EventEmitter { if (parentID === 0) { debug('Add', `new root fiber ${id}`); - this._roots = this._roots.concat(id); - this._rootIDToRendererID.set(id, rendererID); + if (this._idToElement.has(id)) { + // The renderer's tree walking approach sometimes mounts the same Fiber twice with Suspense and Lazy. + // For now, we avoid adding it to the tree twice by checking if it's already been mounted. + // Maybe in the future we'll revisit this. + } else { + this._roots = this._roots.concat(id); + this._rootIDToRendererID.set(id, rendererID); - this._idToElement.set(id, { - children: [], - depth: -1, - displayName: null, - id, - key: null, - ownerID: 0, - parentID: 0, - type, - weight: 0, - }); + this._idToElement.set(id, { + children: [], + depth: -1, + displayName: null, + id, + key: null, + ownerID: 0, + parentID: 0, + type, + weight: 0, + }); - haveRootsChanged = true; + haveRootsChanged = true; + } } else { ownerID = ((operations[i]: any): number); i++; @@ -313,8 +322,6 @@ export default class Store extends EventEmitter { i = i + 2; - debug('Remove', `fiber ${id} from tree`); - element = ((this._idToElement.get(id): any): Element); parentID = element.parentID; @@ -324,9 +331,13 @@ export default class Store extends EventEmitter { parentElement = ((this._idToElement.get(parentID): any): Element); if (parentElement == null) { + debug('Remove', `fiber ${id} root`); + this._roots = this._roots.filter(rootID => rootID !== id); this._rootIDToRendererID.delete(id); } else { + debug('Remove', `fiber ${id} from parent ${parentID}`); + parentElement.children = parentElement.children.filter( childID => childID !== id ); @@ -388,18 +399,42 @@ export default class Store extends EventEmitter { this.emit('mutated', [addedElementIDs, removedElementIDs]); }; + onBridgeShutdown = () => { + debug('onBridgeShutdown', 'unsubscribing from Bridge'); + + bridge.off('operations', this.onBridgeOperations); + bridge.off('shutdown', this.onBridgeShutdown); + }; + // DEBUG - __printTree = (rootID: number) => { - const printElement = (id: number) => { - const element = ((this._idToElement.get(id): any): Element); - console.log( - `${'•'.repeat(element.depth)}${element.id}:${element.displayName || - ''}${element.key ? `key:"${element.key}"` : ''} (${element.weight})` - ); - element.children.forEach(printElement); - }; - const root = ((this._idToElement.get(rootID): any): Element); - console.log('printing root:', rootID); - root.children.forEach(printElement); + __printTree = () => { + console.group('__printTree()'); + this._roots.forEach((rootID: number) => { + const printElement = (id: number) => { + const element = ((this._idToElement.get(id): any): Element); + console.log( + `${'•'.repeat(element.depth)}${element.id}:${element.displayName || + ''}${element.key ? `key:"${element.key}"` : ''} (${element.weight})` + ); + element.children.forEach(printElement); + }; + const root = ((this._idToElement.get(rootID): any): Element); + console.group(`${rootID}:root (${root.weight})`); + root.children.forEach(printElement); + console.groupEnd(); + }); + console.group(`List of ${this.numElements} elements`); + for (let i = 0; i < this.numElements; i++) { + //if (i === 4) { debugger } + const element = this.getElementAtIndex(i); + if (element != null) { + console.log( + `${'•'.repeat(element.depth)}${i}: ${element.displayName || + 'Unknown'}` + ); + } + } + console.groupEnd(); + console.groupEnd(); }; }