| // 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 type {Chrome} from '../../../extension-api/ExtensionAPI.js'; |
| import type * as Platform from '../../core/platform/platform.js'; |
| import type * as SDK from '../../core/sdk/sdk.js'; |
| import * as Bindings from '../bindings/bindings.js'; |
| |
| import {PrivateAPI} from './ExtensionAPI.js'; |
| import {ExtensionEndpoint} from './ExtensionEndpoint.js'; |
| |
| class LanguageExtensionEndpointImpl extends ExtensionEndpoint { |
| private plugin: LanguageExtensionEndpoint; |
| constructor(plugin: LanguageExtensionEndpoint, port: MessagePort) { |
| super(port); |
| this.plugin = plugin; |
| } |
| protected override handleEvent({event}: {event: string}): void { |
| switch (event) { |
| case PrivateAPI.LanguageExtensionPluginEvents.UnregisteredLanguageExtensionPlugin: { |
| this.disconnect(); |
| const {pluginManager} = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(); |
| pluginManager.removePlugin(this.plugin); |
| break; |
| } |
| } |
| } |
| } |
| |
| export class LanguageExtensionEndpoint implements Bindings.DebuggerLanguagePlugins.DebuggerLanguagePlugin { |
| private readonly supportedScriptTypes: { |
| language: string, |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| symbol_types: string[], |
| }; |
| private readonly endpoint: LanguageExtensionEndpointImpl; |
| private readonly extensionOrigin: string; |
| readonly allowFileAccess: boolean; |
| readonly name: string; |
| |
| constructor( |
| allowFileAccess: boolean, extensionOrigin: string, name: string, supportedScriptTypes: { |
| language: string, |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| symbol_types: string[], |
| }, |
| port: MessagePort) { |
| this.name = name; |
| this.extensionOrigin = extensionOrigin; |
| this.supportedScriptTypes = supportedScriptTypes; |
| this.endpoint = new LanguageExtensionEndpointImpl(this, port); |
| this.allowFileAccess = allowFileAccess; |
| } |
| |
| canAccessURL(url: string): boolean { |
| try { |
| return !url || this.allowFileAccess || new URL(url).protocol !== 'file:'; |
| } catch { |
| // If the URL isn't valid, it also isn't a valid file url and it's safe to tell the extensions about it. |
| return true; |
| } |
| } |
| |
| handleScript(script: SDK.Script.Script): boolean { |
| try { |
| if (!this.canAccessURL(script.contentURL()) || (script.hasSourceURL && !this.canAccessURL(script.sourceURL)) || |
| (script.debugSymbols?.externalURL && !this.canAccessURL(script.debugSymbols.externalURL))) { |
| return false; |
| } |
| } catch { |
| return false; |
| } |
| const language = script.scriptLanguage(); |
| return language !== null && script.debugSymbols !== null && language === this.supportedScriptTypes.language && |
| this.supportedScriptTypes.symbol_types.includes(script.debugSymbols.type); |
| } |
| |
| createPageResourceLoadInitiator(): SDK.PageResourceLoader.PageResourceLoadInitiator { |
| return { |
| target: null, |
| frameId: null, |
| extensionId: this.extensionOrigin, |
| initiatorUrl: this.extensionOrigin as Platform.DevToolsPath.UrlString, |
| }; |
| } |
| |
| /** |
| * Notify the plugin about a new script |
| */ |
| addRawModule(rawModuleId: string, symbolsURL: string, rawModule: Chrome.DevTools.RawModule): Promise<string[]> { |
| if (!this.canAccessURL(symbolsURL) || !this.canAccessURL(rawModule.url)) { |
| return Promise.resolve([]); |
| } |
| return this.endpoint.sendRequest( |
| PrivateAPI.LanguageExtensionPluginCommands.AddRawModule, {rawModuleId, symbolsURL, rawModule}); |
| } |
| |
| /** |
| * Notifies the plugin that a script is removed. |
| */ |
| removeRawModule(rawModuleId: string): Promise<void> { |
| return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.RemoveRawModule, {rawModuleId}); |
| } |
| |
| /** |
| * Find locations in raw modules from a location in a source file |
| */ |
| sourceLocationToRawLocation(sourceLocation: Chrome.DevTools.SourceLocation): |
| Promise<Chrome.DevTools.RawLocationRange[]> { |
| return this.endpoint.sendRequest( |
| PrivateAPI.LanguageExtensionPluginCommands.SourceLocationToRawLocation, {sourceLocation}); |
| } |
| |
| /** |
| * Find locations in source files from a location in a raw module |
| */ |
| rawLocationToSourceLocation(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.SourceLocation[]> { |
| return this.endpoint.sendRequest( |
| PrivateAPI.LanguageExtensionPluginCommands.RawLocationToSourceLocation, {rawLocation}); |
| } |
| |
| getScopeInfo(type: string): Promise<Chrome.DevTools.ScopeInfo> { |
| return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetScopeInfo, {type}); |
| } |
| |
| /** |
| * 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.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.ListVariablesInScope, {rawLocation}); |
| } |
| |
| /** |
| * List all function names (including inlined frames) at location |
| */ |
| getFunctionInfo(rawLocation: Chrome.DevTools.RawLocation): Promise<{ |
| frames: Chrome.DevTools.FunctionInfo[], |
| }> { |
| return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetFunctionInfo, {rawLocation}); |
| } |
| |
| /** |
| * 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.endpoint.sendRequest( |
| PrivateAPI.LanguageExtensionPluginCommands.GetInlinedFunctionRanges, {rawLocation}); |
| } |
| |
| /** |
| * 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.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInlinedCalleesRanges, {rawLocation}); |
| } |
| |
| async getMappedLines(rawModuleId: string, sourceFileURL: string): Promise<number[]|undefined> { |
| return await this.endpoint.sendRequest( |
| PrivateAPI.LanguageExtensionPluginCommands.GetMappedLines, {rawModuleId, sourceFileURL}); |
| } |
| |
| async evaluate(expression: string, context: Chrome.DevTools.RawLocation, stopId: number): |
| Promise<Chrome.DevTools.RemoteObject> { |
| return await this.endpoint.sendRequest( |
| PrivateAPI.LanguageExtensionPluginCommands.FormatValue, {expression, context, stopId}); |
| } |
| |
| getProperties(objectId: Chrome.DevTools.RemoteObjectId): Promise<Chrome.DevTools.PropertyDescriptor[]> { |
| return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetProperties, {objectId}); |
| } |
| |
| releaseObject(objectId: Chrome.DevTools.RemoteObjectId): Promise<void> { |
| return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.ReleaseObject, {objectId}); |
| } |
| } |