UNPKG

chrome-devtools-frontend

Version:
353 lines (318 loc) • 13.3 kB
/* * Copyright (C) 2008 Apple Inc. All Rights Reserved. * Copyright (C) 2011 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import * as Bindings from '../bindings/bindings.js'; import * as Common from '../common/common.js'; import * as Components from '../components/components.js'; import * as i18n from '../i18n/i18n.js'; import * as LinearMemoryInspector from '../linear_memory_inspector/linear_memory_inspector.js'; import * as ObjectUI from '../object_ui/object_ui.js'; import * as SDK from '../sdk/sdk.js'; import * as UI from '../ui/ui.js'; import {resolveScopeChain, resolveScopeInObject, resolveThisObject} from './SourceMapNamesResolver.js'; export const UIStrings = { /** *@description Loading indicator in Scope Sidebar Pane of the Sources panel */ loading: 'Loading...', /** *@description Not paused message element text content in Call Stack Sidebar Pane of the Sources panel */ notPaused: 'Not paused', /** *@description Empty placeholder in Scope Chain Sidebar Pane of the Sources panel */ noVariables: 'No variables', /** *@description Text in the Sources panel Scope pane describing a closure scope. *@example {func} PH1 */ closureS: 'Closure ({PH1})', /** *@description Text that refers to closure as a programming term */ closure: 'Closure', /** *@description Text in Scope Chain Sidebar Pane of the Sources panel */ exception: 'Exception', /** *@description Text in Scope Chain Sidebar Pane of the Sources panel */ returnValue: 'Return value', /** *@description A context menu item in the Scope View of the Sources Panel */ revealInMemoryInspectorPanel: 'Reveal in Memory Inspector panel', /** *@description Error message that shows up in the console if a buffer to be opened in the lienar memory inspector cannot be found. */ couldNotOpenLinearMemory: 'Could not open linear memory inspector: failed locating buffer.', }; const str_ = i18n.i18n.registerUIStrings('sources/ScopeChainSidebarPane.js', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); /** @type {!ScopeChainSidebarPane} */ let scopeChainSidebarPaneInstance; /** * @implements {UI.ContextFlavorListener.ContextFlavorListener} */ export class ScopeChainSidebarPane extends UI.Widget.VBox { /** * @private */ constructor() { super(true); this.registerRequiredCSS('sources/scopeChainSidebarPane.css', {enableLegacyPatching: false}); this._treeOutline = new ObjectUI.ObjectPropertiesSection.ObjectPropertiesSectionsTreeOutline(); this._treeOutline.registerRequiredCSS('sources/scopeChainSidebarPane.css', {enableLegacyPatching: false}); this._treeOutline.setShowSelectionOnKeyboardFocus(/* show */ true); this._expandController = new ObjectUI.ObjectPropertiesSection.ObjectPropertiesSectionsTreeExpandController(this._treeOutline); this._linkifier = new Components.Linkifier.Linkifier(); this._infoElement = document.createElement('div'); this._infoElement.className = 'gray-info-message'; this._infoElement.tabIndex = -1; this._update(); } static instance() { if (!scopeChainSidebarPaneInstance) { scopeChainSidebarPaneInstance = new ScopeChainSidebarPane(); } return scopeChainSidebarPaneInstance; } /** * @override * @param {?Object} object */ flavorChanged(object) { this._update(); } /** * @override */ focus() { if (this.hasFocus()) { return; } if (UI.Context.Context.instance().flavor(SDK.DebuggerModel.DebuggerPausedDetails)) { this._treeOutline.forceSelect(); } } async _update() { // The `resolveThisObject(callFrame)` and `resolveScopeChain(callFrame)` calls // below may take a while to complete, so indicate to the user that something // is happening (see https://crbug.com/1162416). this._infoElement.textContent = i18nString(UIStrings.loading); this.contentElement.removeChildren(); this.contentElement.appendChild(this._infoElement); this._linkifier.reset(); const callFrame = UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame); const [thisObject, scopeChain] = await Promise.all([resolveThisObject(callFrame), resolveScopeChain(callFrame)]); // By now the developer might have moved on, and we don't want to show stale // scope information, so check again that we're still on the same CallFrame. if (callFrame === UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame)) { const details = UI.Context.Context.instance().flavor(SDK.DebuggerModel.DebuggerPausedDetails); this._treeOutline.removeChildren(); if (!details || !callFrame || !scopeChain) { this._infoElement.textContent = i18nString(UIStrings.notPaused); return; } this.contentElement.removeChildren(); this.contentElement.appendChild(this._treeOutline.element); let foundLocalScope = false; for (let i = 0; i < scopeChain.length; ++i) { const scope = scopeChain[i]; const extraProperties = this._extraPropertiesForScope(scope, details, callFrame, thisObject, i === 0); if (scope.type() === Protocol.Debugger.ScopeType.Local) { foundLocalScope = true; } const section = this._createScopeSectionTreeElement(scope, extraProperties); if (scope.type() === Protocol.Debugger.ScopeType.Global) { section.collapse(); } else if (!foundLocalScope || scope.type() === Protocol.Debugger.ScopeType.Local) { section.expand(); } this._treeOutline.appendChild(section); if (i === 0) { section.select(/* omitFocus */ true); } } this._sidebarPaneUpdatedForTest(); } } /** * @param {!SDK.DebuggerModel.ScopeChainEntry} scope * @param {!Array.<!SDK.RemoteObject.RemoteObjectProperty>} extraProperties * @return {!ObjectUI.ObjectPropertiesSection.RootElement} */ _createScopeSectionTreeElement(scope, extraProperties) { let emptyPlaceholder = null; if (scope.type() === Protocol.Debugger.ScopeType.Local || Protocol.Debugger.ScopeType.Closure) { emptyPlaceholder = i18nString(UIStrings.noVariables); } let title = scope.typeName(); if (scope.type() === Protocol.Debugger.ScopeType.Closure) { const scopeName = scope.name(); if (scopeName) { title = i18nString(UIStrings.closureS, {PH1: UI.UIUtils.beautifyFunctionName(scopeName)}); } else { title = i18nString(UIStrings.closure); } } /** @type {?string} */ let subtitle = scope.description(); if (!title || title === subtitle) { subtitle = null; } const icon = scope.icon(); const titleElement = document.createElement('div'); titleElement.classList.add('scope-chain-sidebar-pane-section-header'); titleElement.classList.add('tree-element-title'); if (icon) { const iconElement = document.createElement('img'); iconElement.classList.add('scope-chain-sidebar-pane-section-icon'); iconElement.src = icon; titleElement.appendChild(iconElement); } titleElement.createChild('div', 'scope-chain-sidebar-pane-section-subtitle').textContent = subtitle; titleElement.createChild('div', 'scope-chain-sidebar-pane-section-title').textContent = title; const section = new ObjectUI.ObjectPropertiesSection.RootElement( resolveScopeInObject(scope), this._linkifier, emptyPlaceholder, /* ignoreHasOwnProperty */ true, extraProperties); section.title = titleElement; section.listItemElement.classList.add('scope-chain-sidebar-pane-section'); section.listItemElement.setAttribute('aria-label', title); this._expandController.watchSection(title + (subtitle ? ':' + subtitle : ''), section); return section; } /** * @param {!SDK.DebuggerModel.ScopeChainEntry} scope * @param {!SDK.DebuggerModel.DebuggerPausedDetails} details * @param {!SDK.DebuggerModel.CallFrame} callFrame * @param {?SDK.RemoteObject.RemoteObject} thisObject * @param {boolean} isFirstScope * @return {!Array.<!SDK.RemoteObject.RemoteObjectProperty>} */ _extraPropertiesForScope(scope, details, callFrame, thisObject, isFirstScope) { if (scope.type() !== Protocol.Debugger.ScopeType.Local || callFrame.script.isWasm()) { return []; } const extraProperties = []; if (thisObject) { extraProperties.push(new SDK.RemoteObject.RemoteObjectProperty('this', thisObject)); } if (isFirstScope) { const exception = details.exception(); if (exception) { extraProperties.push(new SDK.RemoteObject.RemoteObjectProperty( i18nString(UIStrings.exception), exception, undefined, undefined, undefined, undefined, undefined, /* synthetic */ true)); } const returnValue = callFrame.returnValue(); if (returnValue) { extraProperties.push(new SDK.RemoteObject.RemoteObjectProperty( i18nString(UIStrings.returnValue), returnValue, undefined, undefined, undefined, undefined, undefined, /* synthetic */ true, callFrame.setReturnValue.bind(callFrame))); } } return extraProperties; } _sidebarPaneUpdatedForTest() { } } /** * @type {OpenLinearMemoryInspector} */ let openLinearMemoryInspectorInstance; /** * @implements {UI.ContextMenu.Provider} */ export class OpenLinearMemoryInspector extends UI.Widget.VBox { /** * @param {{forceNew: ?boolean}} opts */ static instance(opts = {forceNew: null}) { const {forceNew} = opts; if (!openLinearMemoryInspectorInstance || forceNew) { openLinearMemoryInspectorInstance = new OpenLinearMemoryInspector(); } return openLinearMemoryInspectorInstance; } /** * @param {!SDK.RemoteObject.RemoteObject} obj */ _isMemoryObjectProperty(obj) { const isWasmMemory = obj.type === 'object' && obj.subtype && LinearMemoryInspector.LinearMemoryInspectorController.ACCEPTED_MEMORY_TYPES.includes(obj.subtype); if (isWasmMemory) { return true; } if (obj instanceof Bindings.DebuggerLanguagePlugins.ValueNode) { const valueNode = /** @type {!Bindings.DebuggerLanguagePlugins.ValueNode} */ obj; return valueNode.inspectableAddress !== undefined; } return false; } /** * @override * @param {!Event} event * @param {!UI.ContextMenu.ContextMenu} contextMenu * @param {!Object} target */ appendApplicableItems(event, contextMenu, target) { if (target instanceof ObjectUI.ObjectPropertiesSection.ObjectPropertyTreeElement) { if (target.property && target.property.value && this._isMemoryObjectProperty(target.property.value)) { contextMenu.debugSection().appendItem( i18nString(UIStrings.revealInMemoryInspectorPanel), this._openMemoryInspector.bind(this, target.property.value)); } } } /** * @param {!SDK.RemoteObject.RemoteObject} obj */ async _openMemoryInspector(obj) { const controller = LinearMemoryInspector.LinearMemoryInspectorController.LinearMemoryInspectorController.instance(); let address = 0; let memoryObj = obj; if (obj instanceof Bindings.DebuggerLanguagePlugins.ValueNode) { const valueNode = /** @type {!Bindings.DebuggerLanguagePlugins.ValueNode} */ obj; address = valueNode.inspectableAddress || 0; const callFrame = valueNode.callFrame; const response = await obj.debuggerModel()._agent.invoke_evaluateOnCallFrame({ callFrameId: callFrame.id, expression: 'memories[0]', }); const error = response.getError(); if (error) { console.error(error); Common.Console.Console.instance().error(i18nString(UIStrings.couldNotOpenLinearMemory)); } const runtimeModel = obj.debuggerModel().runtimeModel(); memoryObj = runtimeModel.createRemoteObject(response.result); } controller.openInspectorView(memoryObj, address); } }