// Copyright 2012 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 * as PublicAPI from '../../../extension-api/ExtensionAPI.js'; import type * as Platform from '../../core/platform/platform.js'; import type * as HAR from '../har/har.js'; /* eslint-disable @typescript-eslint/naming-convention */ export namespace PrivateAPI { export namespace Panels { export const enum SearchAction { CancelSearch = 'cancelSearch', PerformSearch = 'performSearch', NextSearchResult = 'nextSearchResult', PreviousSearchResult = 'previousSearchResult', } } export const enum Events { ButtonClicked = 'button-clicked-', PanelObjectSelected = 'panel-objectSelected-', InspectedURLChanged = 'inspected-url-changed', NetworkRequestFinished = 'network-request-finished', OpenResource = 'open-resource', PanelSearch = 'panel-search-', ProfilingStarted = 'profiling-started-', ProfilingStopped = 'profiling-stopped-', ResourceAdded = 'resource-added', ResourceContentCommitted = 'resource-content-committed', ViewShown = 'view-shown-', ViewHidden = 'view-hidden,', ThemeChange = 'host-theme-change', } export const enum Commands { AddRequestHeaders = 'addRequestHeaders', CreatePanel = 'createPanel', CreateSidebarPane = 'createSidebarPane', CreateToolbarButton = 'createToolbarButton', EvaluateOnInspectedPage = 'evaluateOnInspectedPage', ForwardKeyboardEvent = '_forwardKeyboardEvent', GetHAR = 'getHAR', GetPageResources = 'getPageResources', GetRequestContent = 'getRequestContent', GetResourceContent = 'getResourceContent', OpenResource = 'openResource', Reload = 'Reload', Subscribe = 'subscribe', SetOpenResourceHandler = 'setOpenResourceHandler', SetThemeChangeHandler = 'setThemeChangeHandler', SetResourceContent = 'setResourceContent', SetSidebarContent = 'setSidebarContent', SetSidebarHeight = 'setSidebarHeight', SetSidebarPage = 'setSidebarPage', ShowPanel = 'showPanel', Unsubscribe = 'unsubscribe', UpdateButton = 'updateButton', AttachSourceMapToResource = 'attachSourceMapToResource', RegisterLanguageExtensionPlugin = 'registerLanguageExtensionPlugin', GetWasmLinearMemory = 'getWasmLinearMemory', GetWasmLocal = 'getWasmLocal', GetWasmGlobal = 'getWasmGlobal', GetWasmOp = 'getWasmOp', RegisterRecorderExtensionPlugin = 'registerRecorderExtensionPlugin', CreateRecorderView = 'createRecorderView', ShowRecorderView = 'showRecorderView', ShowNetworkPanel = 'showNetworkPanel', ReportResourceLoad = 'reportResourceLoad', SetFunctionRangesForScript = 'setFunctionRangesForScript', } export const enum LanguageExtensionPluginCommands { AddRawModule = 'addRawModule', RemoveRawModule = 'removeRawModule', SourceLocationToRawLocation = 'sourceLocationToRawLocation', RawLocationToSourceLocation = 'rawLocationToSourceLocation', GetScopeInfo = 'getScopeInfo', ListVariablesInScope = 'listVariablesInScope', GetTypeInfo = 'getTypeInfo', GetFormatter = 'getFormatter', GetInspectableAddress = 'getInspectableAddress', GetFunctionInfo = 'getFunctionInfo', GetInlinedFunctionRanges = 'getInlinedFunctionRanges', GetInlinedCalleesRanges = 'getInlinedCalleesRanges', GetMappedLines = 'getMappedLines', FormatValue = 'formatValue', GetProperties = 'getProperties', ReleaseObject = 'releaseObject', } export const enum LanguageExtensionPluginEvents { UnregisteredLanguageExtensionPlugin = 'unregisteredLanguageExtensionPlugin', } export const enum RecorderExtensionPluginCommands { Stringify = 'stringify', StringifyStep = 'stringifyStep', Replay = 'replay', } export const enum RecorderExtensionPluginEvents { UnregisteredRecorderExtensionPlugin = 'unregisteredRecorderExtensionPlugin', } export interface EvaluateOptions { frameURL?: string; useContentScriptContext?: boolean; scriptExecutionContext?: string; } interface RegisterLanguageExtensionPluginRequest { command: Commands.RegisterLanguageExtensionPlugin; pluginName: string; port: MessagePort; supportedScriptTypes: PublicAPI.Chrome.DevTools.SupportedScriptTypes; } export type RecordingExtensionPluginCapability = 'export'|'replay'; interface RegisterRecorderExtensionPluginRequest { command: Commands.RegisterRecorderExtensionPlugin; pluginName: string; capabilities: RecordingExtensionPluginCapability[]; port: MessagePort; mediaType?: string; } interface CreateRecorderViewRequest { command: Commands.CreateRecorderView; id: string; title: string; pagePath: string; } interface ShowRecorderViewRequest { command: Commands.ShowRecorderView; id: string; } interface SubscribeRequest { command: Commands.Subscribe; type: string; } interface UnsubscribeRequest { command: Commands.Unsubscribe; type: string; } interface AddRequestHeadersRequest { command: Commands.AddRequestHeaders; extensionId: string; headers: Record; } interface CreatePanelRequest { command: Commands.CreatePanel; id: string; title: string; page: string; } interface ShowPanelRequest { command: Commands.ShowPanel; id: string; } interface CreateToolbarButtonRequest { command: Commands.CreateToolbarButton; id: string; icon: string; panel: string; tooltip?: string; disabled?: boolean; } interface UpdateButtonRequest { command: Commands.UpdateButton; id: string; icon?: string; tooltip?: string; disabled?: boolean; } interface CreateSidebarPaneRequest { command: Commands.CreateSidebarPane; id: string; panel: string; title: string; } interface SetSidebarHeightRequest { command: Commands.SetSidebarHeight; id: string; height: string; } interface SetSidebarContentRequest { command: Commands.SetSidebarContent; id: string; expression: string; evaluateOnPage?: boolean; rootTitle?: string; evaluateOptions?: EvaluateOptions; } interface SetSidebarPageRequest { command: Commands.SetSidebarPage; id: string; page: string; } interface OpenResourceRequest { command: Commands.OpenResource; url: Platform.DevToolsPath.UrlString; lineNumber: number; columnNumber: number; } interface SetOpenResourceHandlerRequest { command: Commands.SetOpenResourceHandler; handlerPresent: boolean; urlScheme?: string; } interface SetThemeChangeHandlerRequest { command: Commands.SetThemeChangeHandler; handlerPresent: boolean; } interface ReloadRequest { command: Commands.Reload; options: null|{ userAgent?: string, injectedScript?: string, ignoreCache?: boolean, }; } interface EvaluateOnInspectedPageRequest { command: Commands.EvaluateOnInspectedPage; expression: string; evaluateOptions?: EvaluateOptions; } interface GetRequestContentRequest { command: Commands.GetRequestContent; id: number; } interface GetResourceContentRequest { command: Commands.GetResourceContent; url: string; } interface AttachSourceMapToResourceRequest { command: Commands.AttachSourceMapToResource; contentUrl: string; sourceMapURL: string; } interface SetResourceContentRequest { command: Commands.SetResourceContent; url: string; content: string; commit: boolean; } interface SetFunctionRangesForScriptRequest { command: Commands.SetFunctionRangesForScript; scriptUrl: string; ranges: PublicAPI.Chrome.DevTools.NamedFunctionRange[]; } interface ForwardKeyboardEventRequest { command: Commands.ForwardKeyboardEvent; entries: Array; } interface GetHARRequest { command: Commands.GetHAR; } interface GetPageResourcesRequest { command: Commands.GetPageResources; } interface GetWasmLinearMemoryRequest { command: Commands.GetWasmLinearMemory; offset: number; length: number; stopId: unknown; } interface GetWasmLocalRequest { command: Commands.GetWasmLocal; local: number; stopId: unknown; } interface GetWasmGlobalRequest { command: Commands.GetWasmGlobal; global: number; stopId: unknown; } interface GetWasmOpRequest { command: Commands.GetWasmOp; op: number; stopId: unknown; } interface ShowNetworkPanelRequest { command: Commands.ShowNetworkPanel; filter: string|undefined; } interface ReportResourceLoadRequest { command: Commands.ReportResourceLoad; extensionId: string; resourceUrl: string; status: {success: boolean, errorMessage?: string, size?: number}; } export type ServerRequests = ShowRecorderViewRequest|CreateRecorderViewRequest|RegisterRecorderExtensionPluginRequest| RegisterLanguageExtensionPluginRequest|SubscribeRequest|UnsubscribeRequest|AddRequestHeadersRequest| CreatePanelRequest|ShowPanelRequest|CreateToolbarButtonRequest|UpdateButtonRequest|CreateSidebarPaneRequest| SetSidebarHeightRequest|SetSidebarContentRequest|SetSidebarPageRequest|OpenResourceRequest| SetOpenResourceHandlerRequest|SetThemeChangeHandlerRequest|ReloadRequest|EvaluateOnInspectedPageRequest| GetRequestContentRequest|GetResourceContentRequest|SetResourceContentRequest|SetFunctionRangesForScriptRequest| AttachSourceMapToResourceRequest|ForwardKeyboardEventRequest|GetHARRequest|GetPageResourcesRequest| GetWasmLinearMemoryRequest|GetWasmLocalRequest|GetWasmGlobalRequest|GetWasmOpRequest|ShowNetworkPanelRequest| ReportResourceLoadRequest; export type ExtensionServerRequestMessage = PrivateAPI.ServerRequests&{requestId?: number}; interface AddRawModuleRequest { method: LanguageExtensionPluginCommands.AddRawModule; parameters: {rawModuleId: string, symbolsURL: string|undefined, rawModule: PublicAPI.Chrome.DevTools.RawModule}; } interface SourceLocationToRawLocationRequest { method: LanguageExtensionPluginCommands.SourceLocationToRawLocation; parameters: {sourceLocation: PublicAPI.Chrome.DevTools.SourceLocation}; } interface RawLocationToSourceLocationRequest { method: LanguageExtensionPluginCommands.RawLocationToSourceLocation; parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}; } interface GetScopeInfoRequest { method: LanguageExtensionPluginCommands.GetScopeInfo; parameters: {type: string}; } interface ListVariablesInScopeRequest { method: LanguageExtensionPluginCommands.ListVariablesInScope; parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}; } interface RemoveRawModuleRequest { method: LanguageExtensionPluginCommands.RemoveRawModule; parameters: {rawModuleId: string}; } interface GetFunctionInfoRequest { method: LanguageExtensionPluginCommands.GetFunctionInfo; parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}; } interface GetInlinedFunctionRangesRequest { method: LanguageExtensionPluginCommands.GetInlinedFunctionRanges; parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}; } interface GetInlinedCalleesRangesRequest { method: LanguageExtensionPluginCommands.GetInlinedCalleesRanges; parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}; } interface GetMappedLinesRequest { method: LanguageExtensionPluginCommands.GetMappedLines; parameters: {rawModuleId: string, sourceFileURL: string}; } interface FormatValueRequest { method: LanguageExtensionPluginCommands.FormatValue; parameters: {expression: string, context: PublicAPI.Chrome.DevTools.RawLocation, stopId: number}; } interface GetPropertiesRequest { method: LanguageExtensionPluginCommands.GetProperties; parameters: {objectId: PublicAPI.Chrome.DevTools.RemoteObjectId}; } interface ReleaseObjectRequest { method: LanguageExtensionPluginCommands.ReleaseObject; parameters: {objectId: PublicAPI.Chrome.DevTools.RemoteObjectId}; } export type LanguageExtensionRequests = AddRawModuleRequest|SourceLocationToRawLocationRequest|RawLocationToSourceLocationRequest|GetScopeInfoRequest| ListVariablesInScopeRequest|RemoveRawModuleRequest|GetFunctionInfoRequest|GetInlinedFunctionRangesRequest| GetInlinedCalleesRangesRequest|GetMappedLinesRequest|FormatValueRequest|GetPropertiesRequest|ReleaseObjectRequest; interface StringifyRequest { method: RecorderExtensionPluginCommands.Stringify; parameters: {recording: Record}; } interface StringifyStepRequest { method: RecorderExtensionPluginCommands.StringifyStep; parameters: {step: Record}; } interface ReplayRequest { method: RecorderExtensionPluginCommands.Replay; parameters: {recording: Record}; } export type RecorderExtensionRequests = StringifyRequest|StringifyStepRequest|ReplayRequest; } declare global { interface Window { injectedExtensionAPI: (extensionInfo: ExtensionDescriptor, inspectedTabId: string, themeName: string, keysToForward: number[], testHook: (extensionServer: APIImpl.ExtensionServerClient, extensionAPI: APIImpl.InspectorExtensionAPI) => unknown, injectedScriptId: number, targetWindow?: Window) => void; buildExtensionAPIInjectedScript( extensionInfo: ExtensionDescriptor, inspectedTabId: string, themeName: string, keysToForward: number[], testHook?: (extensionServer: unknown, extensionAPI: unknown) => unknown): string; chrome: PublicAPI.Chrome.DevTools.Chrome; webInspector?: APIImpl.InspectorExtensionAPI; } } export interface ExtensionDescriptor { startPage: string; name: string; exposeExperimentalAPIs: boolean; exposeWebInspectorNamespace?: boolean; allowFileAccess?: boolean; } namespace APIImpl { export interface InspectorExtensionAPI { languageServices: PublicAPI.Chrome.DevTools.LanguageExtensions; recorder: PublicAPI.Chrome.DevTools.RecorderExtensions; performance: PublicAPI.Chrome.DevTools.Performance; network: PublicAPI.Chrome.DevTools.Network; panels: PublicAPI.Chrome.DevTools.Panels; inspectedWindow: PublicAPI.Chrome.DevTools.InspectedWindow; } export interface ExtensionServerClient { _callbacks: Record unknown>; _handlers: Record unknown>; _lastRequestId: number; _lastObjectId: number; _port: MessagePort; _onCallback(request: unknown): void; _onMessage(event: MessageEvent<{command: string, requestId: number, arguments: unknown[]}>): void; _registerCallback(callback: (response: unknown) => unknown): number; registerHandler(command: string, handler: (request: {arguments: unknown[]}) => unknown): void; unregisterHandler(command: string): void; hasHandler(command: string): boolean; sendRequest( request: PrivateAPI.ServerRequests, callback?: ((response: ResponseT) => unknown), transfers?: unknown[]): void; nextObjectId(): string; } export type Callable = (...args: any[]) => void; export interface EventSink extends PublicAPI.Chrome.DevTools.EventSink { _type: string; _listeners: ListenerT[]; _customDispatch?: (this: EventSink, request: {arguments: unknown[]}) => unknown; _fire(..._vararg: Parameters): void; _dispatch(request: {arguments: unknown[]}): void; } export interface Network extends PublicAPI.Chrome.DevTools.Network { addRequestHeaders(headers: Record): void; } export interface Request extends PublicAPI.Chrome.DevTools.Request, HAR.Log.EntryDTO { _id: number; } export interface Panels extends PublicAPI.Chrome.DevTools.Panels { get SearchAction(): Record; setOpenResourceHandler( callback?: (resource: PublicAPI.Chrome.DevTools.Resource, lineNumber: number, columnNumber: number) => unknown): void; setThemeChangeHandler(callback?: (themeName: string) => unknown): void; } export interface ExtensionView extends PublicAPI.Chrome.DevTools.ExtensionView { _id: string|null; } export interface ExtensionSidebarPane extends ExtensionView, PublicAPI.Chrome.DevTools.ExtensionSidebarPane { setExpression( expression: string, rootTitle?: string, evaluteOptions?: PrivateAPI.EvaluateOptions, callback?: () => unknown): void; } export interface PanelWithSidebar extends ExtensionView, PublicAPI.Chrome.DevTools.PanelWithSidebar { _hostPanelName: string; } export interface LanguageExtensions extends PublicAPI.Chrome.DevTools.LanguageExtensions { _plugins: Map; } export interface RecorderExtensions extends PublicAPI.Chrome.DevTools.RecorderExtensions { _plugins: Map; } export interface ExtensionPanel extends ExtensionView, PublicAPI.Chrome.DevTools.ExtensionPanel { show(): void; } export interface RecorderView extends ExtensionView, PublicAPI.Chrome.DevTools.RecorderView {} export interface Button extends PublicAPI.Chrome.DevTools.Button { _id: string; } export interface ResourceData { url: string; type: string; buildId?: string; } export interface Resource extends PublicAPI.Chrome.DevTools.Resource { _type: string; _url: string; _buildId?: string; get type(): string; } } self.injectedExtensionAPI = function( extensionInfo: ExtensionDescriptor, inspectedTabId: string, themeName: string, keysToForward: number[], testHook: (extensionServer: APIImpl.ExtensionServerClient, extensionAPI: APIImpl.InspectorExtensionAPI) => unknown, injectedScriptId: number, targetWindowForTest?: Window): void { const keysToForwardSet = new Set(keysToForward); const chrome = window.chrome || {}; const devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools'); if (devtools_descriptor) { return; } let userAction = false; let userRecorderAction = false; // Here and below, all constructors are private to API implementation. // For a public type Foo, if internal fields are present, these are on // a private FooImpl type, an instance of FooImpl is used in a closure // by Foo consutrctor to re-bind publicly exported members to an instance // of Foo. function EventSinkImpl( this: APIImpl.EventSink, type: string, customDispatch?: (this: APIImpl.EventSink, request: {arguments: unknown[]}) => unknown): void { this._type = type; this._listeners = []; this._customDispatch = customDispatch; } EventSinkImpl.prototype = { addListener: function(this: APIImpl.EventSink, callback: ListenerT): void { if (typeof callback !== 'function') { throw new Error('addListener: callback is not a function'); } if (this._listeners.length === 0) { extensionServer.sendRequest({command: PrivateAPI.Commands.Subscribe, type: this._type}); } this._listeners.push(callback); extensionServer.registerHandler('notify-' + this._type, this._dispatch.bind(this)); }, removeListener: function( this: APIImpl.EventSink, callback: ListenerT): void { const listeners = this._listeners; for (let i = 0; i < listeners.length; ++i) { if (listeners[i] === callback) { listeners.splice(i, 1); break; } } if (this._listeners.length === 0) { extensionServer.sendRequest({command: PrivateAPI.Commands.Unsubscribe, type: this._type}); } }, _fire: function( this: APIImpl.EventSink, ..._vararg: Parameters): void { const listeners = this._listeners.slice(); for (let i = 0; i < listeners.length; ++i) { listeners[i].apply(null, Array.from(arguments)); } }, _dispatch: function( this: APIImpl.EventSink, request: {arguments: unknown[]}): void { if (this._customDispatch) { this._customDispatch.call(this, request); } else { this._fire.apply(this, request.arguments as Parameters); } }, }; function Constructor(ctor: NewT): new (...args: Parameters) => ThisParameterType { return ctor as unknown as new (...args: Parameters) => ThisParameterType; } function InspectorExtensionAPI(this: APIImpl.InspectorExtensionAPI): void { this.inspectedWindow = new (Constructor(InspectedWindow))(); this.panels = new (Constructor(Panels))(); this.network = new (Constructor(Network))(); this.languageServices = new (Constructor(LanguageServicesAPI))(); this.recorder = new (Constructor(RecorderServicesAPI))(); this.performance = new (Constructor(Performance))(); defineDeprecatedProperty(this, 'webInspector', 'resources', 'network'); } function Network(this: APIImpl.Network): void { function dispatchRequestEvent( this: APIImpl.EventSink<(request: PublicAPI.Chrome.DevTools.Request) => unknown>, message: {arguments: unknown[]}): void { const request = message.arguments[1] as APIImpl.Request & {__proto__: APIImpl.Request}; request.__proto__ = new (Constructor(Request))(message.arguments[0] as number); this._fire(request); } this.onRequestFinished = new (Constructor(EventSink))(PrivateAPI.Events.NetworkRequestFinished, dispatchRequestEvent); defineDeprecatedProperty(this, 'network', 'onFinished', 'onRequestFinished'); this.onNavigated = new (Constructor(EventSink))(PrivateAPI.Events.InspectedURLChanged); } (Network.prototype as Pick) = { getHAR: function(this: PublicAPI.Chrome.DevTools.Network, callback?: (harLog: Object) => unknown): void { function callbackWrapper(response: unknown): void { const result = response as ({entries: Array}); const entries = (result?.entries) || []; for (let i = 0; i < entries.length; ++i) { entries[i].__proto__ = new (Constructor(Request))(entries[i]._requestId as number); delete entries[i]._requestId; } callback?.(result as Object); } extensionServer.sendRequest({command: PrivateAPI.Commands.GetHAR}, callback && callbackWrapper); }, addRequestHeaders: function(headers: Record): void { extensionServer.sendRequest( {command: PrivateAPI.Commands.AddRequestHeaders, headers, extensionId: window.location.hostname}); }, }; function RequestImpl(this: APIImpl.Request, id: number): void { this._id = id; } (RequestImpl.prototype as Pick) = { getContent: function(this: APIImpl.Request, callback?: (content: string, encoding: string) => unknown): void { function callbackWrapper(response: unknown): void { const {content, encoding} = response as {content: string, encoding: string}; callback?.(content, encoding); } extensionServer.sendRequest( {command: PrivateAPI.Commands.GetRequestContent, id: this._id}, callback && callbackWrapper); }, }; function Panels(this: APIImpl.Panels): void { const panels: Record = { elements: new ElementsPanel(), sources: new SourcesPanel(), network: new (Constructor(NetworkPanel))(), }; function panelGetter(name: string): ElementsPanel|SourcesPanel|PublicAPI.Chrome.DevTools.NetworkPanel { return panels[name]; } for (const panel in panels) { Object.defineProperty(this, panel, {get: panelGetter.bind(null, panel), enumerable: true}); } } (Panels.prototype as Pick) = { create: function( title: string, _icon: string, page: string, callback: (panel: PublicAPI.Chrome.DevTools.ExtensionPanel) => unknown): void { const id = 'extension-panel-' + extensionServer.nextObjectId(); extensionServer.sendRequest( {command: PrivateAPI.Commands.CreatePanel, id, title, page}, callback && (() => callback.call(this, new (Constructor(ExtensionPanel))(id)))); }, setOpenResourceHandler: function( callback: (resource: PublicAPI.Chrome.DevTools.Resource, lineNumber: number, columnNumber: number) => unknown, urlScheme?: string): void { const hadHandler = extensionServer.hasHandler(PrivateAPI.Events.OpenResource); function callbackWrapper(message: unknown): void { // Allow the panel to show itself when handling the event. userAction = true; try { const {resource, lineNumber, columnNumber} = message as {resource: APIImpl.ResourceData, lineNumber: number, columnNumber: number}; callback.call(null, new (Constructor(Resource))(resource), lineNumber, columnNumber); } finally { userAction = false; } } if (!callback) { extensionServer.unregisterHandler(PrivateAPI.Events.OpenResource); } else { extensionServer.registerHandler(PrivateAPI.Events.OpenResource, callbackWrapper); } // Only send command if we either removed an existing handler or added handler and had none before. if (hadHandler === !callback) { extensionServer.sendRequest( {command: PrivateAPI.Commands.SetOpenResourceHandler, handlerPresent: Boolean(callback), urlScheme}); } }, setThemeChangeHandler: function(callback: (themeName: string) => unknown): void { const hadHandler = extensionServer.hasHandler(PrivateAPI.Events.ThemeChange); function callbackWrapper(message: unknown): void { const {themeName} = message as {themeName: string}; chrome.devtools.panels.themeName = themeName; callback.call(null, themeName); } if (!callback) { extensionServer.unregisterHandler(PrivateAPI.Events.ThemeChange); } else { extensionServer.registerHandler(PrivateAPI.Events.ThemeChange, callbackWrapper); } // Only send command if we either removed an existing handler or added handler and had none before. if (hadHandler === !callback) { extensionServer.sendRequest( {command: PrivateAPI.Commands.SetThemeChangeHandler, handlerPresent: Boolean(callback)}); } }, openResource: function( url: Platform.DevToolsPath.UrlString, lineNumber: number, columnNumber?: number, _callback?: (response: unknown) => unknown): void { const callbackArg = extractCallbackArgument(arguments); // Handle older API: const columnNumberArg = typeof columnNumber === 'number' ? columnNumber : 0; extensionServer.sendRequest( {command: PrivateAPI.Commands.OpenResource, url, lineNumber, columnNumber: columnNumberArg}, callbackArg); }, get SearchAction(): Record { return { CancelSearch: PrivateAPI.Panels.SearchAction.CancelSearch, PerformSearch: PrivateAPI.Panels.SearchAction.PerformSearch, NextSearchResult: PrivateAPI.Panels.SearchAction.NextSearchResult, PreviousSearchResult: PrivateAPI.Panels.SearchAction.PreviousSearchResult, }; }, }; function ExtensionViewImpl(this: APIImpl.ExtensionView, id: string|null): void { this._id = id; function dispatchShowEvent( this: APIImpl.EventSink<(window?: Window) => unknown>, message: {arguments: unknown[]}): void { const frameIndex = message.arguments[0]; if (typeof frameIndex === 'number') { this._fire(window.parent.frames[frameIndex]); } else { this._fire(); } } if (id) { this.onShown = new (Constructor(EventSink))(PrivateAPI.Events.ViewShown + id, dispatchShowEvent); this.onHidden = new (Constructor(EventSink))(PrivateAPI.Events.ViewHidden + id); } } function PanelWithSidebarImpl(this: APIImpl.PanelWithSidebar, hostPanelName: string): void { ExtensionViewImpl.call(this, null); this._hostPanelName = hostPanelName; this.onSelectionChanged = new (Constructor(EventSink))(PrivateAPI.Events.PanelObjectSelected + hostPanelName); } (PanelWithSidebarImpl.prototype as Pick& {__proto__: APIImpl.ExtensionView}) = { createSidebarPane: function( this: APIImpl.PanelWithSidebar, title: string, callback?: (pane: PublicAPI.Chrome.DevTools.ExtensionSidebarPane) => unknown): void { const id = 'extension-sidebar-' + extensionServer.nextObjectId(); function callbackWrapper(): void { callback?.(new (Constructor(ExtensionSidebarPane))(id)); } extensionServer.sendRequest( {command: PrivateAPI.Commands.CreateSidebarPane, panel: this._hostPanelName, id, title}, callback && callbackWrapper); }, __proto__: ExtensionViewImpl.prototype, }; function RecorderServicesAPIImpl(this: APIImpl.RecorderExtensions): void { this._plugins = new Map(); } async function registerRecorderExtensionPluginImpl( this: APIImpl.RecorderExtensions, plugin: PublicAPI.Chrome.DevTools.RecorderExtensionPlugin, pluginName: string, mediaType?: string): Promise { if (this._plugins.has(plugin)) { throw new Error(`Tried to register plugin '${pluginName}' twice`); } const channel = new MessageChannel(); const port = channel.port1; this._plugins.set(plugin, port); port.onmessage = ({data}: MessageEvent<{requestId: number}&PrivateAPI.RecorderExtensionRequests>): void => { const {requestId} = data; dispatchMethodCall(data) .then(result => port.postMessage({requestId, result})) .catch(error => port.postMessage({requestId, error: {message: error.message}})); }; async function dispatchMethodCall(request: PrivateAPI.RecorderExtensionRequests): Promise { switch (request.method) { case PrivateAPI.RecorderExtensionPluginCommands.Stringify: return await (plugin as PublicAPI.Chrome.DevTools.RecorderExtensionExportPlugin) .stringify(request.parameters.recording); case PrivateAPI.RecorderExtensionPluginCommands.StringifyStep: return await (plugin as PublicAPI.Chrome.DevTools.RecorderExtensionExportPlugin) .stringifyStep(request.parameters.step); case PrivateAPI.RecorderExtensionPluginCommands.Replay: try { userAction = true; userRecorderAction = true; return (plugin as PublicAPI.Chrome.DevTools.RecorderExtensionReplayPlugin) .replay(request.parameters.recording); } finally { userAction = false; userRecorderAction = false; } default: // @ts-expect-error throw new Error(`'${request.method}' is not recognized`); } } const capabilities: PrivateAPI.RecordingExtensionPluginCapability[] = []; if ('stringify' in plugin && 'stringifyStep' in plugin) { capabilities.push('export'); } if ('replay' in plugin) { capabilities.push('replay'); } await new Promise(resolve => { extensionServer.sendRequest( { command: PrivateAPI.Commands.RegisterRecorderExtensionPlugin, pluginName, mediaType, capabilities, port: channel.port2, }, () => resolve(), [channel.port2]); }); } (RecorderServicesAPIImpl.prototype as Pick< APIImpl.RecorderExtensions, 'registerRecorderExtensionPlugin'|'unregisterRecorderExtensionPlugin'|'createView'>) = { registerRecorderExtensionPlugin: registerRecorderExtensionPluginImpl, unregisterRecorderExtensionPlugin: async function( this: APIImpl.RecorderExtensions, plugin: PublicAPI.Chrome.DevTools.RecorderExtensionPlugin): Promise { const port = this._plugins.get(plugin); if (!port) { throw new Error('Tried to unregister a plugin that was not previously registered'); } this._plugins.delete(plugin); port.postMessage({event: PrivateAPI.RecorderExtensionPluginEvents.UnregisteredRecorderExtensionPlugin}); port.close(); }, createView: async function(this: APIImpl.RecorderExtensions, title: string, pagePath: string): Promise { const id = 'recorder-extension-view-' + extensionServer.nextObjectId(); await new Promise(resolve => { extensionServer.sendRequest( {command: PrivateAPI.Commands.CreateRecorderView, id, title, pagePath}, resolve); }); return new (Constructor(RecorderView))(id); }, }; function LanguageServicesAPIImpl(this: APIImpl.LanguageExtensions): void { this._plugins = new Map(); } (LanguageServicesAPIImpl.prototype as PublicAPI.Chrome.DevTools.LanguageExtensions) = { registerLanguageExtensionPlugin: async function( this: APIImpl.LanguageExtensions, plugin: PublicAPI.Chrome.DevTools.LanguageExtensionPlugin, pluginName: string, supportedScriptTypes: PublicAPI.Chrome.DevTools.SupportedScriptTypes): Promise { if (this._plugins.has(plugin)) { throw new Error(`Tried to register plugin '${pluginName}' twice`); } const channel = new MessageChannel(); const port = channel.port1; this._plugins.set(plugin, port); port.onmessage = ({data}: MessageEvent<{requestId: number}&PrivateAPI.LanguageExtensionRequests>): void => { const {requestId} = data; console.time(`${requestId}: ${data.method}`); dispatchMethodCall(data) .then(result => port.postMessage({requestId, result})) .catch(error => port.postMessage({requestId, error: {message: error.message}})) .finally(() => console.timeEnd(`${requestId}: ${data.method}`)); }; function dispatchMethodCall(request: PrivateAPI.LanguageExtensionRequests): Promise { switch (request.method) { case PrivateAPI.LanguageExtensionPluginCommands.AddRawModule: return plugin.addRawModule( request.parameters.rawModuleId, request.parameters.symbolsURL, request.parameters.rawModule); case PrivateAPI.LanguageExtensionPluginCommands.RemoveRawModule: return plugin.removeRawModule(request.parameters.rawModuleId); case PrivateAPI.LanguageExtensionPluginCommands.SourceLocationToRawLocation: return plugin.sourceLocationToRawLocation(request.parameters.sourceLocation); case PrivateAPI.LanguageExtensionPluginCommands.RawLocationToSourceLocation: return plugin.rawLocationToSourceLocation(request.parameters.rawLocation); case PrivateAPI.LanguageExtensionPluginCommands.GetScopeInfo: return plugin.getScopeInfo(request.parameters.type); case PrivateAPI.LanguageExtensionPluginCommands.ListVariablesInScope: return plugin.listVariablesInScope(request.parameters.rawLocation); case PrivateAPI.LanguageExtensionPluginCommands.GetFunctionInfo: return plugin.getFunctionInfo(request.parameters.rawLocation); case PrivateAPI.LanguageExtensionPluginCommands.GetInlinedFunctionRanges: return plugin.getInlinedFunctionRanges(request.parameters.rawLocation); case PrivateAPI.LanguageExtensionPluginCommands.GetInlinedCalleesRanges: return plugin.getInlinedCalleesRanges(request.parameters.rawLocation); case PrivateAPI.LanguageExtensionPluginCommands.GetMappedLines: if ('getMappedLines' in plugin) { return plugin.getMappedLines(request.parameters.rawModuleId, request.parameters.sourceFileURL); } return Promise.resolve(undefined); case PrivateAPI.LanguageExtensionPluginCommands.FormatValue: if ('evaluate' in plugin && plugin.evaluate) { return plugin.evaluate( request.parameters.expression, request.parameters.context, request.parameters.stopId); } return Promise.resolve(undefined); case PrivateAPI.LanguageExtensionPluginCommands.GetProperties: if ('getProperties' in plugin && plugin.getProperties) { return plugin.getProperties(request.parameters.objectId); } if (!('evaluate' in plugin && plugin.evaluate)) { // If evalute is defined but the remote objects methods aren't, that's a bug return Promise.resolve(undefined); } break; case PrivateAPI.LanguageExtensionPluginCommands.ReleaseObject: if ('releaseObject' in plugin && plugin.releaseObject) { return plugin.releaseObject(request.parameters.objectId); } break; } throw new Error(`Unknown language plugin method ${request.method}`); } await new Promise(resolve => { extensionServer.sendRequest( { command: PrivateAPI.Commands.RegisterLanguageExtensionPlugin, pluginName, port: channel.port2, supportedScriptTypes, }, () => resolve(), [channel.port2]); }); }, unregisterLanguageExtensionPlugin: async function( this: APIImpl.LanguageExtensions, plugin: PublicAPI.Chrome.DevTools.LanguageExtensionPlugin): Promise { const port = this._plugins.get(plugin); if (!port) { throw new Error('Tried to unregister a plugin that was not previously registered'); } this._plugins.delete(plugin); port.postMessage({event: PrivateAPI.LanguageExtensionPluginEvents.UnregisteredLanguageExtensionPlugin}); port.close(); }, getWasmLinearMemory: async function( this: APIImpl.LanguageExtensions, offset: number, length: number, stopId: number): Promise { const result = await new Promise( resolve => extensionServer.sendRequest( {command: PrivateAPI.Commands.GetWasmLinearMemory, offset, length, stopId}, resolve)); if (Array.isArray(result)) { return new Uint8Array(result).buffer; } return new ArrayBuffer(0); }, getWasmLocal: async function( this: APIImpl.LanguageExtensions, local: number, stopId: number): Promise { return await new Promise( resolve => extensionServer.sendRequest({command: PrivateAPI.Commands.GetWasmLocal, local, stopId}, resolve)); }, getWasmGlobal: async function(this: APIImpl.LanguageExtensions, global: number, stopId: number): Promise { return await new Promise( resolve => extensionServer.sendRequest({command: PrivateAPI.Commands.GetWasmGlobal, global, stopId}, resolve)); }, getWasmOp: async function(this: APIImpl.LanguageExtensions, op: number, stopId: number): Promise { return await new Promise( resolve => extensionServer.sendRequest({command: PrivateAPI.Commands.GetWasmOp, op, stopId}, resolve)); }, reportResourceLoad: function(resourceUrl: string, status: {success: boolean, errorMessage?: string, size?: number}): Promise { return new Promise( resolve => extensionServer.sendRequest( { command: PrivateAPI.Commands.ReportResourceLoad, extensionId: window.location.origin, resourceUrl, status, }, resolve)); }, }; function NetworkPanelImpl(this: PublicAPI.Chrome.DevTools.NetworkPanel): void { } (NetworkPanelImpl.prototype as Pick) = { show: function(options?: {filter: string}): Promise { return new Promise( resolve => extensionServer.sendRequest( {command: PrivateAPI.Commands.ShowNetworkPanel, filter: options?.filter}, () => resolve())); }, }; function PerformanceImpl(this: PublicAPI.Chrome.DevTools.Performance): void { function dispatchProfilingStartedEvent(this: APIImpl.EventSink<() => unknown>): void { this._fire(); } function dispatchProfilingStoppedEvent(this: APIImpl.EventSink<() => unknown>): void { this._fire(); } this.onProfilingStarted = new (Constructor(EventSink))(PrivateAPI.Events.ProfilingStarted, dispatchProfilingStartedEvent); this.onProfilingStopped = new (Constructor(EventSink))(PrivateAPI.Events.ProfilingStopped, dispatchProfilingStoppedEvent); } function declareInterfaceClass(implConstructor: ImplT): ( this: ThisParameterType, ...args: Parameters) => void { return function(this: ThisParameterType, ...args: Parameters): void { const impl = {__proto__: implConstructor.prototype}; implConstructor.apply(impl, args); populateInterfaceClass(this as Record, impl); }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any function defineDeprecatedProperty(object: any, className: string, oldName: string, newName: string): void { let warningGiven = false; function getter(): unknown { if (!warningGiven) { console.warn(className + '.' + oldName + ' is deprecated. Use ' + className + '.' + newName + ' instead'); warningGiven = true; } return object[newName]; } object.__defineGetter__(oldName, getter); } function extractCallbackArgument(args: IArguments): ((...args: unknown[]) => unknown)|undefined { const lastArgument = args[args.length - 1]; return typeof lastArgument === 'function' ? lastArgument as (...args: unknown[]) => unknown : undefined; } const LanguageServicesAPI = declareInterfaceClass(LanguageServicesAPIImpl); const RecorderServicesAPI = declareInterfaceClass(RecorderServicesAPIImpl); const Performance = declareInterfaceClass(PerformanceImpl); const Button = declareInterfaceClass(ButtonImpl); const EventSink = declareInterfaceClass(EventSinkImpl); const ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl); const RecorderView = declareInterfaceClass(RecorderViewImpl); const ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl); const PanelWithSidebarClass = declareInterfaceClass(PanelWithSidebarImpl); const Request = declareInterfaceClass(RequestImpl); const Resource = declareInterfaceClass(ResourceImpl); const NetworkPanel = declareInterfaceClass(NetworkPanelImpl); class ElementsPanel extends (Constructor(PanelWithSidebarClass)) { constructor() { super('elements'); } } class SourcesPanel extends (Constructor(PanelWithSidebarClass)) { constructor() { super('sources'); } } function ExtensionPanelImpl(this: APIImpl.ExtensionPanel, id: string): void { ExtensionViewImpl.call(this, id); this.onSearch = new (Constructor(EventSink))(PrivateAPI.Events.PanelSearch + id); } (ExtensionPanelImpl.prototype as Pick& {__proto__: APIImpl.ExtensionView}) = { createStatusBarButton: function( this: APIImpl.ExtensionPanel, iconPath: string, tooltipText: string, disabled: boolean): PublicAPI.Chrome.DevTools.Button { const id = 'button-' + extensionServer.nextObjectId(); extensionServer.sendRequest({ command: PrivateAPI.Commands.CreateToolbarButton, panel: this._id as string, id, icon: iconPath, tooltip: tooltipText, disabled: Boolean(disabled), }); return new (Constructor(Button))(id); }, show: function(this: APIImpl.ExtensionPanel): void { if (!userAction) { return; } extensionServer.sendRequest({command: PrivateAPI.Commands.ShowPanel, id: this._id as string}); }, __proto__: ExtensionViewImpl.prototype, }; function RecorderViewImpl(this: APIImpl.RecorderView, id: string): void { ExtensionViewImpl.call(this, id); } (RecorderViewImpl.prototype as Pick& {__proto__: APIImpl.ExtensionView}) = { show: function(this: APIImpl.RecorderView): void { if (!userAction || !userRecorderAction) { return; } extensionServer.sendRequest({command: PrivateAPI.Commands.ShowRecorderView, id: this._id as string}); }, __proto__: ExtensionViewImpl.prototype, }; function ExtensionSidebarPaneImpl(this: APIImpl.ExtensionSidebarPane, id: string): void { ExtensionViewImpl.call(this, id); } (ExtensionSidebarPaneImpl.prototype as Pick& {__proto__: APIImpl.ExtensionView}) = { setHeight: function(this: APIImpl.ExtensionSidebarPane, height: string): void { extensionServer.sendRequest({command: PrivateAPI.Commands.SetSidebarHeight, id: this._id as string, height}); }, setExpression: function( this: APIImpl.ExtensionSidebarPane, expression: string, rootTitle: string, evaluateOptions?: PrivateAPI.EvaluateOptions, _callback?: () => unknown): void { extensionServer.sendRequest( { command: PrivateAPI.Commands.SetSidebarContent, id: this._id as string, expression, rootTitle, evaluateOnPage: true, evaluateOptions: (typeof evaluateOptions === 'object' ? evaluateOptions : {}), }, extractCallbackArgument(arguments)); }, setObject: function( this: APIImpl.ExtensionSidebarPane, jsonObject: string, rootTitle?: string, callback?: () => unknown): void { extensionServer.sendRequest( { command: PrivateAPI.Commands.SetSidebarContent, id: this._id as string, expression: jsonObject, rootTitle, }, callback); }, setPage: function(this: APIImpl.ExtensionSidebarPane, page: string): void { extensionServer.sendRequest({command: PrivateAPI.Commands.SetSidebarPage, id: this._id as string, page}); }, __proto__: ExtensionViewImpl.prototype, }; function ButtonImpl(this: APIImpl.Button, id: string): void { this._id = id; this.onClicked = new (Constructor(EventSink))(PrivateAPI.Events.ButtonClicked + id); } (ButtonImpl.prototype as Pick) = { update: function(this: APIImpl.Button, iconPath?: string, tooltipText?: string, disabled?: boolean): void { extensionServer.sendRequest({ command: PrivateAPI.Commands.UpdateButton, id: this._id, icon: iconPath, tooltip: tooltipText, disabled: Boolean(disabled), }); }, }; function InspectedWindow(this: PublicAPI.Chrome.DevTools.InspectedWindow): void { function dispatchResourceEvent( this: APIImpl.EventSink<(resource: APIImpl.Resource) => unknown>, message: {arguments: unknown[]}): void { const resourceData = message.arguments[0] as APIImpl.ResourceData; this._fire(new (Constructor(Resource))(resourceData)); } function dispatchResourceContentEvent( this: APIImpl.EventSink<(resource: APIImpl.Resource, content: string) => unknown>, message: {arguments: unknown[]}): void { const resourceData = message.arguments[0] as APIImpl.ResourceData; this._fire(new (Constructor(Resource))(resourceData), message.arguments[1] as string); } this.onResourceAdded = new (Constructor(EventSink))(PrivateAPI.Events.ResourceAdded, dispatchResourceEvent); this.onResourceContentCommitted = new (Constructor(EventSink))(PrivateAPI.Events.ResourceContentCommitted, dispatchResourceContentEvent); } (InspectedWindow.prototype as Pick) = { reload: function(optionsOrUserAgent: { ignoreCache?: boolean, injectedScript?: string, userAgent?: string, }): void { let options: { ignoreCache?: boolean, injectedScript?: string, userAgent?: string, }|null = null; if (typeof optionsOrUserAgent === 'object') { options = optionsOrUserAgent; } else if (typeof optionsOrUserAgent === 'string') { options = {userAgent: optionsOrUserAgent}; console.warn( 'Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. ' + 'Use inspectedWindow.reload({ userAgent: value}) instead.'); } extensionServer.sendRequest({command: PrivateAPI.Commands.Reload, options}); }, eval: function( expression: string, evaluateOptions: {scriptExecutionContext?: string, frameURL?: string, useContentScriptContext?: boolean}): Object | null { const callback = extractCallbackArgument(arguments); function callbackWrapper(result: unknown): void { const {isError, isException, value} = result as { value: unknown, isError?: boolean, isException?: boolean, }; if (isError || isException) { callback?.(undefined, result); } else { callback?.(value); } } extensionServer.sendRequest( { command: PrivateAPI.Commands.EvaluateOnInspectedPage, expression, evaluateOptions: (typeof evaluateOptions === 'object' ? evaluateOptions : undefined), }, callback && callbackWrapper); return null; }, getResources: function(callback?: (resources: PublicAPI.Chrome.DevTools.Resource[]) => unknown): void { function wrapResource(resourceData: APIImpl.ResourceData): APIImpl.Resource { return new (Constructor(Resource))(resourceData); } function callbackWrapper(resources: unknown): void { callback?.((resources as APIImpl.ResourceData[]).map(wrapResource)); } extensionServer.sendRequest({command: PrivateAPI.Commands.GetPageResources}, callback && callbackWrapper); }, }; function ResourceImpl(this: APIImpl.Resource, resourceData: APIImpl.ResourceData): void { this._url = resourceData.url; this._type = resourceData.type; this._buildId = resourceData.buildId; } (ResourceImpl.prototype as Pick< APIImpl.Resource, 'url'|'type'|'buildId'|'getContent'|'setContent'|'setFunctionRangesForScript'|'attachSourceMapURL'>) = { get url(): string { return (this as APIImpl.Resource)._url; }, get type(): string { return (this as APIImpl.Resource)._type; }, get buildId(): (string | undefined) { return (this as APIImpl.Resource)._buildId; }, getContent: function(this: APIImpl.Resource, callback?: (content: string, encoding: string) => unknown): void { function callbackWrapper(response: unknown): void { const {content, encoding} = response as {content: string, encoding: string}; callback?.(content, encoding); } extensionServer.sendRequest( {command: PrivateAPI.Commands.GetResourceContent, url: this._url}, callback && callbackWrapper); }, setContent: function( this: APIImpl.Resource, content: string, commit: boolean, callback: (error?: Object) => unknown): void { extensionServer.sendRequest( {command: PrivateAPI.Commands.SetResourceContent, url: this._url, content, commit}, callback as (response: unknown) => unknown); }, setFunctionRangesForScript: function( this: APIImpl.Resource, ranges: PublicAPI.Chrome.DevTools.NamedFunctionRange[]): Promise { return new Promise( (resolve, reject) => extensionServer.sendRequest( { command: PrivateAPI.Commands.SetFunctionRangesForScript, scriptUrl: this._url, ranges, }, (response: unknown) => { const result = response as { code: string, description: string, details: unknown[], isError?: boolean, }; if (result.isError) { reject(result); } else { resolve(); } })); }, attachSourceMapURL: function(this: APIImpl.Resource, sourceMapURL: string): Promise { return new Promise( (resolve, reject) => extensionServer.sendRequest( {command: PrivateAPI.Commands.AttachSourceMapToResource, contentUrl: this._url, sourceMapURL}, (response: unknown) => { const result = response as { code: string, description: string, details: unknown[], isError?: boolean, }; if (result.isError) { reject(new Error(result.description)); } else { resolve(); } })); }, }; function getTabId(): string { return inspectedTabId; } let keyboardEventRequestQueue: KeyboardEventInit&Array<{eventType: string}> = []; let forwardTimer: number|null = null; function forwardKeyboardEvent(event: KeyboardEvent): void { // Check if the event should be forwarded. // This is a workaround for crbug.com/923338. const focused = document.activeElement; if (focused) { const isInput = focused.nodeName === 'INPUT' || focused.nodeName === 'TEXTAREA' || (focused as HTMLElement).isContentEditable; if (isInput && !(event.ctrlKey || event.altKey || event.metaKey)) { return; } } let modifiers = 0; if (event.shiftKey) { modifiers |= 1; } if (event.ctrlKey) { modifiers |= 2; } if (event.altKey) { modifiers |= 4; } if (event.metaKey) { modifiers |= 8; } const num = (event.keyCode & 255) | (modifiers << 8); // We only care about global hotkeys, not about random text if (!keysToForwardSet.has(num)) { return; } event.preventDefault(); const requestPayload = { eventType: event.type, ctrlKey: event.ctrlKey, altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey, // @ts-expect-error keyIdentifier is a deprecated non-standard property that typescript doesn't know about. keyIdentifier: event.keyIdentifier, key: event.key, code: event.code, location: event.location, keyCode: event.keyCode, }; keyboardEventRequestQueue.push(requestPayload); if (!forwardTimer) { forwardTimer = window.setTimeout(forwardEventQueue, 0); } } function forwardEventQueue(): void { forwardTimer = null; extensionServer.sendRequest( {command: PrivateAPI.Commands.ForwardKeyboardEvent, entries: keyboardEventRequestQueue}); keyboardEventRequestQueue = []; } document.addEventListener('keydown', forwardKeyboardEvent, false); function ExtensionServerClient(this: APIImpl.ExtensionServerClient, targetWindow: Window): void { this._callbacks = {}; this._handlers = {}; this._lastRequestId = 0; this._lastObjectId = 0; this.registerHandler('callback', this._onCallback.bind(this)); const channel = new MessageChannel(); this._port = channel.port1; this._port.addEventListener('message', this._onMessage.bind(this), false); this._port.start(); targetWindow.postMessage('registerExtension', '*', [channel.port2]); } (ExtensionServerClient.prototype as Pick< APIImpl.ExtensionServerClient, 'sendRequest'|'hasHandler'|'registerHandler'|'unregisterHandler'|'nextObjectId'|'_registerCallback'| '_onCallback'|'_onMessage'>) = { sendRequest: function( this: APIImpl.ExtensionServerClient, message: PrivateAPI.ServerRequests, callback?: (response: ResponseT) => unknown, transfers?: Transferable[]): void { if (typeof callback === 'function') { (message as PrivateAPI.ExtensionServerRequestMessage).requestId = this._registerCallback(callback as (response: unknown) => unknown); } // @ts-expect-error this._port.postMessage(message, transfers); }, hasHandler: function(this: APIImpl.ExtensionServerClient, command: string): boolean { return Boolean(this._handlers[command]); }, registerHandler: function( this: APIImpl.ExtensionServerClient, command: string, handler: (request: {arguments: unknown[]}) => unknown): void { this._handlers[command] = handler; }, unregisterHandler: function(this: APIImpl.ExtensionServerClient, command: string): void { delete this._handlers[command]; }, nextObjectId: function(this: APIImpl.ExtensionServerClient): string { return injectedScriptId.toString() + '_' + ++this._lastObjectId; }, _registerCallback: function(this: APIImpl.ExtensionServerClient, callback: (response: unknown) => unknown): number { const id = ++this._lastRequestId; this._callbacks[id] = callback; return id; }, _onCallback: function(this: APIImpl.ExtensionServerClient, request: {requestId: number, result: unknown}): void { if (request.requestId in this._callbacks) { const callback = this._callbacks[request.requestId]; delete this._callbacks[request.requestId]; callback(request.result); } }, _onMessage: function( this: APIImpl.ExtensionServerClient, event: MessageEvent<{command: string, requestId: number, arguments: unknown[]}>): void { const request = event.data; const handler = this._handlers[request.command]; if (handler) { handler.call(this, request); } }, }; function populateInterfaceClass(interfaze: Record, implementation: Record): void { for (const member in implementation) { if (member.charAt(0) === '_') { continue; } let descriptor: (PropertyDescriptor|undefined)|null = null; // Traverse prototype chain until we find the owner. for (let owner = implementation; owner && !descriptor; owner = owner.__proto__ as Record) { descriptor = Object.getOwnPropertyDescriptor(owner, member); } if (!descriptor) { continue; } if (typeof descriptor.value === 'function') { interfaze[member] = descriptor.value.bind(implementation); } else if (typeof descriptor.get === 'function') { // @ts-expect-error interfaze.__defineGetter__(member, descriptor.get.bind(implementation)); } else { Object.defineProperty(interfaze, member, descriptor); } } } const extensionServer = new (Constructor(ExtensionServerClient))(targetWindowForTest || window.parent); const coreAPI = new (Constructor(InspectorExtensionAPI))(); Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true}); // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow. // @ts-expect-error chrome.devtools.inspectedWindow = {}; Object.defineProperty(chrome.devtools.inspectedWindow, 'tabId', {get: getTabId}); // @ts-expect-error chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow; chrome.devtools.network = coreAPI.network; chrome.devtools.panels = coreAPI.panels; chrome.devtools.panels.themeName = themeName; chrome.devtools.languageServices = coreAPI.languageServices; chrome.devtools.recorder = coreAPI.recorder; chrome.devtools.performance = coreAPI.performance; // default to expose experimental APIs for now. if (extensionInfo.exposeExperimentalAPIs !== false) { chrome.experimental = chrome.experimental || {}; chrome.experimental.devtools = chrome.experimental.devtools || {}; const properties = Object.getOwnPropertyNames(coreAPI); for (let i = 0; i < properties.length; ++i) { const descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]); if (descriptor) { Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor); } } chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow; } if (extensionInfo.exposeWebInspectorNamespace) { window.webInspector = coreAPI; } testHook(extensionServer, coreAPI); }; self.buildExtensionAPIInjectedScript = function( extensionInfo: { startPage: string, name: string, exposeExperimentalAPIs: boolean, }, inspectedTabId: string, themeName: string, keysToForward: number[], testHook: ((extensionServer: APIImpl.ExtensionServerClient, extensionAPI: APIImpl.InspectorExtensionAPI) => unknown)| undefined): string { const argumentsJSON = [extensionInfo, inspectedTabId || null, themeName, keysToForward].map(_ => JSON.stringify(_)).join(','); if (!testHook) { testHook = (): void => {}; } return '(function(injectedScriptId){ ' + '(' + self.injectedExtensionAPI.toString() + ')(' + argumentsJSON + ',' + testHook + ', injectedScriptId);' + '})'; };