chrome-devtools-frontend
Version:
Chrome DevTools UI
146 lines (121 loc) • 6.63 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 {DEFAULT_MODULE_CONFIGURATIONS, type ModuleConfigurations} from './ModuleConfiguration.js';
import type {WasmValue} from './WasmTypes.js';
import {type AsyncHostInterface, type WorkerInterface, WorkerRPC} from './WorkerRPC.js';
export class WorkerPlugin implements Chrome.DevTools.LanguageExtensionPlugin, AsyncHostInterface {
private readonly worker = new Worker('DevToolsPluginWorkerMain.bundle.js', {type: 'module'});
private readonly rpc = new WorkerRPC<AsyncHostInterface, WorkerInterface>(this.worker, this);
getWasmLinearMemory(offset: number, length: number, stopId: unknown): Promise<ArrayBuffer> {
return chrome.devtools.languageServices.getWasmLinearMemory(offset, length, stopId);
}
getWasmLocal(local: number, stopId: unknown): Promise<WasmValue> {
return chrome.devtools.languageServices.getWasmLocal(local, stopId);
}
getWasmGlobal(global: number, stopId: unknown): Promise<WasmValue> {
return chrome.devtools.languageServices.getWasmGlobal(global, stopId);
}
getWasmOp(op: number, stopId: unknown): Promise<WasmValue> {
return chrome.devtools.languageServices.getWasmOp(op, stopId);
}
reportResourceLoad(resourceUrl: string, status: {success: boolean, errorMessage?: string, size?: number}):
Promise<void> {
return chrome.devtools.languageServices.reportResourceLoad(resourceUrl, status);
}
static async create(
moduleConfigurations: ModuleConfigurations = DEFAULT_MODULE_CONFIGURATIONS,
logPluginApiCalls = false): Promise<WorkerPlugin> {
const plugin = new WorkerPlugin();
await plugin.rpc.sendMessage('hello', moduleConfigurations, logPluginApiCalls);
return plugin;
}
addRawModule(rawModuleId: string, symbolsURL: string, rawModule: Chrome.DevTools.RawModule): Promise<string[]|{
missingSymbolFiles: string[],
}> {
return this.rpc.sendMessage('addRawModule', rawModuleId, symbolsURL, rawModule);
}
removeRawModule(rawModuleId: string): Promise<void> {
return this.rpc.sendMessage('removeRawModule', rawModuleId);
}
sourceLocationToRawLocation(sourceLocation: Chrome.DevTools.SourceLocation):
Promise<Chrome.DevTools.RawLocationRange[]> {
return this.rpc.sendMessage('sourceLocationToRawLocation', sourceLocation);
}
rawLocationToSourceLocation(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.SourceLocation[]> {
return this.rpc.sendMessage('rawLocationToSourceLocation', rawLocation);
}
getScopeInfo(type: string): Promise<Chrome.DevTools.ScopeInfo> {
return this.rpc.sendMessage('getScopeInfo', type);
}
listVariablesInScope(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.Variable[]> {
return this.rpc.sendMessage('listVariablesInScope', rawLocation);
}
getFunctionInfo(rawLocation: Chrome.DevTools.RawLocation):
Promise<{frames: Chrome.DevTools.FunctionInfo[], missingSymbolFiles: string[]}|
{frames: Chrome.DevTools.FunctionInfo[]}|{missingSymbolFiles: string[]}> {
return this.rpc.sendMessage('getFunctionInfo', rawLocation);
}
getInlinedFunctionRanges(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.RawLocationRange[]> {
return this.rpc.sendMessage('getInlinedFunctionRanges', rawLocation);
}
getInlinedCalleesRanges(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.RawLocationRange[]> {
return this.rpc.sendMessage('getInlinedCalleesRanges', rawLocation);
}
getMappedLines(rawModuleId: string, sourceFileURL: string): Promise<number[]|undefined> {
return this.rpc.sendMessage('getMappedLines', rawModuleId, sourceFileURL);
}
evaluate(expression: string, context: Chrome.DevTools.RawLocation, stopId: unknown):
Promise<Chrome.DevTools.RemoteObject|Chrome.DevTools.ForeignObject|null> {
return this.rpc.sendMessage('evaluate', expression, context, stopId);
}
getProperties(objectId: Chrome.DevTools.RemoteObjectId): Promise<Chrome.DevTools.PropertyDescriptor[]> {
return this.rpc.sendMessage('getProperties', objectId);
}
releaseObject(objectId: Chrome.DevTools.RemoteObjectId): Promise<void> {
return this.rpc.sendMessage('releaseObject', objectId);
}
}
export interface Storage {
onChanged: Chrome.DevTools
.EventSink<(changes: Record<string, {oldValue: unknown, newValue: unknown}>, namespace: string) => unknown>;
local:
{set<ResultT>(value: ResultT): void, get<ResultT>(keys: ResultT, callback: (result: ResultT) => unknown): void};
}
export declare const chrome: Chrome.DevTools.Chrome&{storage?: Storage};
if (typeof chrome?.storage !== 'undefined') {
const {storage} = chrome;
const {languageServices} = chrome.devtools;
async function registerPlugin(moduleConfigurations: ModuleConfigurations, logPluginApiCalls: boolean):
Promise<Chrome.DevTools.LanguageExtensionPlugin> {
const plugin = await WorkerPlugin.create(moduleConfigurations, logPluginApiCalls);
await languageServices.registerLanguageExtensionPlugin(
plugin, 'C/C++ DevTools Support (DWARF)',
{language: 'WebAssembly', symbol_types: ['EmbeddedDWARF', 'ExternalDWARF']});
return plugin;
}
async function unregisterPlugin(plugin: Chrome.DevTools.LanguageExtensionPlugin): Promise<void> {
await languageServices.unregisterLanguageExtensionPlugin(plugin);
}
const defaultConfig = {
moduleConfigurations: DEFAULT_MODULE_CONFIGURATIONS,
logPluginApiCalls: false,
};
chrome.storage.local.get(defaultConfig, ({moduleConfigurations, logPluginApiCalls}) => {
let pluginPromise = registerPlugin(moduleConfigurations, logPluginApiCalls);
storage.onChanged.addListener(changes => {
// Note that this doesn't use optional chaining '?.' as it is problematic in vscode.
const moduleConfigurations =
changes['moduleConfigurations'] !== undefined ? changes['moduleConfigurations'].newValue : undefined;
const logPluginApiCalls =
changes['logPluginApiCalls'] !== undefined ? changes['logPluginApiCalls'].newValue : undefined;
if (moduleConfigurations || logPluginApiCalls !== undefined) {
storage.local.get(defaultConfig, ({moduleConfigurations, logPluginApiCalls}) => {
pluginPromise =
pluginPromise.then(unregisterPlugin).then(() => registerPlugin(moduleConfigurations, logPluginApiCalls));
});
}
});
});
}