chrome-devtools-frontend
Version:
Chrome DevTools UI
251 lines (232 loc) • 8.49 kB
text/typescript
// Copyright 2023 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 type {Chrome} from '../../../extension-api/ExtensionAPI.js';
import type {Value, WasmInterface} from '../src/CustomFormatters.js';
import {WorkerPlugin} from '../src/DevToolsPluginHost.js';
import type {WasmValue} from '../src/WasmTypes.js';
import type {HostInterface} from '../src/WorkerRPC.js';
import type {Debugger} from './RealBackend.js';
export class TestHostInterface implements HostInterface {
getWasmLinearMemory(_offset: number, _length: number, _stopId: unknown): ArrayBuffer {
throw new Error('Method not implemented.');
}
getWasmLocal(_local: number, _stopId: unknown): WasmValue {
throw new Error('Method not implemented.');
}
getWasmGlobal(_global: number, _stopId: unknown): WasmValue {
throw new Error('Method not implemented.');
}
getWasmOp(_op: number, _stopId: unknown): WasmValue {
throw new Error('Method not implemented.');
}
reportResourceLoad(
_resourceUrl: string,
_status: {success: boolean, errorMessage?: string|undefined, size?: number|undefined}): Promise<void> {
return Promise.resolve();
}
}
export function makeURL(path: string): string {
return new URL(path, document.baseURI).href;
}
export async function createWorkerPlugin(debug?: Debugger): Promise<Chrome.DevTools.LanguageExtensionPlugin> {
return await WorkerPlugin.create([], true).then(p => {
if (debug) {
p.getWasmLinearMemory = debug.getWasmLinearMemory.bind(debug);
p.getWasmLocal = debug.getWasmLocal.bind(debug);
p.getWasmGlobal = debug.getWasmGlobal.bind(debug);
p.getWasmOp = debug.getWasmOp.bind(debug);
}
/* eslint-disable-next-line no-debugger */
debugger; // Halt in the debugger to let developers set breakpoints in C++.
return p;
});
}
export function relativePathname(url: URL, base: URL): string {
const baseSplit = base.pathname.split('/');
const urlSplit = url.pathname.split('/');
let i = 0;
for (; i < Math.min(baseSplit.length, urlSplit.length); ++i) {
if (baseSplit[i] !== urlSplit[i]) {
break;
}
}
const result = new Array(baseSplit.length - i);
result.fill('..');
result.push(...urlSplit.slice(i).filter(p => p.length > 0));
return result.join('/');
}
export function nonNull<T>(value: T|null|undefined): T {
assert.exists(value);
return value;
}
export function remoteObject(value: Chrome.DevTools.RemoteObject|Chrome.DevTools.ForeignObject|null):
Chrome.DevTools.RemoteObject {
assert.exists(value);
assert(value.type !== 'reftype');
return value;
}
export class TestWasmInterface implements WasmInterface {
memory = new ArrayBuffer(0);
locals = new Map<number, WasmValue>();
globals = new Map<number, WasmValue>();
stack = new Map<number, WasmValue>();
readMemory(offset: number, length: number): Uint8Array<ArrayBuffer> {
return new Uint8Array(this.memory, offset, length);
}
getOp(op: number): WasmValue {
const val = this.stack.get(op);
if (val !== undefined) {
return val;
}
throw new Error(`No stack entry ${op}`);
}
getLocal(local: number): WasmValue {
const val = this.locals.get(local);
if (val !== undefined) {
return val;
}
throw new Error(`No local ${local}`);
}
getGlobal(global: number): WasmValue {
const val = this.globals.get(global);
if (val !== undefined) {
return val;
}
throw new Error(`No global ${global}`);
}
}
export class TestValue implements Value {
private dataView: DataView<ArrayBuffer>;
members: {[key: string]: TestValue, [key: number]: TestValue};
location: number;
size: number;
typeNames: string[];
static fromInt8(value: number, typeName = 'int8_t'): TestValue {
const content = new DataView(new ArrayBuffer(1));
content.setInt8(0, value);
return new TestValue(content, typeName);
}
static fromInt16(value: number, typeName = 'int16_t'): TestValue {
const content = new DataView(new ArrayBuffer(2));
content.setInt16(0, value, true);
return new TestValue(content, typeName);
}
static fromInt32(value: number, typeName = 'int32_t'): TestValue {
const content = new DataView(new ArrayBuffer(4));
content.setInt32(0, value, true);
return new TestValue(content, typeName);
}
static fromInt64(value: bigint, typeName = 'int64_t'): TestValue {
const content = new DataView(new ArrayBuffer(8));
content.setBigInt64(0, value, true);
return new TestValue(content, typeName);
}
static fromUint8(value: number, typeName = 'uint8_t'): TestValue {
const content = new DataView(new ArrayBuffer(1));
content.setUint8(0, value);
return new TestValue(content, typeName);
}
static fromUint16(value: number, typeName = 'uint16_t'): TestValue {
const content = new DataView(new ArrayBuffer(2));
content.setUint16(0, value, true);
return new TestValue(content, typeName);
}
static fromUint32(value: number, typeName = 'uint32_t'): TestValue {
const content = new DataView(new ArrayBuffer(4));
content.setUint32(0, value, true);
return new TestValue(content, typeName);
}
static fromUint64(value: bigint, typeName = 'uint64_t'): TestValue {
const content = new DataView(new ArrayBuffer(8));
content.setBigUint64(0, value, true);
return new TestValue(content, typeName);
}
static fromFloat32(value: number, typeName = 'float'): TestValue {
const content = new DataView(new ArrayBuffer(4));
content.setFloat32(0, value, true);
return new TestValue(content, typeName);
}
static fromFloat64(value: number, typeName = 'double'): TestValue {
const content = new DataView(new ArrayBuffer(8));
content.setFloat64(0, value, true);
return new TestValue(content, typeName);
}
static pointerTo(pointeeOrElements: TestValue|TestValue[], address?: number): TestValue {
const content = new DataView(new ArrayBuffer(4));
const elements = Array.isArray(pointeeOrElements) ? pointeeOrElements : [pointeeOrElements];
address = address ?? elements[0].location;
content.setUint32(0, address, true);
const space = elements[0].typeNames[0].endsWith('*') ? '' : ' ';
const members: Record<string|number, TestValue> = {'*': elements[0]};
for (let i = 0; i < elements.length; ++i) {
members[i] = elements[i];
}
const value = new TestValue(content, `${elements[0].typeNames[0]}${space}*`, members);
return value;
}
static fromMembers(typeName: string, members: {[key: string]: TestValue, [key: number]: TestValue}): TestValue {
return new TestValue(new DataView(new ArrayBuffer(0)), typeName, members);
}
asInt8(): number {
return this.dataView.getInt8(0);
}
asInt16(): number {
return this.dataView.getInt16(0, true);
}
asInt32(): number {
return this.dataView.getInt32(0, true);
}
asInt64(): bigint {
return this.dataView.getBigInt64(0, true);
}
asUint8(): number {
return this.dataView.getUint8(0);
}
asUint16(): number {
return this.dataView.getUint16(0, true);
}
asUint32(): number {
return this.dataView.getUint32(0, true);
}
asUint64(): bigint {
return this.dataView.getBigUint64(0, true);
}
asFloat32(): number {
return this.dataView.getFloat32(0, true);
}
asFloat64(): number {
return this.dataView.getFloat64(0, true);
}
asDataView(offset?: number, size?: number): DataView<ArrayBuffer> {
offset = this.location + (offset ?? 0);
size = Math.min(size ?? this.size, this.size - Math.max(0, offset));
return new DataView(this.dataView.buffer, offset, size);
}
getMembers(): string[] {
return Object.keys(this.members);
}
$(member: string|number): Value {
if (typeof member === 'number' || !member.includes('.')) {
return this.members[member];
}
let value: Value = this;
for (const prop of member.split('.')) {
value = value.$(prop);
}
return value;
}
constructor(
content: DataView<ArrayBuffer>, typeName: string,
members?: {[key: string]: TestValue, [key: number]: TestValue}) {
this.location = 0;
this.size = content.byteLength;
this.typeNames = [typeName];
this.members = members || {};
this.dataView = content;
}
}
declare global {
/* eslint-disable-next-line @typescript-eslint/naming-convention */
let __karma__: unknown;
}