diff --git a/src/webkit/wkInterceptableRequest.ts b/src/webkit/wkInterceptableRequest.ts new file mode 100644 index 0000000000..7e279d92de --- /dev/null +++ b/src/webkit/wkInterceptableRequest.ts @@ -0,0 +1,126 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as frames from '../frames'; +import { assert, debugError, helper } from '../helper'; +import * as network from '../network'; +import * as platform from '../platform'; +import { Protocol } from './protocol'; +import { WKSession } from './wkConnection'; + +const errorReasons: { [reason: string]: string } = { + 'aborted': 'Cancellation', + 'accessdenied': 'AccessControl', + 'addressunreachable': 'General', + 'blockedbyclient': 'Cancellation', + 'blockedbyresponse': 'General', + 'connectionaborted': 'General', + 'connectionclosed': 'General', + 'connectionfailed': 'General', + 'connectionrefused': 'General', + 'connectionreset': 'General', + 'internetdisconnected': 'General', + 'namenotresolved': 'General', + 'timedout': 'Timeout', + 'failed': 'General', +}; + +export class WKInterceptableRequest implements network.RequestDelegate { + private readonly _session: WKSession; + readonly request: network.Request; + readonly _requestId: string; + _interceptedCallback: () => void = () => {}; + private _interceptedPromise: Promise; + + constructor(session: WKSession, allowInterception: boolean, frame: frames.Frame | null, event: Protocol.Network.requestWillBeSentPayload, redirectChain: network.Request[], documentId: string | undefined) { + this._session = session; + this._requestId = event.requestId; + this.request = new network.Request(allowInterception ? this : null, frame, redirectChain, documentId, event.request.url, + event.type ? event.type.toLowerCase() : 'Unknown', event.request.method, event.request.postData, headersObject(event.request.headers)); + this._interceptedPromise = new Promise(f => this._interceptedCallback = f); + } + + async abort(errorCode: string) { + const reason = errorReasons[errorCode]; + assert(reason, 'Unknown error code: ' + errorCode); + await this._interceptedPromise; + await this._session.send('Network.interceptAsError', { requestId: this._requestId, reason }).catch(error => { + // In certain cases, protocol will return error if the request was already canceled + // or the page was closed. We should tolerate these errors. + debugError(error); + }); + } + + async fulfill(response: { status: number; headers: network.Headers; contentType: string; body: (string | platform.BufferType); }) { + await this._interceptedPromise; + + const base64Encoded = !!response.body && !helper.isString(response.body); + const responseBody = response.body ? (base64Encoded ? response.body.toString('base64') : response.body as string) : undefined; + + const responseHeaders: { [s: string]: string; } = {}; + if (response.headers) { + for (const header of Object.keys(response.headers)) + responseHeaders[header.toLowerCase()] = String(response.headers[header]); + } + if (response.contentType) + responseHeaders['content-type'] = response.contentType; + if (responseBody && !('content-length' in responseHeaders)) + responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody)); + + await this._session.send('Network.interceptWithResponse', { + requestId: this._requestId, + status: response.status || 200, + statusText: network.STATUS_TEXTS[String(response.status || 200)], + mimeType: response.contentType || (base64Encoded ? 'application/octet-stream' : 'text/plain'), + headers: responseHeaders, + base64Encoded, + content: responseBody + }).catch(error => { + // In certain cases, protocol will return error if the request was already canceled + // or the page was closed. We should tolerate these errors. + debugError(error); + }); + } + + async continue(overrides: { headers?: { [key: string]: string; }; }) { + await this._interceptedPromise; + await this._session.send('Network.interceptContinue', { + requestId: this._requestId, + ...overrides + }).catch(error => { + // In certain cases, protocol will return error if the request was already canceled + // or the page was closed. We should tolerate these errors. + debugError(error); + }); + } + + createResponse(responsePayload: Protocol.Network.Response): network.Response { + const remoteAddress: network.RemoteAddress = { ip: '', port: 0 }; + const getResponseBody = async () => { + const response = await this._session.send('Network.getResponseBody', { requestId: this._requestId }); + return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8'); + }; + return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), remoteAddress, getResponseBody); + } +} + +function headersObject(headers: Protocol.Network.Headers): network.Headers { + const result: network.Headers = {}; + for (const key of Object.keys(headers)) + result[key.toLowerCase()] = headers[key]; + return result; +} diff --git a/src/webkit/wkNetworkManager.ts b/src/webkit/wkNetworkManager.ts deleted file mode 100644 index 19dca43cf6..0000000000 --- a/src/webkit/wkNetworkManager.ts +++ /dev/null @@ -1,280 +0,0 @@ -/** - * Copyright 2017 Google Inc. All rights reserved. - * Modifications copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { WKSession } from './wkConnection'; -import { Page } from '../page'; -import { helper, RegisteredListener, assert, debugError } from '../helper'; -import { Protocol } from './protocol'; -import * as network from '../network'; -import * as frames from '../frames'; -import * as types from '../types'; -import * as platform from '../platform'; - -export class WKNetworkManager { - private readonly _page: Page; - private readonly _pageProxySession: WKSession; - private _session: WKSession; - private readonly _requestIdToRequest = new Map(); - private _userCacheDisabled = false; - private _sessionListeners: RegisteredListener[] = []; - - constructor(page: Page, pageProxySession: WKSession) { - this._page = page; - this._pageProxySession = pageProxySession; - this._session = undefined as any as WKSession; - } - - async initializePageProxySession(credentials: types.Credentials | null) { - await this.authenticate(credentials); - } - - setSession(session: WKSession) { - helper.removeEventListeners(this._sessionListeners); - this._session = session; - this._sessionListeners = [ - helper.addEventListener(session, 'Network.requestWillBeSent', e => this._onRequestWillBeSent(session, e)), - helper.addEventListener(session, 'Network.requestIntercepted', e => this._onRequestIntercepted(e)), - helper.addEventListener(session, 'Network.responseReceived', e => this._onResponseReceived(e)), - helper.addEventListener(session, 'Network.loadingFinished', e => this._onLoadingFinished(e)), - helper.addEventListener(session, 'Network.loadingFailed', e => this._onLoadingFailed(e)), - helper.addEventListener(session, 'Network.webSocketCreated', e => this._page._frameManager.onWebSocketCreated(e.requestId, e.url)), - helper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page._frameManager.onWebSocketRequest(e.requestId, e.request.headers)), - helper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText, e.response.headers)), - helper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)), - helper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)), - helper.addEventListener(session, 'Network.webSocketClosed', e => this._page._frameManager.webSocketClosed(e.requestId)), - helper.addEventListener(session, 'Network.webSocketFrameError', e => this._page._frameManager.webSocketError(e.requestId, e.errorMessage)), - ]; - } - - async initializeSession(session: WKSession, interceptNetwork: boolean | null, offlineMode: boolean | null) { - const promises = []; - promises.push(session.send('Network.enable')); - if (interceptNetwork) - promises.push(session.send('Network.setInterceptionEnabled', { enabled: true, interceptRequests: true })); - if (offlineMode) - promises.push(session.send('Network.setEmulateOfflineState', { offline: true })); - await Promise.all(promises); - } - - dispose() { - helper.removeEventListeners(this._sessionListeners); - } - - async setCacheEnabled(enabled: boolean) { - this._userCacheDisabled = !enabled; - await this._updateProtocolCacheDisabled(); - } - - async setRequestInterception(enabled: boolean): Promise { - await this._session.send('Network.setInterceptionEnabled', { enabled, interceptRequests: enabled }); - } - - private async _updateProtocolCacheDisabled() { - await this._session.send('Network.setResourceCachingDisabled', { - disabled: this._userCacheDisabled - }); - } - - _onRequestWillBeSent(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) { - if (event.request.url.startsWith('data:')) - return; - let redirectChain: network.Request[] = []; - if (event.redirectResponse) { - const request = this._requestIdToRequest.get(event.requestId); - // If we connect late to the target, we could have missed the requestWillBeSent event. - if (request) { - this._handleRequestRedirect(request, event.redirectResponse); - redirectChain = request.request._redirectChain; - } - } - const frame = this._page._frameManager.frame(event.frameId); - // TODO(einbinder) this will fail if we are an XHR document request - const isNavigationRequest = event.type === 'Document'; - const documentId = isNavigationRequest ? event.loaderId : undefined; - const request = new InterceptableRequest(session, !!this._page._state.interceptNetwork, frame, event, redirectChain, documentId); - this._requestIdToRequest.set(event.requestId, request); - this._page._frameManager.requestStarted(request.request); - } - - _onRequestIntercepted(event: Protocol.Network.requestInterceptedPayload) { - const request = this._requestIdToRequest.get(event.requestId); - if (request) - request._interceptedCallback(); - } - - private static _createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response { - const remoteAddress: network.RemoteAddress = { ip: '', port: 0 }; - const getResponseBody = async () => { - const response = await request._session.send('Network.getResponseBody', { requestId: request._requestId }); - return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8'); - }; - return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), remoteAddress, getResponseBody); - } - - private _handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response) { - const response = WKNetworkManager._createResponse(request, responsePayload); - request.request._redirectChain.push(request.request); - response._requestFinished(new Error('Response body is unavailable for redirect responses')); - this._requestIdToRequest.delete(request._requestId); - this._page._frameManager.requestReceivedResponse(response); - this._page._frameManager.requestFinished(request.request); - } - - _onResponseReceived(event: Protocol.Network.responseReceivedPayload) { - const request = this._requestIdToRequest.get(event.requestId); - // FileUpload sends a response without a matching request. - if (!request) - return; - const response = WKNetworkManager._createResponse(request, event.response); - this._page._frameManager.requestReceivedResponse(response); - } - - _onLoadingFinished(event: Protocol.Network.loadingFinishedPayload) { - const request = this._requestIdToRequest.get(event.requestId); - // For certain requestIds we never receive requestWillBeSent event. - // @see https://crbug.com/750469 - if (!request) - return; - - // Under certain conditions we never get the Network.responseReceived - // event from protocol. @see https://crbug.com/883475 - const response = request.request.response(); - if (response) - response._requestFinished(); - this._requestIdToRequest.delete(request._requestId); - this._page._frameManager.requestFinished(request.request); - } - - _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) { - const request = this._requestIdToRequest.get(event.requestId); - // For certain requestIds we never receive requestWillBeSent event. - // @see https://crbug.com/750469 - if (!request) - return; - const response = request.request.response(); - if (response) - response._requestFinished(); - this._requestIdToRequest.delete(request._requestId); - request.request._setFailureText(event.errorText); - this._page._frameManager.requestFailed(request.request, event.errorText.includes('cancelled')); - } - - async authenticate(credentials: types.Credentials | null) { - await this._pageProxySession.send('Emulation.setAuthCredentials', { ...(credentials || { username: '', password: '' }) }); - } - - async setOfflineMode(value: boolean): Promise { - await this._session.send('Network.setEmulateOfflineState', { offline: value }); - } -} - -const errorReasons: { [reason: string]: string } = { - 'aborted': 'Cancellation', - 'accessdenied': 'AccessControl', - 'addressunreachable': 'General', - 'blockedbyclient': 'Cancellation', - 'blockedbyresponse': 'General', - 'connectionaborted': 'General', - 'connectionclosed': 'General', - 'connectionfailed': 'General', - 'connectionrefused': 'General', - 'connectionreset': 'General', - 'internetdisconnected': 'General', - 'namenotresolved': 'General', - 'timedout': 'Timeout', - 'failed': 'General', -}; - -class InterceptableRequest implements network.RequestDelegate { - readonly _session: WKSession; - readonly request: network.Request; - _requestId: string; - _documentId: string | undefined; - _interceptedCallback: () => void = () => {}; - private _interceptedPromise: Promise; - - constructor(session: WKSession, allowInterception: boolean, frame: frames.Frame | null, event: Protocol.Network.requestWillBeSentPayload, redirectChain: network.Request[], documentId: string | undefined) { - this._session = session; - this._requestId = event.requestId; - this._documentId = documentId; - this.request = new network.Request(allowInterception ? this : null, frame, redirectChain, documentId, event.request.url, - event.type ? event.type.toLowerCase() : 'Unknown', event.request.method, event.request.postData, headersObject(event.request.headers)); - this._interceptedPromise = new Promise(f => this._interceptedCallback = f); - } - - async abort(errorCode: string) { - const reason = errorReasons[errorCode]; - assert(reason, 'Unknown error code: ' + errorCode); - await this._interceptedPromise; - await this._session.send('Network.interceptAsError', { requestId: this._requestId, reason }).catch(error => { - // In certain cases, protocol will return error if the request was already canceled - // or the page was closed. We should tolerate these errors. - debugError(error); - }); - } - - async fulfill(response: { status: number; headers: network.Headers; contentType: string; body: (string | platform.BufferType); }) { - await this._interceptedPromise; - - const base64Encoded = !!response.body && !helper.isString(response.body); - const responseBody = response.body ? (base64Encoded ? response.body.toString('base64') : response.body as string) : undefined; - - const responseHeaders: { [s: string]: string; } = {}; - if (response.headers) { - for (const header of Object.keys(response.headers)) - responseHeaders[header.toLowerCase()] = String(response.headers[header]); - } - if (response.contentType) - responseHeaders['content-type'] = response.contentType; - if (responseBody && !('content-length' in responseHeaders)) - responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody)); - - await this._session.send('Network.interceptWithResponse', { - requestId: this._requestId, - status: response.status || 200, - statusText: network.STATUS_TEXTS[String(response.status || 200)], - mimeType: response.contentType || (base64Encoded ? 'application/octet-stream' : 'text/plain'), - headers: responseHeaders, - base64Encoded, - content: responseBody - }).catch(error => { - // In certain cases, protocol will return error if the request was already canceled - // or the page was closed. We should tolerate these errors. - debugError(error); - }); - } - - async continue(overrides: { headers?: { [key: string]: string; }; }) { - await this._interceptedPromise; - await this._session.send('Network.interceptContinue', { - requestId: this._requestId, - ...overrides - }).catch(error => { - // In certain cases, protocol will return error if the request was already canceled - // or the page was closed. We should tolerate these errors. - debugError(error); - }); - } -} - -function headersObject(headers: Protocol.Network.Headers): network.Headers { - const result: network.Headers = {}; - for (const key of Object.keys(headers)) - result[key.toLowerCase()] = headers[key]; - return result; -} diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index b7cc5832ce..329f8b24bf 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -22,7 +22,7 @@ import * as network from '../network'; import { WKSession } from './wkConnection'; import { Events } from '../events'; import { WKExecutionContext, EVALUATION_SCRIPT_URL } from './wkExecutionContext'; -import { WKNetworkManager } from './wkNetworkManager'; +import { WKInterceptableRequest } from './wkInterceptableRequest'; import { WKWorkers } from './wkWorkers'; import { Page, PageDelegate, Coverage } from '../page'; import { Protocol } from './protocol'; @@ -43,7 +43,7 @@ export class WKPage implements PageDelegate { _session: WKSession; readonly _page: Page; private readonly _pageProxySession: WKSession; - readonly _networkManager: WKNetworkManager; + private readonly _requestIdToRequest = new Map(); private readonly _workers: WKWorkers; private readonly _contextIdToContext: Map; private _sessionListeners: RegisteredListener[] = []; @@ -55,7 +55,6 @@ export class WKPage implements PageDelegate { this.rawMouse = new RawMouseImpl(pageProxySession); this._contextIdToContext = new Map(); this._page = new Page(this, browserContext); - this._networkManager = new WKNetworkManager(this._page, pageProxySession); this._workers = new WKWorkers(this._page); this._session = undefined as any as WKSession; } @@ -63,7 +62,7 @@ export class WKPage implements PageDelegate { private async _initializePageProxySession() { const promises : Promise[] = [ this._pageProxySession.send('Dialog.enable'), - this._networkManager.initializePageProxySession(this._page._state.credentials) + this.authenticate(this._page._state.credentials) ]; const contextOptions = this._page.browserContext()._options; if (contextOptions.javaScriptEnabled === false) @@ -79,7 +78,6 @@ export class WKPage implements PageDelegate { this._session = session; this.rawKeyboard.setSession(session); this._addSessionListeners(); - this._networkManager.setSession(session); this._workers.setSession(session); // New bootstrap scripts may have been added during provisional load, push them // again to be on the safe side. @@ -107,9 +105,17 @@ export class WKPage implements PageDelegate { session.send('Page.createIsolatedWorld', { name: UTILITY_WORLD_NAME, source: `//# sourceURL=${EVALUATION_SCRIPT_URL}` }), session.send('Console.enable'), session.send('Page.setInterceptFileChooserDialog', { enabled: true }), - this._networkManager.initializeSession(session, this._page._state.interceptNetwork, this._page._state.offlineMode), + session.send('Network.enable'), this._workers.initializeSession(session) ]; + + if (this._page._state.interceptNetwork) + promises.push(session.send('Network.setInterceptionEnabled', { enabled: true, interceptRequests: true })); + if (this._page._state.offlineMode) + promises.push(session.send('Network.setEmulateOfflineState', { offline: true })); + if (this._page._state.cacheEnabled === false) + promises.push(session.send('Network.setResourceCachingDisabled', { disabled: true })); + const contextOptions = this._page.browserContext()._options; if (contextOptions.userAgent) promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent })); @@ -135,7 +141,6 @@ export class WKPage implements PageDelegate { didClose(crashed: boolean) { helper.removeEventListeners(this._sessionListeners); - this._networkManager.dispose(); this.disconnectFromTarget(); if (crashed) this._page._didCrash(); @@ -160,6 +165,18 @@ export class WKPage implements PageDelegate { helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)), helper.addEventListener(this._pageProxySession, 'Dialog.javascriptDialogOpening', event => this._onDialog(event)), helper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)), + helper.addEventListener(this._session, 'Network.requestWillBeSent', e => this._onRequestWillBeSent(this._session, e)), + helper.addEventListener(this._session, 'Network.requestIntercepted', e => this._onRequestIntercepted(e)), + helper.addEventListener(this._session, 'Network.responseReceived', e => this._onResponseReceived(e)), + helper.addEventListener(this._session, 'Network.loadingFinished', e => this._onLoadingFinished(e)), + helper.addEventListener(this._session, 'Network.loadingFailed', e => this._onLoadingFailed(e)), + helper.addEventListener(this._session, 'Network.webSocketCreated', e => this._page._frameManager.onWebSocketCreated(e.requestId, e.url)), + helper.addEventListener(this._session, 'Network.webSocketWillSendHandshakeRequest', e => this._page._frameManager.onWebSocketRequest(e.requestId, e.request.headers)), + helper.addEventListener(this._session, 'Network.webSocketHandshakeResponseReceived', e => this._page._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText, e.response.headers)), + helper.addEventListener(this._session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)), + helper.addEventListener(this._session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)), + helper.addEventListener(this._session, 'Network.webSocketClosed', e => this._page._frameManager.webSocketClosed(e.requestId)), + helper.addEventListener(this._session, 'Network.webSocketFrameError', e => this._page._frameManager.webSocketError(e.requestId, e.errorMessage)), ]; } @@ -331,20 +348,21 @@ export class WKPage implements PageDelegate { ]); } - setCacheEnabled(enabled: boolean): Promise { - return this._networkManager.setCacheEnabled(enabled); + async setCacheEnabled(enabled: boolean): Promise { + const disabled = !enabled; + await this._session.send('Network.setResourceCachingDisabled', { disabled }); } - setRequestInterception(enabled: boolean): Promise { - return this._networkManager.setRequestInterception(enabled); + async setRequestInterception(enabled: boolean): Promise { + await this._session.send('Network.setInterceptionEnabled', { enabled, interceptRequests: enabled }); } - async setOfflineMode(value: boolean) { - await this._networkManager.setOfflineMode(value); + async setOfflineMode(offline: boolean) { + await this._session.send('Network.setEmulateOfflineState', { offline }); } async authenticate(credentials: types.Credentials | null) { - await this._networkManager.authenticate(credentials); + await this._pageProxySession.send('Emulation.setAuthCredentials', { ...(credentials || { username: '', password: '' }) }); } async reload(): Promise { @@ -502,6 +520,81 @@ export class WKPage implements PageDelegate { coverage(): Coverage | undefined { return undefined; } + + _onRequestWillBeSent(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) { + if (event.request.url.startsWith('data:')) + return; + let redirectChain: network.Request[] = []; + if (event.redirectResponse) { + const request = this._requestIdToRequest.get(event.requestId); + // If we connect late to the target, we could have missed the requestWillBeSent event. + if (request) { + this._handleRequestRedirect(request, event.redirectResponse); + redirectChain = request.request._redirectChain; + } + } + const frame = this._page._frameManager.frame(event.frameId); + // TODO(einbinder) this will fail if we are an XHR document request + const isNavigationRequest = event.type === 'Document'; + const documentId = isNavigationRequest ? event.loaderId : undefined; + const request = new WKInterceptableRequest(session, !!this._page._state.interceptNetwork, frame, event, redirectChain, documentId); + this._requestIdToRequest.set(event.requestId, request); + this._page._frameManager.requestStarted(request.request); + } + + private _handleRequestRedirect(request: WKInterceptableRequest, responsePayload: Protocol.Network.Response) { + const response = request.createResponse(responsePayload); + request.request._redirectChain.push(request.request); + response._requestFinished(new Error('Response body is unavailable for redirect responses')); + this._requestIdToRequest.delete(request._requestId); + this._page._frameManager.requestReceivedResponse(response); + this._page._frameManager.requestFinished(request.request); + } + + _onRequestIntercepted(event: Protocol.Network.requestInterceptedPayload) { + const request = this._requestIdToRequest.get(event.requestId); + if (request) + request._interceptedCallback(); + } + + _onResponseReceived(event: Protocol.Network.responseReceivedPayload) { + const request = this._requestIdToRequest.get(event.requestId); + // FileUpload sends a response without a matching request. + if (!request) + return; + const response = request.createResponse(event.response); + this._page._frameManager.requestReceivedResponse(response); + } + + _onLoadingFinished(event: Protocol.Network.loadingFinishedPayload) { + const request = this._requestIdToRequest.get(event.requestId); + // For certain requestIds we never receive requestWillBeSent event. + // @see https://crbug.com/750469 + if (!request) + return; + + // Under certain conditions we never get the Network.responseReceived + // event from protocol. @see https://crbug.com/883475 + const response = request.request.response(); + if (response) + response._requestFinished(); + this._requestIdToRequest.delete(request._requestId); + this._page._frameManager.requestFinished(request.request); + } + + _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) { + const request = this._requestIdToRequest.get(event.requestId); + // For certain requestIds we never receive requestWillBeSent event. + // @see https://crbug.com/750469 + if (!request) + return; + const response = request.request.response(); + if (response) + response._requestFinished(); + this._requestIdToRequest.delete(request._requestId); + request.request._setFailureText(event.errorText); + this._page._frameManager.requestFailed(request.request, event.errorText.includes('cancelled')); + } } function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject { diff --git a/src/webkit/wkProvisionalPage.ts b/src/webkit/wkProvisionalPage.ts index 5c81b580e4..899e1061c6 100644 --- a/src/webkit/wkProvisionalPage.ts +++ b/src/webkit/wkProvisionalPage.ts @@ -37,17 +37,16 @@ export class WKProvisionalPage { handler(payload); }; }; - const networkManager = this._wkPage._networkManager; + const wkPage = this._wkPage; this._sessionListeners = [ - helper.addEventListener(session, 'Network.requestWillBeSent', overrideFrameId(e => networkManager._onRequestWillBeSent(session, e))), - helper.addEventListener(session, 'Network.requestIntercepted', overrideFrameId(e => networkManager._onRequestIntercepted(e))), - helper.addEventListener(session, 'Network.responseReceived', overrideFrameId(e => networkManager._onResponseReceived(e))), - helper.addEventListener(session, 'Network.loadingFinished', overrideFrameId(e => networkManager._onLoadingFinished(e))), - helper.addEventListener(session, 'Network.loadingFailed', overrideFrameId(e => networkManager._onLoadingFailed(e))), + helper.addEventListener(session, 'Network.requestWillBeSent', overrideFrameId(e => wkPage._onRequestWillBeSent(session, e))), + helper.addEventListener(session, 'Network.requestIntercepted', overrideFrameId(e => wkPage._onRequestIntercepted(e))), + helper.addEventListener(session, 'Network.responseReceived', overrideFrameId(e => wkPage._onResponseReceived(e))), + helper.addEventListener(session, 'Network.loadingFinished', overrideFrameId(e => wkPage._onLoadingFinished(e))), + helper.addEventListener(session, 'Network.loadingFailed', overrideFrameId(e => wkPage._onLoadingFailed(e))), ]; - this._wkPage._initializeSession(session, ({frameTree}) => this._handleFrameTree(frameTree)); }