From 4e008e6defd54c1b5c1d5fdffc7e3f35d0a7c067 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 23 Sep 2021 18:06:48 -0700 Subject: [PATCH 01/11] fix(webkit): support Cross-Origin-Opener-Policy navigation --- src/server/frames.ts | 4 + src/server/webkit/wkInterceptableRequest.ts | 2 +- src/server/webkit/wkPage.ts | 91 ++++++++++++++++++++- src/server/webkit/wkProvisionalPage.ts | 4 +- tests/page/page-goto.spec.ts | 64 ++++++++++++++- 5 files changed, 157 insertions(+), 8 deletions(-) diff --git a/src/server/frames.ts b/src/server/frames.ts index 3e99bff193955..4c329830ef1ce 100644 --- a/src/server/frames.ts +++ b/src/server/frames.ts @@ -823,6 +823,10 @@ export class Frame extends SdkObject { return this._parentFrame; } + isMainFrame(): boolean { + return this._page.mainFrame() === this; + } + childFrames(): Frame[] { return Array.from(this._childFrames); } diff --git a/src/server/webkit/wkInterceptableRequest.ts b/src/server/webkit/wkInterceptableRequest.ts index 8aff250bb224a..eb096a27eed22 100644 --- a/src/server/webkit/wkInterceptableRequest.ts +++ b/src/server/webkit/wkInterceptableRequest.ts @@ -45,7 +45,7 @@ const errorReasons: { [reason: string]: Protocol.Network.ResourceErrorType } = { export class WKInterceptableRequest { private readonly _session: WKSession; readonly request: network.Request; - readonly _requestId: string; + _requestId: string; _timestamp: number; _wallTime: number; readonly _route: WKRouteImpl | null; diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index d278547a5636c..dac60070cd038 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -76,6 +76,7 @@ export class WKPage implements PageDelegate { private _nextWindowOpenPopupFeatures?: string[]; private _recordingVideoFile: string | null = null; private _screencastGeneration: number = 0; + private _currentMainFrameNavigation: WKNavigation | undefined; constructor(browserContext: WKBrowserContext, pageProxySession: WKSession, opener: WKPage | null) { this._pageProxySession = pageProxySession; @@ -411,6 +412,8 @@ export class WKPage implements PageDelegate { } private _onFrameStoppedLoading(frameId: string) { + if (this._currentMainFrameNavigation?.shouldIgnoreFrameStoppedLoading(frameId)) + return; this._page._frameManager.frameStoppedLoading(frameId); } @@ -940,7 +943,26 @@ export class WKPage implements PageDelegate { return result.handle; } - _onRequestWillBeSent(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) { + _onRequestWillBeSentProvisional(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) { + if (!this._currentMainFrameNavigation?.checkIfSameNavigationInNewProcess(event)) { + this._onRequestWillBeSent(session, event); + return; + } + const oldId = this._currentMainFrameNavigation._originalRequestId; + const request = this._requestIdToRequest.get(oldId); + if (!request) + return; + const payload = this._requestIdToResponseReceivedPayloadEvent.get(oldId); + this._requestIdToRequest.delete(oldId); + this._requestIdToResponseReceivedPayloadEvent.delete(oldId); + const newId = event.requestId; + request._requestId = newId; + this._requestIdToRequest.set(newId, request); + if (payload) + this._requestIdToResponseReceivedPayloadEvent.set(newId, payload); + } + + private _onRequestWillBeSent(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) { if (event.request.url.startsWith('data:')) return; let redirectedFrom: WKInterceptableRequest | null = null; @@ -961,6 +983,8 @@ export class WKPage implements PageDelegate { // TODO(einbinder) this will fail if we are an XHR document request const isNavigationRequest = event.type === 'Document'; const documentId = isNavigationRequest ? event.loaderId : undefined; + if (isNavigationRequest && frame.isMainFrame()) + this._currentMainFrameNavigation = new WKNavigation(this, event); let route = null; // We do not support intercepting redirects. if (this._page._needsRequestInterception() && !redirectedFrom) @@ -1006,6 +1030,8 @@ export class WKPage implements PageDelegate { } _onResponseReceived(event: Protocol.Network.responseReceivedPayload) { + if (this._currentMainFrameNavigation?.checkIfDuplicateResponseEvent(event)) + return; const request = this._requestIdToRequest.get(event.requestId); // FileUpload sends a response without a matching request. if (!request) @@ -1063,7 +1089,12 @@ export class WKPage implements PageDelegate { this._page._frameManager.reportRequestFinished(request.request, response); } - _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) { + private _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) { + if (!this._currentMainFrameNavigation?.shouldIgnoreLoadingFailedEvent(event)) + this._onLoadingFailedShared(event); + } + + _onLoadingFailedShared(event: Protocol.Network.loadingFailedPayload) { const request = this._requestIdToRequest.get(event.requestId); // For certain requestIds we never receive requestWillBeSent event. // @see https://crbug.com/750469 @@ -1099,6 +1130,10 @@ export class WKPage implements PageDelegate { async _clearPermissions() { await this._pageProxySession.send('Emulation.resetPermissions', {}); } + + hasProvisionalPage() { + return !!this._provisionalPage; + } } /** @@ -1160,3 +1195,55 @@ function isLoadedSecurely(url: string, timing: network.ResourceTiming) { return true; } catch (_) {} } + + +class WKNavigation { + private readonly _page: WKPage; + // loaderId is a navigation id which is unique within browser and persists during cross-process navigation. + private readonly _loaderId: string; + readonly _originalRequestId: Protocol.Network.RequestId; + private readonly _frameId: string; + private _responseReceived: boolean = false; + private _newProcessRequestId: Protocol.Network.RequestId; + + constructor(page: WKPage, event: Protocol.Network.requestWillBeSentPayload) { + this._page = page; + this._loaderId = event.loaderId; + this._originalRequestId = event.requestId; + this._frameId = event.frameId; + } + + shouldIgnoreFrameStoppedLoading(frameId: string) { + if (frameId !== this._frameId) + return false; + // Navigation in the original frame is canceled and actually continues in the + // provisional page, so we ignore failure events from the original page. + return this._page.hasProvisionalPage(); + } + + shouldIgnoreLoadingFailedEvent(event: Protocol.Network.loadingFailedPayload) { + if (event.requestId !== this._originalRequestId) + return false; + if (!event.canceled) + return true; + // Navigation in the original frame is canceled and actually continues in the + // provisional page, so we ignore failure events from the original page. + return this._page.hasProvisionalPage(); + } + + checkIfSameNavigationInNewProcess(event: Protocol.Network.requestWillBeSentPayload) { + if (event.loaderId === this._loaderId) { + this._newProcessRequestId = event.requestId; + return true; + } + return false; + } + + checkIfDuplicateResponseEvent(event: Protocol.Network.responseReceivedPayload) { + if (event.loaderId !== this._loaderId) + return false; + const result = this._responseReceived; + this._responseReceived = true; + return result; + } +} \ No newline at end of file diff --git a/src/server/webkit/wkProvisionalPage.ts b/src/server/webkit/wkProvisionalPage.ts index cccb8080edc1f..cf3dc42237277 100644 --- a/src/server/webkit/wkProvisionalPage.ts +++ b/src/server/webkit/wkProvisionalPage.ts @@ -42,12 +42,12 @@ export class WKProvisionalPage { const wkPage = this._wkPage; this._sessionListeners = [ - eventsHelper.addEventListener(session, 'Network.requestWillBeSent', overrideFrameId(e => wkPage._onRequestWillBeSent(session, e))), + eventsHelper.addEventListener(session, 'Network.requestWillBeSent', overrideFrameId(e => wkPage._onRequestWillBeSentProvisional(session, e))), eventsHelper.addEventListener(session, 'Network.requestIntercepted', overrideFrameId(e => wkPage._onRequestIntercepted(session, e))), eventsHelper.addEventListener(session, 'Network.responseIntercepted', overrideFrameId(e => wkPage._onResponseIntercepted(session, e))), eventsHelper.addEventListener(session, 'Network.responseReceived', overrideFrameId(e => wkPage._onResponseReceived(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._onLoadingFailedShared(e))), ]; this.initializationPromise = this._wkPage._initializeSession(session, true, ({ frameTree }) => this._handleFrameTree(frameTree)); diff --git a/tests/page/page-goto.spec.ts b/tests/page/page-goto.spec.ts index 7fdfae4f8aef7..0fa997fe71b19 100644 --- a/tests/page/page-goto.spec.ts +++ b/tests/page/page-goto.spec.ts @@ -58,14 +58,72 @@ it('should work cross-process', async ({ page, server }) => { expect(response.url()).toBe(url); }); -it('should work with Cross-Origin-Opener-Policy', async ({ page, server, browserName }) => { - it.fail(browserName === 'webkit', 'Regressed in https://trac.webkit.org/changeset/281516/webkit'); +it('should work with Cross-Origin-Opener-Policy', async ({ page, server }) => { server.setRoute('/empty.html', (req, res) => { res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); res.end(); }); - await page.goto(server.EMPTY_PAGE); + const requests = new Set(); + const events = []; + page.on('request', r => { + events.push('request'); + requests.add(r); + }); + page.on('requestfailed', r => { + events.push('requestfailed'); + requests.add(r); + }); + page.on('requestfinished', r => { + events.push('requestfinished'); + requests.add(r); + }); + page.on('response', r => { + events.push('response'); + requests.add(r.request()); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); + await response.finished(); + expect(events).toEqual(['request', 'response', 'requestfinished']); + expect(requests.size).toBe(1); + expect(response.request().failure()).toBeNull(); +}); + +it('should work with Cross-Origin-Opener-Policy after redirect', async ({ page, server }) => { + server.setRedirect('/redirect', '/empty.html'); + server.setRoute('/empty.html', (req, res) => { + res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); + res.end(); + }); + const requests = new Set(); + const events = []; + page.on('request', r => { + events.push('request'); + requests.add(r); + }); + page.on('requestfailed', r => { + events.push('requestfailed'); + requests.add(r); + }); + page.on('requestfinished', r => { + events.push('requestfinished'); + requests.add(r); + }); + page.on('response', r => { + events.push('response'); + requests.add(r.request()); + }); + console.log('\n\n\n\n'); + // const response = await page.goto(server.EMPTY_PAGE); + const response = await page.goto(server.PREFIX + '/redirect'); expect(page.url()).toBe(server.EMPTY_PAGE); + await response.finished(); + expect(events).toEqual(['request', 'response', 'requestfinished', 'request', 'response', 'requestfinished']); + expect(requests.size).toBe(2); + expect(response.request().failure()).toBeNull(); + const firstRequest = response.request().redirectedFrom(); + expect(firstRequest).toBeTruthy(); + expect(firstRequest.url()).toBe(server.PREFIX + '/redirect'); }); it('should capture iframe navigation request', async ({ page, server }) => { From ef66621e32978e4f8ac973287d4918e252d5b213 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 23 Sep 2021 19:16:14 -0700 Subject: [PATCH 02/11] Remove unused field --- src/server/webkit/wkPage.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index dac60070cd038..a7ff83524e0be 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -944,7 +944,7 @@ export class WKPage implements PageDelegate { } _onRequestWillBeSentProvisional(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) { - if (!this._currentMainFrameNavigation?.checkIfSameNavigationInNewProcess(event)) { + if (!this._currentMainFrameNavigation?.isSameNavigationInNewProcess(event)) { this._onRequestWillBeSent(session, event); return; } @@ -1204,7 +1204,6 @@ class WKNavigation { readonly _originalRequestId: Protocol.Network.RequestId; private readonly _frameId: string; private _responseReceived: boolean = false; - private _newProcessRequestId: Protocol.Network.RequestId; constructor(page: WKPage, event: Protocol.Network.requestWillBeSentPayload) { this._page = page; @@ -1231,12 +1230,8 @@ class WKNavigation { return this._page.hasProvisionalPage(); } - checkIfSameNavigationInNewProcess(event: Protocol.Network.requestWillBeSentPayload) { - if (event.loaderId === this._loaderId) { - this._newProcessRequestId = event.requestId; - return true; - } - return false; + isSameNavigationInNewProcess(event: Protocol.Network.requestWillBeSentPayload) { + return event.loaderId === this._loaderId; } checkIfDuplicateResponseEvent(event: Protocol.Network.responseReceivedPayload) { From 3f5117dd51dce1c8a5848779fe5d7ab39a5f3daa Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 24 Sep 2021 09:07:26 -0700 Subject: [PATCH 03/11] Fix .canceled check --- src/server/webkit/wkPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index a7ff83524e0be..d74f5f21bb3a8 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -1224,7 +1224,7 @@ class WKNavigation { if (event.requestId !== this._originalRequestId) return false; if (!event.canceled) - return true; + return false; // Navigation in the original frame is canceled and actually continues in the // provisional page, so we ignore failure events from the original page. return this._page.hasProvisionalPage(); From 1f7fbb131ce720f3b279b213799084f6776716a5 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 24 Sep 2021 09:21:34 -0700 Subject: [PATCH 04/11] Check only top level navigation requests --- src/server/webkit/wkPage.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index d74f5f21bb3a8..ef2d3d9bceb7e 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -944,7 +944,7 @@ export class WKPage implements PageDelegate { } _onRequestWillBeSentProvisional(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) { - if (!this._currentMainFrameNavigation?.isSameNavigationInNewProcess(event)) { + if (!this._currentMainFrameNavigation?.checkIfSameNavigationInNewProcess(event)) { this._onRequestWillBeSent(session, event); return; } @@ -1204,6 +1204,8 @@ class WKNavigation { readonly _originalRequestId: Protocol.Network.RequestId; private readonly _frameId: string; private _responseReceived: boolean = false; + private _didCheckFirstRequestInProvisionalPage: boolean = false; + private _provisionalPageRequestId: string | undefined; constructor(page: WKPage, event: Protocol.Network.requestWillBeSentPayload) { this._page = page; @@ -1230,12 +1232,17 @@ class WKNavigation { return this._page.hasProvisionalPage(); } - isSameNavigationInNewProcess(event: Protocol.Network.requestWillBeSentPayload) { - return event.loaderId === this._loaderId; + checkIfSameNavigationInNewProcess(event: Protocol.Network.requestWillBeSentPayload) { + if (this._didCheckFirstRequestInProvisionalPage) + return false; + this._didCheckFirstRequestInProvisionalPage = true; + if (event.loaderId !== this._loaderId) + return false; + this._provisionalPageRequestId = event.requestId; } checkIfDuplicateResponseEvent(event: Protocol.Network.responseReceivedPayload) { - if (event.loaderId !== this._loaderId) + if (this._provisionalPageRequestId !== event.requestId) return false; const result = this._responseReceived; this._responseReceived = true; From e958b2e3e7c8a054216f16eddce671d5037f1106 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 24 Sep 2021 10:15:26 -0700 Subject: [PATCH 05/11] Remove stray logging --- tests/page/page-goto.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/page/page-goto.spec.ts b/tests/page/page-goto.spec.ts index 0fa997fe71b19..cb7c5dab8557a 100644 --- a/tests/page/page-goto.spec.ts +++ b/tests/page/page-goto.spec.ts @@ -113,8 +113,6 @@ it('should work with Cross-Origin-Opener-Policy after redirect', async ({ page, events.push('response'); requests.add(r.request()); }); - console.log('\n\n\n\n'); - // const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.PREFIX + '/redirect'); expect(page.url()).toBe(server.EMPTY_PAGE); await response.finished(); From 1500b60b01a1bafac027a443f108d596315977f9 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 24 Sep 2021 12:59:46 -0700 Subject: [PATCH 06/11] Fix test --- src/server/webkit/wkPage.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index ef2d3d9bceb7e..40313d035307f 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -1214,7 +1214,7 @@ class WKNavigation { this._frameId = event.frameId; } - shouldIgnoreFrameStoppedLoading(frameId: string) { + shouldIgnoreFrameStoppedLoading(frameId: string): boolean { if (frameId !== this._frameId) return false; // Navigation in the original frame is canceled and actually continues in the @@ -1222,7 +1222,7 @@ class WKNavigation { return this._page.hasProvisionalPage(); } - shouldIgnoreLoadingFailedEvent(event: Protocol.Network.loadingFailedPayload) { + shouldIgnoreLoadingFailedEvent(event: Protocol.Network.loadingFailedPayload): boolean { if (event.requestId !== this._originalRequestId) return false; if (!event.canceled) @@ -1232,20 +1232,25 @@ class WKNavigation { return this._page.hasProvisionalPage(); } - checkIfSameNavigationInNewProcess(event: Protocol.Network.requestWillBeSentPayload) { + checkIfSameNavigationInNewProcess(event: Protocol.Network.requestWillBeSentPayload): boolean { if (this._didCheckFirstRequestInProvisionalPage) return false; this._didCheckFirstRequestInProvisionalPage = true; if (event.loaderId !== this._loaderId) return false; this._provisionalPageRequestId = event.requestId; + return true; } - checkIfDuplicateResponseEvent(event: Protocol.Network.responseReceivedPayload) { - if (this._provisionalPageRequestId !== event.requestId) - return false; - const result = this._responseReceived; - this._responseReceived = true; - return result; + checkIfDuplicateResponseEvent(event: Protocol.Network.responseReceivedPayload): boolean { + if (this._originalRequestId === event.requestId || this._provisionalPageRequestId === event.requestId) { + // In case of cross-process navigation caused by Cross-Origin-Opener-Policy response header + // WebKit sends two responseReceived events from the old web process and one such event from the + // new provisonal web process. + if (this._responseReceived) + return true; + this._responseReceived = true; + } + return false; } } \ No newline at end of file From d51b9ee67793b8bbb2face52428b2e43ebc5bc3b Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 24 Sep 2021 17:58:36 -0700 Subject: [PATCH 07/11] New test for failing cross-process navigation --- tests/page/page-goto.spec.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/page/page-goto.spec.ts b/tests/page/page-goto.spec.ts index cb7c5dab8557a..5706724dbe32e 100644 --- a/tests/page/page-goto.spec.ts +++ b/tests/page/page-goto.spec.ts @@ -58,6 +58,21 @@ it('should work cross-process', async ({ page, server }) => { expect(response.url()).toBe(url); }); +it('should work with cross-process that fails before committing', async ({ page, server, browserName }) => { + server.setRoute('/empty.html', (req, res) => { + req.socket.destroy(); + }); + const response1 = await page.goto(server.CROSS_PROCESS_PREFIX + '/title.html'); + await response1.finished(); + const error = await page.goto(server.EMPTY_PAGE).catch(e => e); + if (browserName === 'chromium') + expect(error.message).toContain('net::ERR_EMPTY_RESPONSE'); + if (browserName === 'webkit') + expect(error.message).toContain('Message Corrupt'); + if (browserName === 'firefox') + expect(error.message).toContain('NS_ERROR_NET_RESET'); +}); + it('should work with Cross-Origin-Opener-Policy', async ({ page, server }) => { server.setRoute('/empty.html', (req, res) => { res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); From 554db2de381031e404987988fd0b27d1a317cb52 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 27 Sep 2021 15:39:05 -0700 Subject: [PATCH 08/11] Add request interception test --- src/server/webkit/wkPage.ts | 8 +++- tests/page/page-request-continue.spec.ts | 49 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index 40313d035307f..cc3586464a2a4 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -1010,7 +1010,7 @@ export class WKPage implements PageDelegate { session.sendMayFail('Network.interceptRequestWithError', { errorType: 'Cancellation', requestId: event.requestId }); return; } - if (!request._route) { + if (!request._route || this._currentMainFrameNavigation.shoudIgnoreRequestInterception(event)) { // Intercepted, although we do not intend to allow interception. // Just continue. session.sendMayFail('Network.interceptWithRequest', { requestId: request._requestId }); @@ -1253,4 +1253,10 @@ class WKNavigation { } return false; } + + shoudIgnoreRequestInterception(event: Protocol.Network.requestInterceptedPayload): boolean { + // It only makes sense to intercept request in the old process before it is sent to + // the server (in the new process the response is already received and will be replayed) + return this._provisionalPageRequestId === event.requestId; + } } \ No newline at end of file diff --git a/tests/page/page-request-continue.spec.ts b/tests/page/page-request-continue.spec.ts index d1d4c24b2cd7a..8cac932ed1f7c 100644 --- a/tests/page/page-request-continue.spec.ts +++ b/tests/page/page-request-continue.spec.ts @@ -167,3 +167,52 @@ it.describe('', () => { expect(arr[i]).toBe(buffer[i]); }); }); + +it('should work with Cross-Origin-Opener-Policy', async ({ page, server }) => { + let serverHeaders; + const serverRequests = []; + server.setRoute('/empty.html', (req, res) => { + serverRequests.push(req.url); + serverHeaders ??= req.headers; + res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); + res.end(); + }); + + const intercepted = []; + await page.route('**/*', (route, req) => { + intercepted.push(req.url()); + route.continue({ + headers: { + foo: 'bar' + } + }); + }); + const requests = new Set(); + const events = []; + page.on('request', r => { + events.push('request'); + requests.add(r); + }); + page.on('requestfailed', r => { + events.push('requestfailed'); + requests.add(r); + }); + page.on('requestfinished', r => { + events.push('requestfinished'); + requests.add(r); + }); + page.on('response', r => { + events.push('response'); + requests.add(r.request()); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([server.EMPTY_PAGE]); + // There should be only one request to the server. + expect(serverRequests).toEqual(['/empty.html']); + expect(serverHeaders['foo']).toBe('bar'); + expect(page.url()).toBe(server.EMPTY_PAGE); + await response.finished(); + expect(events).toEqual(['request', 'response', 'requestfinished']); + expect(requests.size).toBe(1); + expect(response.request().failure()).toBeNull(); +}); From 67c548c74d038012c4d70382188dc13e47515e2c Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 27 Sep 2021 15:48:43 -0700 Subject: [PATCH 09/11] Add download test --- tests/download.spec.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/download.spec.ts b/tests/download.spec.ts index fb338f9c51045..52b975ce3ce7c 100644 --- a/tests/download.spec.ts +++ b/tests/download.spec.ts @@ -39,6 +39,12 @@ it.describe('download event', () => { res.write('foo'); res.uncork(); }); + server.setRoute('/downloadWithCOOP', (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.setHeader('Content-Disposition', 'attachment'); + res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); + res.end(`Hello world`); + }); }); it('should report download when navigation turns into download', async ({ browser, server, browserName }) => { @@ -67,6 +73,32 @@ it.describe('download event', () => { await page.close(); }); + it('should work with Cross-Origin-Opener-Policy', async ({ browser, server, browserName }) => { + const page = await browser.newPage({ acceptDownloads: true }); + const [ download, responseOrError ] = await Promise.all([ + page.waitForEvent('download'), + page.goto(server.PREFIX + '/downloadWithCOOP').catch(e => e) + ]); + expect(download.page()).toBe(page); + expect(download.url()).toBe(`${server.PREFIX}/downloadWithCOOP`); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + expect(fs.readFileSync(path).toString()).toBe('Hello world'); + if (browserName === 'chromium') { + expect(responseOrError instanceof Error).toBeTruthy(); + expect(responseOrError.message).toContain('net::ERR_ABORTED'); + expect(page.url()).toBe('about:blank'); + } else if (browserName === 'webkit') { + expect(responseOrError instanceof Error).toBeTruthy(); + expect(responseOrError.message).toContain('Download is starting'); + expect(page.url()).toBe('about:blank'); + } else { + expect(responseOrError.status()).toBe(200); + expect(page.url()).toBe(server.PREFIX + '/download'); + } + await page.close(); + }); + it('should report downloads with acceptDownloads: false', async ({ browser, server }) => { const page = await browser.newPage(); await page.setContent(`download`); From 6c32eff40a97646eb106d8f40fc287f1f0bcd579 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 27 Sep 2021 15:52:02 -0700 Subject: [PATCH 10/11] Update expectation --- tests/page/page-request-continue.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/page/page-request-continue.spec.ts b/tests/page/page-request-continue.spec.ts index 8cac932ed1f7c..c9553d655dec7 100644 --- a/tests/page/page-request-continue.spec.ts +++ b/tests/page/page-request-continue.spec.ts @@ -168,7 +168,7 @@ it.describe('', () => { }); }); -it('should work with Cross-Origin-Opener-Policy', async ({ page, server }) => { +it('should work with Cross-Origin-Opener-Policy', async ({ page, server, browserName }) => { let serverHeaders; const serverRequests = []; server.setRoute('/empty.html', (req, res) => { @@ -208,7 +208,10 @@ it('should work with Cross-Origin-Opener-Policy', async ({ page, server }) => { const response = await page.goto(server.EMPTY_PAGE); expect(intercepted).toEqual([server.EMPTY_PAGE]); // There should be only one request to the server. - expect(serverRequests).toEqual(['/empty.html']); + if (browserName === 'webkit') + expect(serverRequests).toEqual(['/empty.html', '/empty.html']); + else + expect(serverRequests).toEqual(['/empty.html']); expect(serverHeaders['foo']).toBe('bar'); expect(page.url()).toBe(server.EMPTY_PAGE); await response.finished(); From d3b004f1d8ef95ba983fddc8917edd9f972a370a Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 27 Sep 2021 16:24:49 -0700 Subject: [PATCH 11/11] Fix lint --- src/server/webkit/wkPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index cc3586464a2a4..3f6246ec873a1 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -1010,7 +1010,7 @@ export class WKPage implements PageDelegate { session.sendMayFail('Network.interceptRequestWithError', { errorType: 'Cancellation', requestId: event.requestId }); return; } - if (!request._route || this._currentMainFrameNavigation.shoudIgnoreRequestInterception(event)) { + if (!request._route || this._currentMainFrameNavigation?.shoudIgnoreRequestInterception(event)) { // Intercepted, although we do not intend to allow interception. // Just continue. session.sendMayFail('Network.interceptWithRequest', { requestId: request._requestId });