diff --git a/devtools/client/debugger/packages/devtools-components/src/tree.js b/devtools/client/debugger/packages/devtools-components/src/tree.js index 5f094f5699d6..71304cd74ee1 100644 --- a/devtools/client/debugger/packages/devtools-components/src/tree.js +++ b/devtools/client/debugger/packages/devtools-components/src/tree.js @@ -488,7 +488,7 @@ class Tree extends Component { super(props); this.state = { - seen: new Set(), + autoExpanded: new Set(), }; this.treeRef = React.createRef(); @@ -536,29 +536,40 @@ class Tree extends Component { } _autoExpand() { - const { autoExpandDepth, autoExpandNodeChildrenLimit } = this.props; - if (!autoExpandDepth) { + const { + autoExpandDepth, + autoExpandNodeChildrenLimit, + initiallyExpanded, + } = this.props; + + if (!autoExpandDepth && !initiallyExpanded) { return; } // Automatically expand the first autoExpandDepth levels for new items. Do // not use the usual DFS infrastructure because we don't want to ignore - // collapsed nodes. + // collapsed nodes. Any initially expanded items will be expanded regardless + // of how deep they are. const autoExpand = (item, currentDepth) => { - if (currentDepth >= autoExpandDepth || this.state.seen.has(item)) { + const initial = initiallyExpanded && initiallyExpanded(item); + + if (!initial && currentDepth >= autoExpandDepth) { return; } const children = this.props.getChildren(item); if ( + !initial && autoExpandNodeChildrenLimit && children.length > autoExpandNodeChildrenLimit ) { return; } - this.props.onExpand(item); - this.state.seen.add(item); + if (!this.state.autoExpanded.has(item)) { + this.props.onExpand(item); + this.state.autoExpanded.add(item); + } const length = children.length; for (let i = 0; i < length; i++) { @@ -574,6 +585,14 @@ class Tree extends Component { } } else if (length != 0) { autoExpand(roots[0], 0); + + if (initiallyExpanded) { + for (let i = 1; i < length; i++) { + if (initiallyExpanded(roots[i])) { + autoExpand(roots[i], 0); + } + } + } } } diff --git a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspector.js b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspector.js index 7c28a21cfcf4..e83672ae6d30 100644 --- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspector.js +++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspector.js @@ -198,6 +198,7 @@ class ObjectInspector extends Component { nodeExpand, nodeCollapse, recordTelemetryEvent, + setExpanded, roots, } = this.props; @@ -210,6 +211,10 @@ class ObjectInspector extends Component { } else { nodeCollapse(item); } + + if (setExpanded) { + setExpanded(item, expand); + } } focusItem(item: Node) { @@ -249,6 +254,7 @@ class ObjectInspector extends Component { const { autoExpandAll = true, autoExpandDepth = 1, + initiallyExpanded, focusable = true, disableWrap = false, expandedPaths, @@ -264,6 +270,7 @@ class ObjectInspector extends Component { autoExpandAll, autoExpandDepth, + initiallyExpanded, isExpanded: item => expandedPaths && expandedPaths.has(item.path), isExpandable: this.isNodeExpandable, diff --git a/devtools/client/debugger/src/actions/pause/expandScopes.js b/devtools/client/debugger/src/actions/pause/expandScopes.js new file mode 100644 index 000000000000..034d8098582b --- /dev/null +++ b/devtools/client/debugger/src/actions/pause/expandScopes.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// @flow + +import { getScopeItemPath } from "../../utils/pause/scopes/utils"; +import type { ThunkArgs } from "../types"; +import type { ThreadContext } from "../../types"; + +export function setExpandedScope( + cx: ThreadContext, + item: Object, + expanded: boolean +) { + return function({ dispatch, getState }: ThunkArgs) { + return dispatch({ + type: "SET_EXPANDED_SCOPE", + cx, + thread: cx.thread, + path: getScopeItemPath(item), + expanded, + }); + }; +} diff --git a/devtools/client/debugger/src/actions/pause/index.js b/devtools/client/debugger/src/actions/pause/index.js index f41bc22667fb..7dddedfe255a 100644 --- a/devtools/client/debugger/src/actions/pause/index.js +++ b/devtools/client/debugger/src/actions/pause/index.js @@ -28,3 +28,4 @@ export { pauseOnExceptions } from "./pauseOnExceptions"; export { selectFrame } from "./selectFrame"; export { toggleSkipPausing } from "./skipPausing"; export { toggleMapScopes } from "./mapScopes"; +export { setExpandedScope } from "./expandScopes"; diff --git a/devtools/client/debugger/src/actions/pause/moz.build b/devtools/client/debugger/src/actions/pause/moz.build index b25f8576b0cd..47fd0291b717 100644 --- a/devtools/client/debugger/src/actions/pause/moz.build +++ b/devtools/client/debugger/src/actions/pause/moz.build @@ -11,6 +11,7 @@ CompiledModules( 'breakOnNext.js', 'commands.js', 'continueToHere.js', + 'expandScopes.js', 'fetchScopes.js', 'index.js', 'mapFrames.js', diff --git a/devtools/client/debugger/src/actions/types/PauseAction.js b/devtools/client/debugger/src/actions/types/PauseAction.js index ba6cef19831d..9e1fce19e4c8 100644 --- a/devtools/client/debugger/src/actions/types/PauseAction.js +++ b/devtools/client/debugger/src/actions/types/PauseAction.js @@ -147,4 +147,11 @@ export type PauseAction = | {| +type: "TOGGLE_MAP_SCOPES", +mapScopes: boolean, + |} + | {| + +type: "SET_EXPANDED_SCOPE", + +cx: ThreadContext, + +thread: string, + +path: string, + +expanded: boolean, |}; diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js b/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js index 83f18260a544..676509443682 100644 --- a/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js +++ b/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js @@ -13,17 +13,18 @@ import { getSelectedFrame, getGeneratedFrameScope, getOriginalFrameScope, - getIsPaused, getPauseReason, isMapScopesEnabled, - getCurrentThread, + getThreadContext, + getLastExpandedScopes, } from "../../selectors"; import { getScopes } from "../../utils/pause/scopes"; +import { getScopeItemPath } from "../../utils/pause/scopes/utils"; // eslint-disable-next-line import/named import { objectInspector } from "devtools-reps"; -import type { Why } from "../../types"; +import type { ThreadContext, Why } from "../../types"; import type { NamedValue } from "../../utils/pause/scopes/types"; import "./Scopes.css"; @@ -31,7 +32,7 @@ import "./Scopes.css"; const { ObjectInspector } = objectInspector; type Props = { - isPaused: boolean, + cx: ThreadContext, selectedFrame: Object, generatedFrameScopes: Object, originalFrameScopes: Object | null, @@ -43,6 +44,8 @@ type Props = { highlightDomElement: typeof actions.highlightDomElement, unHighlightDomElement: typeof actions.unHighlightDomElement, toggleMapScopes: typeof actions.toggleMapScopes, + setExpandedScope: typeof actions.setExpandedScope, + expandedScopes: string[], }; type State = { @@ -71,12 +74,12 @@ class Scopes extends PureComponent { componentWillReceiveProps(nextProps) { const { - isPaused, + cx, selectedFrame, originalFrameScopes, generatedFrameScopes, } = this.props; - const isPausedChanged = isPaused !== nextProps.isPaused; + const isPausedChanged = cx.isPaused !== nextProps.cx.isPaused; const selectedFrameChanged = selectedFrame !== nextProps.selectedFrame; const originalFrameScopesChanged = originalFrameScopes !== nextProps.originalFrameScopes; @@ -110,19 +113,25 @@ class Scopes extends PureComponent { renderScopesList() { const { - isPaused, + cx, isLoading, openLink, openElementInInspector, highlightDomElement, unHighlightDomElement, mapScopesEnabled, + setExpandedScope, + expandedScopes, } = this.props; const { originalScopes, generatedScopes, showOriginal } = this.state; const scopes = (showOriginal && mapScopesEnabled && originalScopes) || generatedScopes; + function initiallyExpanded(item) { + return expandedScopes.some(path => path == getScopeItemPath(item)); + } + if (scopes && scopes.length > 0 && !isLoading) { return (
@@ -138,13 +147,15 @@ class Scopes extends PureComponent { onInspectIconClick={grip => openElementInInspector(grip)} onDOMNodeMouseOver={grip => highlightDomElement(grip)} onDOMNodeMouseOut={grip => unHighlightDomElement(grip)} + setExpanded={(path, expand) => setExpandedScope(cx, path, expand)} + initiallyExpanded={initiallyExpanded} />
); } let stateText = L10N.getStr("scopes.notPaused"); - if (isPaused) { + if (cx.isPaused) { if (isLoading) { stateText = L10N.getStr("loadingText"); } else { @@ -165,8 +176,8 @@ class Scopes extends PureComponent { } const mapStateToProps = state => { - const thread = getCurrentThread(state); - const selectedFrame = getSelectedFrame(state, thread); + const cx = getThreadContext(state); + const selectedFrame = getSelectedFrame(state, cx.thread); const selectedSource = getSelectedSource(state); const { @@ -174,7 +185,7 @@ const mapStateToProps = state => { pending: originalPending, } = getOriginalFrameScope( state, - thread, + cx.thread, selectedSource && selectedSource.id, selectedFrame && selectedFrame.id ) || { scope: null, pending: false }; @@ -184,7 +195,7 @@ const mapStateToProps = state => { pending: generatedPending, } = getGeneratedFrameScope( state, - thread, + cx.thread, selectedFrame && selectedFrame.id ) || { scope: null, @@ -192,13 +203,14 @@ const mapStateToProps = state => { }; return { + cx, selectedFrame, mapScopesEnabled: isMapScopesEnabled(state), - isPaused: getIsPaused(state, thread), isLoading: generatedPending || originalPending, - why: getPauseReason(state, thread), + why: getPauseReason(state, cx.thread), originalFrameScopes, generatedFrameScopes, + expandedScopes: getLastExpandedScopes(state, cx.thread), }; }; @@ -210,5 +222,6 @@ export default connect( highlightDomElement: actions.highlightDomElement, unHighlightDomElement: actions.unHighlightDomElement, toggleMapScopes: actions.toggleMapScopes, + setExpandedScope: actions.setExpandedScope, } )(Scopes); diff --git a/devtools/client/debugger/src/reducers/pause.js b/devtools/client/debugger/src/reducers/pause.js index 112ef07c9430..e7a5bf5339c0 100644 --- a/devtools/client/debugger/src/reducers/pause.js +++ b/devtools/client/debugger/src/reducers/pause.js @@ -64,6 +64,18 @@ type ThreadPauseState = { }, }, selectedFrameId: ?string, + + // Scope items that have been expanded in the current pause. + expandedScopes: Set, + + // Scope items that were expanded in the last pause. This is separate from + // expandedScopes so that (a) the scope pane's ObjectInspector does not depend + // on the current expanded scopes and we don't have to re-render the entire + // ObjectInspector when an element is expanded or collapsed, and (b) so that + // the expanded scopes are regenerated when we pause at a new location and we + // don't have to worry about pruning obsolete scope entries. + lastExpandedScopes: string[], + command: Command, lastCommand: Command, wasStepping: boolean, @@ -120,6 +132,8 @@ const createInitialPauseState = () => ({ command: null, lastCommand: null, previousLocation: null, + expandedScopes: new Set(), + lastExpandedScopes: [], }); function getThreadPauseState(state: PauseState, thread: ThreadId) { @@ -292,6 +306,8 @@ function update( return updateThreadState({ ...resumedPauseState, wasStepping: !!action.wasStepping, + expandedScopes: new Set(), + lastExpandedScopes: [...threadState().expandedScopes], }); } @@ -315,7 +331,7 @@ function update( }, threads: { [action.mainThread.actor]: { - ...state.threads[action.mainThread.actor], + ...getThreadPauseState(state, action.mainThread.actor), ...resumedPauseState, }, }, @@ -334,6 +350,17 @@ function update( prefs.mapScopes = mapScopes; return { ...state, mapScopes }; } + + case "SET_EXPANDED_SCOPE": { + const { path, expanded } = action; + const expandedScopes = new Set(threadState().expandedScopes); + if (expanded) { + expandedScopes.add(path); + } else { + expandedScopes.delete(path); + } + return updateThreadState({ expandedScopes }); + } } return state; @@ -582,4 +609,8 @@ export function getChromeScopes(state: State, thread: ThreadId) { return frame ? frame.scopeChain : undefined; } +export function getLastExpandedScopes(state: State, thread: ThreadId) { + return getThreadPauseState(state.pause, thread).lastExpandedScopes; +} + export default update; diff --git a/devtools/client/debugger/src/utils/pause/scopes/utils.js b/devtools/client/debugger/src/utils/pause/scopes/utils.js index 74ad486c0cdc..d7258b4906c7 100644 --- a/devtools/client/debugger/src/utils/pause/scopes/utils.js +++ b/devtools/client/debugger/src/utils/pause/scopes/utils.js @@ -51,3 +51,10 @@ export function getThisVariable(this_: any, path: string): ?NamedValue { contents: { value: this_ }, }; } + +// Get a string path for an scope item which can be used in different pauses for +// a thread. +export function getScopeItemPath(item: Object) { + // Calling toString() on item.path allows symbols to be handled. + return item.path.toString(); +} diff --git a/devtools/client/debugger/test/mochitest/browser.ini b/devtools/client/debugger/test/mochitest/browser.ini index e2597721d0dd..4e1869f9cf22 100644 --- a/devtools/client/debugger/test/mochitest/browser.ini +++ b/devtools/client/debugger/test/mochitest/browser.ini @@ -150,3 +150,4 @@ skip-if = (os == 'linux' && debug) || (os == 'linux' && asan) || ccov #Bug 1456 [browser_dbg-scopes-xrays.js] [browser_dbg-merge-scopes.js] [browser_dbg-message-run-to-completion.js] +[browser_dbg-remember-expanded-scopes.js] diff --git a/devtools/client/debugger/test/mochitest/browser_dbg-inspector-integration.js b/devtools/client/debugger/test/mochitest/browser_dbg-inspector-integration.js index 9bf564097bfb..0f5d3848886c 100644 --- a/devtools/client/debugger/test/mochitest/browser_dbg-inspector-integration.js +++ b/devtools/client/debugger/test/mochitest/browser_dbg-inspector-integration.js @@ -10,8 +10,8 @@ function waitForInspectorPanelChange(dbg) { return new Promise(resolve => { toolbox.getPanelWhenReady("inspector").then(() => { - ok(toolbox.inspector, "Inspector is shown."); - resolve(toolbox.inspector); + ok(toolbox.inspectorFront, "Inspector is shown."); + resolve(toolbox.inspectorFront); }); }); } @@ -31,9 +31,14 @@ add_task(async function() { // Ensure hovering over button highlights the node in content pane const view = inspectorNode.ownerDocument.defaultView; - const onNodeHighlight = toolbox.target.once("inspector") + const onNodeHighlight = toolbox.target + .once("inspector") .then(inspector => inspector.highlighter.once("node-highlight")); - EventUtils.synthesizeMouseAtCenter(inspectorNode, {type: "mousemove"}, view); + EventUtils.synthesizeMouseAtCenter( + inspectorNode, + { type: "mousemove" }, + view + ); const nodeFront = await onNodeHighlight; is(nodeFront.displayName, "button", "The correct node was highlighted"); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg-remember-expanded-scopes.js b/devtools/client/debugger/test/mochitest/browser_dbg-remember-expanded-scopes.js new file mode 100644 index 000000000000..7e29edd8fd35 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg-remember-expanded-scopes.js @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// Ignore strange errors when shutting down. +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/PromiseTestUtils.jsm" +); +PromiseTestUtils.whitelistRejectionsGlobally(/No such actor/); +PromiseTestUtils.whitelistRejectionsGlobally(/connection just closed/); + +const MaxItems = 10; + +function findNode(dbg, text) { + for (let index = 0; index < MaxItems; index++) { + var elem = findElement(dbg, "scopeNode", index); + if (elem && elem.innerText == text) { + return elem; + } + } + return null; +} + +async function toggleNode(dbg, text) { + const node = await waitUntilPredicate(() => findNode(dbg, text)); + return toggleObjectInspectorNode(node); +} + +// Test that expanded scopes stay expanded after resuming and pausing again. +add_task(async function() { + const dbg = await initDebugger("doc-remember-expanded-scopes.html"); + invokeInTab("main", "doc-remember-expanded-scopes.html"); + await waitForPaused(dbg); + + const MaxItems = 10; + + await toggleNode(dbg, "object"); + await toggleNode(dbg, "innerObject"); + await stepOver(dbg); + await waitForPaused(dbg); + + await waitUntil(() => findNode(dbg, "innerData")); + ok("Inner object data automatically expanded after stepping"); +}); diff --git a/devtools/client/debugger/test/mochitest/examples/doc-remember-expanded-scopes.html b/devtools/client/debugger/test/mochitest/examples/doc-remember-expanded-scopes.html new file mode 100644 index 000000000000..7648b1841c2f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/examples/doc-remember-expanded-scopes.html @@ -0,0 +1,7 @@ + diff --git a/devtools/client/definitions.js b/devtools/client/definitions.js index 7aefc5306b50..6671645278a7 100644 --- a/devtools/client/definitions.js +++ b/devtools/client/definitions.js @@ -161,7 +161,7 @@ Tools.inspector = { preventClosingOnKey: true, onkey: function(panel, toolbox) { - toolbox.inspector.nodePicker.togglePicker(); + toolbox.inspectorFront.nodePicker.togglePicker(); }, isTargetSupported: function(target) { @@ -617,7 +617,7 @@ function createHighlightButton(highlighterName, id) { isTargetSupported: target => !target.chrome, async onClick(event, toolbox) { await toolbox.initInspector(); - const highlighter = await toolbox.inspector.getOrCreateHighlighterByType( + const highlighter = await toolbox.inspectorFront.getOrCreateHighlighterByType( highlighterName ); if (highlighter.isShown()) { diff --git a/devtools/client/framework/test/browser_keybindings_01.js b/devtools/client/framework/test/browser_keybindings_01.js index 9b250cc2613c..868c880d4d7c 100644 --- a/devtools/client/framework/test/browser_keybindings_01.js +++ b/devtools/client/framework/test/browser_keybindings_01.js @@ -104,12 +104,12 @@ add_task(async function() { async function inspectorShouldBeOpenAndHighlighting(inspector) { is(toolbox.currentToolId, "inspector", "Correct tool has been loaded"); - await toolbox.inspector.nodePicker.once("picker-started"); + await toolbox.inspectorFront.nodePicker.once("picker-started"); ok(true, "picker-started event received, highlighter started"); inspector.synthesizeKey(); - await toolbox.inspector.nodePicker.once("picker-stopped"); + await toolbox.inspectorFront.nodePicker.once("picker-stopped"); ok(true, "picker-stopped event received, highlighter stopped"); } diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js index 28f887d7f5bc..f1de4afd038c 100644 --- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -496,7 +496,7 @@ Toolbox.prototype = { * Get the toolbox's inspector front. Note that it may not always have been * initialized first. Use `initInspector()` if needed. */ - get inspector() { + get inspectorFront() { return this._inspector; }, @@ -1720,10 +1720,10 @@ Toolbox.prototype = { if (currentPanel.togglePicker) { currentPanel.togglePicker(focus); } else { - if (!this.inspector) { + if (!this.inspectorFront) { await this.initInspector(); } - this.inspector.nodePicker.togglePicker(focus); + this.inspectorFront.nodePicker.togglePicker(focus); } }, @@ -1737,7 +1737,7 @@ Toolbox.prototype = { if (currentPanel.cancelPicker) { currentPanel.cancelPicker(); } else { - this.inspector.nodePicker.cancel(); + this.inspectorFront.nodePicker.cancel(); } // Stop the console from toggling. event.stopImmediatePropagation(); @@ -1748,7 +1748,7 @@ Toolbox.prototype = { this.tellRDMAboutPickerState(true); this.pickerButton.isChecked = true; await this.selectTool("inspector", "inspect_dom"); - this.on("select", this.inspector.nodePicker.stop); + this.on("select", this.inspectorFront.nodePicker.stop); }, _onPickerStarted: async function() { @@ -1758,7 +1758,7 @@ Toolbox.prototype = { _onPickerStopped: function() { this.tellRDMAboutPickerState(false); - this.off("select", this.inspector.nodePicker.stop); + this.off("select", this.inspectorFront.nodePicker.stop); this.doc.removeEventListener("keypress", this._onPickerKeypress, true); this.pickerButton.isChecked = false; }, @@ -1881,7 +1881,7 @@ Toolbox.prototype = { * Update the buttons. */ updateToolboxButtons() { - const inspector = this.inspector; + const inspector = this.inspectorFront; // two of the buttons have highlighters that need to be cleared // on will-navigate, otherwise we hold on to the stale highlighter const hasHighlighters = @@ -3267,14 +3267,23 @@ Toolbox.prototype = { // TODO: replace with getFront once inspector is separated from the toolbox // TODO: remove these bindings this._inspector = await this.target.getInspector(); - this._walker = this.inspector.walker; - this._highlighter = this.inspector.highlighter; - this._selection = this.inspector.selection; + this._walker = this.inspectorFront.walker; + this._highlighter = this.inspectorFront.highlighter; + this._selection = this.inspectorFront.selection; - this.inspector.nodePicker.on("picker-starting", this._onPickerStarting); - this.inspector.nodePicker.on("picker-started", this._onPickerStarted); - this.inspector.nodePicker.on("picker-stopped", this._onPickerStopped); - this.inspector.nodePicker.on( + this.inspectorFront.nodePicker.on( + "picker-starting", + this._onPickerStarting + ); + this.inspectorFront.nodePicker.on( + "picker-started", + this._onPickerStarted + ); + this.inspectorFront.nodePicker.on( + "picker-stopped", + this._onPickerStopped + ); + this.inspectorFront.nodePicker.on( "picker-node-canceled", this._onPickerCanceled ); diff --git a/devtools/client/inspector/animation/animation.js b/devtools/client/inspector/animation/animation.js index d7612f34f508..3388e4658b37 100644 --- a/devtools/client/inspector/animation/animation.js +++ b/devtools/client/inspector/animation/animation.js @@ -150,11 +150,11 @@ class AnimationInspector { this.provider = provider; this.inspector.sidebar.on("select", this.onSidebarSelectionChanged); - this.inspector.inspector.nodePicker.on( + this.inspector.inspectorFront.nodePicker.on( "picker-started", this.onElementPickerStarted ); - this.inspector.inspector.nodePicker.on( + this.inspector.inspectorFront.nodePicker.on( "picker-stopped", this.onElementPickerStopped ); @@ -183,11 +183,11 @@ class AnimationInspector { "inspector-sidebar-resized", this.onSidebarResized ); - this.inspector.inspector.nodePicker.off( + this.inspector.inspectorFront.nodePicker.off( "picker-started", this.onElementPickerStarted ); - this.inspector.inspector.nodePicker.off( + this.inspector.inspectorFront.nodePicker.off( "picker-stopped", this.onElementPickerStopped ); @@ -711,7 +711,7 @@ class AnimationInspector { } toggleElementPicker() { - this.inspector.inspector.nodePicker.togglePicker(); + this.inspector.inspectorFront.nodePicker.togglePicker(); } async update() { diff --git a/devtools/client/inspector/fonts/fonts.js b/devtools/client/inspector/fonts/fonts.js index 092caa1ac06f..f070725e01b9 100644 --- a/devtools/client/inspector/fonts/fonts.js +++ b/devtools/client/inspector/fonts/fonts.js @@ -852,7 +852,7 @@ class FontInspector { async onToggleFontHighlight(font, show, isForCurrentElement = true) { if (!this.fontsHighlighter) { try { - this.fontsHighlighter = await this.inspector.inspector.getHighlighterByType( + this.fontsHighlighter = await this.inspector.inspectorFront.getHighlighterByType( "FontsHighlighter" ); } catch (e) { diff --git a/devtools/client/inspector/inspector.js b/devtools/client/inspector/inspector.js index 794b6ff8c792..0ebb93f020d0 100644 --- a/devtools/client/inspector/inspector.js +++ b/devtools/client/inspector/inspector.js @@ -227,8 +227,8 @@ Inspector.prototype = { return this._toolbox; }, - get inspector() { - return this.toolbox.inspector; + get inspectorFront() { + return this.toolbox.inspectorFront; }, get walker() { @@ -417,7 +417,7 @@ Inspector.prototype = { }, _getPageStyle: function() { - return this.inspector.getPageStyle().then(pageStyle => { + return this.inspectorFront.getPageStyle().then(pageStyle => { this.pageStyle = pageStyle; }, this._handleRejectionIfNotDestroyed); }, @@ -1208,7 +1208,7 @@ Inspector.prototype = { */ async supportsEyeDropper() { try { - return await this.inspector.supportsHighlighters(); + return await this.inspectorFront.supportsHighlighters(); } catch (e) { console.error(e); return false; @@ -1706,14 +1706,14 @@ Inspector.prototype = { }, startEyeDropperListeners: function() { - this.inspector.once("color-pick-canceled", this.onEyeDropperDone); - this.inspector.once("color-picked", this.onEyeDropperDone); + this.inspectorFront.once("color-pick-canceled", this.onEyeDropperDone); + this.inspectorFront.once("color-picked", this.onEyeDropperDone); this.walker.once("new-root", this.onEyeDropperDone); }, stopEyeDropperListeners: function() { - this.inspector.off("color-pick-canceled", this.onEyeDropperDone); - this.inspector.off("color-picked", this.onEyeDropperDone); + this.inspectorFront.off("color-pick-canceled", this.onEyeDropperDone); + this.inspectorFront.off("color-picked", this.onEyeDropperDone); this.walker.off("new-root", this.onEyeDropperDone); }, @@ -1736,7 +1736,7 @@ Inspector.prototype = { this.telemetry.scalarSet(TELEMETRY_EYEDROPPER_OPENED, 1); this.eyeDropperButton.classList.add("checked"); this.startEyeDropperListeners(); - return this.inspector + return this.inspectorFront .pickColorFromPage({ copyOnSelect: true }) .catch(console.error); }, @@ -1753,7 +1753,7 @@ Inspector.prototype = { this.eyeDropperButton.classList.remove("checked"); this.stopEyeDropperListeners(); - return this.inspector.cancelPickColorFromPage().catch(console.error); + return this.inspectorFront.cancelPickColorFromPage().catch(console.error); }, /** diff --git a/devtools/client/inspector/markup/markup-context-menu.js b/devtools/client/inspector/markup/markup-context-menu.js index e947d1eef807..28cb6b436e6c 100644 --- a/devtools/client/inspector/markup/markup-context-menu.js +++ b/devtools/client/inspector/markup/markup-context-menu.js @@ -79,7 +79,7 @@ class MarkupContextMenu { * This method is here for the benefit of copying links. */ _copyAttributeLink(link) { - this.inspector.inspector + this.inspector.inspectorFront .resolveRelativeURL(link, this.selection.nodeFront) .then(url => { clipboardHelper.copyString(url); diff --git a/devtools/client/inspector/markup/markup.js b/devtools/client/inspector/markup/markup.js index fbc7b2b4037c..c0314c2fdb7b 100644 --- a/devtools/client/inspector/markup/markup.js +++ b/devtools/client/inspector/markup/markup.js @@ -183,11 +183,11 @@ function MarkupView(inspector, frame, controllerWindow) { this.walker.on("mutations", this._mutationObserver); this.win.addEventListener("copy", this._onCopy); this.win.addEventListener("mouseup", this._onMouseUp); - this.inspector.inspector.nodePicker.on( + this.inspector.inspectorFront.nodePicker.on( "picker-node-canceled", this._onToolboxPickerCanceled ); - this.inspector.inspector.nodePicker.on( + this.inspector.inspectorFront.nodePicker.on( "picker-node-hovered", this._onToolboxPickerHover ); @@ -932,7 +932,7 @@ MarkupView.prototype = { if (type === "uri" || type === "cssresource" || type === "jsresource") { // Open link in a new tab. - this.inspector.inspector + this.inspector.inspectorFront .resolveRelativeURL(link, this.inspector.selection.nodeFront) .then(url => { if (type === "uri") { @@ -2245,7 +2245,7 @@ MarkupView.prototype = { this._elt.removeEventListener("mouseout", this._onMouseOut); this._frame.removeEventListener("focus", this._onFocus); this.inspector.selection.off("new-node-front", this._onNewSelection); - this.inspector.inspector.nodePicker.off( + this.inspector.inspectorFront.nodePicker.off( "picker-node-hovered", this._onToolboxPickerHover ); diff --git a/devtools/client/inspector/markup/test/browser_markup_links_04.js b/devtools/client/inspector/markup/test/browser_markup_links_04.js index 013c2092196b..0c8fd2908b6c 100644 --- a/devtools/client/inspector/markup/test/browser_markup_links_04.js +++ b/devtools/client/inspector/markup/test/browser_markup_links_04.js @@ -140,7 +140,7 @@ add_task(async function() { info("Get link from node attribute"); const link = await nodeFront.getAttribute(test.attributeName); info("Resolve link to absolue URL"); - const expected = await inspector.inspector.resolveRelativeURL( + const expected = await inspector.inspectorFront.resolveRelativeURL( link, nodeFront ); diff --git a/devtools/client/inspector/rules/new-rules.js b/devtools/client/inspector/rules/new-rules.js index 7125d74bd017..87ac8eef1455 100644 --- a/devtools/client/inspector/rules/new-rules.js +++ b/devtools/client/inspector/rules/new-rules.js @@ -328,7 +328,7 @@ class RulesView { } try { - const front = this.inspector.inspector; + const front = this.inspector.inspectorFront; this._selectorHighlighter = await front.getHighlighterByType( "SelectorHighlighter" ); diff --git a/devtools/client/inspector/rules/rules.js b/devtools/client/inspector/rules/rules.js index de73bb1bfaf3..c9d77ee45138 100644 --- a/devtools/client/inspector/rules/rules.js +++ b/devtools/client/inspector/rules/rules.js @@ -322,7 +322,7 @@ CssRuleView.prototype = { } try { - const front = this.inspector.inspector; + const front = this.inspector.inspectorFront; const h = await front.getHighlighterByType("SelectorHighlighter"); this.selectorHighlighter = h; return h; diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-element-picker.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-element-picker.js index c876b1afdd2a..be47ad1d024f 100644 --- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-element-picker.js +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-element-picker.js @@ -12,7 +12,9 @@ add_task(async function() { await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); const { view, toolbox } = await openRuleView(); - const pickerStopped = toolbox.inspector.nodePicker.once("picker-stopped"); + const pickerStopped = toolbox.inspectorFront.nodePicker.once( + "picker-stopped" + ); await startPicker(toolbox); diff --git a/devtools/client/inspector/rules/test/browser_rules_eyedropper.js b/devtools/client/inspector/rules/test/browser_rules_eyedropper.js index 6f09479587ee..fbcfe811d2d6 100644 --- a/devtools/client/inspector/rules/test/browser_rules_eyedropper.js +++ b/devtools/client/inspector/rules/test/browser_rules_eyedropper.js @@ -95,7 +95,7 @@ async function runTest(testActor, inspector, view) { async function testESC(swatch, inspector, testActor) { info("Press escape"); const onCanceled = new Promise(resolve => { - inspector.inspector.once("color-pick-canceled", resolve); + inspector.inspectorFront.once("color-pick-canceled", resolve); }); await testActor.synthesizeKey({ key: "VK_ESCAPE", options: {} }); await onCanceled; @@ -107,7 +107,7 @@ async function testESC(swatch, inspector, testActor) { async function testSelect(view, swatch, inspector, testActor) { info("Click at x:10px y:10px"); const onPicked = new Promise(resolve => { - inspector.inspector.once("color-picked", resolve); + inspector.inspectorFront.once("color-picked", resolve); }); // The change to the content is done async after rule view change const onRuleViewChanged = view.once("ruleview-changed"); diff --git a/devtools/client/inspector/shared/highlighters-overlay.js b/devtools/client/inspector/shared/highlighters-overlay.js index 02890e9e7740..02fabd539078 100644 --- a/devtools/client/inspector/shared/highlighters-overlay.js +++ b/devtools/client/inspector/shared/highlighters-overlay.js @@ -34,7 +34,7 @@ class HighlightersOverlay { */ constructor(inspector) { this.inspector = inspector; - this.inspectorFront = this.inspector.inspector; + this.inspectorFront = this.inspector.inspectorFront; this.store = this.inspector.store; this.target = this.inspector.target; this.telemetry = this.inspector.telemetry; diff --git a/devtools/client/inspector/shared/style-inspector-menu.js b/devtools/client/inspector/shared/style-inspector-menu.js index e89be2f00a9d..e4588ddf0c4a 100644 --- a/devtools/client/inspector/shared/style-inspector-menu.js +++ b/devtools/client/inspector/shared/style-inspector-menu.js @@ -414,7 +414,7 @@ StyleInspectorMenu.prototype = { let message; try { - const inspectorFront = this.inspector.inspector; + const inspectorFront = this.inspector.inspectorFront; const imageUrl = this._clickedNodeInfo.value.url; const data = await inspectorFront.getImageDataFromURL(imageUrl); message = await data.data.string(); diff --git a/devtools/client/inspector/shared/tooltips-overlay.js b/devtools/client/inspector/shared/tooltips-overlay.js index a05028e2552b..4a24f470c452 100644 --- a/devtools/client/inspector/shared/tooltips-overlay.js +++ b/devtools/client/inspector/shared/tooltips-overlay.js @@ -422,7 +422,7 @@ TooltipsOverlay.prototype = { naturalWidth = size.naturalWidth; naturalHeight = size.naturalHeight; } else { - const inspectorFront = this.view.inspector.inspector; + const inspectorFront = this.view.inspector.inspectorFront; const { data, size } = await inspectorFront.getImageDataFromURL( imageUrl, maxDim diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-03.js b/devtools/client/inspector/test/browser_inspector_highlighter-03.js index 13f9741deb12..9e1d0040509c 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-03.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-03.js @@ -66,7 +66,7 @@ add_task(async function() { await testActor.isNodeCorrectlyHighlighted(iframeBodySelector, is); info("Waiting for the element picker to deactivate."); - await inspector.inspector.nodePicker.stop(); + await inspector.inspectorFront.nodePicker.stop(); function moveMouseOver(selector, x, y) { info("Waiting for element " + selector + " to be highlighted"); @@ -76,6 +76,6 @@ add_task(async function() { y, options: { type: "mousemove" }, }); - return inspector.inspector.nodePicker.once("picker-node-hovered"); + return inspector.inspectorFront.nodePicker.once("picker-node-hovered"); } }); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-07.js b/devtools/client/inspector/test/browser_inspector_highlighter-07.js index bf8eda067687..ec680b194d88 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-07.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-07.js @@ -95,6 +95,6 @@ add_task(async function() { y, options: { type: "mousemove" }, }); - return inspector.inspector.nodePicker.once("picker-node-hovered"); + return inspector.inspectorFront.nodePicker.once("picker-node-hovered"); } }); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-by-type.js b/devtools/client/inspector/test/browser_inspector_highlighter-by-type.js index 1876111b0193..27d88eeca90f 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-by-type.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-by-type.js @@ -18,23 +18,27 @@ add_task(async function() { await unknownHighlighterTypeShouldntBeAccepted(inspector); }); -async function onlyOneInstanceOfMainHighlighter({ inspector }) { +async function onlyOneInstanceOfMainHighlighter({ inspectorFront }) { info("Check that the inspector always sends back the same main highlighter"); - const h1 = await inspector.getHighlighter(false); - const h2 = await inspector.getHighlighter(false); + const h1 = await inspectorFront.getHighlighter(false); + const h2 = await inspectorFront.getHighlighter(false); is(h1, h2, "The same highlighter front was returned"); is(h1.typeName, "highlighter", "The right front type was returned"); } -async function manyInstancesOfCustomHighlighters({ inspector }) { - const h1 = await inspector.getHighlighterByType("BoxModelHighlighter"); - const h2 = await inspector.getHighlighterByType("BoxModelHighlighter"); +async function manyInstancesOfCustomHighlighters({ inspectorFront }) { + const h1 = await inspectorFront.getHighlighterByType("BoxModelHighlighter"); + const h2 = await inspectorFront.getHighlighterByType("BoxModelHighlighter"); ok(h1 !== h2, "getHighlighterByType returns new instances every time (1)"); - const h3 = await inspector.getHighlighterByType("CssTransformHighlighter"); - const h4 = await inspector.getHighlighterByType("CssTransformHighlighter"); + const h3 = await inspectorFront.getHighlighterByType( + "CssTransformHighlighter" + ); + const h4 = await inspectorFront.getHighlighterByType( + "CssTransformHighlighter" + ); ok(h3 !== h4, "getHighlighterByType returns new instances every time (2)"); ok( h3 !== h1 && h3 !== h2, @@ -51,9 +55,11 @@ async function manyInstancesOfCustomHighlighters({ inspector }) { await h4.finalize(); } -async function showHideMethodsAreAvailable({ inspector }) { - const h1 = await inspector.getHighlighterByType("BoxModelHighlighter"); - const h2 = await inspector.getHighlighterByType("CssTransformHighlighter"); +async function showHideMethodsAreAvailable({ inspectorFront }) { + const h1 = await inspectorFront.getHighlighterByType("BoxModelHighlighter"); + const h2 = await inspectorFront.getHighlighterByType( + "CssTransformHighlighter" + ); ok("show" in h1, "Show method is present on the front API"); ok("show" in h2, "Show method is present on the front API"); @@ -64,7 +70,7 @@ async function showHideMethodsAreAvailable({ inspector }) { await h2.finalize(); } -async function unknownHighlighterTypeShouldntBeAccepted({ inspector }) { - const h = await inspector.getHighlighterByType("whatever"); +async function unknownHighlighterTypeShouldntBeAccepted({ inspectorFront }) { + const h = await inspectorFront.getHighlighterByType("whatever"); ok(!h, "No highlighter was returned for the invalid type"); } diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-cancel.js b/devtools/client/inspector/test/browser_inspector_highlighter-cancel.js index a366fd8f89bc..5073393d7bbf 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-cancel.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-cancel.js @@ -39,7 +39,7 @@ add_task(async function() { function cancelPickerByShortcut() { info("Key pressed. Waiting for picker to be canceled."); testActor.synthesizeKey({ key: "VK_ESCAPE", options: {} }); - return inspector.inspector.nodePicker.once("picker-node-canceled"); + return inspector.inspectorFront.nodePicker.once("picker-node-canceled"); } function moveMouseOver(selector) { diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_01.js index 6300cdbbcc2b..4a670dbd6716 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_01.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_01.js @@ -42,7 +42,7 @@ add_task(async function() { const { inspector, testActor } = await openInspectorForURL( "data:text/html;charset=utf-8," + encodeURIComponent(TEST_URL) ); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType(HIGHLIGHTER_TYPE); await isHiddenByDefault(testActor, highlighter); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_02.js index 9e2f1c14a509..80cb45f4e7e6 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_02.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-cssgrid_02.js @@ -23,7 +23,7 @@ add_task(async function() { const { inspector, testActor } = await openInspectorForURL( "data:text/html;charset=utf-8," + encodeURIComponent(TEST_URL) ); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType(HIGHLIGHTER_TYPE); info("Try to show the highlighter on the grid container"); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_01.js index d9f32f10f0ed..585858c3a2ed 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_01.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_01.js @@ -30,7 +30,7 @@ const SHAPE_TYPES = [ add_task(async function() { const { inspector, testActor } = await openInspectorForURL(TEST_URL); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType(HIGHLIGHTER_TYPE); await isHiddenByDefault(testActor, highlighter); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_02.js index 1f6e0c004de5..c057229180a1 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_02.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_02.js @@ -11,7 +11,7 @@ const HIGHLIGHTER_TYPE = "ShapesHighlighter"; add_task(async function() { const { inspector, testActor } = await openInspectorForURL(TEST_URL); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType(HIGHLIGHTER_TYPE); await polygonHasCorrectAttrs(testActor, inspector, highlighter); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_01.js index 12bddaafa864..088daa78de5d 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_01.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_01.js @@ -23,7 +23,7 @@ add_task(async function() { const { inspector, testActor } = await openInspectorForURL( "data:text/html;charset=utf-8," + encodeURI(TEST_URL) ); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType( "CssTransformHighlighter" diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_02.js index a9219eac2bac..4333d3669f50 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_02.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-csstransform_02.js @@ -22,7 +22,7 @@ const TEST_URL = URL_ROOT + "doc_inspector_highlighter_csstransform.html"; add_task(async function() { const { inspector, testActor } = await openInspectorForURL(TEST_URL); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType( "CssTransformHighlighter" diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-iframes_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-iframes_01.js index b0c06cefaebf..931a3fd11263 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-iframes_01.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-iframes_01.js @@ -70,7 +70,7 @@ add_task(async function() { ); info("Waiting for element picker to deactivate."); - await inspector.inspector.nodePicker.stop(); + await inspector.inspectorFront.nodePicker.stop(); function moveMouseOver(selector) { info("Waiting for element " + selector + " to be highlighted"); @@ -80,6 +80,8 @@ add_task(async function() { options: { type: "mousemove" }, center: true, }) - .then(() => inspector.inspector.nodePicker.once("picker-node-hovered")); + .then(() => + inspector.inspectorFront.nodePicker.once("picker-node-hovered") + ); } }); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_01.js index f648f5aae090..ca8d5722b882 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_01.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_01.js @@ -54,12 +54,12 @@ add_task(async function() { info("First child selection test Passed."); info("Stopping the picker"); - await toolbox.inspector.nodePicker.stop(); + await toolbox.inspectorFront.nodePicker.stop(); function doKeyHover(args) { info("Key pressed. Waiting for element to be highlighted/hovered"); testActor.synthesizeKey(args); - return toolbox.inspector.nodePicker.once("picker-node-hovered"); + return toolbox.inspectorFront.nodePicker.once("picker-node-hovered"); } function moveMouseOver(selector) { @@ -69,6 +69,6 @@ add_task(async function() { center: true, selector: selector, }); - return toolbox.inspector.nodePicker.once("picker-node-hovered"); + return toolbox.inspectorFront.nodePicker.once("picker-node-hovered"); } }); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_02.js index 8f684be541d5..537009116a55 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_02.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_02.js @@ -48,12 +48,12 @@ add_task(async function() { info("Previously chosen child is remembered. Passed."); info("Stopping the picker"); - await toolbox.inspector.nodePicker.stop(); + await toolbox.inspectorFront.nodePicker.stop(); function doKeyHover(args) { info("Key pressed. Waiting for element to be highlighted/hovered"); const onHighlighterReady = toolbox.once("highlighter-ready"); - const onPickerNodeHovered = toolbox.inspector.nodePicker.once( + const onPickerNodeHovered = toolbox.inspectorFront.nodePicker.once( "picker-node-hovered" ); testActor.synthesizeKey(args); @@ -63,7 +63,7 @@ add_task(async function() { function moveMouseOver(selector) { info("Waiting for element " + selector + " to be highlighted"); const onHighlighterReady = toolbox.once("highlighter-ready"); - const onPickerNodeHovered = toolbox.inspector.nodePicker.once( + const onPickerNodeHovered = toolbox.inspectorFront.nodePicker.once( "picker-node-hovered" ); testActor.synthesizeMouse({ diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js index 76e54ad68e54..262cbf56bf34 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js @@ -57,20 +57,20 @@ add_task(async function() { return promise.all([ inspector.selection.once("new-node-front"), inspector.once("inspector-updated"), - inspector.inspector.nodePicker.once("picker-stopped"), + inspector.inspectorFront.nodePicker.once("picker-stopped"), ]); } function doKeyStop(args) { info("Key pressed. Waiting for picker to be canceled"); testActor.synthesizeKey(args); - return inspector.inspector.nodePicker.once("picker-stopped"); + return inspector.inspectorFront.nodePicker.once("picker-stopped"); } function moveMouseOver(selector) { info("Waiting for element " + selector + " to be highlighted"); const onHighlighterReady = toolbox.once("highlighter-ready"); - const onPickerNodeHovered = inspector.inspector.nodePicker.once( + const onPickerNodeHovered = inspector.inspectorFront.nodePicker.once( "picker-node-hovered" ); testActor.synthesizeMouse({ diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_04.js b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_04.js index 5dec208c7393..7e799a84c0a9 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_04.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_04.js @@ -15,7 +15,7 @@ add_task(async function() { await startPicker(toolbox); info("Start using the picker by hovering over nodes"); - const onHover = toolbox.inspector.nodePicker.once("picker-node-hovered"); + const onHover = toolbox.inspectorFront.nodePicker.once("picker-node-hovered"); testActor.synthesizeMouse({ options: { type: "mousemove" }, center: true, @@ -24,7 +24,7 @@ add_task(async function() { await onHover; info("Press escape and wait for the picker to stop"); - const onPickerStopped = toolbox.inspector.nodePicker.once( + const onPickerStopped = toolbox.inspectorFront.nodePicker.once( "picker-node-canceled" ); testActor.synthesizeKey({ diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-rulers_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_01.js index 6e61397c1eff..814f03d93e37 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-rulers_01.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_01.js @@ -23,7 +23,7 @@ const RULERS_TEXT_STEP = 100; add_task(async function() { const { inspector, testActor } = await openInspectorForURL(TEST_URL); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType("RulersHighlighter"); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-rulers_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_02.js index 957eaa143bde..7f324432e912 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-rulers_02.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_02.js @@ -15,7 +15,7 @@ const ID = "rulers-highlighter-"; add_task(async function() { const { inspector, testActor } = await openInspectorForURL(TEST_URL); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType("RulersHighlighter"); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-rulers_03.js b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_03.js index 6517ffce084b..a387ac8ee49a 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-rulers_03.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-rulers_03.js @@ -17,7 +17,7 @@ var { Toolbox } = require("devtools/client/framework/toolbox"); add_task(async function() { const { inspector, testActor } = await openInspectorForURL(TEST_URL); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType("RulersHighlighter"); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-selector_01.js b/devtools/client/inspector/test/browser_inspector_highlighter-selector_01.js index e3245cba2382..fe31019f0e31 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-selector_01.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-selector_01.js @@ -49,7 +49,7 @@ requestLongerTimeout(5); add_task(async function() { const { inspector, testActor } = await openInspectorForURL(TEST_URL); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType("SelectorHighlighter"); const contextNode = await getNodeFront("body", inspector); diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-selector_02.js b/devtools/client/inspector/test/browser_inspector_highlighter-selector_02.js index 182ea5a99c62..a6ebab2dba90 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-selector_02.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-selector_02.js @@ -42,7 +42,7 @@ requestLongerTimeout(5); add_task(async function() { const { inspector, testActor } = await openInspectorForURL(TEST_URL); - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType("SelectorHighlighter"); for (const { inIframe, selector, containerCount } of TEST_DATA) { diff --git a/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js b/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js index 19efb9bf34d5..16e30cfaedcc 100644 --- a/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js +++ b/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js @@ -37,6 +37,6 @@ add_task(async function() { center: true, selector: selector, }); - return inspector.inspector.nodePicker.once("picker-node-hovered"); + return inspector.inspectorFront.nodePicker.once("picker-node-hovered"); } }); diff --git a/devtools/client/inspector/test/browser_inspector_iframe-navigation.js b/devtools/client/inspector/test/browser_inspector_iframe-navigation.js index 4d55720e4246..4d3433c9a50d 100644 --- a/devtools/client/inspector/test/browser_inspector_iframe-navigation.js +++ b/devtools/client/inspector/test/browser_inspector_iframe-navigation.js @@ -40,5 +40,5 @@ add_task(async function() { ok(isVisible, "Inspector is highlighting after iframe nav."); info("Stopping element picker."); - await toolbox.inspector.nodePicker.stop(); + await toolbox.inspectorFront.nodePicker.stop(); }); diff --git a/devtools/client/inspector/test/browser_inspector_picker-stop-on-tool-change.js b/devtools/client/inspector/test/browser_inspector_picker-stop-on-tool-change.js index 201697cf5c69..7a6fe371f25c 100644 --- a/devtools/client/inspector/test/browser_inspector_picker-stop-on-tool-change.js +++ b/devtools/client/inspector/test/browser_inspector_picker-stop-on-tool-change.js @@ -13,7 +13,9 @@ const TEST_URI = add_task(async function() { const { toolbox } = await openInspectorForURL(TEST_URI); - const pickerStopped = toolbox.inspector.nodePicker.once("picker-stopped"); + const pickerStopped = toolbox.inspectorFront.nodePicker.once( + "picker-stopped" + ); info("Starting the inspector picker"); await startPicker(toolbox); diff --git a/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js b/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js index 8c5b1f0cb443..5761fdb95310 100644 --- a/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js +++ b/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js @@ -67,7 +67,7 @@ add_task(async function() { await startPickerAndAssertSwitchToInspector(toolbox); info("Stoppping element picker."); - await toolbox.inspector.nodePicker.stop(); + await toolbox.inspectorFront.nodePicker.stop(); checkResults(); }); diff --git a/devtools/client/inspector/test/head.js b/devtools/client/inspector/test/head.js index 1c407e8b44a6..a1e714836b8d 100644 --- a/devtools/client/inspector/test/head.js +++ b/devtools/client/inspector/test/head.js @@ -76,7 +76,7 @@ var navigateTo = async function(inspector, url) { var startPicker = async function(toolbox, skipFocus) { info("Start the element picker"); toolbox.win.focus(); - await toolbox.inspector.nodePicker.start(); + await toolbox.inspectorFront.nodePicker.start(); if (!skipFocus) { // By default make sure the content window is focused since the picker may not focus // the content window by default. @@ -129,7 +129,9 @@ function pickElement(inspector, testActor, selector, x, y) { */ function hoverElement(inspector, testActor, selector, x, y) { info("Waiting for element " + selector + " to be hovered"); - const onHovered = inspector.inspector.nodePicker.once("picker-node-hovered"); + const onHovered = inspector.inspectorFront.nodePicker.once( + "picker-node-hovered" + ); testActor.synthesizeMouse({ selector, x, y, options: { type: "mousemove" } }); return onHovered; } @@ -504,7 +506,7 @@ async function poll(check, desc, attempts = 10, timeBetweenAttempts = 200) { */ const getHighlighterHelperFor = type => async function({ inspector, testActor }) { - const front = inspector.inspector; + const front = inspector.inspectorFront; const highlighter = await front.getHighlighterByType(type); let prefix = ""; diff --git a/devtools/client/shared/components/reps/reps.js b/devtools/client/shared/components/reps/reps.js index 51bd8a061926..75d3a6295b21 100644 --- a/devtools/client/shared/components/reps/reps.js +++ b/devtools/client/shared/components/reps/reps.js @@ -588,7 +588,7 @@ class Tree extends Component { super(props); this.state = { - seen: new Set() + autoExpanded: new Set() }; this.treeRef = _react2.default.createRef(); @@ -632,26 +632,36 @@ class Tree extends Component { } _autoExpand() { - const { autoExpandDepth, autoExpandNodeChildrenLimit } = this.props; - if (!autoExpandDepth) { + const { + autoExpandDepth, + autoExpandNodeChildrenLimit, + initiallyExpanded + } = this.props; + + if (!autoExpandDepth && !initiallyExpanded) { return; } // Automatically expand the first autoExpandDepth levels for new items. Do // not use the usual DFS infrastructure because we don't want to ignore - // collapsed nodes. + // collapsed nodes. Any initially expanded items will be expanded regardless + // of how deep they are. const autoExpand = (item, currentDepth) => { - if (currentDepth >= autoExpandDepth || this.state.seen.has(item)) { + const initial = initiallyExpanded && initiallyExpanded(item); + + if (!initial && currentDepth >= autoExpandDepth) { return; } const children = this.props.getChildren(item); - if (autoExpandNodeChildrenLimit && children.length > autoExpandNodeChildrenLimit) { + if (!initial && autoExpandNodeChildrenLimit && children.length > autoExpandNodeChildrenLimit) { return; } - this.props.onExpand(item); - this.state.seen.add(item); + if (!this.state.autoExpanded.has(item)) { + this.props.onExpand(item); + this.state.autoExpanded.add(item); + } const length = children.length; for (let i = 0; i < length; i++) { @@ -667,6 +677,14 @@ class Tree extends Component { } } else if (length != 0) { autoExpand(roots[0], 0); + + if (initiallyExpanded) { + for (let i = 1; i < length; i++) { + if (initiallyExpanded(roots[i])) { + autoExpand(roots[i], 0); + } + } + } } } @@ -7001,6 +7019,7 @@ class ObjectInspector extends Component { nodeExpand, nodeCollapse, recordTelemetryEvent, + setExpanded, roots } = this.props; @@ -7013,6 +7032,10 @@ class ObjectInspector extends Component { } else { nodeCollapse(item); } + + if (setExpanded) { + setExpanded(item, expand); + } } focusItem(item) { @@ -7052,6 +7075,7 @@ class ObjectInspector extends Component { const { autoExpandAll = true, autoExpandDepth = 1, + initiallyExpanded, focusable = true, disableWrap = false, expandedPaths, @@ -7067,6 +7091,7 @@ class ObjectInspector extends Component { autoExpandAll, autoExpandDepth, + initiallyExpanded, isExpanded: item => expandedPaths && expandedPaths.has(item.path), isExpandable: this.isNodeExpandable, @@ -7212,8 +7237,7 @@ function rootsChanged(props) { function releaseActors(state, client) { const actors = getActors(state); for (const actor of actors) { - // Ignore release failure, since the object actor may have been already GC. - client.releaseActor(actor).catch(() => {}); + client.releaseActor(actor); } } @@ -7586,4 +7610,4 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! /***/ }) /******/ }); -}); +}); \ No newline at end of file diff --git a/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js b/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js index 98ef934f145e..87e8ea77a681 100644 --- a/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js +++ b/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js @@ -236,34 +236,34 @@ class SwatchColorPickerTooltip extends SwatchBasedEditorTooltip { } _openEyeDropper() { - const { inspector, toolbox, telemetry } = this.inspector; + const { inspectorFront, toolbox, telemetry } = this.inspector; telemetry .getHistogramById(TELEMETRY_PICKER_EYEDROPPER_OPEN_COUNT) .add(true); // cancelling picker(if it is already selected) on opening eye-dropper - inspector.nodePicker.cancel(); + inspectorFront.nodePicker.cancel(); // pickColorFromPage will focus the content document. If the devtools are in a // separate window, the colorpicker tooltip will be closed before pickColorFromPage // resolves. Flip the flag early to avoid issues with onTooltipHidden(). this.eyedropperOpen = true; - inspector.pickColorFromPage({ copyOnSelect: false }).then(() => { + inspectorFront.pickColorFromPage({ copyOnSelect: false }).then(() => { // close the colorpicker tooltip so that only the eyedropper is open. this.hide(); this.tooltip.emit("eyedropper-opened"); }, console.error); - inspector.once("color-picked", color => { + inspectorFront.once("color-picked", color => { toolbox.win.focus(); this._selectColor(color); this._onEyeDropperDone(); }); - inspector.once("color-pick-canceled", () => { + inspectorFront.once("color-pick-canceled", () => { this._onEyeDropperDone(); }); } diff --git a/devtools/client/styleeditor/StyleEditorUI.jsm b/devtools/client/styleeditor/StyleEditorUI.jsm index 70bc261e60f3..9d7ddfb1b986 100644 --- a/devtools/client/styleeditor/StyleEditorUI.jsm +++ b/devtools/client/styleeditor/StyleEditorUI.jsm @@ -158,7 +158,7 @@ StyleEditorUI.prototype = { this._walker = this._toolbox.walker; try { - this._highlighter = await this._toolbox.inspector.getHighlighterByType( + this._highlighter = await this._toolbox.inspectorFront.getHighlighterByType( SELECTOR_HIGHLIGHTER_TYPE ); } catch (e) { diff --git a/devtools/client/webconsole/test/mochitest/head.js b/devtools/client/webconsole/test/mochitest/head.js index 6a1b708de0c1..3cf0078a8d34 100644 --- a/devtools/client/webconsole/test/mochitest/head.js +++ b/devtools/client/webconsole/test/mochitest/head.js @@ -1139,7 +1139,7 @@ function isReverseSearchInputFocused(hud) { */ async function selectNodeWithPicker(toolbox, testActor, selector) { const inspector = toolbox.getPanel("inspector"); - const inspectorFront = inspector.inspector; + const inspectorFront = inspector.inspectorFront; const onPickerStarted = inspectorFront.nodePicker.once("picker-started"); inspectorFront.nodePicker.start(); diff --git a/devtools/client/webreplay/mochitest/browser_rr_inspector-02.js b/devtools/client/webreplay/mochitest/browser_rr_inspector-02.js index f4a1050884c6..0df95da0ee99 100644 --- a/devtools/client/webreplay/mochitest/browser_rr_inspector-02.js +++ b/devtools/client/webreplay/mochitest/browser_rr_inspector-02.js @@ -28,7 +28,7 @@ add_task(async function() { info("Waiting for element picker to become active."); toolbox.win.focus(); - await toolbox.inspector.nodePicker.start(); + await toolbox.inspectorFront.nodePicker.start(); info("Moving mouse over div."); await moveMouseOver("#maindiv", 1, 1); @@ -50,6 +50,6 @@ add_task(async function() { y, options: { type: "mousemove" }, }); - return inspector.inspector.nodePicker.once("picker-node-hovered"); + return inspector.inspectorFront.nodePicker.once("picker-node-hovered"); } }); diff --git a/devtools/server/actors/replay/control.js b/devtools/server/actors/replay/control.js index 847289cb5d69..dc9912232b51 100644 --- a/devtools/server/actors/replay/control.js +++ b/devtools/server/actors/replay/control.js @@ -111,6 +111,9 @@ function ChildProcess(id, recording) { // The last point we paused at. this.lastPausePoint = null; + // Last reported memory usage for this child. + this.lastMemoryUsage = null; + // Manifests which this child needs to send asynchronously. this.asyncManifests = []; @@ -168,19 +171,25 @@ ChildProcess.prototype = { this.paused = false; this.manifest = manifest; - dumpv(`SendManifest #${this.id} ${JSON.stringify(manifest.contents)}`); + dumpv(`SendManifest #${this.id} ${stringify(manifest.contents)}`); RecordReplayControl.sendManifest(this.id, manifest.contents); }, // Called when the child's current manifest finishes. manifestFinished(response) { assert(!this.paused); - if (response && response.point) { - this.lastPausePoint = response.point; + if (response) { + if (response.point) { + this.lastPausePoint = response.point; + } + if (response.memoryUsage) { + this.lastMemoryUsage = response.memoryUsage; + } } this.paused = true; this.manifest.onFinished(response); this.manifest = null; + maybeDumpStatistics(); }, // Block until this child is paused. If maybeCreateCheckpoint is specified @@ -346,6 +355,15 @@ function CheckpointInfo() { // If the checkpoint is saved, the replaying child responsible for saving it // and scanning the region up to the next saved checkpoint. this.owner = null; + + // If the checkpoint is saved, the time it was assigned an owner. + this.assignTime = null; + + // If the checkpoint is saved and scanned, the time it finished being scanned. + this.scanTime = null; + + // If the checkpoint is saved and scanned, the duration of the scan. + this.scanDuration = null; } function getCheckpointInfo(id) { @@ -364,18 +382,22 @@ function timeSinceCheckpoint(id) { return time; } +// How much execution time is captured by a saved checkpoint. +function timeForSavedCheckpoint(id) { + const next = nextSavedCheckpoint(id); + let time = 0; + for (let i = id; i < next; i++) { + time += gCheckpoints[i].duration; + } + return time; +} + // The checkpoint up to which the recording runs. let gLastFlushCheckpoint = InvalidCheckpointId; -// The last saved checkpoint. -let gLastSavedCheckpoint = FirstCheckpointId; - // How often we want to flush the recording. const FlushMs = 0.5 * 1000; -// How often we want to save a checkpoint. -const SavedCheckpointMs = 0.25 * 1000; - function addSavedCheckpoint(checkpoint) { if (getCheckpointInfo(checkpoint).owner) { return; @@ -383,26 +405,16 @@ function addSavedCheckpoint(checkpoint) { const owner = pickReplayingChild(); getCheckpointInfo(checkpoint).owner = owner; + getCheckpointInfo(checkpoint).assignTime = Date.now(); owner.addSavedCheckpoint(checkpoint); - gLastSavedCheckpoint = checkpoint; } function addCheckpoint(checkpoint, duration) { assert(!getCheckpointInfo(checkpoint).duration); getCheckpointInfo(checkpoint).duration = duration; - - // Mark saved checkpoints as required, unless we haven't spawned any replaying - // children yet. - if ( - timeSinceCheckpoint(gLastSavedCheckpoint) >= SavedCheckpointMs && - gReplayingChildren.length > 0 - ) { - addSavedCheckpoint(checkpoint + 1); - } } function ownerChild(checkpoint) { - assert(checkpoint <= gLastSavedCheckpoint); while (!getCheckpointInfo(checkpoint).owner) { checkpoint--; } @@ -559,7 +571,14 @@ function scanRecording(checkpoint) { needSaveCheckpoints: child.flushNeedSaveCheckpoints(), }; }, - onFinished: child => child.scannedCheckpoints.add(checkpoint), + onFinished(child, { duration }) { + child.scannedCheckpoints.add(checkpoint); + const info = getCheckpointInfo(checkpoint); + if (!info.scanTime) { + info.scanTime = Date.now(); + info.scanDuration = duration; + } + }, }, checkpointExecutionPoint(checkpoint) ); @@ -1006,8 +1025,8 @@ function handleResumeManifestResponse({ consoleMessages.forEach(msg => gDebugger.onConsoleMessage(msg)); } - if (gDebugger && gDebugger.onNewScript) { - scripts.forEach(script => gDebugger.onNewScript(script)); + if (gDebugger) { + scripts.forEach(script => gDebugger._onNewScript(script)); } } @@ -1071,8 +1090,8 @@ function ensureFlushed() { spawnReplayingChildren(); } - // Checkpoints where the recording was flushed to disk are always saved. - // This allows the recording to be scanned as soon as it has been flushed. + // Checkpoints where the recording was flushed to disk are saved. This allows + // the recording to be scanned as soon as it has been flushed. addSavedCheckpoint(gLastFlushCheckpoint); // Flushing creates a new region of the recording for replaying children @@ -1156,7 +1175,7 @@ function Initialize(recordingChildId) { // eslint-disable-next-line no-unused-vars function ManifestFinished(id, response) { try { - dumpv(`ManifestFinished #${id} ${JSON.stringify(response)}`); + dumpv(`ManifestFinished #${id} ${stringify(response)}`); lookupChild(id).manifestFinished(response); } catch (e) { dump(`ERROR: ManifestFinished threw exception: ${e} ${e.stack}\n`); @@ -1331,6 +1350,75 @@ const gControl = { }, }; +/////////////////////////////////////////////////////////////////////////////// +// Statistics +/////////////////////////////////////////////////////////////////////////////// + +let lastDumpTime = Date.now(); + +function maybeDumpStatistics() { + const now = Date.now(); + if (now - lastDumpTime < 5000) { + return; + } + lastDumpTime = now; + + let delayTotal = 0; + let unscannedTotal = 0; + let timeTotal = 0; + let scanDurationTotal = 0; + + forSavedCheckpointsInRange( + FirstCheckpointId, + gLastFlushCheckpoint, + checkpoint => { + const checkpointTime = timeForSavedCheckpoint(checkpoint); + const info = getCheckpointInfo(checkpoint); + + timeTotal += checkpointTime; + if (info.scanTime) { + delayTotal += checkpointTime * (info.scanTime - info.assignTime); + scanDurationTotal += info.scanDuration; + } else { + unscannedTotal += checkpointTime; + } + } + ); + + const memoryUsage = []; + let totalSaved = 0; + + for (const child of gReplayingChildren) { + if (!child) { + continue; + } + totalSaved += child.savedCheckpoints.size; + if (!child.lastMemoryUsage) { + continue; + } + for (const [name, value] of Object.entries(child.lastMemoryUsage)) { + if (!memoryUsage[name]) { + memoryUsage[name] = 0; + } + memoryUsage[name] += value; + } + } + + const delay = delayTotal / timeTotal; + const overhead = scanDurationTotal / (timeTotal - unscannedTotal); + + dumpv(`Statistics:`); + dumpv(`Total recording time: ${timeTotal}`); + dumpv(`Unscanned fraction: ${unscannedTotal / timeTotal}`); + dumpv(`Average scan delay: ${delay}`); + dumpv(`Average scanning overhead: ${overhead}`); + + dumpv(`Saved checkpoints: ${totalSaved}`); + for (const [name, value] of Object.entries(memoryUsage)) { + dumpv(`Memory ${name}: ${value}`); + } +} + /////////////////////////////////////////////////////////////////////////////// // Utilities /////////////////////////////////////////////////////////////////////////////// @@ -1341,8 +1429,15 @@ function ConnectDebugger(dbg) { dbg._control = gControl; } +const startTime = Date.now(); + +// eslint-disable-next-line no-unused-vars +function currentTime() { + return (((Date.now() - startTime) / 10) | 0) / 100; +} + function dumpv(str) { - //dump(`[ReplayControl] ${str}\n`); + //dump(`[ReplayControl ${currentTime()}] ${str}\n`); } function assert(v) { @@ -1357,6 +1452,14 @@ function ThrowError(msg) { throw error; } +function stringify(object) { + const str = JSON.stringify(object); + if (str && str.length >= 4096) { + return `${str.substr(0, 4096)} TRIMMED ${str.length}`; + } + return str; +} + // eslint-disable-next-line no-unused-vars var EXPORTED_SYMBOLS = [ "Initialize", diff --git a/devtools/server/actors/replay/debugger.js b/devtools/server/actors/replay/debugger.js index 836670cc8309..c02acbaad475 100644 --- a/devtools/server/actors/replay/debugger.js +++ b/devtools/server/actors/replay/debugger.js @@ -123,9 +123,7 @@ ReplayDebugger.prototype = { }, _processResponse(request, response, divergeResponse) { - dumpv( - `SendRequest: ${JSON.stringify(request)} -> ${JSON.stringify(response)}` - ); + dumpv(`SendRequest: ${stringify(request)} -> ${stringify(response)}`); if (response.exception) { ThrowError(response.exception); } @@ -324,7 +322,7 @@ ReplayDebugger.prototype = { replayPushThreadPause() { // The thread has paused so that the user can interact with it. The child // will stay paused until this thread-wide pause has been popped. - assert(this._paused); + this._ensurePaused(); assert(!this._resumeCallback); if (++this._threadPauseCount == 1) { // There is no preferred direction of travel after an explicit pause. @@ -363,7 +361,8 @@ ReplayDebugger.prototype = { }, _performResume() { - assert(this._paused && !this._threadPauseCount); + this._ensurePaused(); + assert(!this._threadPauseCount); if (this._resumeCallback && !this._threadPauseCount) { const callback = this._resumeCallback; this._invalidateAfterUnpause(); @@ -528,11 +527,11 @@ ReplayDebugger.prototype = { return data.map(script => this._addScript(script)); }, - findAllConsoleMessages() { - const messages = this._sendRequestMainChild({ - type: "findConsoleMessages", - }); - return messages.map(this._convertConsoleMessage.bind(this)); + _onNewScript(data) { + if (this.onNewScript) { + const script = this._addScript(data); + this.onNewScript(script); + } }, ///////////////////////////////////////////////////////// @@ -544,7 +543,9 @@ ReplayDebugger.prototype = { if (source) { return source; } - return this._addSource(this._sendRequest({ type: "getSource", id })); + return this._addSource( + this._sendRequestMainChild({ type: "getSource", id }) + ); }, _addSource(data) { @@ -692,6 +693,13 @@ ReplayDebugger.prototype = { return message; }, + findAllConsoleMessages() { + const messages = this._sendRequestMainChild({ + type: "findConsoleMessages", + }); + return messages.map(this._convertConsoleMessage.bind(this)); + }, + ///////////////////////////////////////////////////////// // Handlers ///////////////////////////////////////////////////////// @@ -1362,4 +1370,12 @@ function isNonNullObject(obj) { return obj && (typeof obj == "object" || typeof obj == "function"); } +function stringify(object) { + const str = JSON.stringify(object); + if (str.length >= 4096) { + return `${str.substr(0, 4096)} TRIMMED ${str.length}`; + } + return str; +} + module.exports = ReplayDebugger; diff --git a/devtools/server/actors/replay/replay.js b/devtools/server/actors/replay/replay.js index a3b1aed43617..5976fef2e5f7 100644 --- a/devtools/server/actors/replay/replay.js +++ b/devtools/server/actors/replay/replay.js @@ -68,7 +68,9 @@ dbg.onNewGlobalObject = function(global) { // Utilities /////////////////////////////////////////////////////////////////////////////// -const dump = RecordReplayControl.dump; +const dump = str => { + RecordReplayControl.dump(`[Child #${RecordReplayControl.childId()}]: ${str}`); +}; function assert(v) { if (!v) { @@ -143,6 +145,24 @@ function isNonNullObject(obj) { return obj && (typeof obj == "object" || typeof obj == "function"); } +function getMemoryUsage() { + const memoryKinds = { + Generic: [1], + Snapshots: [2, 3, 4, 5, 6, 7], + ScriptHits: [8], + }; + + const rv = {}; + for (const [name, kinds] of Object.entries(memoryKinds)) { + let total = 0; + kinds.forEach(kind => { + total += RecordReplayControl.memoryUsage(kind); + }); + rv[name] = total; + } + return rv; +} + /////////////////////////////////////////////////////////////////////////////// // Persistent Script State /////////////////////////////////////////////////////////////////////////////// @@ -637,16 +657,16 @@ function ClearPausedState() { // The manifest that is currently being processed. let gManifest; -// When processing "resume" manifests this tracks the execution time when we -// started execution from the initial checkpoint. -let gTimeWhenResuming; +// When processing certain manifests this tracks the execution time when the +// manifest started executing. +let gManifestStartTime; // Handlers that run when a manifest is first received. This must be specified // for all manifests. const gManifestStartHandlers = { resume({ breakpoints }) { RecordReplayControl.resumeExecution(); - gTimeWhenResuming = RecordReplayControl.currentExecutionTime(); + gManifestStartTime = RecordReplayControl.currentExecutionTime(); breakpoints.forEach(ensurePositionHandler); }, @@ -663,6 +683,7 @@ const gManifestStartHandlers = { }, scanRecording(manifest) { + gManifestStartTime = RecordReplayControl.currentExecutionTime(); gManifestStartHandlers.runToPoint(manifest); }, @@ -801,7 +822,7 @@ const gManifestFinishedAfterCheckpointHandlers = { resume(_, point) { RecordReplayControl.manifestFinished({ point, - duration: RecordReplayControl.currentExecutionTime() - gTimeWhenResuming, + duration: RecordReplayControl.currentExecutionTime() - gManifestStartTime, consoleMessages: gNewConsoleMessages, scripts: gNewScripts, }); @@ -810,6 +831,7 @@ const gManifestFinishedAfterCheckpointHandlers = { }, runToPoint({ endpoint }, point) { + assert(endpoint.checkpoint >= point.checkpoint); if (!endpoint.position && point.checkpoint == endpoint.checkpoint) { RecordReplayControl.manifestFinished({ point }); } @@ -817,7 +839,13 @@ const gManifestFinishedAfterCheckpointHandlers = { scanRecording({ endpoint }, point) { if (point.checkpoint == endpoint) { - RecordReplayControl.manifestFinished({ point }); + const duration = + RecordReplayControl.currentExecutionTime() - gManifestStartTime; + RecordReplayControl.manifestFinished({ + point, + duration, + memoryUsage: getMemoryUsage(), + }); } }, }; @@ -892,6 +920,7 @@ let gFrameStepsFrameIndex = 0; // This must be specified for any manifest that uses ensurePositionHandler. const gManifestPositionHandlers = { resume(manifest, point) { + clearPositionHandlers(); RecordReplayControl.manifestFinished({ point, consoleMessages: gNewConsoleMessages, @@ -1066,7 +1095,7 @@ function getObjectData(id) { optimizedOut: object.optimizedOut, }; } - throwError("Unknown object kind"); + throwError(`Unknown object kind: ${object}`); } function getObjectProperties(object) { @@ -1249,6 +1278,7 @@ function getPauseData() { const names = getEnvironmentNames(env); rv.environments[id] = { data, names }; + addObject(data.callee); addEnvironment(data.parent); } diff --git a/toolkit/recordreplay/MemorySnapshot.cpp b/toolkit/recordreplay/MemorySnapshot.cpp index 6a625d15f50c..1ad8db973005 100644 --- a/toolkit/recordreplay/MemorySnapshot.cpp +++ b/toolkit/recordreplay/MemorySnapshot.cpp @@ -1312,5 +1312,12 @@ void RestoreMemoryToLastSavedDiffCheckpoint() { gMemoryInfo->mSnapshotThreadsShouldRestore.ActivateEnd(); } +size_t GetMemoryUsage(MemoryKind aKind) { + if (!gMemoryInfo) { + return 0; + } + return gMemoryInfo->mMemoryBalance[(size_t)aKind]; +} + } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/MemorySnapshot.h b/toolkit/recordreplay/MemorySnapshot.h index d9a8d4d0e194..03f63e42a072 100644 --- a/toolkit/recordreplay/MemorySnapshot.h +++ b/toolkit/recordreplay/MemorySnapshot.h @@ -126,6 +126,9 @@ void MemoryMove(void* aDst, const void* aSrc, size_t aSize); // dynamic code loading. void MemoryZero(void* aDst, size_t aSize); +// Get the amount of allocated memory used by data of the specified kind. +size_t GetMemoryUsage(MemoryKind aKind); + } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/ProcessRedirectDarwin.cpp b/toolkit/recordreplay/ProcessRedirectDarwin.cpp index b02cc61f3027..e88b6e1d2997 100644 --- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp +++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp @@ -340,10 +340,30 @@ static void MM_CFTypeOutputArg(MiddlemanCallContext& aCx) { MM_CFTypeOutput(aCx, arg, /* aOwnsReference = */ false); } +static void SendMessageToObject(const void* aObject, const char* aMessage) { + CallArguments arguments; + arguments.Arg<0, const void*>() = aObject; + arguments.Arg<1, SEL>() = sel_registerName(aMessage); + RecordReplayInvokeCall(gOriginal_objc_msgSend, &arguments); +} + // For APIs whose result will be released by the middleman's autorelease pool. static void MM_AutoreleaseCFTypeRval(MiddlemanCallContext& aCx) { auto& rval = aCx.mArguments->Rval(); MM_SystemOutput(aCx, &rval); + + if (rval) { + switch (aCx.mPhase) { + case MiddlemanCallPhase::MiddlemanOutput: + SendMessageToObject(rval, "retain"); + break; + case MiddlemanCallPhase::MiddlemanRelease: + SendMessageToObject(rval, "autorelease"); + break; + default: + break; + } + } } // For functions which have an input CFType value and also have side effects on diff --git a/toolkit/recordreplay/ipc/ChildIPC.cpp b/toolkit/recordreplay/ipc/ChildIPC.cpp index 871f7286b0ca..22d313b32904 100644 --- a/toolkit/recordreplay/ipc/ChildIPC.cpp +++ b/toolkit/recordreplay/ipc/ChildIPC.cpp @@ -345,6 +345,10 @@ void ReportFatalError(const Maybe& aMinidump, const char* aFormat, Thread::WaitForeverNoIdle(); } +size_t GetId() { + return gChannel->GetId(); +} + /////////////////////////////////////////////////////////////////////////////// // Vsyncs /////////////////////////////////////////////////////////////////////////////// diff --git a/toolkit/recordreplay/ipc/ChildInternal.h b/toolkit/recordreplay/ipc/ChildInternal.h index e99fa84b7b5f..62282a614b66 100644 --- a/toolkit/recordreplay/ipc/ChildInternal.h +++ b/toolkit/recordreplay/ipc/ChildInternal.h @@ -39,6 +39,9 @@ struct MinidumpInfo { void ReportFatalError(const Maybe& aMinidumpInfo, const char* aFormat, ...); +// Get the unique ID of this child. +size_t GetId(); + // Monitor used for various synchronization tasks. extern Monitor* gMonitor; diff --git a/toolkit/recordreplay/ipc/ChildProcess.cpp b/toolkit/recordreplay/ipc/ChildProcess.cpp index 812f5e3cb74a..d4df5df870fa 100644 --- a/toolkit/recordreplay/ipc/ChildProcess.cpp +++ b/toolkit/recordreplay/ipc/ChildProcess.cpp @@ -264,13 +264,17 @@ void ChildProcessInfo::WaitUntilPaused() { bool sentTerminateMessage = false; while (true) { - MonitorAutoLock lock(*gMonitor); + Maybe lock; + lock.emplace(*gMonitor); + + MaybeHandlePendingSyncMessage(); // Search for the first message received from this process. ChildProcessInfo* process = this; Message::UniquePtr msg = ExtractChildMessage(&process); if (msg) { + lock.reset(); OnIncomingMessage(*msg); if (IsPaused()) { return; diff --git a/toolkit/recordreplay/ipc/JSControl.cpp b/toolkit/recordreplay/ipc/JSControl.cpp index c5fd3a562f8a..8e9bb34e2c9c 100644 --- a/toolkit/recordreplay/ipc/JSControl.cpp +++ b/toolkit/recordreplay/ipc/JSControl.cpp @@ -565,6 +565,13 @@ static bool FetchContent(JSContext* aCx, HandleString aURL, // Recording/Replaying Methods /////////////////////////////////////////////////////////////////////////////// +static bool RecordReplay_ChildId(JSContext* aCx, unsigned aArgc, Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + args.rval().setInt32(child::GetId()); + return true; +} + static bool RecordReplay_AreThreadEventsDisallowed(JSContext* aCx, unsigned aArgc, Value* aVp) { CallArgs args = CallArgsFromVp(aArgc, aVp); @@ -971,6 +978,26 @@ static bool RecordReplay_Repaint(JSContext* aCx, unsigned aArgc, Value* aVp) { return true; } +static bool RecordReplay_MemoryUsage(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + if (!args.get(0).isNumber()) { + JS_ReportErrorASCII(aCx, "Bad memory kind"); + return false; + } + + size_t kind = args.get(0).toNumber(); + + if (kind >= (size_t) MemoryKind::Count) { + JS_ReportErrorASCII(aCx, "Memory kind out of range"); + return false; + } + + args.rval().setDouble(GetMemoryUsage((MemoryKind) kind)); + return true; +} + static bool RecordReplay_Dump(JSContext* aCx, unsigned aArgc, Value* aVp) { // This method is an alternative to dump() that can be used in places where // thread events are disallowed. @@ -1007,6 +1034,7 @@ static const JSFunctionSpec gMiddlemanMethods[] = { JS_FS_END}; static const JSFunctionSpec gRecordReplayMethods[] = { + JS_FN("childId", RecordReplay_ChildId, 0, 0), JS_FN("areThreadEventsDisallowed", RecordReplay_AreThreadEventsDisallowed, 0, 0), JS_FN("divergeFromRecording", RecordReplay_DivergeFromRecording, 0, 0), @@ -1025,6 +1053,7 @@ static const JSFunctionSpec gRecordReplayMethods[] = { JS_FN("findScriptHits", RecordReplay_FindScriptHits, 2, 0), JS_FN("getContent", RecordReplay_GetContent, 1, 0), JS_FN("repaint", RecordReplay_Repaint, 0, 0), + JS_FN("memoryUsage", RecordReplay_MemoryUsage, 0, 0), JS_FN("dump", RecordReplay_Dump, 1, 0), JS_FS_END}; diff --git a/toolkit/recordreplay/ipc/ParentForwarding.cpp b/toolkit/recordreplay/ipc/ParentForwarding.cpp index 5c78f3b9623c..3545de9239f9 100644 --- a/toolkit/recordreplay/ipc/ParentForwarding.cpp +++ b/toolkit/recordreplay/ipc/ParentForwarding.cpp @@ -260,89 +260,83 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol { return MsgProcessed; } - static void ForwardMessageSync(MiddlemanProtocol* aProtocol, - Message* aMessage, Message** aReply) { - PrintSpew("ForwardSyncMsg %s\n", - IPC::StringFromIPCMessageType(aMessage->type())); + Message* mSyncMessage = nullptr; + Message* mSyncMessageReply = nullptr; + bool mSyncMessageIsCall = false; - MOZ_RELEASE_ASSERT(!*aReply); - Message* nReply = new Message(); - if (!aProtocol->GetIPCChannel()->Send(aMessage, nReply)) { - MOZ_RELEASE_ASSERT(aProtocol->mSide == ipc::ParentSide); + void MaybeSendSyncMessage(bool aLockHeld) { + Maybe lock; + if (!aLockHeld) { + lock.emplace(*gMonitor); + } + + if (!mSyncMessage) { + return; + } + + PrintSpew("ForwardSyncMsg %s\n", + IPC::StringFromIPCMessageType(mSyncMessage->type())); + + MOZ_RELEASE_ASSERT(!mSyncMessageReply); + mSyncMessageReply = new Message(); + if (mSyncMessageIsCall + ? !mOpposite->GetIPCChannel()->Call(mSyncMessage, mSyncMessageReply) + : !mOpposite->GetIPCChannel()->Send(mSyncMessage, mSyncMessageReply)) { + MOZ_RELEASE_ASSERT(mSide == ipc::ChildSide); BeginShutdown(); } - MonitorAutoLock lock(*gMonitor); - *aReply = nReply; - gMonitor->Notify(); + mSyncMessage = nullptr; + + gMonitor->NotifyAll(); + } + + static void StaticMaybeSendSyncMessage(MiddlemanProtocol* aProtocol) { + aProtocol->MaybeSendSyncMessage(false); + } + + void HandleSyncMessage(const Message& aMessage, Message*& aReply, bool aCall) { + MOZ_RELEASE_ASSERT(mOppositeMessageLoop); + + mSyncMessage = new Message(); + mSyncMessage->CopyFrom(aMessage); + mSyncMessageIsCall = aCall; + + mOppositeMessageLoop->PostTask( + NewRunnableFunction("StaticMaybeSendSyncMessage", StaticMaybeSendSyncMessage, this)); + + if (mSide == ipc::ChildSide) { + AutoMarkMainThreadWaitingForIPDLReply blocked; + while (!mSyncMessageReply) { + MOZ_CRASH("NYI"); + } + } else { + MonitorAutoLock lock(*gMonitor); + + // If the main thread is blocked waiting for the recording child to pause, + // wake it up so it can call MaybeHandlePendingSyncMessage(). + gMonitor->NotifyAll(); + + while (!mSyncMessageReply) { + gMonitor->Wait(); + } + } + + aReply = mSyncMessageReply; + mSyncMessageReply = nullptr; + + PrintSpew("SyncMsgDone\n"); } virtual Result OnMessageReceived(const Message& aMessage, Message*& aReply) override { - MOZ_RELEASE_ASSERT(mOppositeMessageLoop); - - Message* nMessage = new Message(); - nMessage->CopyFrom(aMessage); - mOppositeMessageLoop->PostTask( - NewRunnableFunction("ForwardMessageSync", ForwardMessageSync, mOpposite, - nMessage, &aReply)); - - if (mSide == ipc::ChildSide) { - AutoMarkMainThreadWaitingForIPDLReply blocked; - while (!aReply) { - MOZ_CRASH("NYI"); - } - } else { - MonitorAutoLock lock(*gMonitor); - while (!aReply) { - gMonitor->Wait(); - } - } - - PrintSpew("SyncMsgDone\n"); + HandleSyncMessage(aMessage, aReply, false); return MsgProcessed; } - static void ForwardCallMessage(MiddlemanProtocol* aProtocol, - Message* aMessage, Message** aReply) { - PrintSpew("ForwardSyncCall %s\n", - IPC::StringFromIPCMessageType(aMessage->type())); - - MOZ_RELEASE_ASSERT(!*aReply); - Message* nReply = new Message(); - if (!aProtocol->GetIPCChannel()->Call(aMessage, nReply)) { - MOZ_RELEASE_ASSERT(aProtocol->mSide == ipc::ParentSide); - BeginShutdown(); - } - - MonitorAutoLock lock(*gMonitor); - *aReply = nReply; - gMonitor->Notify(); - } - virtual Result OnCallReceived(const Message& aMessage, Message*& aReply) override { - MOZ_RELEASE_ASSERT(mOppositeMessageLoop); - - Message* nMessage = new Message(); - nMessage->CopyFrom(aMessage); - mOppositeMessageLoop->PostTask( - NewRunnableFunction("ForwardCallMessage", ForwardCallMessage, mOpposite, - nMessage, &aReply)); - - if (mSide == ipc::ChildSide) { - AutoMarkMainThreadWaitingForIPDLReply blocked; - while (!aReply) { - MOZ_CRASH("NYI"); - } - } else { - MonitorAutoLock lock(*gMonitor); - while (!aReply) { - gMonitor->Wait(); - } - } - - PrintSpew("SyncCallDone\n"); + HandleSyncMessage(aMessage, aReply, true); return MsgProcessed; } @@ -357,6 +351,12 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol { static MiddlemanProtocol* gChildProtocol; static MiddlemanProtocol* gParentProtocol; +void MaybeHandlePendingSyncMessage() { + if (gParentProtocol) { + gParentProtocol->MaybeSendSyncMessage(true); + } +} + ipc::MessageChannel* ChannelToUIProcess() { return gChildProtocol->GetIPCChannel(); } diff --git a/toolkit/recordreplay/ipc/ParentInternal.h b/toolkit/recordreplay/ipc/ParentInternal.h index 943a324c21e7..c4b7c2f2baac 100644 --- a/toolkit/recordreplay/ipc/ParentInternal.h +++ b/toolkit/recordreplay/ipc/ParentInternal.h @@ -51,6 +51,11 @@ bool MainThreadIsWaitingForIPDLReply(); // to block while waiting on an IPDL reply from the child. void ResumeBeforeWaitingForIPDLReply(); +// Immediately forward any sync child->parent IPDL message. These are sent on +// the main thread, which might be blocked waiting for a response from the +// recording child and unable to run an event loop. +void MaybeHandlePendingSyncMessage(); + // Initialize state which handles incoming IPDL messages from the UI and // recording child processes. void InitializeForwarding();