UNPKG

chrome-devtools-frontend

Version:
146 lines (121 loc) • 6.63 kB
// 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)); }); } }); }); }