diff --git a/src/server/chromium/crNetworkManager.ts b/src/server/chromium/crNetworkManager.ts index 7f91b5cfe9..303b92c8ef 100644 --- a/src/server/chromium/crNetworkManager.ts +++ b/src/server/chromium/crNetworkManager.ts @@ -200,6 +200,11 @@ export class CRNetworkManager { } } let frame = requestWillBeSentEvent.frameId ? this._page._frameManager.frame(requestWillBeSentEvent.frameId) : workerFrame; + // Requests from workers lack frameId, because we receive Network.requestWillBeSent + // on the worker target. However, we receive Fetch.requestPaused on the page target, + // and lack workerFrame there. Luckily, Fetch.requestPaused provides a frameId. + if (!frame && requestPausedEvent && requestPausedEvent.frameId) + frame = this._page._frameManager.frame(requestPausedEvent.frameId); // Check if it's main resource request interception (targetId === main frame id). if (!frame && requestPausedEvent && requestWillBeSentEvent.frameId === (this._page._delegate as CRPage)._targetId) { @@ -209,32 +214,36 @@ export class CRNetworkManager { frame = this._page._frameManager.frameAttached(requestWillBeSentEvent.frameId, null); } - if (!frame) { - if (requestPausedEvent) { - // CORS options request is generated by the network stack, it is not associated with the frame id. - // If URL matches interception pattern, accept it, assuming that this was intended when setting route. - if (requestPausedEvent.request.method === 'OPTIONS' && this._page._needsRequestInterception()) { - const requestHeaders = requestPausedEvent.request.headers; - const responseHeaders: Protocol.Fetch.HeaderEntry[] = [ - { name: 'Access-Control-Allow-Origin', value: requestHeaders['Origin'] || '*' }, - { name: 'Access-Control-Allow-Methods', value: requestHeaders['Access-Control-Request-Method'] || 'GET, POST, OPTIONS, DELETE' }, - { name: 'Access-Control-Allow-Credentials', value: 'true' } - ]; - if (requestHeaders['Access-Control-Request-Headers']) - responseHeaders.push({ name: 'Access-Control-Allow-Headers', value: requestHeaders['Access-Control-Request-Headers'] }); - this._client._sendMayFail('Fetch.fulfillRequest', { - requestId: requestPausedEvent.requestId, - responseCode: 204, - responsePhrase: network.STATUS_TEXTS['204'], - responseHeaders, - body: '', - }); - } else { - this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId }); - } - } + // CORS options request is generated by the network stack. If interception is enabled, + // we accept all CORS options, assuming that this was intended when setting route. + // + // Note: it would be better to match the URL against interception patterns, but + // that information is only available to the client. Perhaps we can just route to the client? + if (requestPausedEvent && requestPausedEvent.request.method === 'OPTIONS' && this._page._needsRequestInterception()) { + const requestHeaders = requestPausedEvent.request.headers; + const responseHeaders: Protocol.Fetch.HeaderEntry[] = [ + { name: 'Access-Control-Allow-Origin', value: requestHeaders['Origin'] || '*' }, + { name: 'Access-Control-Allow-Methods', value: requestHeaders['Access-Control-Request-Method'] || 'GET, POST, OPTIONS, DELETE' }, + { name: 'Access-Control-Allow-Credentials', value: 'true' } + ]; + if (requestHeaders['Access-Control-Request-Headers']) + responseHeaders.push({ name: 'Access-Control-Allow-Headers', value: requestHeaders['Access-Control-Request-Headers'] }); + this._client._sendMayFail('Fetch.fulfillRequest', { + requestId: requestPausedEvent.requestId, + responseCode: 204, + responsePhrase: network.STATUS_TEXTS['204'], + responseHeaders, + body: '', + }); return; } + + if (!frame) { + if (requestPausedEvent) + this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId }); + return; + } + let allowInterception = this._userRequestInterceptionEnabled; if (redirectedFrom) { allowInterception = false; diff --git a/test/interception.spec.ts b/test/interception.spec.ts index b4fcd2cfd0..d4fbb237d5 100644 --- a/test/interception.spec.ts +++ b/test/interception.spec.ts @@ -88,18 +88,15 @@ it('should work with glob', async () => { expect(globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.css')).toBeFalsy(); }); -it('should intercept network activity from worker', (test, {browserName}) => { - // @see https://github.com/microsoft/playwright/issues/4487 - test.fixme(browserName === 'chromium'); -}, async function({page, server}) { +it('should intercept network activity from worker', async function({page, server}) { await page.goto(server.EMPTY_PAGE); server.setRoute('/data_for_worker', (req, res) => res.end('failed to intercept')); const url = server.PREFIX + '/data_for_worker'; - page.route(url, async route => { - await route.fulfill({ + await page.route(url, route => { + route.fulfill({ status: 200, body: 'intercepted', - }); + }).catch(e => null); }); const [msg] = await Promise.all([ page.waitForEvent('console'), @@ -110,6 +107,22 @@ it('should intercept network activity from worker', (test, {browserName}) => { expect(msg.text()).toBe('intercepted'); }); +it('should intercept network activity from worker', async function({page, server}) { + const url = server.PREFIX + '/worker/worker.js'; + await page.route(url, route => { + route.fulfill({ + status: 200, + body: 'console.log("intercepted");', + contentType: 'application/javascript', + }).catch(e => null); + }); + const [msg] = await Promise.all([ + page.waitForEvent('console'), + page.goto(server.PREFIX + '/worker/worker.html'), + ]); + expect(msg.text()).toBe('intercepted'); +}); + it('should work with regular expression passed from a different context', async ({page, server}) => { const ctx = vm.createContext(); const regexp = vm.runInContext('new RegExp("empty\\.html")', ctx);