blob: b13592a7a7c7ba2a66819f2d4ccf5b53b3363a18 [file]
// Copyright 2020 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 * as SDK from '../../core/sdk/sdk.js';
import * as Bindings from '../bindings/bindings.js';
import type {Chrome} from '../../../extension-api/ExtensionAPI.js'; // eslint-disable-line rulesdir/es_modules_import
import {PrivateAPI} from './ExtensionAPI.js';
export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.DebuggerLanguagePlugin {
private readonly supportedScriptTypes: {
language: string,
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
// eslint-disable-next-line @typescript-eslint/naming-convention
symbol_types: Array<string>,
};
private readonly port: MessagePort;
private nextRequestId: number;
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private pendingRequests: Map<any, any>;
constructor(
name: string, supportedScriptTypes: {
language: string,
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
// eslint-disable-next-line @typescript-eslint/naming-convention
symbol_types: Array<string>,
},
port: MessagePort) {
super(name);
this.supportedScriptTypes = supportedScriptTypes;
this.port = port;
this.port.onmessage = this.onResponse.bind(this);
this.nextRequestId = 0;
this.pendingRequests = new Map();
}
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private sendRequest(method: string, parameters: any): Promise<any> {
return new Promise((resolve, reject) => {
const requestId = this.nextRequestId++;
this.pendingRequests.set(requestId, {resolve, reject});
this.port.postMessage({requestId, method, parameters});
});
}
private onResponse({data}: MessageEvent<{
requestId: number,
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result: any,
error: Error|null,
}|{
event: string,
}>): void {
if ('event' in data) {
const {event} = data;
switch (event) {
case PrivateAPI.LanguageExtensionPluginEvents.UnregisteredLanguageExtensionPlugin: {
for (const {reject} of this.pendingRequests.values()) {
reject(new Error('Language extension endpoint disconnected'));
}
this.pendingRequests.clear();
this.port.close();
const {pluginManager} = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
if (pluginManager) {
pluginManager.removePlugin(this);
}
break;
}
}
return;
}
const {requestId, result, error} = data;
if (!this.pendingRequests.has(requestId)) {
console.error(`No pending request ${requestId}`);
return;
}
const {resolve, reject} = this.pendingRequests.get(requestId);
this.pendingRequests.delete(requestId);
if (error) {
reject(new Error(error.message));
} else {
resolve(result);
}
}
handleScript(script: SDK.Script.Script): boolean {
const language = script.scriptLanguage();
return language !== null && script.debugSymbols !== null && language === this.supportedScriptTypes.language &&
this.supportedScriptTypes.symbol_types.includes(script.debugSymbols.type);
}
/** Notify the plugin about a new script
*/
addRawModule(rawModuleId: string, symbolsURL: string, rawModule: Chrome.DevTools.RawModule): Promise<string[]> {
return this.sendRequest(
PrivateAPI.LanguageExtensionPluginCommands.AddRawModule, {rawModuleId, symbolsURL, rawModule}) as
Promise<string[]>;
}
/**
* Notifies the plugin that a script is removed.
*/
removeRawModule(rawModuleId: string): Promise<void> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.RemoveRawModule, {rawModuleId}) as Promise<void>;
}
/** Find locations in raw modules from a location in a source file
*/
sourceLocationToRawLocation(sourceLocation: Chrome.DevTools.SourceLocation):
Promise<Chrome.DevTools.RawLocationRange[]> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.SourceLocationToRawLocation, {sourceLocation}) as
Promise<Chrome.DevTools.RawLocationRange[]>;
}
/** Find locations in source files from a location in a raw module
*/
rawLocationToSourceLocation(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.SourceLocation[]> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.RawLocationToSourceLocation, {rawLocation}) as
Promise<Chrome.DevTools.SourceLocation[]>;
}
getScopeInfo(type: string): Promise<Chrome.DevTools.ScopeInfo> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetScopeInfo, {type}) as
Promise<Chrome.DevTools.ScopeInfo>;
}
/** List all variables in lexical scope at a given location in a raw module
*/
listVariablesInScope(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.Variable[]> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.ListVariablesInScope, {rawLocation}) as
Promise<Chrome.DevTools.Variable[]>;
}
/** List all function names (including inlined frames) at location
*/
getFunctionInfo(rawLocation: Chrome.DevTools.RawLocation): Promise<{
frames: Array<Chrome.DevTools.FunctionInfo>,
}> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetFunctionInfo, {rawLocation}) as Promise<{
frames: Array<Chrome.DevTools.FunctionInfo>,
}>;
}
/** Find locations in raw modules corresponding to the inline function
* that rawLocation is in.
*/
getInlinedFunctionRanges(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.RawLocationRange[]> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInlinedFunctionRanges, {rawLocation}) as
Promise<Chrome.DevTools.RawLocationRange[]>;
}
/** Find locations in raw modules corresponding to inline functions
* called by the function or inline frame that rawLocation is in.
*/
getInlinedCalleesRanges(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.RawLocationRange[]> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInlinedCalleesRanges, {rawLocation}) as
Promise<Chrome.DevTools.RawLocationRange[]>;
}
getTypeInfo(expression: string, context: Chrome.DevTools.RawLocation): Promise<{
typeInfos: Array<Chrome.DevTools.TypeInfo>,
base: Chrome.DevTools.EvalBase,
}|null> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetTypeInfo, {expression, context}) as Promise<{
typeInfos: Array<Chrome.DevTools.TypeInfo>,
base: Chrome.DevTools.EvalBase,
}|null>;
}
getFormatter(
expressionOrField: string|{
base: Chrome.DevTools.EvalBase,
field: Array<Chrome.DevTools.FieldInfo>,
},
context: Chrome.DevTools.RawLocation): Promise<{
js: string,
}> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetFormatter, {expressionOrField, context}) as
Promise<{
js: string,
}>;
}
getInspectableAddress(field: {
base: Chrome.DevTools.EvalBase,
field: Array<Chrome.DevTools.FieldInfo>,
}): Promise<{
js: string,
}> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInspectableAddress, {field}) as Promise<{
js: string,
}>;
}
async getMappedLines(rawModuleId: string, sourceFileURL: string): Promise<number[]|undefined> {
return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetMappedLines, {rawModuleId, sourceFileURL});
}
dispose(): void {
}
}