UNPKG

chrome-devtools-frontend

Version:
240 lines (208 loc) • 8.35 kB
// Copyright (c) 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../../core/common/common.js'; import * as i18n from '../../../core/i18n/i18n.js'; import * as UI from '../../legacy/legacy.js'; import type {AddressChangedEvent, MemoryRequestEvent, Settings, SettingsChangedEvent} from './LinearMemoryInspector.js'; import {LinearMemoryInspector} from './LinearMemoryInspector.js'; import type {LazyUint8Array} from './LinearMemoryInspectorController.js'; import {LinearMemoryInspectorController} from './LinearMemoryInspectorController.js'; import type {HighlightInfo} from './LinearMemoryViewerUtils.js'; const UIStrings = { /** *@description Label in the Linear Memory Inspector tool that serves as a placeholder if no inspections are open (i.e. nothing to see here). * Inspection hereby refers to viewing, navigating and understanding the memory through this tool. */ noOpenInspections: 'No open inspections', }; const str_ = i18n.i18n.registerUIStrings('ui/components/linear_memory_inspector/LinearMemoryInspectorPane.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); let inspectorInstance: LinearMemoryInspectorPaneImpl; let wrapperInstance: Wrapper; export class Wrapper extends UI.Widget.VBox { view: LinearMemoryInspectorPaneImpl; private constructor() { super(); this.view = LinearMemoryInspectorPaneImpl.instance(); } static instance(opts: { forceNew: boolean|null, } = {forceNew: null}): Wrapper { const {forceNew} = opts; if (!wrapperInstance || forceNew) { wrapperInstance = new Wrapper(); } return wrapperInstance; } wasShown(): void { this.view.show(this.contentElement); } } export class LinearMemoryInspectorPaneImpl extends Common.ObjectWrapper.eventMixin<EventTypes, typeof UI.Widget.VBox>( UI.Widget.VBox) { readonly #tabbedPane: UI.TabbedPane.TabbedPane; readonly #tabIdToInspectorView: Map<string, LinearMemoryInspectorView>; constructor() { super(false); const placeholder = document.createElement('div'); placeholder.textContent = i18nString(UIStrings.noOpenInspections); placeholder.style.display = 'flex'; this.#tabbedPane = new UI.TabbedPane.TabbedPane(); this.#tabbedPane.setPlaceholderElement(placeholder); this.#tabbedPane.setCloseableTabs(true); this.#tabbedPane.setAllowTabReorder(true, true); this.#tabbedPane.addEventListener(UI.TabbedPane.Events.TabClosed, this.#tabClosed, this); this.#tabbedPane.show(this.contentElement); this.#tabIdToInspectorView = new Map(); } static instance(): LinearMemoryInspectorPaneImpl { if (!inspectorInstance) { inspectorInstance = new LinearMemoryInspectorPaneImpl(); } return inspectorInstance; } // Introduced to access Views for testings. getViewForTabId(tabId: string): LinearMemoryInspectorView { const view = this.#tabIdToInspectorView.get(tabId); if (!view) { throw new Error(`No linear memory inspector view for given tab id: ${tabId}`); } return view; } create(tabId: string, title: string, arrayWrapper: LazyUint8Array, address?: number, highlightInfo?: HighlightInfo): void { const inspectorView = new LinearMemoryInspectorView(arrayWrapper, address, highlightInfo); this.#tabIdToInspectorView.set(tabId, inspectorView); this.#tabbedPane.appendTab(tabId, title, inspectorView, undefined, false, true); this.#tabbedPane.selectTab(tabId); } close(tabId: string): void { this.#tabbedPane.closeTab(tabId, false); } reveal(tabId: string, address?: number, highlightInfo?: HighlightInfo): void { const view = this.getViewForTabId(tabId); if (address !== undefined) { view.updateAddress(address); } if (highlightInfo !== undefined) { view.updateHighlightInfo(highlightInfo); } this.refreshView(tabId); this.#tabbedPane.selectTab(tabId); } refreshView(tabId: string): void { const view = this.getViewForTabId(tabId); view.refreshData(); } resetHighlightInfo(tabId: string): void { const view = this.getViewForTabId(tabId); view.updateHighlightInfo(undefined); } #tabClosed(event: Common.EventTarget.EventTargetEvent<UI.TabbedPane.EventData>): void { const {tabId} = event.data; this.#tabIdToInspectorView.delete(tabId); this.dispatchEventToListeners(Events.ViewClosed, tabId); } } export const enum Events { ViewClosed = 'ViewClosed', } export type EventTypes = { [Events.ViewClosed]: string, }; class LinearMemoryInspectorView extends UI.Widget.VBox { #memoryWrapper: LazyUint8Array; #address: number; #highlightInfo?: HighlightInfo; #inspector: LinearMemoryInspector; firstTimeOpen: boolean; constructor(memoryWrapper: LazyUint8Array, address: number|undefined = 0, highlightInfo?: HighlightInfo) { super(false); if (address < 0 || address >= memoryWrapper.length()) { throw new Error('Requested address is out of bounds.'); } this.#memoryWrapper = memoryWrapper; this.#address = address; this.#highlightInfo = highlightInfo; this.#inspector = new LinearMemoryInspector(); this.#inspector.addEventListener('memoryrequest', (event: MemoryRequestEvent) => { this.#memoryRequested(event); }); this.#inspector.addEventListener('addresschanged', (event: AddressChangedEvent) => { this.updateAddress(event.data); }); this.#inspector.addEventListener('settingschanged', (event: SettingsChangedEvent) => { // Stop event from bubbling up, since no element further up needs the event. event.stopPropagation(); this.saveSettings(event.data); }); this.contentElement.appendChild(this.#inspector); this.firstTimeOpen = true; } wasShown(): void { this.refreshData(); } saveSettings(settings: Settings): void { LinearMemoryInspectorController.instance().saveSettings(settings); } updateAddress(address: number): void { if (address < 0 || address >= this.#memoryWrapper.length()) { throw new Error('Requested address is out of bounds.'); } this.#address = address; } updateHighlightInfo(highlightInfo?: HighlightInfo): void { if (highlightInfo !== undefined) { if (highlightInfo.startAddress < 0 || highlightInfo.startAddress >= this.#memoryWrapper.length()) { throw new Error('Highlight info start address is out of bounds.'); } if (highlightInfo.size < 0) { throw new Error('Highlight size cannot be negative.'); } } this.#highlightInfo = highlightInfo; } refreshData(): void { void LinearMemoryInspectorController.getMemoryForAddress(this.#memoryWrapper, this.#address).then(({ memory, offset, }) => { let valueTypes; let valueTypeModes; let endianness; if (this.firstTimeOpen) { const settings = LinearMemoryInspectorController.instance().loadSettings(); valueTypes = settings.valueTypes; valueTypeModes = settings.modes; endianness = settings.endianness; this.firstTimeOpen = false; } this.#inspector.data = { memory, address: this.#address, memoryOffset: offset, outerMemoryLength: this.#memoryWrapper.length(), valueTypes, valueTypeModes, endianness, highlightInfo: this.#highlightInfo, }; }); } #memoryRequested(event: MemoryRequestEvent): void { const {start, end, address} = event.data; if (address < start || address >= end) { throw new Error('Requested address is out of bounds.'); } void LinearMemoryInspectorController.getMemoryRange(this.#memoryWrapper, start, end).then(memory => { this.#inspector.data = { memory: memory, address: address, memoryOffset: start, outerMemoryLength: this.#memoryWrapper.length(), }; }); } }