chrome-devtools-frontend
Version:
Chrome DevTools UI
1,234 lines (1,108 loc) • 64.4 kB
text/typescript
/*
* 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 type * as PublicAPI from '../../../extension-api/ExtensionAPI'; // eslint-disable-line rulesdir/es-modules-import
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<string, string>;
}
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;
}
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<KeyboardEventInit&{eventType: string}>;
}
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<string, unknown>};
}
interface StringifyStepRequest {
method: RecorderExtensionPluginCommands.StringifyStep;
parameters: {step: Record<string, unknown>};
}
interface 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 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<string, (response: unknown) => unknown>;
_handlers: Record<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;
}
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: Record<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(): Record<string, string>;
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 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<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 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<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.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<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?.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<string, string>): 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<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?.(content, encoding);
}
extensionServer.sendRequest(
{command: PrivateAPI.Commands.GetRequestContent, id: this._id}, callback && callbackWrapper);
},
};
function Panels(this: APIImpl.Panels): void {
const panels: Record<string, ElementsPanel|SourcesPanel|PublicAPI.Chrome.DevTools.NetworkPanel> = {
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<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 && (() => 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};
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(): Record<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?.(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 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<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 await 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 await 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 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<void> {
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<PublicAPI.Chrome.DevTools.NetworkPanel, 'show'>) = {
show: function(options?: {filter: string}): Promise<void> {
return new Promise<void>(
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<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 Record<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 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<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,
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});
},
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<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 InspectedWindow(this: PublicAPI.Chrome.DevTools.InspectedWindow): void {
function dispatchResourceEvent(
this: APIImpl.EventSink<(resource: APIImpl.Resource) => unknown>, message: {arguments: unknown[]}): void {
const resourceData = message