chore: introduce session.sendMayFail to ease error logging (#2480)

This commit is contained in:
Dmitry Gozman 2020-06-05 07:50:26 -07:00 committed by GitHub
parent fc2432a23a
commit c08da50bb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 98 additions and 111 deletions

View file

@ -29,7 +29,6 @@ import { readProtocolStream } from './crProtocolHelper';
import { Events } from './events'; import { Events } from './events';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { CRExecutionContext } from './crExecutionContext'; import { CRExecutionContext } from './crExecutionContext';
import { logError } from '../logger';
import { CRDevTools } from '../debug/crDevTools'; import { CRDevTools } from '../debug/crDevTools';
export class CRBrowser extends BrowserBase { export class CRBrowser extends BrowserBase {
@ -133,8 +132,8 @@ export class CRBrowser extends BrowserBase {
if (targetInfo.type === 'other' || !context) { if (targetInfo.type === 'other' || !context) {
if (waitingForDebugger) { if (waitingForDebugger) {
// Ideally, detaching should resume any target, but there is a bug in the backend. // Ideally, detaching should resume any target, but there is a bug in the backend.
session.send('Runtime.runIfWaitingForDebugger').catch(logError(this)).then(() => { session._sendMayFail('Runtime.runIfWaitingForDebugger').then(() => {
this._session.send('Target.detachFromTarget', { sessionId }).catch(logError(this)); this._session._sendMayFail('Target.detachFromTarget', { sessionId });
}); });
} }
return; return;

View file

@ -19,7 +19,7 @@ import { assert } from '../helper';
import { ConnectionTransport, ProtocolRequest, ProtocolResponse, protocolLog } from '../transport'; import { ConnectionTransport, ProtocolRequest, ProtocolResponse, protocolLog } from '../transport';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { InnerLogger } from '../logger'; import { InnerLogger, errorLog } from '../logger';
import { rewriteErrorMessage } from '../debug/stackTrace'; import { rewriteErrorMessage } from '../debug/stackTrace';
export const ConnectionEvents = { export const ConnectionEvents = {
@ -36,7 +36,7 @@ export class CRConnection extends EventEmitter {
private readonly _sessions = new Map<string, CRSession>(); private readonly _sessions = new Map<string, CRSession>();
readonly rootSession: CRSession; readonly rootSession: CRSession;
_closed = false; _closed = false;
private _logger: InnerLogger; readonly _logger: InnerLogger;
constructor(transport: ConnectionTransport, logger: InnerLogger) { constructor(transport: ConnectionTransport, logger: InnerLogger) {
super(); super();
@ -165,6 +165,13 @@ export class CRSession extends EventEmitter {
}); });
} }
_sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> {
return this.send(method, params).catch(error => {
if (this._connection)
this._connection._logger._log(errorLog, error, []);
});
}
_onMessage(object: ProtocolResponse) { _onMessage(object: ProtocolResponse) {
if (object.id && this._callbacks.has(object.id)) { if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id)!; const callback = this._callbacks.get(object.id)!;

View file

@ -20,7 +20,6 @@ import { assert, helper, RegisteredListener } from '../helper';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import * as types from '../types'; import * as types from '../types';
import * as debugSupport from '../debug/debugSupport'; import * as debugSupport from '../debug/debugSupport';
import { logError, InnerLogger } from '../logger';
type JSRange = { type JSRange = {
startOffset: number, startOffset: number,
@ -50,9 +49,9 @@ export class CRCoverage {
private _jsCoverage: JSCoverage; private _jsCoverage: JSCoverage;
private _cssCoverage: CSSCoverage; private _cssCoverage: CSSCoverage;
constructor(client: CRSession, logger: InnerLogger) { constructor(client: CRSession) {
this._jsCoverage = new JSCoverage(client, logger); this._jsCoverage = new JSCoverage(client);
this._cssCoverage = new CSSCoverage(client, logger); this._cssCoverage = new CSSCoverage(client);
} }
async startJSCoverage(options?: types.JSCoverageOptions) { async startJSCoverage(options?: types.JSCoverageOptions) {
@ -80,11 +79,9 @@ class JSCoverage {
_eventListeners: RegisteredListener[]; _eventListeners: RegisteredListener[];
_resetOnNavigation: boolean; _resetOnNavigation: boolean;
_reportAnonymousScripts = false; _reportAnonymousScripts = false;
private _logger: InnerLogger;
constructor(client: CRSession, logger: InnerLogger) { constructor(client: CRSession) {
this._client = client; this._client = client;
this._logger = logger;
this._enabled = false; this._enabled = false;
this._scriptIds = new Set(); this._scriptIds = new Set();
this._scriptSources = new Map(); this._scriptSources = new Map();
@ -131,13 +128,10 @@ class JSCoverage {
// Ignore other anonymous scripts unless the reportAnonymousScripts option is true. // Ignore other anonymous scripts unless the reportAnonymousScripts option is true.
if (!event.url && !this._reportAnonymousScripts) if (!event.url && !this._reportAnonymousScripts)
return; return;
try { // This might fail if the page has already navigated away.
const response = await this._client.send('Debugger.getScriptSource', {scriptId: event.scriptId}); const response = await this._client._sendMayFail('Debugger.getScriptSource', {scriptId: event.scriptId});
if (response)
this._scriptSources.set(event.scriptId, response.scriptSource); this._scriptSources.set(event.scriptId, response.scriptSource);
} catch (e) {
// This might happen if the page has already navigated away.
logError(this._logger)(e);
}
} }
async stop(): Promise<JSCoverageEntry[]> { async stop(): Promise<JSCoverageEntry[]> {
@ -174,11 +168,9 @@ class CSSCoverage {
_stylesheetSources: Map<string, string>; _stylesheetSources: Map<string, string>;
_eventListeners: RegisteredListener[]; _eventListeners: RegisteredListener[];
_resetOnNavigation: boolean; _resetOnNavigation: boolean;
private _logger: InnerLogger;
constructor(client: CRSession, logger: InnerLogger) { constructor(client: CRSession) {
this._client = client; this._client = client;
this._logger = logger;
this._enabled = false; this._enabled = false;
this._stylesheetURLs = new Map(); this._stylesheetURLs = new Map();
this._stylesheetSources = new Map(); this._stylesheetSources = new Map();
@ -216,13 +208,11 @@ class CSSCoverage {
// Ignore anonymous scripts // Ignore anonymous scripts
if (!header.sourceURL) if (!header.sourceURL)
return; return;
try { // This might fail if the page has already navigated away.
const response = await this._client.send('CSS.getStyleSheetText', {styleSheetId: header.styleSheetId}); const response = await this._client._sendMayFail('CSS.getStyleSheetText', {styleSheetId: header.styleSheetId});
if (response) {
this._stylesheetURLs.set(header.styleSheetId, header.sourceURL); this._stylesheetURLs.set(header.styleSheetId, header.sourceURL);
this._stylesheetSources.set(header.styleSheetId, response.text); this._stylesheetSources.set(header.styleSheetId, response.text);
} catch (e) {
// This might happen if the page has already navigated away.
logError(this._logger)(e);
} }
} }

View file

@ -23,7 +23,6 @@ import * as network from '../network';
import * as frames from '../frames'; import * as frames from '../frames';
import { Credentials } from '../types'; import { Credentials } from '../types';
import { CRPage } from './crPage'; import { CRPage } from './crPage';
import { logError } from '../logger';
export class CRNetworkManager { export class CRNetworkManager {
private _client: CRSession; private _client: CRSession;
@ -130,26 +129,26 @@ export class CRNetworkManager {
this._attemptedAuthentications.add(event.requestId); this._attemptedAuthentications.add(event.requestId);
} }
const {username, password} = this._credentials || {username: undefined, password: undefined}; const {username, password} = this._credentials || {username: undefined, password: undefined};
this._client.send('Fetch.continueWithAuth', { this._client._sendMayFail('Fetch.continueWithAuth', {
requestId: event.requestId, requestId: event.requestId,
authChallengeResponse: { response, username, password }, authChallengeResponse: { response, username, password },
}).catch(logError(this._page)); });
} }
_onRequestPaused(workerFrame: frames.Frame | undefined, event: Protocol.Fetch.requestPausedPayload) { _onRequestPaused(workerFrame: frames.Frame | undefined, event: Protocol.Fetch.requestPausedPayload) {
if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) { if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) {
this._client.send('Fetch.continueRequest', { this._client._sendMayFail('Fetch.continueRequest', {
requestId: event.requestId requestId: event.requestId
}).catch(logError(this._page)); });
} }
if (!event.networkId) { if (!event.networkId) {
// Fetch without networkId means that request was not recongnized by inspector, and // Fetch without networkId means that request was not recongnized by inspector, and
// it will never receive Network.requestWillBeSent. Most likely, this is an internal request // it will never receive Network.requestWillBeSent. Most likely, this is an internal request
// that we can safely fail. // that we can safely fail.
this._client.send('Fetch.failRequest', { this._client._sendMayFail('Fetch.failRequest', {
requestId: event.requestId, requestId: event.requestId,
errorReason: 'Aborted', errorReason: 'Aborted',
}).catch(logError(this._page)); });
return; return;
} }
if (event.request.url.startsWith('data:')) if (event.request.url.startsWith('data:'))
@ -189,7 +188,7 @@ export class CRNetworkManager {
if (!frame) { if (!frame) {
if (requestPausedEvent) if (requestPausedEvent)
this._client.send('Fetch.continueRequest', { requestId: requestPausedEvent.requestId }).catch(logError(this._page)); this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId });
return; return;
} }
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document'; const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
@ -325,15 +324,13 @@ class InterceptableRequest implements network.RouteDelegate {
} }
async continue(overrides: { method?: string; headers?: network.Headers; postData?: string } = {}) { async continue(overrides: { method?: string; headers?: network.Headers; postData?: string } = {}) {
await this._client.send('Fetch.continueRequest', { // In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._client._sendMayFail('Fetch.continueRequest', {
requestId: this._interceptionId!, requestId: this._interceptionId!,
headers: overrides.headers ? headersArray(overrides.headers) : undefined, headers: overrides.headers ? headersArray(overrides.headers) : undefined,
method: overrides.method, method: overrides.method,
postData: overrides.postData postData: overrides.postData
}).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.
logError(this.request._page)(error);
}); });
} }
@ -350,29 +347,25 @@ class InterceptableRequest implements network.RouteDelegate {
if (responseBody && !('content-length' in responseHeaders)) if (responseBody && !('content-length' in responseHeaders))
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
await this._client.send('Fetch.fulfillRequest', { // In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._client._sendMayFail('Fetch.fulfillRequest', {
requestId: this._interceptionId!, requestId: this._interceptionId!,
responseCode: response.status || 200, responseCode: response.status || 200,
responsePhrase: network.STATUS_TEXTS[String(response.status || 200)], responsePhrase: network.STATUS_TEXTS[String(response.status || 200)],
responseHeaders: headersArray(responseHeaders), responseHeaders: headersArray(responseHeaders),
body: responseBody ? responseBody.toString('base64') : undefined, body: responseBody ? responseBody.toString('base64') : undefined,
}).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.
logError(this.request._page)(error);
}); });
} }
async abort(errorCode: string = 'failed') { async abort(errorCode: string = 'failed') {
const errorReason = errorReasons[errorCode]; const errorReason = errorReasons[errorCode];
assert(errorReason, 'Unknown error code: ' + errorCode); assert(errorReason, 'Unknown error code: ' + errorCode);
await this._client.send('Fetch.failRequest', {
requestId: this._interceptionId!,
errorReason
}).catch(error => {
// In certain cases, protocol will return error if the request was already canceled // In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors. // or the page was closed. We should tolerate these errors.
logError(this.request._page)(error); await this._client._sendMayFail('Fetch.failRequest', {
requestId: this._interceptionId!,
errorReason
}); });
} }
} }

View file

@ -36,7 +36,6 @@ import { CRBrowserContext } from './crBrowser';
import * as types from '../types'; import * as types from '../types';
import { ConsoleMessage } from '../console'; import { ConsoleMessage } from '../console';
import { NotConnectedError } from '../errors'; import { NotConnectedError } from '../errors';
import { logError } from '../logger';
import * as debugSupport from '../debug/debugSupport'; import * as debugSupport from '../debug/debugSupport';
import { rewriteErrorMessage } from '../debug/stackTrace'; import { rewriteErrorMessage } from '../debug/stackTrace';
@ -70,7 +69,7 @@ export class CRPage implements PageDelegate {
this.rawKeyboard = new RawKeyboardImpl(client); this.rawKeyboard = new RawKeyboardImpl(client);
this.rawMouse = new RawMouseImpl(client); this.rawMouse = new RawMouseImpl(client);
this._pdf = new CRPDF(client); this._pdf = new CRPDF(client);
this._coverage = new CRCoverage(client, browserContext); this._coverage = new CRCoverage(client);
this._browserContext = browserContext; this._browserContext = browserContext;
this._page = new Page(this, browserContext); this._page = new Page(this, browserContext);
this._mainFrameSession = new FrameSession(this, client, targetId, null); this._mainFrameSession = new FrameSession(this, client, targetId, null);
@ -121,7 +120,7 @@ export class CRPage implements PageDelegate {
async exposeBinding(binding: PageBinding) { async exposeBinding(binding: PageBinding) {
await this._forAllFrameSessions(frame => frame._initBinding(binding)); await this._forAllFrameSessions(frame => frame._initBinding(binding));
await Promise.all(this._page.frames().map(frame => frame.evaluate(binding.source).catch(logError(this._page)))); await Promise.all(this._page.frames().map(frame => frame.evaluate(binding.source).catch(e => {})));
} }
async updateExtraHTTPHeaders(): Promise<void> { async updateExtraHTTPHeaders(): Promise<void> {
@ -384,13 +383,13 @@ class FrameSession {
const localFrames = this._isMainFrame() ? this._page.frames() : [ this._page._frameManager.frame(this._targetId)! ]; const localFrames = this._isMainFrame() ? this._page.frames() : [ this._page._frameManager.frame(this._targetId)! ];
for (const frame of localFrames) { for (const frame of localFrames) {
// Note: frames might be removed before we send these. // Note: frames might be removed before we send these.
this._client.send('Page.createIsolatedWorld', { this._client._sendMayFail('Page.createIsolatedWorld', {
frameId: frame._id, frameId: frame._id,
grantUniveralAccess: true, grantUniveralAccess: true,
worldName: UTILITY_WORLD_NAME, worldName: UTILITY_WORLD_NAME,
}).catch(logError(this._page)); });
for (const binding of this._crPage._browserContext._pageBindings.values()) for (const binding of this._crPage._browserContext._pageBindings.values())
frame.evaluate(binding.source).catch(logError(this._page)); frame.evaluate(binding.source).catch(e => {});
} }
const isInitialEmptyPage = this._isMainFrame() && this._page.mainFrame().url() === ':'; const isInitialEmptyPage = this._isMainFrame() && this._page.mainFrame().url() === ':';
if (isInitialEmptyPage) { if (isInitialEmptyPage) {
@ -560,8 +559,8 @@ class FrameSession {
if (event.targetInfo.type !== 'worker') { if (event.targetInfo.type !== 'worker') {
// Ideally, detaching should resume any target, but there is a bug in the backend. // Ideally, detaching should resume any target, but there is a bug in the backend.
session.send('Runtime.runIfWaitingForDebugger').catch(logError(this._page)).then(() => { session._sendMayFail('Runtime.runIfWaitingForDebugger').then(() => {
this._client.send('Target.detachFromTarget', { sessionId: event.sessionId }).catch(logError(this._page)); this._client._sendMayFail('Target.detachFromTarget', { sessionId: event.sessionId });
}); });
return; return;
} }
@ -573,10 +572,10 @@ class FrameSession {
worker._createExecutionContext(new CRExecutionContext(session, event.context)); worker._createExecutionContext(new CRExecutionContext(session, event.context));
}); });
Promise.all([ Promise.all([
session.send('Runtime.enable'), session._sendMayFail('Runtime.enable'),
session.send('Network.enable'), session._sendMayFail('Network.enable'),
session.send('Runtime.runIfWaitingForDebugger'), session._sendMayFail('Runtime.runIfWaitingForDebugger'),
]).catch(logError(this._page)); // This might fail if the target is closed before we initialize. ]); // This might fail if the target is closed before we initialize.
session.on('Runtime.consoleAPICalled', event => { session.on('Runtime.consoleAPICalled', event => {
const args = event.args.map(o => worker._existingExecutionContext!.createHandle(o)); const args = event.args.map(o => worker._existingExecutionContext!.createHandle(o));
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace)); this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
@ -816,9 +815,9 @@ class FrameSession {
} }
async _getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> { async _getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
const result = await this._client.send('DOM.getBoxModel', { const result = await this._client._sendMayFail('DOM.getBoxModel', {
objectId: handle._objectId objectId: handle._objectId
}).catch(logError(this._page)); });
if (!result) if (!result)
return null; return null;
const quad = result.model.border; const quad = result.model.border;
@ -843,9 +842,9 @@ class FrameSession {
} }
async _getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> { async _getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._client.send('DOM.getContentQuads', { const result = await this._client._sendMayFail('DOM.getContentQuads', {
objectId: handle._objectId objectId: handle._objectId
}).catch(logError(this._page)); });
if (!result) if (!result)
return null; return null;
return result.quads.map(quad => [ return result.quads.map(quad => [
@ -864,10 +863,10 @@ class FrameSession {
} }
async _adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.FrameExecutionContext): Promise<dom.ElementHandle> { async _adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.FrameExecutionContext): Promise<dom.ElementHandle> {
const result = await this._client.send('DOM.resolveNode', { const result = await this._client._sendMayFail('DOM.resolveNode', {
backendNodeId, backendNodeId,
executionContextId: (to._delegate as CRExecutionContext)._contextId, executionContextId: (to._delegate as CRExecutionContext)._contextId,
}).catch(logError(this._page)); });
if (!result || result.object.subtype === 'null') if (!result || result.object.subtype === 'null')
throw new Error('Unable to adopt element handle from a different document'); throw new Error('Unable to adopt element handle from a different document');
return to.createHandle(result.object).asElement()!; return to.createHandle(result.object).asElement()!;

View file

@ -19,7 +19,7 @@ import { EventEmitter } from 'events';
import { assert } from '../helper'; import { assert } from '../helper';
import { ConnectionTransport, ProtocolRequest, ProtocolResponse, protocolLog } from '../transport'; import { ConnectionTransport, ProtocolRequest, ProtocolResponse, protocolLog } from '../transport';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { InnerLogger } from '../logger'; import { InnerLogger, errorLog } from '../logger';
import { rewriteErrorMessage } from '../debug/stackTrace'; import { rewriteErrorMessage } from '../debug/stackTrace';
export const ConnectionEvents = { export const ConnectionEvents = {
@ -34,7 +34,7 @@ export class FFConnection extends EventEmitter {
private _lastId: number; private _lastId: number;
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>; private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
private _transport: ConnectionTransport; private _transport: ConnectionTransport;
private _logger: InnerLogger; readonly _logger: InnerLogger;
readonly _sessions: Map<string, FFSession>; readonly _sessions: Map<string, FFSession>;
_closed: boolean; _closed: boolean;
@ -185,6 +185,12 @@ export class FFSession extends EventEmitter {
}); });
} }
sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> {
return this.send(method, params).catch(error => {
this._connection._logger._log(errorLog, error, []);
});
}
dispatchMessage(object: ProtocolResponse) { dispatchMessage(object: ProtocolResponse) {
if (object.id && this._callbacks.has(object.id)) { if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id)!; const callback = this._callbacks.get(object.id)!;

View file

@ -21,7 +21,6 @@ import { Page } from '../page';
import * as network from '../network'; import * as network from '../network';
import * as frames from '../frames'; import * as frames from '../frames';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { logError } from '../logger';
export class FFNetworkManager { export class FFNetworkManager {
private _session: FFSession; private _session: FFSession;
@ -164,12 +163,12 @@ class InterceptableRequest implements network.RouteDelegate {
headers, headers,
postData postData
} = overrides; } = overrides;
await this._session.send('Network.resumeInterceptedRequest', { await this._session.sendMayFail('Network.resumeInterceptedRequest', {
requestId: this._id, requestId: this._id,
method, method,
headers: headers ? headersArray(headers) : undefined, headers: headers ? headersArray(headers) : undefined,
postData: postData ? Buffer.from(postData).toString('base64') : undefined postData: postData ? Buffer.from(postData).toString('base64') : undefined
}).catch(logError(this.request._page)); });
} }
async fulfill(response: network.FulfillResponse) { async fulfill(response: network.FulfillResponse) {
@ -185,20 +184,20 @@ class InterceptableRequest implements network.RouteDelegate {
if (responseBody && !('content-length' in responseHeaders)) if (responseBody && !('content-length' in responseHeaders))
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
await this._session.send('Network.fulfillInterceptedRequest', { await this._session.sendMayFail('Network.fulfillInterceptedRequest', {
requestId: this._id, requestId: this._id,
status: response.status || 200, status: response.status || 200,
statusText: network.STATUS_TEXTS[String(response.status || 200)] || '', statusText: network.STATUS_TEXTS[String(response.status || 200)] || '',
headers: headersArray(responseHeaders), headers: headersArray(responseHeaders),
base64body: responseBody ? responseBody.toString('base64') : undefined, base64body: responseBody ? responseBody.toString('base64') : undefined,
}).catch(logError(this.request._page)); });
} }
async abort(errorCode: string) { async abort(errorCode: string) {
await this._session.send('Network.abortInterceptedRequest', { await this._session.sendMayFail('Network.abortInterceptedRequest', {
requestId: this._id, requestId: this._id,
errorCode, errorCode,
}).catch(logError(this.request._page)); });
} }
} }

View file

@ -32,7 +32,6 @@ import { FFNetworkManager, headersArray } from './ffNetworkManager';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { selectors } from '../selectors'; import { selectors } from '../selectors';
import { NotConnectedError } from '../errors'; import { NotConnectedError } from '../errors';
import { logError } from '../logger';
import { rewriteErrorMessage } from '../debug/stackTrace'; import { rewriteErrorMessage } from '../debug/stackTrace';
const UTILITY_WORLD_NAME = '__playwright_utility_world__'; const UTILITY_WORLD_NAME = '__playwright_utility_world__';
@ -193,7 +192,7 @@ export class FFPage implements PageDelegate {
params.type, params.type,
params.message, params.message,
async (accept: boolean, promptText?: string) => { async (accept: boolean, promptText?: string) => {
await this._session.send('Page.handleDialog', { dialogId: params.dialogId, accept, promptText }).catch(logError(this._page)); await this._session.sendMayFail('Page.handleDialog', { dialogId: params.dialogId, accept, promptText });
}, },
params.defaultValue)); params.defaultValue));
} }
@ -429,10 +428,10 @@ export class FFPage implements PageDelegate {
} }
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> { async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._session.send('Page.getContentQuads', { const result = await this._session.sendMayFail('Page.getContentQuads', {
frameId: handle._context.frame._id, frameId: handle._context.frame._id,
objectId: handle._objectId, objectId: handle._objectId,
}).catch(logError(this._page)); });
if (!result) if (!result)
return null; return null;
return result.quads.map(quad => [ quad.p1, quad.p2, quad.p3, quad.p4 ]); return result.quads.map(quad => [ quad.p1, quad.p2, quad.p3, quad.p4 ]);

View file

@ -19,7 +19,6 @@ import * as mime from 'mime';
import * as util from 'util'; import * as util from 'util';
import * as frames from './frames'; import * as frames from './frames';
import { assert, helper } from './helper'; import { assert, helper } from './helper';
import { Page } from './page';
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
export type NetworkCookie = { export type NetworkCookie = {
@ -112,14 +111,12 @@ export class Request {
private _frame: frames.Frame; private _frame: frames.Frame;
private _waitForResponsePromise: Promise<Response | null>; private _waitForResponsePromise: Promise<Response | null>;
private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {}; private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {};
readonly _page: Page;
constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined, constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined,
url: string, resourceType: string, method: string, postData: string | null, headers: Headers) { url: string, resourceType: string, method: string, postData: string | null, headers: Headers) {
assert(!url.startsWith('data:'), 'Data urls should not fire requests'); assert(!url.startsWith('data:'), 'Data urls should not fire requests');
this._routeDelegate = routeDelegate; this._routeDelegate = routeDelegate;
this._frame = frame; this._frame = frame;
this._page = frame._page;
this._redirectedFrom = redirectedFrom; this._redirectedFrom = redirectedFrom;
if (redirectedFrom) if (redirectedFrom)
redirectedFrom._redirectedTo = this; redirectedFrom._redirectedTo = this;

View file

@ -19,7 +19,7 @@ import { EventEmitter } from 'events';
import { assert } from '../helper'; import { assert } from '../helper';
import { ConnectionTransport, ProtocolRequest, ProtocolResponse, protocolLog } from '../transport'; import { ConnectionTransport, ProtocolRequest, ProtocolResponse, protocolLog } from '../transport';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { InnerLogger } from '../logger'; import { InnerLogger, errorLog } from '../logger';
import { rewriteErrorMessage } from '../debug/stackTrace'; import { rewriteErrorMessage } from '../debug/stackTrace';
// WKPlaywright uses this special id to issue Browser.close command which we // WKPlaywright uses this special id to issue Browser.close command which we
@ -36,9 +36,8 @@ export class WKConnection {
private readonly _onDisconnect: () => void; private readonly _onDisconnect: () => void;
private _lastId = 0; private _lastId = 0;
private _closed = false; private _closed = false;
readonly browserSession: WKSession; readonly browserSession: WKSession;
private _logger: InnerLogger; readonly _logger: InnerLogger;
constructor(transport: ConnectionTransport, logger: InnerLogger, onDisconnect: () => void) { constructor(transport: ConnectionTransport, logger: InnerLogger, onDisconnect: () => void) {
this._transport = transport; this._transport = transport;
@ -138,6 +137,12 @@ export class WKSession extends EventEmitter {
}); });
} }
sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> {
return this.send(method, params).catch(error => {
this.connection._logger._log(errorLog, error, []);
});
}
markAsCrashed() { markAsCrashed() {
this._crashed = true; this._crashed = true;
} }

View file

@ -20,7 +20,6 @@ import { assert, helper } from '../helper';
import * as network from '../network'; import * as network from '../network';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { WKSession } from './wkConnection'; import { WKSession } from './wkConnection';
import { logError } from '../logger';
const errorReasons: { [reason: string]: string } = { const errorReasons: { [reason: string]: string } = {
'aborted': 'Cancellation', 'aborted': 'Cancellation',
@ -59,11 +58,9 @@ export class WKInterceptableRequest implements network.RouteDelegate {
const reason = errorReasons[errorCode]; const reason = errorReasons[errorCode];
assert(reason, 'Unknown error code: ' + errorCode); assert(reason, 'Unknown error code: ' + errorCode);
await this._interceptedPromise; 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 // In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors. // or the page was closed. We should tolerate these errors.
logError(this.request._page); await this._session.sendMayFail('Network.interceptAsError', { requestId: this._requestId, reason });
});
} }
async fulfill(response: network.FulfillResponse) { async fulfill(response: network.FulfillResponse) {
@ -89,7 +86,9 @@ export class WKInterceptableRequest implements network.RouteDelegate {
if (responseBody && !('content-length' in responseHeaders)) if (responseBody && !('content-length' in responseHeaders))
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
await this._session.send('Network.interceptWithResponse', { // In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._session.sendMayFail('Network.interceptWithResponse', {
requestId: this._requestId, requestId: this._requestId,
status: response.status || 200, status: response.status || 200,
statusText: network.STATUS_TEXTS[String(response.status || 200)], statusText: network.STATUS_TEXTS[String(response.status || 200)],
@ -97,24 +96,18 @@ export class WKInterceptableRequest implements network.RouteDelegate {
headers: responseHeaders, headers: responseHeaders,
base64Encoded, base64Encoded,
content: responseBody 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.
logError(this.request._page);
}); });
} }
async continue(overrides: { method?: string; headers?: network.Headers; postData?: string }) { async continue(overrides: { method?: string; headers?: network.Headers; postData?: string }) {
await this._interceptedPromise; await this._interceptedPromise;
await this._session.send('Network.interceptContinue', { // In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._session.sendMayFail('Network.interceptContinue', {
requestId: this._requestId, requestId: this._requestId,
method: overrides.method, method: overrides.method,
headers: overrides.headers, headers: overrides.headers,
postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined
}).catch((error: Error) => {
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
logError(this.request._page);
}); });
} }

View file

@ -287,7 +287,7 @@ export class WKPage implements PageDelegate {
pageOrError = e; pageOrError = e;
} }
if (targetInfo.isPaused) if (targetInfo.isPaused)
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(logError(this._page)); this._pageProxySession.sendMayFail('Target.resume', { targetId: targetInfo.targetId });
if ((pageOrError instanceof Page) && this._page.mainFrame().url() === '') { if ((pageOrError instanceof Page) && this._page.mainFrame().url() === '') {
try { try {
// Initial empty page has an empty url. We should wait until the first real url has been loaded, // Initial empty page has an empty url. We should wait until the first real url has been loaded,
@ -309,7 +309,7 @@ export class WKPage implements PageDelegate {
this._provisionalPage = new WKProvisionalPage(session, this); this._provisionalPage = new WKProvisionalPage(session, this);
if (targetInfo.isPaused) { if (targetInfo.isPaused) {
this._provisionalPage.initializationPromise.then(() => { this._provisionalPage.initializationPromise.then(() => {
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(logError(this._page)); this._pageProxySession.sendMayFail('Target.resume', { targetId: targetInfo.targetId });
}); });
} }
} }
@ -652,7 +652,7 @@ export class WKPage implements PageDelegate {
private async _evaluateBindingScript(binding: PageBinding): Promise<void> { private async _evaluateBindingScript(binding: PageBinding): Promise<void> {
const script = this._bindingToScript(binding); const script = this._bindingToScript(binding);
await Promise.all(this._page.frames().map(frame => frame.evaluate(script).catch(logError(this._page)))); await Promise.all(this._page.frames().map(frame => frame.evaluate(script).catch(e => {})));
} }
async evaluateOnNewDocument(script: string): Promise<void> { async evaluateOnNewDocument(script: string): Promise<void> {
@ -680,10 +680,10 @@ export class WKPage implements PageDelegate {
} }
async closePage(runBeforeUnload: boolean): Promise<void> { async closePage(runBeforeUnload: boolean): Promise<void> {
this._pageProxySession.send('Target.close', { this._pageProxySession.sendMayFail('Target.close', {
targetId: this._session.sessionId, targetId: this._session.sessionId,
runBeforeUnload runBeforeUnload
}).catch(logError(this._page)); });
} }
canScreenshotOutsideViewport(): boolean { canScreenshotOutsideViewport(): boolean {
@ -767,9 +767,9 @@ export class WKPage implements PageDelegate {
} }
async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> { async getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
const result = await this._session.send('DOM.getContentQuads', { const result = await this._session.sendMayFail('DOM.getContentQuads', {
objectId: handle._objectId objectId: handle._objectId
}).catch(logError(this._page)); });
if (!result) if (!result)
return null; return null;
return result.quads.map(quad => [ return result.quads.map(quad => [
@ -790,10 +790,10 @@ export class WKPage implements PageDelegate {
} }
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> { async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> {
const result = await this._session.send('DOM.resolveNode', { const result = await this._session.sendMayFail('DOM.resolveNode', {
objectId: handle._objectId, objectId: handle._objectId,
executionContextId: (to._delegate as WKExecutionContext)._contextId executionContextId: (to._delegate as WKExecutionContext)._contextId
}).catch(logError(this._page)); });
if (!result || result.object.subtype === 'null') if (!result || result.object.subtype === 'null')
throw new Error('Unable to adopt element handle from a different document'); throw new Error('Unable to adopt element handle from a different document');
return to.createHandle(result.object) as dom.ElementHandle<T>; return to.createHandle(result.object) as dom.ElementHandle<T>;