UNPKG

chrome-devtools-frontend

Version:
302 lines (254 loc) • 10.9 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 './LinearMemoryNavigator.js'; import './LinearMemoryValueInterpreter.js'; import './LinearMemoryViewer.js'; import * as Common from '../common/common.js'; import * as LitHtml from '../third_party/lit-html/lit-html.js'; const {render, html} = LitHtml; import {Mode, AddressInputChangedEvent, HistoryNavigationEvent, LinearMemoryNavigatorData, Navigation, PageNavigationEvent} from './LinearMemoryNavigator.js'; import type {EndiannessChangedEvent, LinearMemoryValueInterpreterData, ValueTypeToggledEvent} from './LinearMemoryValueInterpreter.js'; import type {ByteSelectedEvent, LinearMemoryViewerData, ResizeEvent} from './LinearMemoryViewer.js'; import {VALUE_INTEPRETER_MAX_NUM_BYTES, Endianness, DEFAULT_MODE_MAPPING} from './ValueInterpreterDisplayUtils.js'; import {formatAddress, parseAddress} from './LinearMemoryInspectorUtils.js'; import type {ValueTypeModeChangedEvent} from './ValueInterpreterDisplay.js'; import * as i18n from '../i18n/i18n.js'; export const UIStrings = { /** *@description Tooltip text that appears when hovering over an invalid address in the address line in the Linear Memory Inspector *@example {0x00000000} PH1 *@example {0x00400000} PH2 */ addressHasToBeANumberBetweenSAnd: 'Address has to be a number between {PH1} and {PH2}', }; const str_ = i18n.i18n.registerUIStrings('linear_memory_inspector/LinearMemoryInspector.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); // If the LinearMemoryInspector only receives a portion // of the original Uint8Array to show, it requires information // on the 1. memoryOffset (at which index this portion starts), // and on the 2. outerMemoryLength (length of the original Uint8Array). export interface LinearMemoryInspectorData { memory: Uint8Array; address: number; memoryOffset: number; outerMemoryLength: number; } class AddressHistoryEntry implements Common.SimpleHistoryManager.HistoryEntry { private address = 0; private callback; constructor(address: number, callback: (x: number) => void) { if (address < 0) { throw new Error('Address should be a greater or equal to zero'); } this.address = address; this.callback = callback; } valid(): boolean { return true; } reveal(): void { this.callback(this.address); } } export class MemoryRequestEvent extends Event { data: {start: number, end: number, address: number}; constructor(start: number, end: number, address: number) { super('memory-request'); this.data = {start, end, address}; } } export class AddressChangedEvent extends Event { data: number; constructor(address: number) { super('address-changed'); this.data = address; } } export class LinearMemoryInspector extends HTMLElement { private readonly shadow = this.attachShadow({mode: 'open'}); private readonly history = new Common.SimpleHistoryManager.SimpleHistoryManager(10); private memory = new Uint8Array(); private memoryOffset = 0; private outerMemoryLength = 0; private address = 0; private currentNavigatorMode = Mode.Submitted; private currentNavigatorAddressLine = `${this.address}`; private numBytesPerPage = 4; private valueTypes = new Set(DEFAULT_MODE_MAPPING.keys()); private valueTypeModes = DEFAULT_MODE_MAPPING; private endianness = Endianness.Little; set data(data: LinearMemoryInspectorData) { if (data.address < data.memoryOffset || data.address > data.memoryOffset + data.memory.length || data.address < 0) { throw new Error('Address is out of bounds.'); } if (data.memoryOffset < 0) { throw new Error('Memory offset has to be greater or equal to zero.'); } this.memory = data.memory; this.memoryOffset = data.memoryOffset; this.outerMemoryLength = data.outerMemoryLength; this.setAddress(data.address); this.render(); } private render(): void { const {start, end} = this.getPageRangeForAddress(this.address, this.numBytesPerPage); const navigatorAddressToShow = this.currentNavigatorMode === Mode.Submitted ? formatAddress(this.address) : this.currentNavigatorAddressLine; const navigatorAddressIsValid = this.isValidAddress(navigatorAddressToShow); const invalidAddressMsg = i18nString( UIStrings.addressHasToBeANumberBetweenSAnd, {PH1: formatAddress(0), PH2: formatAddress(this.outerMemoryLength)}); const errorMsg = navigatorAddressIsValid ? undefined : invalidAddressMsg; // Disabled until https://crbug.com/1079231 is fixed. // clang-format off render(html` <style> :host { flex: auto; display: flex; } .view { width: 100%; display: flex; flex: 1; flex-direction: column; font-family: var(--monospace-font-family); font-size: var(--monospace-font-size); padding: 9px 12px 9px 7px; } devtools-linear-memory-inspector-navigator + devtools-linear-memory-inspector-viewer { margin-top: 12px; } .value-interpreter { display: flex; } </style> <div class="view"> <devtools-linear-memory-inspector-navigator .data=${{address: navigatorAddressToShow, valid: navigatorAddressIsValid, mode: this.currentNavigatorMode, error: errorMsg} as LinearMemoryNavigatorData} @refresh-requested=${this.onRefreshRequest} @address-input-changed=${this.onAddressChange} @page-navigation=${this.navigatePage} @history-navigation=${this.navigateHistory}></devtools-linear-memory-inspector-navigator> <devtools-linear-memory-inspector-viewer .data=${{memory: this.memory.slice(start - this.memoryOffset, end - this.memoryOffset), address: this.address, memoryOffset: start, focus: this.currentNavigatorMode === Mode.Submitted} as LinearMemoryViewerData} @byte-selected=${this.onByteSelected} @resize=${this.resize}> </devtools-linear-memory-inspector-viewer> </div> <div class="value-interpreter"> <devtools-linear-memory-inspector-interpreter .data=${{ value: this.memory.slice(this.address - this.memoryOffset, this.address + VALUE_INTEPRETER_MAX_NUM_BYTES).buffer, valueTypes: this.valueTypes, valueTypeModes: this.valueTypeModes, endianness: this.endianness } as LinearMemoryValueInterpreterData} @value-type-toggled=${this.onValueTypeToggled} @value-type-mode-changed=${this.onValueTypeModeChanged} @endianness-changed=${this.onEndiannessChanged}> </devtools-linear-memory-inspector-interpreter/> </div> `, this.shadow, { eventContext: this, }); // clang-format on } private onRefreshRequest(): void { const {start, end} = this.getPageRangeForAddress(this.address, this.numBytesPerPage); this.dispatchEvent(new MemoryRequestEvent(start, end, this.address)); } private onByteSelected(e: ByteSelectedEvent): void { this.currentNavigatorMode = Mode.Submitted; const addressInRange = Math.max(0, Math.min(e.data, this.outerMemoryLength - 1)); this.jumpToAddress(addressInRange); } private onEndiannessChanged(e: EndiannessChangedEvent): void { this.endianness = e.data; this.render(); } private isValidAddress(address: string): boolean { const newAddress = parseAddress(address); return newAddress !== undefined && newAddress >= 0 && newAddress < this.outerMemoryLength; } private onAddressChange(e: AddressInputChangedEvent): void { const {address, mode} = e.data; const isValid = this.isValidAddress(address); const newAddress = parseAddress(address); this.currentNavigatorAddressLine = address; if (newAddress !== undefined && isValid) { this.currentNavigatorMode = mode; this.jumpToAddress(newAddress); return; } if (mode === Mode.Submitted && !isValid) { this.currentNavigatorMode = Mode.InvalidSubmit; } else { this.currentNavigatorMode = Mode.Edit; } this.render(); } private onValueTypeToggled(e: ValueTypeToggledEvent): void { const {type, checked} = e.data; if (checked) { this.valueTypes.add(type); } else { this.valueTypes.delete(type); } this.render(); } private onValueTypeModeChanged(e: ValueTypeModeChangedEvent): void { e.stopImmediatePropagation(); const {type, mode} = e.data; this.valueTypeModes.set(type, mode); this.render(); } private navigateHistory(e: HistoryNavigationEvent): boolean { return e.data === Navigation.Forward ? this.history.rollover() : this.history.rollback(); } private navigatePage(e: PageNavigationEvent): void { const newAddress = e.data === Navigation.Forward ? this.address + this.numBytesPerPage : this.address - this.numBytesPerPage; const addressInRange = Math.max(0, Math.min(newAddress, this.outerMemoryLength - 1)); this.jumpToAddress(addressInRange); } private jumpToAddress(address: number): void { if (address < 0 || address >= this.outerMemoryLength) { console.warn(`Specified address is out of bounds: ${address}`); return; } this.setAddress(address); this.update(); } private getPageRangeForAddress(address: number, numBytesPerPage: number): {start: number, end: number} { const pageNumber = Math.floor(address / numBytesPerPage); const pageStartAddress = pageNumber * numBytesPerPage; const pageEndAddress = Math.min(pageStartAddress + numBytesPerPage, this.outerMemoryLength); return {start: pageStartAddress, end: pageEndAddress}; } private resize(event: ResizeEvent): void { this.numBytesPerPage = event.data; this.update(); } private update(): void { const {start, end} = this.getPageRangeForAddress(this.address, this.numBytesPerPage); if (start < this.memoryOffset || end > this.memoryOffset + this.memory.length) { this.dispatchEvent(new MemoryRequestEvent(start, end, this.address)); } else { this.render(); } } private setAddress(address: number): void { const historyEntry = new AddressHistoryEntry(address, () => this.jumpToAddress(address)); this.history.push(historyEntry); this.address = address; this.dispatchEvent(new AddressChangedEvent(this.address)); } } customElements.define('devtools-linear-memory-inspector-inspector', LinearMemoryInspector); declare global { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface HTMLElementTagNameMap { 'devtools-linear-memory-inspector-inspector': LinearMemoryInspector; } }