/** * @license * Copyright 2024 Google Inc. * SPDX-License-Identifier: Apache-2.0 */ import type {ConnectionTransport} from '../common/ConnectionTransport.js'; const tabTargetInfo = { targetId: 'tabTargetId', type: 'tab', title: 'tab', url: 'about:blank', attached: false, canAccessOpener: false, }; const pageTargetInfo = { targetId: 'pageTargetId', type: 'page', title: 'page', url: 'about:blank', attached: false, canAccessOpener: false, }; /** * Experimental ExtensionTransport allows establishing a connection via * chrome.debugger API if Puppeteer runs in an extension. Since Chrome * DevTools Protocol is restricted for extensions, the transport * implements missing commands and events. * * @experimental * @public */ export class ExtensionTransport implements ConnectionTransport { static async connectTab(tabId: number): Promise<ExtensionTransport> { await chrome.debugger.attach({tabId}, '1.3'); return new ExtensionTransport(tabId); } onmessage?: (message: string) => void; onclose?: () => void; #tabId: number; /** * @internal */ constructor(tabId: number) { this.#tabId = tabId; chrome.debugger.onEvent.addListener(this.#debuggerEventHandler); } #debuggerEventHandler = ( source: chrome.debugger.Debuggee, method: string, params?: object | undefined ): void => { if (source.tabId !== this.#tabId) { return; } this.#dispatchResponse({ // @ts-expect-error sessionId is not in stable yet. sessionId: source.sessionId ?? 'pageTargetSessionId', method: method, params: params, }); }; #dispatchResponse(message: object): void { this.onmessage?.(JSON.stringify(message)); } send(message: string): void { const parsed = JSON.parse(message); switch (parsed.method) { case 'Browser.getVersion': { this.#dispatchResponse({ id: parsed.id, sessionId: parsed.sessionId, method: parsed.method, result: { protocolVersion: '1.3', product: 'chrome', revision: 'unknown', userAgent: 'chrome', jsVersion: 'unknown', }, }); return; } case 'Target.getBrowserContexts': { this.#dispatchResponse({ id: parsed.id, sessionId: parsed.sessionId, method: parsed.method, result: { browserContextIds: [], }, }); return; } case 'Target.setDiscoverTargets': { this.#dispatchResponse({ method: 'Target.targetCreated', params: { targetInfo: tabTargetInfo, }, }); this.#dispatchResponse({ method: 'Target.targetCreated', params: { targetInfo: pageTargetInfo, }, }); this.#dispatchResponse({ id: parsed.id, sessionId: parsed.sessionId, method: parsed.method, result: {}, }); return; } case 'Target.setAutoAttach': { if (parsed.sessionId === 'tabTargetSessionId') { this.#dispatchResponse({ method: 'Target.attachedToTarget', params: { targetInfo: pageTargetInfo, sessionId: 'pageTargetSessionId', }, }); this.#dispatchResponse({ id: parsed.id, sessionId: parsed.sessionId, method: parsed.method, result: {}, }); return; } else if (!parsed.sessionId) { this.#dispatchResponse({ method: 'Target.attachedToTarget', params: { targetInfo: tabTargetInfo, sessionId: 'tabTargetSessionId', }, }); this.#dispatchResponse({ id: parsed.id, sessionId: parsed.sessionId, method: parsed.method, result: {}, }); return; } } } if (parsed.sessionId === 'pageTargetSessionId') { delete parsed.sessionId; } chrome.debugger .sendCommand( // @ts-expect-error sessionId is not in stable yet. {tabId: this.#tabId, sessionId: parsed.sessionId}, parsed.method, parsed.params ) .then(response => { this.#dispatchResponse({ id: parsed.id, sessionId: parsed.sessionId ?? 'pageTargetSessionId', method: parsed.method, result: response, }); }) .catch(err => { this.#dispatchResponse({ id: parsed.id, sessionId: parsed.sessionId ?? 'pageTargetSessionId', method: parsed.method, error: err, }); }); } close(): void { chrome.debugger.onEvent.removeListener(this.#debuggerEventHandler); void chrome.debugger.detach({tabId: this.#tabId}); } }