diff --git a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts index 5ef8503b10c38..55aabf0575d8f 100644 --- a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts +++ b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts @@ -45,7 +45,6 @@ export class WKInterceptableRequest { readonly _requestId: string; _timestamp: number; _wallTime: number; - _willBeIntercepted = false; constructor(session: WKSession, frame: frames.Frame, event: Protocol.Network.requestWillBeSentPayload, redirectedFrom: WKInterceptableRequest | null, documentId: string | undefined) { this._session = session; diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index ae1a9145e524b..7af03c4ae87b8 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -60,6 +60,7 @@ export class WKPage implements PageDelegate { private readonly _pageProxySession: WKSession; readonly _opener: WKPage | null; private readonly _requestIdToRequest = new Map(); + private readonly _requestIdToRequestWillBeSentEvent = new Map(); private readonly _workers: WKWorkers; private readonly _contextIdToContext: Map; private _sessionListeners: RegisteredListener[] = []; @@ -388,9 +389,9 @@ export class WKPage implements PageDelegate { eventsHelper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)), eventsHelper.addEventListener(this._session, 'Network.requestWillBeSent', e => this._onRequestWillBeSent(this._session, e)), eventsHelper.addEventListener(this._session, 'Network.requestIntercepted', e => this._onRequestIntercepted(this._session, e)), - eventsHelper.addEventListener(this._session, 'Network.responseReceived', e => this._onResponseReceived(e)), + eventsHelper.addEventListener(this._session, 'Network.responseReceived', e => this._onResponseReceived(this._session, e)), eventsHelper.addEventListener(this._session, 'Network.loadingFinished', e => this._onLoadingFinished(e)), - eventsHelper.addEventListener(this._session, 'Network.loadingFailed', e => this._onLoadingFailed(e)), + eventsHelper.addEventListener(this._session, 'Network.loadingFailed', e => this._onLoadingFailed(this._session, e)), eventsHelper.addEventListener(this._session, 'Network.webSocketCreated', e => this._page._frameManager.onWebSocketCreated(e.requestId, e.url)), eventsHelper.addEventListener(this._session, 'Network.webSocketWillSendHandshakeRequest', e => this._page._frameManager.onWebSocketRequest(e.requestId)), eventsHelper.addEventListener(this._session, 'Network.webSocketHandshakeResponseReceived', e => this._page._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)), @@ -1011,6 +1012,15 @@ export class WKPage implements PageDelegate { _onRequestWillBeSent(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) { if (event.request.url.startsWith('data:')) return; + + // We do not support intercepting redirects. + if (this._page.needsRequestInterception() && !event.redirectResponse) + this._requestIdToRequestWillBeSentEvent.set(event.requestId, event); + else + this._onRequest(session, event, false); + } + + private _onRequest(session: WKSession, event: Protocol.Network.requestWillBeSentPayload, interceted: boolean) { let redirectedFrom: WKInterceptableRequest | null = null; if (event.redirectResponse) { const request = this._requestIdToRequest.get(event.requestId); @@ -1030,12 +1040,15 @@ export class WKPage implements PageDelegate { const isNavigationRequest = event.type === 'Document'; const documentId = isNavigationRequest ? event.loaderId : undefined; const request = new WKInterceptableRequest(session, frame, event, redirectedFrom, documentId); - // We do not support intercepting redirects. - if (this._page.needsRequestInterception() && !redirectedFrom) - request._willBeIntercepted = true; + let route; + if (interceted) { + route = new WKRouteImpl(session, request._requestId); + // There is no point in waiting for the raw headers in Network.responseReceived when intercepting. + // Use provisional headers as raw headers, so that client can call allHeaders() from the route handler. + request.request.setRawRequestHeaders(null); + } this._requestIdToRequest.set(event.requestId, request); - if (!request._willBeIntercepted) - this._page._frameManager.requestStarted(request.request, undefined); + this._page._frameManager.requestStarted(request.request, route); } private _handleRequestRedirect(request: WKInterceptableRequest, responsePayload: Protocol.Network.Response, timestamp: number) { @@ -1051,42 +1064,36 @@ export class WKPage implements PageDelegate { } _onRequestIntercepted(session: WKSession, event: Protocol.Network.requestInterceptedPayload) { - const request = this._requestIdToRequest.get(event.requestId); - if (!request) { - session.sendMayFail('Network.interceptRequestWithError', { errorType: 'Cancellation', requestId: event.requestId }); - return; - } - // There is no point in waiting for the raw headers in Network.responseReceived when intercepting. - // Use provisional headers as raw headers, so that client can call allHeaders() from the route handler. - request.request.setRawRequestHeaders(null); - if (!request._willBeIntercepted) { + const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId); + if (!requestWillBeSentEvent) { // Intercepted, although we do not intend to allow interception. // Just continue. - session.sendMayFail('Network.interceptWithRequest', { requestId: request._requestId }); - } else { - const route = new WKRouteImpl(session, request._requestId); - request._willBeIntercepted = false; - this._page._frameManager.requestStarted(request.request, route); + session.sendMayFail('Network.interceptWithRequest', { requestId: event.requestId }); + return; } + this._requestIdToRequestWillBeSentEvent.delete(event.requestId); + this._onRequest(session, requestWillBeSentEvent, true); } - _onResponseReceived(event: Protocol.Network.responseReceivedPayload) { + _onResponseReceived(session: WKSession, event: Protocol.Network.responseReceivedPayload) { + const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId); + if (requestWillBeSentEvent) { + this._requestIdToRequestWillBeSentEvent.delete(event.requestId); + // We received a response, so the request won't be intercepted (e.g. it was handled by a + // service worker and we don't intercept service workers). + this._onRequest(session, requestWillBeSentEvent, false); + } const request = this._requestIdToRequest.get(event.requestId); // FileUpload sends a response without a matching request. if (!request) return; - if (request._willBeIntercepted) { - // We received a response, so the request won't be intercepted (e.g. it was handled by service - // worker and we don't intercept service workers). - request._willBeIntercepted = false; - this._page._frameManager.requestStarted(request.request, undefined); - } + this._requestIdToResponseReceivedPayloadEvent.set(request._requestId, event); const response = request.createResponse(event.response); this._page._frameManager.requestReceivedResponse(response); if (response.status() === 204) { - this._onLoadingFailed({ + this._onLoadingFailed(session, { requestId: event.requestId, errorText: 'Aborted: 204 No Content', timestamp: event.timestamp @@ -1129,17 +1136,21 @@ export class WKPage implements PageDelegate { this._page._frameManager.reportRequestFinished(request.request, response); } - _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) { + _onLoadingFailed(session: WKSession, event: Protocol.Network.loadingFailedPayload) { + const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId); + if (requestWillBeSentEvent) { + this._requestIdToRequestWillBeSentEvent.delete(event.requestId); + // We received a response, so the request won't be intercepted (e.g. it was handled by a + // service worker and we don't intercept service workers). + this._onRequest(session, requestWillBeSentEvent, false); + } + const request = this._requestIdToRequest.get(event.requestId); // For certain requestIds we never receive requestWillBeSent event. // @see https://crbug.com/750469 if (!request) return; - if (request._willBeIntercepted) { - request._willBeIntercepted = false; - this._page._frameManager.requestStarted(request.request, undefined); - } const response = request.request._existingResponse(); if (response) { response._serverAddrFinished(); diff --git a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts index aa6c06e57d565..0b19ca7a17940 100644 --- a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts +++ b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts @@ -45,9 +45,9 @@ export class WKProvisionalPage { this._sessionListeners = [ eventsHelper.addEventListener(session, 'Network.requestWillBeSent', overrideFrameId(e => wkPage._onRequestWillBeSent(session, e))), eventsHelper.addEventListener(session, 'Network.requestIntercepted', overrideFrameId(e => wkPage._onRequestIntercepted(session, e))), - eventsHelper.addEventListener(session, 'Network.responseReceived', overrideFrameId(e => wkPage._onResponseReceived(e))), + eventsHelper.addEventListener(session, 'Network.responseReceived', overrideFrameId(e => wkPage._onResponseReceived(session, e))), eventsHelper.addEventListener(session, 'Network.loadingFinished', overrideFrameId(e => wkPage._onLoadingFinished(e))), - eventsHelper.addEventListener(session, 'Network.loadingFailed', overrideFrameId(e => wkPage._onLoadingFailed(e))), + eventsHelper.addEventListener(session, 'Network.loadingFailed', overrideFrameId(e => wkPage._onLoadingFailed(session, e))), ]; this.initializationPromise = this._wkPage._initializeSession(session, true, ({ frameTree }) => this._handleFrameTree(frameTree)); diff --git a/tests/page/page-event-request.spec.ts b/tests/page/page-event-request.spec.ts index e9de55958b02d..07c1fcc3ce5e5 100644 --- a/tests/page/page-event-request.spec.ts +++ b/tests/page/page-event-request.spec.ts @@ -69,12 +69,16 @@ it('should report requests and responses handled by service worker', async ({ pa expect(await failedRequest.response()).toBe(null); }); -it('should report requests and responses handled by service worker with routing', async ({ page, server, isAndroid, isElectron, mode, platform }) => { +it('should report requests and responses handled by service worker with routing', async ({ page, server, isAndroid, isElectron, mode, browserName, platform }) => { it.fixme(isAndroid); it.fixme(isElectron); it.fixme(mode.startsWith('service') && platform === 'linux', 'Times out for no clear reason'); - await page.route('**/*', route => route.continue()); + const interceptedUrls = []; + await page.route('**/*', route => { + interceptedUrls.push(route.request().url()); + void route.continue(); + }); await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html'); await page.evaluate(() => window['activationPromise']); const [swResponse, request] = await Promise.all([ @@ -96,34 +100,7 @@ it('should report requests and responses handled by service worker with routing' expect(failedRequest.failure()).not.toBe(null); expect(failedRequest.serviceWorker()).toBe(null); expect(await failedRequest.response()).toBe(null); -}); - -it('should not intercept requests handled by service worker with routing', async ({ page, server, isAndroid, isElectron, mode, platform, browserName }) => { - it.fixme(isAndroid); - it.fixme(isElectron); - it.fixme(mode.startsWith('service') && platform === 'linux', 'Times out for no clear reason'); - - const interceptedUrls = []; - await page.route('**/*', async route => { - interceptedUrls.push(route.request().url()); - await route.continue(); - }); - await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html'); - await page.evaluate(() => window['activationPromise']); - const [swResponse, request] = await Promise.all([ - page.evaluate(() => window['fetchDummy']('foo')), - page.waitForEvent('request'), - ]); - expect(swResponse).toBe('responseFromServiceWorker:foo'); - const response = await request.response(); - expect(response.url()).toBe(server.PREFIX + '/serviceworkers/fetchdummy/foo'); - const [failedRequest] = await Promise.all([ - page.waitForEvent('requestfailed'), - page.evaluate(() => window['fetchDummy']('error')).catch(e => e), - ]); - expect(failedRequest.url()).toBe(server.PREFIX + '/serviceworkers/fetchdummy/error'); - expect(failedRequest.failure()).not.toBe(null); const expectedUrls = [server.PREFIX + '/serviceworkers/fetchdummy/sw.html']; if (browserName === 'webkit') expectedUrls.push(server.PREFIX + '/serviceworkers/fetchdummy/sw.js');