diff --git a/browser_patches/firefox/juggler/NetworkObserver.js b/browser_patches/firefox/juggler/NetworkObserver.js index 2b4b3c5463f7c..b472f65a83cd1 100644 --- a/browser_patches/firefox/juggler/NetworkObserver.js +++ b/browser_patches/firefox/juggler/NetworkObserver.js @@ -46,6 +46,7 @@ export class PageNetwork { this._extraHTTPHeaders = null; this._responseStorage = new ResponseStorage(MAX_RESPONSE_STORAGE_SIZE, MAX_RESPONSE_STORAGE_SIZE / 10); this._requestInterceptionEnabled = false; + this._bypassServiceWorker = false; // This is requestId => NetworkRequest map, only contains requests that are // awaiting interception action (abort, resume, fulfill) over the protocol. this._interceptedRequests = new Map(); @@ -62,12 +63,14 @@ export class PageNetwork { ]; } - enableRequestInterception() { + enableRequestInterception(bypassServiceWorker) { this._requestInterceptionEnabled = true; + this._bypassServiceWorker = !!bypassServiceWorker; } disableRequestInterception() { this._requestInterceptionEnabled = false; + this._bypassServiceWorker = false; for (const intercepted of this._interceptedRequests.values()) intercepted.resume(); this._interceptedRequests.clear(); @@ -318,7 +321,7 @@ class NetworkRequest { // nsINetworkInterceptController shouldPrepareForIntercept(aURI, channel) { - const interceptController = this._fallThroughInterceptController(); + const interceptController = this._shouldBypassServiceWorker() ? undefined : this._fallThroughInterceptController(); if (interceptController && interceptController.shouldPrepareForIntercept(aURI, channel)) { // We assume that interceptController is a service worker if there is one, // and yield interception to it. @@ -462,6 +465,10 @@ class NetworkRequest { return false; } + _shouldBypassServiceWorker() { + return !!this._pageNetwork?._bypassServiceWorker && this._shouldIntercept(); + } + _fallThroughInterceptController() { try { return this._previousCallbacks?.getInterface(Ci.nsINetworkInterceptController); diff --git a/browser_patches/firefox/juggler/content/Runtime.js b/browser_patches/firefox/juggler/content/Runtime.js index 78c05de7acee4..ec5c9487a76af 100644 --- a/browser_patches/firefox/juggler/content/Runtime.js +++ b/browser_patches/firefox/juggler/content/Runtime.js @@ -250,10 +250,7 @@ class Runtime { if (obj.promiseState === 'fulfilled') return {success: true, obj: obj.promiseValue}; if (obj.promiseState === 'rejected') { - const debuggee = executionContext._debuggee; - const errorInfo = debuggee.executeInGlobalWithBindings('({m: e?.message, s: e?.stack})', {e: obj.promiseReason}, {useInnerBindings: true}).return; - exceptionDetails.text = errorInfo.getOwnPropertyDescriptor('m').value; - exceptionDetails.stack = errorInfo.getOwnPropertyDescriptor('s').value; + executionContext._fillExceptionDetails(obj.promiseReason, exceptionDetails); return {success: false, obj: null}; } let resolve, reject; @@ -284,10 +281,7 @@ class Runtime { pendingPromise.resolve({success: true, obj: obj.promiseValue}); continue; } - const debuggee = pendingPromise.executionContext._debuggee; - const errorInfo = debuggee.executeInGlobalWithBindings('({m: e?.message, s: e?.stack})', {e: obj.promiseReason}, {useInnerBindings: true}).return; - pendingPromise.exceptionDetails.text = errorInfo.getOwnPropertyDescriptor('m').value; - pendingPromise.exceptionDetails.stack = errorInfo.getOwnPropertyDescriptor('s').value; + pendingPromise.executionContext._fillExceptionDetails(obj.promiseReason, pendingPromise.exceptionDetails); pendingPromise.resolve({success: false, obj: null}); } if (!this._pendingPromises.size) @@ -578,18 +572,37 @@ class ExecutionContext { _getResult(completionValue, exceptionDetails = {}) { if (!completionValue) throw new Error('evaluation terminated'); - if (completionValue.throw) { - const errorInfo = this._debuggee.executeInGlobalWithBindings('e instanceof Error ? ({m: e.message, s: e.stack}) : null', {e: completionValue.throw}, {useInnerBindings: true}).return; - if (errorInfo) { - exceptionDetails.text = errorInfo.getOwnPropertyDescriptor('m').value; - exceptionDetails.stack = errorInfo.getOwnPropertyDescriptor('s').value; - } else { - exceptionDetails.value = this._serialize(completionValue.throw); - } + if ('throw' in completionValue) { + this._fillExceptionDetails(completionValue.throw, exceptionDetails); return {success: false, obj: null}; } return {success: true, obj: completionValue.return}; } + + _fillExceptionDetails(thrown, exceptionDetails) { + // Always populate `text`, so that the client reports something for any thrown + // value. Evaluate inside the debuggee and guard against throwing getters/toString. + const info = this._debuggee.executeInGlobalWithBindings(`(() => { + try { + if (e instanceof Error) + return {isError: true, text: e.message, stack: e.stack}; + return {isError: false, text: String(e)}; + } catch { + return {isError: false, text: 'Unserializable exception'}; + } + })()`, {e: thrown}, {useInnerBindings: true}).return; + const property = name => info.getOwnPropertyDescriptor(name).value; + exceptionDetails.text = property('text'); + if (property('isError')) { + exceptionDetails.stack = property('stack'); + return; + } + try { + exceptionDetails.value = this._serialize(thrown); + } catch (e) { + // Not serializable (bigint, symbol, cyclic object): `text` is all we have. + } + } } const listenersSymbol = Symbol('listeners'); diff --git a/browser_patches/firefox/juggler/protocol/PageHandler.js b/browser_patches/firefox/juggler/protocol/PageHandler.js index a4ab723611ae7..8f779df81881b 100644 --- a/browser_patches/firefox/juggler/protocol/PageHandler.js +++ b/browser_patches/firefox/juggler/protocol/PageHandler.js @@ -266,9 +266,9 @@ export class PageHandler { this._pageNetwork.setExtraHTTPHeaders(headers); } - async ['Network.setRequestInterception']({enabled}) { + async ['Network.setRequestInterception']({enabled, bypassServiceWorker}) { if (enabled) - this._pageNetwork.enableRequestInterception(); + this._pageNetwork.enableRequestInterception(bypassServiceWorker); else this._pageNetwork.disableRequestInterception(); } diff --git a/browser_patches/firefox/juggler/protocol/Protocol.js b/browser_patches/firefox/juggler/protocol/Protocol.js index f6a3f89f81ee2..5b8cdfad665ec 100644 --- a/browser_patches/firefox/juggler/protocol/Protocol.js +++ b/browser_patches/firefox/juggler/protocol/Protocol.js @@ -505,6 +505,7 @@ const Network = { 'setRequestInterception': { params: { enabled: t.Boolean, + bypassServiceWorker: t.Optional(t.Boolean), }, }, 'setExtraHTTPHeaders': { diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 9560879ca2647..af9e96047b801 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -17,7 +17,7 @@ }, { "name": "firefox", - "revision": "1544", + "revision": "1546", "installByDefault": true, "browserVersion": "155.0", "title": "Firefox" diff --git a/packages/playwright-core/src/server/firefox/protocol.d.ts b/packages/playwright-core/src/server/firefox/protocol.d.ts index 18da296d70272..73aa5763aa687 100644 --- a/packages/playwright-core/src/server/firefox/protocol.d.ts +++ b/packages/playwright-core/src/server/firefox/protocol.d.ts @@ -958,6 +958,7 @@ export namespace Protocol { } export type setRequestInterceptionParameters = { enabled: boolean; + bypassServiceWorker?: boolean; }; export type setRequestInterceptionReturnValue = void; export type setExtraHTTPHeadersParameters = {