UNPKG

@quick-game/cli

Version:

Command line interface for rapid qg development

979 lines 58.5 kB
/* * Copyright (C) 2011 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 Common from '../../core/common/common.js'; import * as Host from '../../core/host/host.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as Platform from '../../core/platform/platform.js'; import * as Root from '../../core/root/root.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Logs from '../../models/logs/logs.js'; import * as Components from '../../ui/legacy/components/utils/utils.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js'; import * as Bindings from '../bindings/bindings.js'; import * as HAR from '../har/har.js'; import * as Workspace from '../workspace/workspace.js'; import { ExtensionButton, ExtensionPanel, ExtensionSidebarPane } from './ExtensionPanel.js'; import { HostUrlPattern } from './HostUrlPattern.js'; import { LanguageExtensionEndpoint } from './LanguageExtensionEndpoint.js'; import { RecorderExtensionEndpoint } from './RecorderExtensionEndpoint.js'; import { RecorderPluginManager } from './RecorderPluginManager.js'; const extensionOrigins = new WeakMap(); const kAllowedOrigins = [].map(url => (new URL(url)).origin); let extensionServerInstance; export class HostsPolicy { runtimeAllowedHosts; runtimeBlockedHosts; static create(policy) { const runtimeAllowedHosts = []; const runtimeBlockedHosts = []; if (policy) { for (const pattern of policy.runtimeAllowedHosts) { const parsedPattern = HostUrlPattern.parse(pattern); if (!parsedPattern) { return null; } runtimeAllowedHosts.push(parsedPattern); } for (const pattern of policy.runtimeBlockedHosts) { const parsedPattern = HostUrlPattern.parse(pattern); if (!parsedPattern) { return null; } runtimeBlockedHosts.push(parsedPattern); } } return new HostsPolicy(runtimeAllowedHosts, runtimeBlockedHosts); } constructor(runtimeAllowedHosts, runtimeBlockedHosts) { this.runtimeAllowedHosts = runtimeAllowedHosts; this.runtimeBlockedHosts = runtimeBlockedHosts; } isAllowedOnURL(inspectedURL) { if (!inspectedURL) { // If there aren't any blocked hosts retain the old behavior and don't worry about the inspectedURL return this.runtimeBlockedHosts.length === 0; } if (this.runtimeBlockedHosts.some(pattern => pattern.matchesUrl(inspectedURL)) && !this.runtimeAllowedHosts.some(pattern => pattern.matchesUrl(inspectedURL))) { return false; } return true; } } class RegisteredExtension { name; hostsPolicy; allowFileAccess; constructor(name, hostsPolicy, allowFileAccess) { this.name = name; this.hostsPolicy = hostsPolicy; this.allowFileAccess = allowFileAccess; } isAllowedOnTarget(inspectedURL) { if (!inspectedURL) { inspectedURL = SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.inspectedURL(); } if (!inspectedURL) { return false; } if (!ExtensionServer.canInspectURL(inspectedURL)) { return false; } if (!this.hostsPolicy.isAllowedOnURL(inspectedURL)) { return false; } if (!this.allowFileAccess) { let parsedURL; try { parsedURL = new URL(inspectedURL); } catch (exception) { return false; } return parsedURL.protocol !== 'file:'; } return true; } } export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper { clientObjects; handlers; subscribers; subscriptionStartHandlers; subscriptionStopHandlers; extraHeaders; requests; requestIds; lastRequestId; registeredExtensions; status; sidebarPanesInternal; extensionsEnabled; inspectedTabId; extensionAPITestHook; themeChangeHandlers = new Map(); #pendingExtensions = []; constructor() { super(); this.clientObjects = new Map(); this.handlers = new Map(); this.subscribers = new Map(); this.subscriptionStartHandlers = new Map(); this.subscriptionStopHandlers = new Map(); this.extraHeaders = new Map(); this.requests = new Map(); this.requestIds = new Map(); this.lastRequestId = 0; this.registeredExtensions = new Map(); this.status = new ExtensionStatus(); this.sidebarPanesInternal = []; // TODO(caseq): properly unload extensions when we disable them. this.extensionsEnabled = true; this.registerHandler("addRequestHeaders" /* PrivateAPI.Commands.AddRequestHeaders */, this.onAddRequestHeaders.bind(this)); this.registerHandler("applyStyleSheet" /* PrivateAPI.Commands.ApplyStyleSheet */, this.onApplyStyleSheet.bind(this)); this.registerHandler("createPanel" /* PrivateAPI.Commands.CreatePanel */, this.onCreatePanel.bind(this)); this.registerHandler("createSidebarPane" /* PrivateAPI.Commands.CreateSidebarPane */, this.onCreateSidebarPane.bind(this)); this.registerHandler("createToolbarButton" /* PrivateAPI.Commands.CreateToolbarButton */, this.onCreateToolbarButton.bind(this)); this.registerHandler("evaluateOnInspectedPage" /* PrivateAPI.Commands.EvaluateOnInspectedPage */, this.onEvaluateOnInspectedPage.bind(this)); this.registerHandler("_forwardKeyboardEvent" /* PrivateAPI.Commands.ForwardKeyboardEvent */, this.onForwardKeyboardEvent.bind(this)); this.registerHandler("getHAR" /* PrivateAPI.Commands.GetHAR */, this.onGetHAR.bind(this)); this.registerHandler("getPageResources" /* PrivateAPI.Commands.GetPageResources */, this.onGetPageResources.bind(this)); this.registerHandler("getRequestContent" /* PrivateAPI.Commands.GetRequestContent */, this.onGetRequestContent.bind(this)); this.registerHandler("getResourceContent" /* PrivateAPI.Commands.GetResourceContent */, this.onGetResourceContent.bind(this)); this.registerHandler("Reload" /* PrivateAPI.Commands.Reload */, this.onReload.bind(this)); this.registerHandler("setOpenResourceHandler" /* PrivateAPI.Commands.SetOpenResourceHandler */, this.onSetOpenResourceHandler.bind(this)); this.registerHandler("setThemeChangeHandler" /* PrivateAPI.Commands.SetThemeChangeHandler */, this.onSetThemeChangeHandler.bind(this)); this.registerHandler("setResourceContent" /* PrivateAPI.Commands.SetResourceContent */, this.onSetResourceContent.bind(this)); this.registerHandler("setSidebarHeight" /* PrivateAPI.Commands.SetSidebarHeight */, this.onSetSidebarHeight.bind(this)); this.registerHandler("setSidebarContent" /* PrivateAPI.Commands.SetSidebarContent */, this.onSetSidebarContent.bind(this)); this.registerHandler("setSidebarPage" /* PrivateAPI.Commands.SetSidebarPage */, this.onSetSidebarPage.bind(this)); this.registerHandler("showPanel" /* PrivateAPI.Commands.ShowPanel */, this.onShowPanel.bind(this)); this.registerHandler("subscribe" /* PrivateAPI.Commands.Subscribe */, this.onSubscribe.bind(this)); this.registerHandler("openResource" /* PrivateAPI.Commands.OpenResource */, this.onOpenResource.bind(this)); this.registerHandler("unsubscribe" /* PrivateAPI.Commands.Unsubscribe */, this.onUnsubscribe.bind(this)); this.registerHandler("updateButton" /* PrivateAPI.Commands.UpdateButton */, this.onUpdateButton.bind(this)); this.registerHandler("registerLanguageExtensionPlugin" /* PrivateAPI.Commands.RegisterLanguageExtensionPlugin */, this.registerLanguageExtensionEndpoint.bind(this)); this.registerHandler("getWasmLinearMemory" /* PrivateAPI.Commands.GetWasmLinearMemory */, this.onGetWasmLinearMemory.bind(this)); this.registerHandler("getWasmGlobal" /* PrivateAPI.Commands.GetWasmGlobal */, this.onGetWasmGlobal.bind(this)); this.registerHandler("getWasmLocal" /* PrivateAPI.Commands.GetWasmLocal */, this.onGetWasmLocal.bind(this)); this.registerHandler("getWasmOp" /* PrivateAPI.Commands.GetWasmOp */, this.onGetWasmOp.bind(this)); this.registerHandler("registerRecorderExtensionPlugin" /* PrivateAPI.Commands.RegisterRecorderExtensionPlugin */, this.registerRecorderExtensionEndpoint.bind(this)); this.registerHandler("createRecorderView" /* PrivateAPI.Commands.CreateRecorderView */, this.onCreateRecorderView.bind(this)); this.registerHandler("showRecorderView" /* PrivateAPI.Commands.ShowRecorderView */, this.onShowRecorderView.bind(this)); window.addEventListener('message', this.onWindowMessage, false); // Only for main window. const existingTabId = window.DevToolsAPI && window.DevToolsAPI.getInspectedTabId && window.DevToolsAPI.getInspectedTabId(); if (existingTabId) { this.setInspectedTabId({ data: existingTabId }); } Host.InspectorFrontendHost.InspectorFrontendHostInstance.events.addEventListener(Host.InspectorFrontendHostAPI.Events.SetInspectedTabId, this.setInspectedTabId, this); this.initExtensions(); ThemeSupport.ThemeSupport.instance().addEventListener(ThemeSupport.ThemeChangeEvent.eventName, this.#onThemeChange); } dispose() { ThemeSupport.ThemeSupport.instance().removeEventListener(ThemeSupport.ThemeChangeEvent.eventName, this.#onThemeChange); // Set up by this.initExtensions in the constructor. SDK.TargetManager.TargetManager.instance().removeEventListener(SDK.TargetManager.Events.InspectedURLChanged, this.inspectedURLChanged, this); Host.InspectorFrontendHost.InspectorFrontendHostInstance.events.removeEventListener(Host.InspectorFrontendHostAPI.Events.SetInspectedTabId, this.setInspectedTabId, this); window.removeEventListener('message', this.onWindowMessage, false); } #onThemeChange = () => { const themeName = ThemeSupport.ThemeSupport.instance().themeName(); for (const port of this.themeChangeHandlers.values()) { port.postMessage({ command: "host-theme-change" /* PrivateAPI.Events.ThemeChange */, themeName }); } }; static instance(opts = { forceNew: null }) { const { forceNew } = opts; if (!extensionServerInstance || forceNew) { extensionServerInstance?.dispose(); extensionServerInstance = new ExtensionServer(); } return extensionServerInstance; } initializeExtensions() { // Defer initialization until DevTools is fully loaded. if (this.inspectedTabId !== null) { Host.InspectorFrontendHost.InspectorFrontendHostInstance.setAddExtensionCallback(this.addExtension.bind(this)); } } hasExtensions() { return Boolean(this.registeredExtensions.size); } notifySearchAction(panelId, action, searchString) { this.postNotification("panel-search-" /* PrivateAPI.Events.PanelSearch */ + panelId, action, searchString); } notifyViewShown(identifier, frameIndex) { this.postNotification("view-shown-" /* PrivateAPI.Events.ViewShown */ + identifier, frameIndex); } notifyViewHidden(identifier) { this.postNotification("view-hidden," /* PrivateAPI.Events.ViewHidden */ + identifier); } notifyButtonClicked(identifier) { this.postNotification("button-clicked-" /* PrivateAPI.Events.ButtonClicked */ + identifier); } registerLanguageExtensionEndpoint(message, _shared_port) { if (message.command !== "registerLanguageExtensionPlugin" /* PrivateAPI.Commands.RegisterLanguageExtensionPlugin */) { return this.status.E_BADARG('command', `expected ${"registerLanguageExtensionPlugin" /* PrivateAPI.Commands.RegisterLanguageExtensionPlugin */}`); } const { pluginManager } = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(); if (!pluginManager) { return this.status.E_FAILED('WebAssembly DWARF support needs to be enabled to use this extension'); } const { pluginName, port, supportedScriptTypes: { language, symbol_types } } = message; const symbol_types_array = (Array.isArray(symbol_types) && symbol_types.every(e => typeof e === 'string') ? symbol_types : []); const endpoint = new LanguageExtensionEndpoint(pluginName, { language, symbol_types: symbol_types_array }, port); pluginManager.addPlugin(endpoint); return this.status.OK(); } async loadWasmValue(expression, stopId) { const { pluginManager } = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(); if (!pluginManager) { return this.status.E_FAILED('WebAssembly DWARF support needs to be enabled to use this extension'); } const callFrame = pluginManager.callFrameForStopId(stopId); if (!callFrame) { return this.status.E_BADARG('stopId', 'Unknown stop id'); } const result = await callFrame.debuggerModel.agent.invoke_evaluateOnCallFrame({ callFrameId: callFrame.id, expression, silent: true, returnByValue: true, throwOnSideEffect: true, }); if (!result.exceptionDetails && !result.getError()) { return result.result.value; } return this.status.E_FAILED('Failed'); } async onGetWasmLinearMemory(message) { if (message.command !== "getWasmLinearMemory" /* PrivateAPI.Commands.GetWasmLinearMemory */) { return this.status.E_BADARG('command', `expected ${"getWasmLinearMemory" /* PrivateAPI.Commands.GetWasmLinearMemory */}`); } return await this.loadWasmValue(`[].slice.call(new Uint8Array(memories[0].buffer, ${Number(message.offset)}, ${Number(message.length)}))`, message.stopId); } async onGetWasmGlobal(message) { if (message.command !== "getWasmGlobal" /* PrivateAPI.Commands.GetWasmGlobal */) { return this.status.E_BADARG('command', `expected ${"getWasmGlobal" /* PrivateAPI.Commands.GetWasmGlobal */}`); } return this.loadWasmValue(`globals[${Number(message.global)}]`, message.stopId); } async onGetWasmLocal(message) { if (message.command !== "getWasmLocal" /* PrivateAPI.Commands.GetWasmLocal */) { return this.status.E_BADARG('command', `expected ${"getWasmLocal" /* PrivateAPI.Commands.GetWasmLocal */}`); } return this.loadWasmValue(`locals[${Number(message.local)}]`, message.stopId); } async onGetWasmOp(message) { if (message.command !== "getWasmOp" /* PrivateAPI.Commands.GetWasmOp */) { return this.status.E_BADARG('command', `expected ${"getWasmOp" /* PrivateAPI.Commands.GetWasmOp */}`); } return this.loadWasmValue(`stack[${Number(message.op)}]`, message.stopId); } registerRecorderExtensionEndpoint(message, _shared_port) { if (message.command !== "registerRecorderExtensionPlugin" /* PrivateAPI.Commands.RegisterRecorderExtensionPlugin */) { return this.status.E_BADARG('command', `expected ${"registerRecorderExtensionPlugin" /* PrivateAPI.Commands.RegisterRecorderExtensionPlugin */}`); } const { pluginName, mediaType, port, capabilities } = message; RecorderPluginManager.instance().addPlugin(new RecorderExtensionEndpoint(pluginName, port, capabilities, mediaType)); return this.status.OK(); } onShowRecorderView(message) { if (message.command !== "showRecorderView" /* PrivateAPI.Commands.ShowRecorderView */) { return this.status.E_BADARG('command', `expected ${"showRecorderView" /* PrivateAPI.Commands.ShowRecorderView */}`); } RecorderPluginManager.instance().showView(message.id); return undefined; } onCreateRecorderView(message, port) { if (message.command !== "createRecorderView" /* PrivateAPI.Commands.CreateRecorderView */) { return this.status.E_BADARG('command', `expected ${"createRecorderView" /* PrivateAPI.Commands.CreateRecorderView */}`); } const id = message.id; // The ids are generated on the client API side and must be unique, so the check below // shouldn't be hit unless someone is bypassing the API. if (this.clientObjects.has(id)) { return this.status.E_EXISTS(id); } const pagePath = ExtensionServer.expandResourcePath(this.getExtensionOrigin(port), message.pagePath); if (pagePath === undefined) { return this.status.E_BADARG('pagePath', 'Resources paths cannot point to non-extension resources'); } const onShown = () => this.notifyViewShown(id); const onHidden = () => this.notifyViewHidden(id); RecorderPluginManager.instance().registerView({ id, pagePath, title: message.title, onShown, onHidden, }); return this.status.OK(); } inspectedURLChanged(event) { if (!ExtensionServer.canInspectURL(event.data.inspectedURL())) { this.disableExtensions(); return; } if (event.data !== SDK.TargetManager.TargetManager.instance().primaryPageTarget()) { return; } this.requests = new Map(); const url = event.data.inspectedURL(); this.postNotification("inspected-url-changed" /* PrivateAPI.Events.InspectedURLChanged */, url); this.#pendingExtensions.forEach(e => this.addExtension(e)); this.#pendingExtensions.splice(0); } hasSubscribers(type) { return this.subscribers.has(type); } postNotification(type, ..._vararg) { if (!this.extensionsEnabled) { return; } const subscribers = this.subscribers.get(type); if (!subscribers) { return; } const message = { command: 'notify-' + type, arguments: Array.prototype.slice.call(arguments, 1) }; for (const subscriber of subscribers) { if (this.extensionEnabled(subscriber)) { subscriber.postMessage(message); } } } onSubscribe(message, port) { if (message.command !== "subscribe" /* PrivateAPI.Commands.Subscribe */) { return this.status.E_BADARG('command', `expected ${"subscribe" /* PrivateAPI.Commands.Subscribe */}`); } const subscribers = this.subscribers.get(message.type); if (subscribers) { subscribers.add(port); } else { this.subscribers.set(message.type, new Set([port])); const handler = this.subscriptionStartHandlers.get(message.type); if (handler) { handler(); } } return undefined; } onUnsubscribe(message, port) { if (message.command !== "unsubscribe" /* PrivateAPI.Commands.Unsubscribe */) { return this.status.E_BADARG('command', `expected ${"unsubscribe" /* PrivateAPI.Commands.Unsubscribe */}`); } const subscribers = this.subscribers.get(message.type); if (!subscribers) { return; } subscribers.delete(port); if (!subscribers.size) { this.subscribers.delete(message.type); const handler = this.subscriptionStopHandlers.get(message.type); if (handler) { handler(); } } return undefined; } onAddRequestHeaders(message) { if (message.command !== "addRequestHeaders" /* PrivateAPI.Commands.AddRequestHeaders */) { return this.status.E_BADARG('command', `expected ${"addRequestHeaders" /* PrivateAPI.Commands.AddRequestHeaders */}`); } const id = message.extensionId; if (typeof id !== 'string') { return this.status.E_BADARGTYPE('extensionId', typeof id, 'string'); } let extensionHeaders = this.extraHeaders.get(id); if (!extensionHeaders) { extensionHeaders = new Map(); this.extraHeaders.set(id, extensionHeaders); } for (const name in message.headers) { extensionHeaders.set(name, message.headers[name]); } const allHeaders = {}; for (const headers of this.extraHeaders.values()) { for (const [name, value] of headers) { if (name !== '__proto__' && typeof value === 'string') { allHeaders[name] = value; } } } SDK.NetworkManager.MultitargetNetworkManager.instance().setExtraHTTPHeaders(allHeaders); return undefined; } onApplyStyleSheet(message) { if (message.command !== "applyStyleSheet" /* PrivateAPI.Commands.ApplyStyleSheet */) { return this.status.E_BADARG('command', `expected ${"applyStyleSheet" /* PrivateAPI.Commands.ApplyStyleSheet */}`); } if (!Root.Runtime.experiments.isEnabled('applyCustomStylesheet')) { return; } const styleSheet = document.createElement('style'); styleSheet.textContent = message.styleSheet; document.head.appendChild(styleSheet); ThemeSupport.ThemeSupport.instance().addCustomStylesheet(message.styleSheet); // Add to all the shadow roots that have already been created for (let node = document.body; node; node = node.traverseNextNode(document.body)) { if (node instanceof ShadowRoot) { ThemeSupport.ThemeSupport.instance().injectCustomStyleSheets(node); } } return undefined; } getExtensionOrigin(port) { const origin = extensionOrigins.get(port); if (!origin) { throw new Error('Received a message from an unregistered extension'); } return origin; } onCreatePanel(message, port) { if (message.command !== "createPanel" /* PrivateAPI.Commands.CreatePanel */) { return this.status.E_BADARG('command', `expected ${"createPanel" /* PrivateAPI.Commands.CreatePanel */}`); } const id = message.id; // The ids are generated on the client API side and must be unique, so the check below // shouldn't be hit unless someone is bypassing the API. if (this.clientObjects.has(id) || UI.InspectorView.InspectorView.instance().hasPanel(id)) { return this.status.E_EXISTS(id); } const page = ExtensionServer.expandResourcePath(this.getExtensionOrigin(port), message.page); if (page === undefined) { return this.status.E_BADARG('page', 'Resources paths cannot point to non-extension resources'); } let persistentId = this.getExtensionOrigin(port) + message.title; persistentId = persistentId.replace(/\s/g, ''); const panelView = new ExtensionServerPanelView(persistentId, i18n.i18n.lockedString(message.title), new ExtensionPanel(this, persistentId, id, page)); this.clientObjects.set(id, panelView); UI.InspectorView.InspectorView.instance().addPanel(panelView); return this.status.OK(); } onShowPanel(message) { if (message.command !== "showPanel" /* PrivateAPI.Commands.ShowPanel */) { return this.status.E_BADARG('command', `expected ${"showPanel" /* PrivateAPI.Commands.ShowPanel */}`); } let panelViewId = message.id; const panelView = this.clientObjects.get(message.id); if (panelView && panelView instanceof ExtensionServerPanelView) { panelViewId = panelView.viewId(); } void UI.InspectorView.InspectorView.instance().showPanel(panelViewId); return undefined; } onCreateToolbarButton(message, port) { if (message.command !== "createToolbarButton" /* PrivateAPI.Commands.CreateToolbarButton */) { return this.status.E_BADARG('command', `expected ${"createToolbarButton" /* PrivateAPI.Commands.CreateToolbarButton */}`); } const panelView = this.clientObjects.get(message.panel); if (!panelView || !(panelView instanceof ExtensionServerPanelView)) { return this.status.E_NOTFOUND(message.panel); } const resourcePath = ExtensionServer.expandResourcePath(this.getExtensionOrigin(port), message.icon); if (resourcePath === undefined) { return this.status.E_BADARG('icon', 'Resources paths cannot point to non-extension resources'); } const button = new ExtensionButton(this, message.id, resourcePath, message.tooltip, message.disabled); this.clientObjects.set(message.id, button); void panelView.widget().then(appendButton); function appendButton(panel) { panel.addToolbarItem(button.toolbarButton()); } return this.status.OK(); } onUpdateButton(message, port) { if (message.command !== "updateButton" /* PrivateAPI.Commands.UpdateButton */) { return this.status.E_BADARG('command', `expected ${"updateButton" /* PrivateAPI.Commands.UpdateButton */}`); } const button = this.clientObjects.get(message.id); if (!button || !(button instanceof ExtensionButton)) { return this.status.E_NOTFOUND(message.id); } const resourcePath = message.icon && ExtensionServer.expandResourcePath(this.getExtensionOrigin(port), message.icon); if (message.icon && resourcePath === undefined) { return this.status.E_BADARG('icon', 'Resources paths cannot point to non-extension resources'); } button.update(resourcePath, message.tooltip, message.disabled); return this.status.OK(); } onCreateSidebarPane(message) { if (message.command !== "createSidebarPane" /* PrivateAPI.Commands.CreateSidebarPane */) { return this.status.E_BADARG('command', `expected ${"createSidebarPane" /* PrivateAPI.Commands.CreateSidebarPane */}`); } const id = message.id; const sidebar = new ExtensionSidebarPane(this, message.panel, i18n.i18n.lockedString(message.title), id); this.sidebarPanesInternal.push(sidebar); this.clientObjects.set(id, sidebar); this.dispatchEventToListeners(Events.SidebarPaneAdded, sidebar); return this.status.OK(); } sidebarPanes() { return this.sidebarPanesInternal; } onSetSidebarHeight(message) { if (message.command !== "setSidebarHeight" /* PrivateAPI.Commands.SetSidebarHeight */) { return this.status.E_BADARG('command', `expected ${"setSidebarHeight" /* PrivateAPI.Commands.SetSidebarHeight */}`); } const sidebar = this.clientObjects.get(message.id); if (!sidebar || !(sidebar instanceof ExtensionSidebarPane)) { return this.status.E_NOTFOUND(message.id); } sidebar.setHeight(message.height); return this.status.OK(); } onSetSidebarContent(message, port) { if (message.command !== "setSidebarContent" /* PrivateAPI.Commands.SetSidebarContent */) { return this.status.E_BADARG('command', `expected ${"setSidebarContent" /* PrivateAPI.Commands.SetSidebarContent */}`); } const { requestId, id, rootTitle, expression, evaluateOptions, evaluateOnPage } = message; const sidebar = this.clientObjects.get(id); if (!sidebar || !(sidebar instanceof ExtensionSidebarPane)) { return this.status.E_NOTFOUND(message.id); } function callback(error) { const result = error ? this.status.E_FAILED(error) : this.status.OK(); this.dispatchCallback(requestId, port, result); } if (evaluateOnPage) { sidebar.setExpression(expression, rootTitle, evaluateOptions, this.getExtensionOrigin(port), callback.bind(this)); return undefined; } sidebar.setObject(message.expression, message.rootTitle, callback.bind(this)); return undefined; } onSetSidebarPage(message, port) { if (message.command !== "setSidebarPage" /* PrivateAPI.Commands.SetSidebarPage */) { return this.status.E_BADARG('command', `expected ${"setSidebarPage" /* PrivateAPI.Commands.SetSidebarPage */}`); } const sidebar = this.clientObjects.get(message.id); if (!sidebar || !(sidebar instanceof ExtensionSidebarPane)) { return this.status.E_NOTFOUND(message.id); } const resourcePath = ExtensionServer.expandResourcePath(this.getExtensionOrigin(port), message.page); if (resourcePath === undefined) { return this.status.E_BADARG('page', 'Resources paths cannot point to non-extension resources'); } sidebar.setPage(resourcePath); return undefined; } onOpenResource(message) { if (message.command !== "openResource" /* PrivateAPI.Commands.OpenResource */) { return this.status.E_BADARG('command', `expected ${"openResource" /* PrivateAPI.Commands.OpenResource */}`); } const uiSourceCode = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(message.url); if (uiSourceCode) { void Common.Revealer.reveal(uiSourceCode.uiLocation(message.lineNumber, message.columnNumber)); return this.status.OK(); } const resource = Bindings.ResourceUtils.resourceForURL(message.url); if (resource) { void Common.Revealer.reveal(resource); return this.status.OK(); } const request = Logs.NetworkLog.NetworkLog.instance().requestForURL(message.url); if (request) { void Common.Revealer.reveal(request); return this.status.OK(); } return this.status.E_NOTFOUND(message.url); } onSetOpenResourceHandler(message, port) { if (message.command !== "setOpenResourceHandler" /* PrivateAPI.Commands.SetOpenResourceHandler */) { return this.status.E_BADARG('command', `expected ${"setOpenResourceHandler" /* PrivateAPI.Commands.SetOpenResourceHandler */}`); } const extension = this.registeredExtensions.get(this.getExtensionOrigin(port)); if (!extension) { throw new Error('Received a message from an unregistered extension'); } const { name } = extension; if (message.handlerPresent) { Components.Linkifier.Linkifier.registerLinkHandler(name, this.handleOpenURL.bind(this, port)); } else { Components.Linkifier.Linkifier.unregisterLinkHandler(name); } return undefined; } onSetThemeChangeHandler(message, port) { if (message.command !== "setThemeChangeHandler" /* PrivateAPI.Commands.SetThemeChangeHandler */) { return this.status.E_BADARG('command', `expected ${"setThemeChangeHandler" /* PrivateAPI.Commands.SetThemeChangeHandler */}`); } const extensionOrigin = this.getExtensionOrigin(port); const extension = this.registeredExtensions.get(extensionOrigin); if (!extension) { throw new Error('Received a message from an unregistered extension'); } if (message.handlerPresent) { this.themeChangeHandlers.set(extensionOrigin, port); } else { this.themeChangeHandlers.delete(extensionOrigin); } return undefined; } handleOpenURL(port, contentProvider, lineNumber) { port.postMessage({ command: 'open-resource', resource: this.makeResource(contentProvider), lineNumber: lineNumber + 1 }); } onReload(message) { if (message.command !== "Reload" /* PrivateAPI.Commands.Reload */) { return this.status.E_BADARG('command', `expected ${"Reload" /* PrivateAPI.Commands.Reload */}`); } const options = (message.options || {}); SDK.NetworkManager.MultitargetNetworkManager.instance().setUserAgentOverride(typeof options.userAgent === 'string' ? options.userAgent : '', null); let injectedScript; if (options.injectedScript) { injectedScript = '(function(){' + options.injectedScript + '})()'; } SDK.ResourceTreeModel.ResourceTreeModel.reloadAllPages(Boolean(options.ignoreCache), injectedScript); return this.status.OK(); } onEvaluateOnInspectedPage(message, port) { if (message.command !== "evaluateOnInspectedPage" /* PrivateAPI.Commands.EvaluateOnInspectedPage */) { return this.status.E_BADARG('command', `expected ${"evaluateOnInspectedPage" /* PrivateAPI.Commands.EvaluateOnInspectedPage */}`); } const { requestId, expression, evaluateOptions } = message; function callback(error, object, wasThrown) { let result; if (error || !object) { result = this.status.E_PROTOCOLERROR(error?.toString()); } else if (wasThrown) { result = { isException: true, value: object.description }; } else { result = { value: object.value }; } this.dispatchCallback(requestId, port, result); } return this.evaluate(expression, true, true, evaluateOptions, this.getExtensionOrigin(port), callback.bind(this)); } async onGetHAR(message) { if (message.command !== "getHAR" /* PrivateAPI.Commands.GetHAR */) { return this.status.E_BADARG('command', `expected ${"getHAR" /* PrivateAPI.Commands.GetHAR */}`); } const requests = Logs.NetworkLog.NetworkLog.instance().requests(); const harLog = await HAR.Log.Log.build(requests); for (let i = 0; i < harLog.entries.length; ++i) { // @ts-ignore harLog.entries[i]._requestId = this.requestId(requests[i]); } return harLog; } makeResource(contentProvider) { return { url: contentProvider.contentURL(), type: contentProvider.contentType().name() }; } onGetPageResources() { const resources = new Map(); function pushResourceData(contentProvider) { if (!resources.has(contentProvider.contentURL())) { resources.set(contentProvider.contentURL(), this.makeResource(contentProvider)); } return false; } let uiSourceCodes = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodesForProjectType(Workspace.Workspace.projectTypes.Network); uiSourceCodes = uiSourceCodes.concat(Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodesForProjectType(Workspace.Workspace.projectTypes.ContentScripts)); uiSourceCodes.forEach(pushResourceData.bind(this)); for (const resourceTreeModel of SDK.TargetManager.TargetManager.instance().models(SDK.ResourceTreeModel.ResourceTreeModel)) { resourceTreeModel.forAllResources(pushResourceData.bind(this)); } return [...resources.values()]; } async getResourceContent(contentProvider, message, port) { const url = contentProvider.contentURL(); const origin = extensionOrigins.get(port); const extension = origin && this.registeredExtensions.get(origin); if (!extension?.isAllowedOnTarget(url)) { this.dispatchCallback(message.requestId, port, this.status.E_FAILED('Permission denied')); return undefined; } const { content, isEncoded } = await contentProvider.requestContent(); this.dispatchCallback(message.requestId, port, { encoding: isEncoded ? 'base64' : '', content: content }); } onGetRequestContent(message, port) { if (message.command !== "getRequestContent" /* PrivateAPI.Commands.GetRequestContent */) { return this.status.E_BADARG('command', `expected ${"getRequestContent" /* PrivateAPI.Commands.GetRequestContent */}`); } const request = this.requestById(message.id); if (!request) { return this.status.E_NOTFOUND(message.id); } void this.getResourceContent(request, message, port); return undefined; } onGetResourceContent(message, port) { if (message.command !== "getResourceContent" /* PrivateAPI.Commands.GetResourceContent */) { return this.status.E_BADARG('command', `expected ${"getResourceContent" /* PrivateAPI.Commands.GetResourceContent */}`); } const url = message.url; const contentProvider = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(url) || Bindings.ResourceUtils.resourceForURL(url); if (!contentProvider) { return this.status.E_NOTFOUND(url); } void this.getResourceContent(contentProvider, message, port); return undefined; } onSetResourceContent(message, port) { if (message.command !== "setResourceContent" /* PrivateAPI.Commands.SetResourceContent */) { return this.status.E_BADARG('command', `expected ${"setResourceContent" /* PrivateAPI.Commands.SetResourceContent */}`); } const { url, requestId, content, commit } = message; function callbackWrapper(error) { const response = error ? this.status.E_FAILED(error) : this.status.OK(); this.dispatchCallback(requestId, port, response); } const origin = extensionOrigins.get(port); const extension = origin && this.registeredExtensions.get(origin); if (!extension?.isAllowedOnTarget(url)) { return this.status.E_FAILED('Permission denied'); } const uiSourceCode = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(url); if (!uiSourceCode || !uiSourceCode.contentType().isDocumentOrScriptOrStyleSheet()) { const resource = SDK.ResourceTreeModel.ResourceTreeModel.resourceForURL(url); if (!resource) { return this.status.E_NOTFOUND(url); } return this.status.E_NOTSUPPORTED('Resource is not editable'); } uiSourceCode.setWorkingCopy(content); if (commit) { uiSourceCode.commitWorkingCopy(); } callbackWrapper.call(this, null); return undefined; } requestId(request) { const requestId = this.requestIds.get(request); if (requestId === undefined) { const newId = ++this.lastRequestId; this.requestIds.set(request, newId); this.requests.set(newId, request); return newId; } return requestId; } requestById(id) { return this.requests.get(id); } onForwardKeyboardEvent(message) { if (message.command !== "_forwardKeyboardEvent" /* PrivateAPI.Commands.ForwardKeyboardEvent */) { return this.status.E_BADARG('command', `expected ${"_forwardKeyboardEvent" /* PrivateAPI.Commands.ForwardKeyboardEvent */}`); } message.entries.forEach(handleEventEntry); function handleEventEntry(entry) { // Fool around closure compiler -- it has its own notion of both KeyboardEvent constructor // and initKeyboardEvent methods and overriding these in externs.js does not have effect. const event = new window.KeyboardEvent(entry.eventType, { key: entry.key, code: entry.code, keyCode: entry.keyCode, location: entry.location, ctrlKey: entry.ctrlKey, altKey: entry.altKey, shiftKey: entry.shiftKey, metaKey: entry.metaKey, }); // @ts-ignore event.__keyCode = keyCodeForEntry(entry); document.dispatchEvent(event); } function keyCodeForEntry(entry) { let keyCode = entry.keyCode; if (!keyCode) { // This is required only for synthetic events (e.g. dispatched in tests). if (entry.key === Platform.KeyboardUtilities.ESCAPE_KEY) { keyCode = 27; } } return keyCode || 0; } return undefined; } dispatchCallback(requestId, port, result) { if (requestId) { port.postMessage({ command: 'callback', requestId: requestId, result: result }); } } initExtensions() { this.registerAutosubscriptionHandler("resource-added" /* PrivateAPI.Events.ResourceAdded */, Workspace.Workspace.WorkspaceImpl.instance(), Workspace.Workspace.Events.UISourceCodeAdded, this.notifyResourceAdded); this.registerAutosubscriptionTargetManagerHandler("network-request-finished" /* PrivateAPI.Events.NetworkRequestFinished */, SDK.NetworkManager.NetworkManager, SDK.NetworkManager.Events.RequestFinished, this.notifyRequestFinished); function onElementsSubscriptionStarted() { UI.Context.Context.instance().addFlavorChangeListener(SDK.DOMModel.DOMNode, this.notifyElementsSelectionChanged, this); } function onElementsSubscriptionStopped() { UI.Context.Context.instance().removeFlavorChangeListener(SDK.DOMModel.DOMNode, this.notifyElementsSelectionChanged, this); } this.registerSubscriptionHandler("panel-objectSelected-" /* PrivateAPI.Events.PanelObjectSelected */ + 'elements', onElementsSubscriptionStarted.bind(this), onElementsSubscriptionStopped.bind(this)); this.registerResourceContentCommittedHandler(this.notifyUISourceCodeContentCommitted); SDK.TargetManager.TargetManager.instance().addEventListener(SDK.TargetManager.Events.InspectedURLChanged, this.inspectedURLChanged, this); } notifyResourceAdded(event) { const uiSourceCode = event.data; this.postNotification("resource-added" /* PrivateAPI.Events.ResourceAdded */, this.makeResource(uiSourceCode)); } notifyUISourceCodeContentCommitted(event) { const { uiSourceCode, content } = event.data; this.postNotification("resource-content-committed" /* PrivateAPI.Events.ResourceContentCommitted */, this.makeResource(uiSourceCode), content); } async notifyRequestFinished(event) { const request = event.data; const entry = await HAR.Log.Entry.build(request); this.postNotification("network-request-finished" /* PrivateAPI.Events.NetworkRequestFinished */, this.requestId(request), entry); } notifyElementsSelectionChanged() { this.postNotification("panel-objectSelected-" /* PrivateAPI.Events.PanelObjectSelected */ + 'elements'); } sourceSelectionChanged(url, range) { this.postNotification("panel-objectSelected-" /* PrivateAPI.Events.PanelObjectSelected */ + 'sources', { startLine: range.startLine, startColumn: range.startColumn, endLine: range.endLine, endColumn: range.endColumn, url: url, }); } setInspectedTabId(event) { const oldId = this.inspectedTabId; this.inspectedTabId = event.data; if (oldId === null) { // Run deferred init this.initializeExtensions(); } } addExtensionFrame({ startPage, name }) { const iframe = document.createElement('iframe'); iframe.src = startPage; iframe.dataset.devtoolsExtension = name; iframe.style.display = 'none'; document.body.appendChild(iframe); // Only for main window. } addExtension(extensionInfo) { const startPage = extensionInfo.startPage; const inspectedURL = SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.inspectedURL() ?? ''; if (inspectedURL === '') { this.#pendingExtensions.push(extensionInfo); return; } if (!ExtensionServer.canInspectURL(inspectedURL)) { this.disableExtensions(); } if (!this.extensionsEnabled) { return; } const hostsPolicy = HostsPolicy.create(extensionInfo.hostsPolicy); if (!hostsPolicy) { return; } try { const startPageURL = new URL(startPage); const extensionOrigin = startPageURL.origin; const name = extensionInfo.name || `Extension ${extensionOrigin}`; const extensionRegistration = new RegisteredExtension(name, hostsPolicy, Boolean(extensionInfo.allowFileAccess)); if (!extensionRegistration.isAllowedOnTarget(inspectedURL)) { return; } if (!this.registeredExtensions.get(extensionOrigin)) { // See ExtensionAPI.js for details. const injectedAPI = self.buildExtensionAPIInjectedScript(extensionInfo, this.inspectedTabId, ThemeSupport.ThemeSupport.instance().themeName(), UI.ShortcutRegistry.ShortcutRegistry.instance().globalShortcutKeys(), ExtensionServer.instance().extensionAPITestHook); Host.InspectorFrontendHost.InspectorFrontendHostInstance.setInjectedScriptForOrigin(extensionOrigin, injectedAPI); this.registeredExtensions.set(extensionOrigin, extensionRegistration); } this.addExtensionFrame(extensionInfo); } catch (e) { console.error('Failed to initialize extension ' + startPage + ':' + e); return false; } return true; } registerExtension(origin, port) { if (!this.registeredExtensions.has(origin)) { if (origin !== window.location.origin) { // Just ignore inspector frames. console.error('Ignoring unauthorized client request from ' + origin); } return; } extensionOrigins.set(port, origin); port.addEventListener('message', this.onmessage.bind(this), false); port.start(); } onWindowMessage = (event) => { if (event.data === 'registerExtension') { this.registerExtension(event.origin, event.ports[0]); } }; extensionEnabled(port) { if (!this.extensionsEnabled) { return false; } const origin = extensionOrigins.get(port); if (!origin) { return false; } const extension = this.registeredExtensions.get(origin); if (!extension) { return false; } return extension.isAllowedOnTarget(); } async onmessage(event) { const message = event.data; let result; const port = event.currentTarget; const handler = this.handlers.get(message.command); if (!handler) { result = this.status.E_NOTSUPPORTED(message.command); } else if (!this.extensionEnabled(port)) { result = this.status.E_FAILED('Permission denied'); } else { result = await handler(message, event.target); } if (result && message.requestId) { this.dispatchCallback(message.requestId, event.target, result); } } registerHandler(command, callback) { console.assert(Boolean(command)); this.handlers.set(command, callback); } registerSubscriptionHandler(eventTopic, onSubscribeFirst, onUnsubscribeLast) { this.subscriptionStartHandlers.set(eventTopic, onSubscribeFirst); this.subscriptionStopHandlers.set(eventTopic, onUnsubscribeLast); } registerAutosubscriptionHandler(eventTopic, eventTarget, frontendEventType, handler) { this.registerSubscriptionHandler(eventTopic, () => eventTarget.addEventListener(frontendEventType, handler, this), () => eventTarget.removeEventListener(frontendEventType, handler, this)); } registerAutosubscriptionTargetManagerHandler(eventTopic, modelClass, frontendEventType, handler) { this.registerSubscriptionHandler(eventTopic, () => SDK.TargetManager.TargetManager.instance().addModelListener(modelClass, frontendEventType, handler, this), () => SDK.TargetManager.TargetManager.instance().removeM