// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../../../ui/kit/kit.js'; import * as i18n from '../../../core/i18n/i18n.js'; import * as Buttons from '../../../ui/components/buttons/buttons.js'; import * as UI from '../../../ui/legacy/legacy.js'; import * as Lit from '../../../ui/lit/lit.js'; import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js'; import valueInterpreterDisplayStyles from './valueInterpreterDisplay.css.js'; import { Endianness, format, getDefaultValueTypeMapping, getPointerAddress, isNumber, isPointer, isValidMode, VALUE_TYPE_MODE_LIST, ValueType, ValueTypeMode, } from './ValueInterpreterDisplayUtils.js'; const UIStrings = { /** * @description Tooltip text that appears when hovering over an unsigned interpretation of the memory under the Value Interpreter */ unsignedValue: '`Unsigned` value', /** * @description Tooltip text that appears when hovering over the element to change value type modes of under the Value Interpreter. Value type modes * are different ways of viewing a certain value, e.g.: 10 (decimal) can be 0xa in hexadecimal mode, or 12 in octal mode. */ changeValueTypeMode: 'Change mode', /** * @description Tooltip text that appears when hovering over a signed interpretation of the memory under the Value Interpreter */ signedValue: '`Signed` value', /** * @description Tooltip text that appears when hovering over a 'jump-to-address' button that is next to a pointer (32-bit or 64-bit) under the Value Interpreter */ jumpToPointer: 'Jump to address', /** * @description Tooltip text that appears when hovering over a 'jump-to-address' button that is next to a pointer (32-bit or 64-bit) with an invalid address under the Value Interpreter. */ addressOutOfRange: 'Address out of memory range', } as const; const str_ = i18n.i18n.registerUIStrings('panels/linear_memory_inspector/components/ValueInterpreterDisplay.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); const {render, nothing, html} = Lit; const SORTED_VALUE_TYPES = Array.from(getDefaultValueTypeMapping().keys()); export interface ValueDisplayData { buffer: ArrayBuffer; valueTypes: Set; endianness: Endianness; memoryLength: number; valueTypeModes?: Map; } export interface ViewInput { buffer: ArrayBuffer; valueTypes: ValueType[]; endianness: Endianness; memoryLength: number; valueTypeModes: Map; onValueTypeModeChange: (type: ValueType, mode: ValueTypeMode) => void; onJumpToAddressClicked: (address: number) => void; } type View = (input: ViewInput, output: undefined, target: HTMLElement) => void; export const DEFAULT_VIEW: View = (input: ViewInput, _output: undefined, target: HTMLElement): void => { function parse(signed: boolean, type: ValueType): string { return format( {buffer: input.buffer, endianness: input.endianness, type, signed, mode: input.valueTypeModes.get(type)}); } const parseSigned = parse.bind(this, true); const parseUnsigned = parse.bind(this, false); // Disabled until https://crbug.com/1079231 is fixed. // clang-format off render(html`
${input.valueTypes.map(type => { const address = isPointer(type)? getPointerAddress(type, input.buffer, input.endianness): 0; const jumpDisabled = Number.isNaN(address) || BigInt(address) >= BigInt(input.memoryLength); const signed = parseSigned(type); const unsigned = parseUnsigned(type); return isNumber(type) ? html` ${i18n.i18n.lockedString(type)}
${renderSignedAndUnsigned(signed, unsigned, type, input.valueTypeModes.get(type))}`: isPointer(type) ? html` ${i18n.i18n.lockedString(type)}
`: nothing; })}
`, target); // clang-format on }; function renderSignedAndUnsigned( signedValue: string, unsignedValue: string, type: ValueType, mode: ValueTypeMode|undefined): Lit.TemplateResult { const showSignedAndUnsigned = signedValue !== unsignedValue && mode !== ValueTypeMode.HEXADECIMAL && mode !== ValueTypeMode.OCTAL; const unsignedRendered = html`${unsignedValue}`; if (!showSignedAndUnsigned) { return unsignedRendered; } // Some values are too long to show in one line, we're putting them into the next line. const showInMultipleLines = type === ValueType.INT32 || type === ValueType.INT64; const signedRendered = html`${signedValue}`; if (showInMultipleLines) { return html`
${unsignedRendered} ${signedRendered}
`; } return html`
${unsignedRendered} ${signedRendered}
`; } export class ValueInterpreterDisplay extends UI.Widget.Widget { readonly #view: View; #endianness = Endianness.LITTLE; #buffer = new ArrayBuffer(0); #valueTypes = new Set(); #valueTypeModeConfig: Map = getDefaultValueTypeMapping(); #memoryLength = 0; #onValueTypeModeChange: (type: ValueType, mode: ValueTypeMode) => void = () => {}; #onJumpToAddressClicked: (address: number) => void = () => {}; constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) { super(element); this.#view = view; } set onValueTypeModeChange(callback: (type: ValueType, mode: ValueTypeMode) => void) { this.#onValueTypeModeChange = callback; this.performUpdate(); } get onValueTypeModeChange(): (type: ValueType, mode: ValueTypeMode) => void { return this.#onValueTypeModeChange; } set onJumpToAddressClicked(callback: (address: number) => void) { this.#onJumpToAddressClicked = callback; this.performUpdate(); } get onJumpToAddressClicked(): (address: number) => void { return this.#onJumpToAddressClicked; } get valueTypeModes(): Map { return this.#valueTypeModeConfig; } set valueTypeModes(modes: Map) { const newMap = getDefaultValueTypeMapping(); modes.forEach((mode, type) => { if (isValidMode(type, mode)) { newMap.set(type, mode); } }); this.#valueTypeModeConfig = newMap; this.requestUpdate(); } get valueTypes(): Set { return this.#valueTypes; } set valueTypes(valueTypes: Set) { this.#valueTypes = valueTypes; this.requestUpdate(); } get buffer(): ArrayBuffer { return this.#buffer; } set buffer(buffer: ArrayBuffer) { this.#buffer = buffer; this.requestUpdate(); } get endianness(): Endianness { return this.#endianness; } set endianness(endianness: Endianness) { this.#endianness = endianness; this.requestUpdate(); } get memoryLength(): number { return this.#memoryLength; } set memoryLength(memoryLength: number) { this.#memoryLength = memoryLength; this.requestUpdate(); } override performUpdate(): void { const valueTypes = SORTED_VALUE_TYPES.filter(type => this.#valueTypes.has(type)); this.#view( { buffer: this.#buffer, valueTypes, endianness: this.#endianness, memoryLength: this.#memoryLength, valueTypeModes: this.#valueTypeModeConfig, onValueTypeModeChange: this.#onValueTypeModeChange, onJumpToAddressClicked: this.#onJumpToAddressClicked, }, undefined, this.contentElement); } }