UNPKG

chrome-devtools-frontend

Version:
1,164 lines (1,036 loc) • 63.7 kB
/* * Copyright (C) 2012 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import * as Platform from '../../core/platform/platform.js'; import type * as PublicAPI from '../../../extension-api/ExtensionAPI'; // eslint-disable-line rulesdir/es_modules_import import type * as HAR from '../har/har.js'; /* eslint-disable @typescript-eslint/naming-convention,@typescript-eslint/no-non-null-assertion */ 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-', RecordingStarted = 'trace-recording-started-', RecordingStopped = 'trace-recording-stopped-', ResourceAdded = 'resource-added', ResourceContentCommitted = 'resource-content-committed', ViewShown = 'view-shown-', ViewHidden = 'view-hidden,', ThemeChange = 'host-theme-change', } export const enum Commands { AddRequestHeaders = 'addRequestHeaders', AddTraceProvider = 'addTraceProvider', ApplyStyleSheet = 'applyStyleSheet', CompleteTraceSession = 'completeTra.eSession', 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', RegisterLanguageExtensionPlugin = 'registerLanguageExtensionPlugin', GetWasmLinearMemory = 'getWasmLinearMemory', GetWasmLocal = 'getWasmLocal', GetWasmGlobal = 'getWasmGlobal', GetWasmOp = 'getWasmOp', RegisterRecorderExtensionPlugin = 'registerRecorderExtensionPlugin', CreateRecorderView = 'createRecorderView', ShowRecorderView = 'showRecorderView', } 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; } type RegisterLanguageExtensionPluginRequest = { command: Commands.RegisterLanguageExtensionPlugin, pluginName: string, port: MessagePort, supportedScriptTypes: PublicAPI.Chrome.DevTools.SupportedScriptTypes, }; export type RecordingExtensionPluginCapability = 'export'|'replay'; type RegisterRecorderExtensionPluginRequest = { command: Commands.RegisterRecorderExtensionPlugin, pluginName: string, mediaType?: string, capabilities: RecordingExtensionPluginCapability[], port: MessagePort, }; type CreateRecorderViewRequest = { command: Commands.CreateRecorderView, id: string, title: string, pagePath: string, }; type ShowRecorderViewRequest = { command: Commands.ShowRecorderView, id: string, }; type SubscribeRequest = {command: Commands.Subscribe, type: string}; type UnsubscribeRequest = {command: Commands.Unsubscribe, type: string}; type AddRequestHeadersRequest = { command: Commands.AddRequestHeaders, extensionId: string, headers: {[key: string]: string}, }; type ApplyStyleSheetRequest = {command: Commands.ApplyStyleSheet, styleSheet: string}; type CreatePanelRequest = {command: Commands.CreatePanel, id: string, title: string, page: string}; type ShowPanelRequest = {command: Commands.ShowPanel, id: string}; type CreateToolbarButtonRequest = { command: Commands.CreateToolbarButton, id: string, icon: string, panel: string, tooltip?: string, disabled?: boolean, }; type UpdateButtonRequest = {command: Commands.UpdateButton, id: string, icon?: string, tooltip?: string, disabled?: boolean}; type CompleteTraceSessionRequest = {command: Commands.CompleteTraceSession, id: string, url: Platform.DevToolsPath.UrlString, timeOffset: number}; type CreateSidebarPaneRequest = {command: Commands.CreateSidebarPane, id: string, panel: string, title: string}; type SetSidebarHeightRequest = {command: Commands.SetSidebarHeight, id: string, height: string}; type SetSidebarContentRequest = { command: Commands.SetSidebarContent, id: string, evaluateOnPage?: boolean, expression: string, rootTitle?: string, evaluateOptions?: EvaluateOptions, }; type SetSidebarPageRequest = {command: Commands.SetSidebarPage, id: string, page: string}; type OpenResourceRequest = {command: Commands.OpenResource, url: Platform.DevToolsPath.UrlString, lineNumber: number, columnNumber: number}; type SetOpenResourceHandlerRequest = {command: Commands.SetOpenResourceHandler, handlerPresent: boolean}; type SetThemeChangeHandlerRequest = {command: Commands.SetThemeChangeHandler, handlerPresent: boolean}; type ReloadRequest = { command: Commands.Reload, options: null|{ userAgent?: string, injectedScript?: string, ignoreCache?: boolean, }, }; type EvaluateOnInspectedPageRequest = { command: Commands.EvaluateOnInspectedPage, expression: string, evaluateOptions?: EvaluateOptions, }; type GetRequestContentRequest = {command: Commands.GetRequestContent, id: number}; type GetResourceContentRequest = {command: Commands.GetResourceContent, url: string}; type SetResourceContentRequest = {command: Commands.SetResourceContent, url: string, content: string, commit: boolean}; type AddTraceProviderRequest = {command: Commands.AddTraceProvider, id: string, categoryName: string, categoryTooltip: string}; type ForwardKeyboardEventRequest = { command: Commands.ForwardKeyboardEvent, entries: Array<KeyboardEventInit&{eventType: string}>, }; type GetHARRequest = {command: Commands.GetHAR}; type GetPageResourcesRequest = {command: Commands.GetPageResources}; type GetWasmLinearMemoryRequest = { command: Commands.GetWasmLinearMemory, offset: number, length: number, stopId: unknown, }; type GetWasmLocalRequest = { command: Commands.GetWasmLocal, local: number, stopId: unknown, }; type GetWasmGlobalRequest = { command: Commands.GetWasmGlobal, global: number, stopId: unknown, }; type GetWasmOpRequest = {command: Commands.GetWasmOp, op: number, stopId: unknown}; export type ServerRequests = ShowRecorderViewRequest|CreateRecorderViewRequest|RegisterRecorderExtensionPluginRequest| RegisterLanguageExtensionPluginRequest|SubscribeRequest|UnsubscribeRequest|AddRequestHeadersRequest| ApplyStyleSheetRequest|CreatePanelRequest|ShowPanelRequest|CreateToolbarButtonRequest|UpdateButtonRequest| CompleteTraceSessionRequest|CreateSidebarPaneRequest|SetSidebarHeightRequest|SetSidebarContentRequest| SetSidebarPageRequest|OpenResourceRequest|SetOpenResourceHandlerRequest|SetThemeChangeHandlerRequest| ReloadRequest|EvaluateOnInspectedPageRequest|GetRequestContentRequest|GetResourceContentRequest| SetResourceContentRequest|AddTraceProviderRequest|ForwardKeyboardEventRequest|GetHARRequest| GetPageResourcesRequest|GetWasmLinearMemoryRequest|GetWasmLocalRequest|GetWasmGlobalRequest|GetWasmOpRequest; export type ExtensionServerRequestMessage = PrivateAPI.ServerRequests&{requestId?: number}; type AddRawModuleRequest = { method: LanguageExtensionPluginCommands.AddRawModule, parameters: {rawModuleId: string, symbolsURL: string|undefined, rawModule: PublicAPI.Chrome.DevTools.RawModule}, }; type SourceLocationToRawLocationRequest = { method: LanguageExtensionPluginCommands.SourceLocationToRawLocation, parameters: {sourceLocation: PublicAPI.Chrome.DevTools.SourceLocation}, }; type RawLocationToSourceLocationRequest = { method: LanguageExtensionPluginCommands.RawLocationToSourceLocation, parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}, }; type GetScopeInfoRequest = {method: LanguageExtensionPluginCommands.GetScopeInfo, parameters: {type: string}}; type ListVariablesInScopeRequest = { method: LanguageExtensionPluginCommands.ListVariablesInScope, parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}, }; type RemoveRawModuleRequest = { method: LanguageExtensionPluginCommands.RemoveRawModule, parameters: {rawModuleId: string}, }; type GetFunctionInfoRequest = { method: LanguageExtensionPluginCommands.GetFunctionInfo, parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}, }; type GetInlinedFunctionRangesRequest = { method: LanguageExtensionPluginCommands.GetInlinedFunctionRanges, parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}, }; type GetInlinedCalleesRangesRequest = { method: LanguageExtensionPluginCommands.GetInlinedCalleesRanges, parameters: {rawLocation: PublicAPI.Chrome.DevTools.RawLocation}, }; type GetMappedLinesRequest = { method: LanguageExtensionPluginCommands.GetMappedLines, parameters: {rawModuleId: string, sourceFileURL: string}, }; type FormatValueRequest = { method: LanguageExtensionPluginCommands.FormatValue, parameters: {expression: string, context: PublicAPI.Chrome.DevTools.RawLocation, stopId: number}, }; type GetPropertiesRequest = { method: LanguageExtensionPluginCommands.GetProperties, parameters: {objectId: PublicAPI.Chrome.DevTools.RemoteObjectId}, }; type 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; type StringifyRequest = { method: RecorderExtensionPluginCommands.Stringify, parameters: {recording: Record<string, unknown>}, }; type StringifyStepRequest = { method: RecorderExtensionPluginCommands.StringifyStep, parameters: {step: Record<string, unknown>}, }; type ReplayRequest = { method: RecorderExtensionPluginCommands.Replay, parameters: {recording: Record<string, unknown>}, }; 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: undefined|((extensionServer: unknown, extensionAPI: unknown) => unknown)): string; chrome: PublicAPI.Chrome.DevTools.Chrome; webInspector?: APIImpl.InspectorExtensionAPI; } } export type 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; timeline: Timeline; network: PublicAPI.Chrome.DevTools.Network; panels: PublicAPI.Chrome.DevTools.Panels; inspectedWindow: PublicAPI.Chrome.DevTools.InspectedWindow; } export interface ExtensionServerClient { _callbacks: {[key: string]: (response: unknown) => unknown}; _handlers: {[key: string]: (request: {arguments: unknown[]}) => 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<ResponseT>( request: PrivateAPI.ServerRequests, callback?: ((response: ResponseT) => unknown), transfers?: unknown[]): void; nextObjectId(): string; } // We cannot use the stronger `unknown` type in place of `any` in the following type definition. The type is used as // the right-hand side of `extends` in a few places, which doesn't narrow `unknown`. Without narrowing, overload // resolution and meaningful type inference of arguments break, for example. // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any export type Callable = (...args: any) => void; export interface EventSink<ListenerT extends Callable> extends PublicAPI.Chrome.DevTools.EventSink<ListenerT> { _type: string; _listeners: ListenerT[]; _customDispatch: undefined|((this: EventSink<ListenerT>, request: {arguments: unknown[]}) => unknown); _fire(..._vararg: Parameters<ListenerT>): void; _dispatch(request: {arguments: unknown[]}): void; } export interface Network extends PublicAPI.Chrome.DevTools.Network { addRequestHeaders(headers: {[key: string]: string}): void; } export interface Request extends PublicAPI.Chrome.DevTools.Request, HAR.Log.EntryDTO { _id: number; } export interface Panels extends PublicAPI.Chrome.DevTools.Panels { get SearchAction(): {[key: string]: string}; applyStyleSheet(styleSheet: string): void; setOpenResourceHandler(callback?: (resource: PublicAPI.Chrome.DevTools.Resource, lineNumber: 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<PublicAPI.Chrome.DevTools.LanguageExtensionPlugin, MessagePort>; } export interface RecorderExtensions extends PublicAPI.Chrome.DevTools.RecorderExtensions { _plugins: Map<PublicAPI.Chrome.DevTools.RecorderExtensionPlugin, MessagePort>; } 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 TraceSession { _id: string; complete(url?: string, timeOffset?: number): void; } export interface TraceProvider { onRecordingStarted: EventSink<(session: TraceSession) => unknown>; onRecordingStopped: EventSink<() => unknown>; } export interface Timeline { addTraceProvider(categoryName: string, categoryTooltip: string): TraceProvider; } export type ResourceData = {url: string, type: string}; export interface Resource extends PublicAPI.Chrome.DevTools.Resource { _type: string; _url: 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<number>(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<ListenerT extends APIImpl.Callable>( this: APIImpl.EventSink<ListenerT>, type: string, customDispatch?: (this: APIImpl.EventSink<ListenerT>, request: {arguments: unknown[]}) => unknown): void { this._type = type; this._listeners = []; this._customDispatch = customDispatch; } EventSinkImpl.prototype = { addListener: function<ListenerT extends APIImpl.Callable>(this: APIImpl.EventSink<ListenerT>, callback: ListenerT): void { if (typeof callback !== 'function') { throw '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<ListenerT extends APIImpl.Callable>( this: APIImpl.EventSink<ListenerT>, 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<ListenerT extends APIImpl.Callable>( this: APIImpl.EventSink<ListenerT>, ..._vararg: Parameters<ListenerT>): void { const listeners = this._listeners.slice(); for (let i = 0; i < listeners.length; ++i) { listeners[i].apply(null, Array.from(arguments)); } }, _dispatch: function<ListenerT extends APIImpl.Callable>( this: APIImpl.EventSink<ListenerT>, request: {arguments: unknown[]}): void { if (this._customDispatch) { this._customDispatch.call(this, request); } else { this._fire.apply(this, request.arguments as Parameters<ListenerT>); } }, }; function Constructor<NewT extends APIImpl.Callable>(ctor: NewT): new (...args: Parameters<NewT>) => ThisParameterType<NewT> { return ctor as unknown as new (...args: Parameters<NewT>) => ThisParameterType<NewT>; } function InspectorExtensionAPI(this: APIImpl.InspectorExtensionAPI): void { this.inspectedWindow = new (Constructor(InspectedWindow))(); this.panels = new (Constructor(Panels))(); this.network = new (Constructor(Network))(); this.timeline = new (Constructor(Timeline))(); this.languageServices = new (Constructor(LanguageServicesAPI))(); this.recorder = new (Constructor(RecorderServicesAPI))(); 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<APIImpl.Network, 'getHAR'|'addRequestHeaders'>) = { getHAR: function(this: PublicAPI.Chrome.DevTools.Network, callback?: (harLog: Object) => unknown): void { function callbackWrapper(response: unknown): void { const result = response as ({entries: Array<HAR.Log.EntryDTO&{__proto__?: APIImpl.Request, _requestId?: number}>}); const entries = (result && 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 && callback(result as Object); } extensionServer.sendRequest({command: PrivateAPI.Commands.GetHAR}, callback && callbackWrapper); }, addRequestHeaders: function(headers: {[key: string]: string}): void { extensionServer.sendRequest( {command: PrivateAPI.Commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname}); }, }; function RequestImpl(this: APIImpl.Request, id: number): void { this._id = id; } (RequestImpl.prototype as Pick<APIImpl.Request, 'getContent'>) = { 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 && callback(content, encoding); } extensionServer.sendRequest( {command: PrivateAPI.Commands.GetRequestContent, id: this._id}, callback && callbackWrapper); }, }; function Panels(this: APIImpl.Panels): void { const panels: {[key: string]: ElementsPanel|SourcesPanel} = { elements: new ElementsPanel(), sources: new SourcesPanel(), }; function panelGetter(name: string): ElementsPanel|SourcesPanel { return panels[name]; } for (const panel in panels) { Object.defineProperty(this, panel, {get: panelGetter.bind(null, panel), enumerable: true}); } this.applyStyleSheet = function(styleSheet: string): void { extensionServer.sendRequest({command: PrivateAPI.Commands.ApplyStyleSheet, styleSheet: styleSheet}); }; } (Panels.prototype as Pick<APIImpl.Panels, 'create'|'setOpenResourceHandler'|'openResource'|'SearchAction'|'setThemeChangeHandler'>) = { 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 && ((): unknown => callback.call(this, new (Constructor(ExtensionPanel))(id)))); }, setOpenResourceHandler: function( callback: (resource: PublicAPI.Chrome.DevTools.Resource, lineNumber: number) => unknown): 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} = message as {resource: APIImpl.ResourceData, lineNumber: number}; if (canAccessResource(resource)) { callback.call(null, new (Constructor(Resource))(resource), lineNumber); } } 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)}); } }, 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(): {[key: string]: string} { 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<APIImpl.PanelWithSidebar, 'createSidebarPane'>& {__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 && 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<void> { 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<unknown> { switch (request.method) { case PrivateAPI.RecorderExtensionPluginCommands.Stringify: return (plugin as PublicAPI.Chrome.DevTools.RecorderExtensionExportPlugin) .stringify(request.parameters.recording); case PrivateAPI.RecorderExtensionPluginCommands.StringifyStep: return (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<void>(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<void> { 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<PublicAPI.Chrome.DevTools.RecorderView> { 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<void> { 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<unknown> { 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<void>(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<void> { 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<ArrayBuffer> { 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<PublicAPI.Chrome.DevTools.WasmValue> { return new Promise( resolve => extensionServer.sendRequest({command: PrivateAPI.Commands.GetWasmLocal, local, stopId}, resolve)); }, getWasmGlobal: async function(this: APIImpl.LanguageExtensions, global: number, stopId: number): Promise<PublicAPI.Chrome.DevTools.WasmValue> { return new Promise( resolve => extensionServer.sendRequest({command: PrivateAPI.Commands.GetWasmGlobal, global, stopId}, resolve)); }, getWasmOp: async function(this: APIImpl.LanguageExtensions, op: number, stopId: number): Promise<PublicAPI.Chrome.DevTools.WasmValue> { return new Promise( resolve => extensionServer.sendRequest({command: PrivateAPI.Commands.GetWasmOp, op, stopId}, resolve)); }, }; function declareInterfaceClass<ImplT extends APIImpl.Callable>(implConstructor: ImplT): ( this: ThisParameterType<ImplT>, ...args: Parameters<ImplT>) => void { return function(this: ThisParameterType<ImplT>, ...args: Parameters<ImplT>): void { const impl = {__proto__: implConstructor.prototype}; implConstructor.apply(impl, args); populateInterfaceClass(this as {[key: string]: unknown}, 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 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 TraceSession = declareInterfaceClass(TraceSessionImpl); 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<APIImpl.ExtensionPanel, 'createStatusBarButton'|'show'>& {__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: 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<APIImpl.RecorderView, 'show'>& {__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<APIImpl.ExtensionSidebarPane, 'setHeight'|'setExpression'|'setObject'|'setPage'>& {__proto__: APIImpl.ExtensionView}) = { setHeight: function(this: APIImpl.ExtensionSidebarPane, height: string): void { extensionServer.sendRequest( {command: PrivateAPI.Commands.SetSidebarHeight, id: this._id as string, height: 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: expression, rootTitle: 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: rootTitle, }, callback); }, setPage: function(this: APIImpl.ExtensionSidebarPane, page: string): void { extensionServer.sendRequest({command: PrivateAPI.Commands.SetSidebarPage, id: this._id as string, page: 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<APIImpl.Button, 'update'>) = { 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 Timeline(this: APIImpl.Timeline): void { } (Timeline.prototype as Pick<APIImpl.Timeline, 'addTraceProvider'>) = { addTraceProvider: function(this: APIImpl.Timeline, categoryName: string, categoryTooltip: string): APIImpl.TraceProvider { const id = 'extension-trace-provider-' + extensionServer.nextObjectId(); extensionServer.sendRequest({ command: PrivateAPI.Commands.AddTraceProvider, id: id, categoryName: categoryName, categoryTooltip: categoryTooltip, }); return new (Constructor(TraceProvider))(id); }, }; function TraceSessionImpl(this: APIImpl.TraceSession, id: string): void { this._id = id; } (TraceSessionImpl.prototype as Pick<APIImpl.TraceSession, 'complete'>) = { complete: function(this: APIImpl.TraceSession, url?: Platform.DevToolsPath.UrlString, timeOffset?: number): void { extensionServer.sendRequest({ command: PrivateAPI.Commands.CompleteTraceSession, id: this._id, url: url || Platform.DevToolsPath.EmptyUrlString, timeOffset: timeOffset || 0, }); }, }; function TraceProvider(this: APIImpl.TraceProvider, id: string): void { function dispatchRecordingStarted( this: APIImpl.EventSink<APIImpl.Callable>, message: {arguments: unknown[]}): void { const sessionId = message.arguments[0]