do not try to detach tab twice

This commit is contained in:
Tommy D. Rossi
2025-11-22 11:43:23 +01:00
parent 9ab94933a2
commit 881155a7de
+38 -32
View File
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import type { Protocol } from 'playwriter/src/cdp-types'; import type { CDPEvent, Protocol } from 'playwriter/src/cdp-types';
import type { ExtensionCommandMessage, ExtensionResponseMessage } from 'playwriter/src/extension/protocol'; import type { ExtensionCommandMessage, ExtensionResponseMessage } from 'playwriter/src/extension/protocol';
export function debugLog(...args: unknown[]): void { export function debugLog(...args: unknown[]): void {
@@ -151,13 +151,17 @@ export class RelayConnection {
} }
detachTab(tabId: number): void { detachTab(tabId: number): void {
this._cleanupTab(tabId, true);
}
private _cleanupTab(tabId: number, shouldDetachDebugger: boolean): void {
const tab = this._attachedTabs.get(tabId); const tab = this._attachedTabs.get(tabId);
if (!tab) { if (!tab) {
debugLog('detachTab: tab not found in map:', tabId); debugLog('cleanupTab: tab not found in map:', tabId);
return; return;
} }
debugLog('Detaching tab:', tabId, 'sessionId:', tab.sessionId); debugLog('Cleaning up tab:', tabId, 'sessionId:', tab.sessionId, 'shouldDetach:', shouldDetachDebugger);
this._sendMessage({ this._sendMessage({
method: 'forwardCDPEvent', method: 'forwardCDPEvent',
@@ -173,13 +177,15 @@ export class RelayConnection {
this._attachedTabs.delete(tabId); this._attachedTabs.delete(tabId);
debugLog('Removed tab from _attachedTabs map. Remaining tabs:', this._attachedTabs.size); debugLog('Removed tab from _attachedTabs map. Remaining tabs:', this._attachedTabs.size);
chrome.debugger.detach(tab.debuggee) if (shouldDetachDebugger) {
.then(() => { chrome.debugger.detach(tab.debuggee)
debugLog('Successfully detached debugger from tab:', tabId); .then(() => {
}) debugLog('Successfully detached debugger from tab:', tabId);
.catch((err) => { })
debugLog('Error detaching debugger from tab:', tabId, err.message); .catch((err) => {
}); debugLog('Error detaching debugger from tab:', tabId, err.message);
});
}
} }
close(message: string): void { close(message: string): void {
@@ -222,7 +228,7 @@ export class RelayConnection {
this._onCloseCallback?.(); this._onCloseCallback?.();
} }
private _onDebuggerEvent = (source: chrome.debugger.DebuggerSession, method: CDPEvent['method'], params: any): void => { private _onDebuggerEvent = (source: chrome.debugger.DebuggerSession, method: string, params: any): void => {
const tab = this._attachedTabs.get(source.tabId!); const tab = this._attachedTabs.get(source.tabId!);
if (!tab) return; if (!tab) return;
@@ -267,19 +273,19 @@ export class RelayConnection {
debugLog('User closed debugger via Chrome automation bar, calling onTabDetached callback'); debugLog('User closed debugger via Chrome automation bar, calling onTabDetached callback');
this._onTabDetachedCallback?.(tabId, reason); this._onTabDetachedCallback?.(tabId, reason);
this.detachTab(tabId); this._cleanupTab(tabId, false);
}; };
private async _handleCommand(message: ExtensionCommandMessage): Promise<any> { private async _handleCommand(msg: ExtensionCommandMessage): Promise<any> {
if (message.method === 'attachToTab') { if (msg.method === 'attachToTab') {
return {}; return {};
} }
if (message.method === 'forwardCDPCommand') { if (msg.method === 'forwardCDPCommand') {
const { sessionId, method, params } = message.params;
if (method === 'Target.createTarget') {
const url = params?.url || 'about:blank'; if (msg.params.method === 'Target.createTarget') {
const url = msg.params.params?.url || 'about:blank';
debugLog('Creating new tab with URL:', url); debugLog('Creating new tab with URL:', url);
const tab = await chrome.tabs.create({ url, active: false }); const tab = await chrome.tabs.create({ url, active: false });
@@ -298,58 +304,58 @@ export class RelayConnection {
return { targetId: targetInfo.targetId }; return { targetId: targetInfo.targetId };
} }
if (method === 'Target.closeTarget' && params?.targetId) { if (msg.params.method === 'Target.closeTarget' && msg.params.params?.targetId) {
debugLog('Closing target:', params.targetId); debugLog('Closing target:', msg.params.params.targetId);
for (const [tabId, tab] of this._attachedTabs) { for (const [tabId, tab] of this._attachedTabs) {
if (tab.targetId === params.targetId) { if (tab.targetId === msg.params.params.targetId) {
debugLog('Found tab to close:', tabId); debugLog('Found tab to close:', tabId);
await chrome.tabs.remove(tabId); await chrome.tabs.remove(tabId);
return { success: true }; return { success: true };
} }
} }
debugLog('Target not found:', params.targetId); debugLog('Target not found:', msg.params.params.targetId);
throw new Error(`Target not found: ${params.targetId}`); throw new Error(`Target not found: ${msg.params.params.targetId}`);
} }
let targetTab: AttachedTab | undefined; let targetTab: AttachedTab | undefined;
for (const [tabId, tab] of this._attachedTabs) { for (const [tabId, tab] of this._attachedTabs) {
if (tab.sessionId === sessionId) { if (tab.sessionId === msg.params.sessionId) {
targetTab = tab; targetTab = tab;
break; break;
} }
} }
if (!targetTab) { if (!targetTab) {
if (method === 'Browser.getVersion' || method === 'Target.getTargets') { if (msg.params.method === 'Browser.getVersion' || msg.params.method === 'Target.getTargets') {
targetTab = this._attachedTabs.values().next().value; targetTab = this._attachedTabs.values().next().value;
} }
if (!targetTab) { if (!targetTab) {
throw new Error(`No tab found for method ${method} sessionId: ${sessionId}`); throw new Error(`No tab found for method ${msg.params.method} sessionId: ${msg.params.sessionId}`);
} }
} }
debugLog('CDP command:', method, 'for tab:', targetTab.debuggee.tabId); debugLog('CDP command:', msg.params.method, 'for tab:', targetTab.debuggee.tabId);
const debuggerSession: chrome.debugger.DebuggerSession = { const debuggerSession: chrome.debugger.DebuggerSession = {
...targetTab.debuggee, ...targetTab.debuggee,
sessionId: sessionId !== targetTab.sessionId ? sessionId : undefined, sessionId: msg.params.sessionId !== targetTab.sessionId ? msg.params.sessionId : undefined,
}; };
const result = await chrome.debugger.sendCommand( const result = await chrome.debugger.sendCommand(
debuggerSession, debuggerSession,
method, msg.params.method,
params msg.params.params
); );
// When Playwright reconnects and calls Runtime.enable, Chrome does NOT automatically // When Playwright reconnects and calls Runtime.enable, Chrome does NOT automatically
// re-send Runtime.executionContextCreated events for contexts that already exist. // re-send Runtime.executionContextCreated events for contexts that already exist.
// This causes page.evaluate() to hang because Playwright has no execution context IDs. // This causes page.evaluate() to hang because Playwright has no execution context IDs.
// Solution: manually replay all cached contexts so Playwright gets fresh, valid IDs. // Solution: manually replay all cached contexts so Playwright gets fresh, valid IDs.
if (method === 'Runtime.enable' && targetTab.executionContexts.size > 0) { if (msg.params.method === 'Runtime.enable' && targetTab.executionContexts.size > 0) {
debugLog('Runtime.enable called, replaying', targetTab.executionContexts.size, 'cached execution contexts for tab:', targetTab.debuggee.tabId); debugLog('Runtime.enable called, replaying', targetTab.executionContexts.size, 'cached execution contexts for tab:', targetTab.debuggee.tabId);
for (const contextEvent of targetTab.executionContexts.values()) { for (const contextEvent of targetTab.executionContexts.values()) {
@@ -357,7 +363,7 @@ export class RelayConnection {
this._sendMessage({ this._sendMessage({
method: 'forwardCDPEvent', method: 'forwardCDPEvent',
params: { params: {
sessionId: sessionId, sessionId: msg.params.sessionId,
method: 'Runtime.executionContextCreated', method: 'Runtime.executionContextCreated',
params: contextEvent, params: contextEvent,
}, },