chrome-devtools-frontend
Version:
Chrome DevTools UI
270 lines (237 loc) • 9.98 kB
text/typescript
// 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<ValueType>;
endianness: Endianness;
memoryLength: number;
valueTypeModes?: Map<ValueType, ValueTypeMode>;
}
export interface ViewInput {
buffer: ArrayBuffer;
valueTypes: ValueType[];
endianness: Endianness;
memoryLength: number;
valueTypeModes: Map<ValueType, ValueTypeMode>;
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`
<style>${UI.inspectorCommonStyles}</style>
<style>${valueInterpreterDisplayStyles}</style>
<div class="value-types">
${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`
<span class="value-type-cell selectable-text">${i18n.i18n.lockedString(type)}</span>
<div>
<select title=${i18nString(UIStrings.changeValueTypeMode)}
data-mode-settings="true"
jslog=${VisualLogging.dropDown('linear-memory-inspector.value-type-mode').track({change: true})}
@change=${(e: Event) =>
input.onValueTypeModeChange(type, (e.target as HTMLSelectElement).value as ValueTypeMode)}>
${VALUE_TYPE_MODE_LIST.filter(x => isValidMode(type, x)).map(mode => {
return html`
<option value=${mode} .selected=${input.valueTypeModes.get(type) === mode}
jslog=${VisualLogging.item(mode).track({click: true})}>${
i18n.i18n.lockedString(mode)}
</option>`;
})}
</select>
</div>
${renderSignedAndUnsigned(signed, unsigned, type, input.valueTypeModes.get(type))}`:
isPointer(type) ? html`
<span class="value-type-cell-no-mode value-type-cell selectable-text">${i18n.i18n.lockedString(type)}</span>
<div class="value-type-cell">
<div class="value-type-value-with-link" data-value="true">
<span class="selectable-text">${unsigned}</span>
<devtools-button
data-jump="true"
title=${jumpDisabled ? i18nString(UIStrings.addressOutOfRange) : i18nString(UIStrings.jumpToPointer)}
.disabled=${jumpDisabled}
jslog=${VisualLogging.action('linear-memory-inspector.jump-to-address').track({click: true})}
@click=${() => input.onJumpToAddressClicked(Number(address))}
.variant=${Buttons.Button.Variant.ICON_TOGGLE}
.iconName=${'open-externally'}
.size=${Buttons.Button.Size.SMALL}>
</devtools-button>
</div>
</div>`: nothing;
})}
</div>
`, 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`<span class="value-type-cell selectable-text" title=${
i18nString(UIStrings.unsignedValue)} data-value="true">${unsignedValue}</span>`;
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`<span class="selectable-text" data-value="true" title=${
i18nString(UIStrings.signedValue)}>${signedValue}</span>`;
if (showInMultipleLines) {
return html`
<div class="value-type-cell">
${unsignedRendered}
${signedRendered}
</div>
`;
}
return html`
<div class="value-type-cell" style="flex-direction: row;">
${unsignedRendered}
<span class="signed-divider"></span>
${signedRendered}
</div>
`;
}
export class ValueInterpreterDisplay extends UI.Widget.Widget {
readonly #view: View;
#endianness = Endianness.LITTLE;
#buffer = new ArrayBuffer(0);
#valueTypes = new Set<ValueType>();
#valueTypeModeConfig: Map<ValueType, ValueTypeMode> = 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<ValueType, ValueTypeMode> {
return this.#valueTypeModeConfig;
}
set valueTypeModes(modes: Map<ValueType, ValueTypeMode>) {
const newMap = getDefaultValueTypeMapping();
modes.forEach((mode, type) => {
if (isValidMode(type, mode)) {
newMap.set(type, mode);
}
});
this.#valueTypeModeConfig = newMap;
this.requestUpdate();
}
get valueTypes(): Set<ValueType> {
return this.#valueTypes;
}
set valueTypes(valueTypes: Set<ValueType>) {
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);
}
}