fix: avoid unhandled promise rejection in WKSession.send (#770)

This commit is contained in:
Yury Semikhatsky 2020-01-30 17:30:47 -08:00 committed by GitHub
parent fd3a93084c
commit 985faebd12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 39 additions and 32 deletions

View File

@ -119,20 +119,19 @@ export class WKSession extends platform.EventEmitter {
this.once = super.once; this.once = super.once;
} }
send<T extends keyof Protocol.CommandParameters>( async send<T extends keyof Protocol.CommandParameters>(
method: T, method: T,
params?: Protocol.CommandParameters[T] params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> { ): Promise<Protocol.CommandReturnValues[T]> {
if (this._disposed) if (this._disposed)
return Promise.reject(new Error(`Protocol error (${method}): ${this.errorText}`)); throw new Error(`Protocol error (${method}): ${this.errorText}`);
const id = this.connection.nextMessageId(); const id = this.connection.nextMessageId();
const messageObj = { id, method, params }; const messageObj = { id, method, params };
platform.debug('pw:wrapped:' + this.sessionId)('SEND ► ' + JSON.stringify(messageObj, null, 2)); platform.debug('pw:wrapped:' + this.sessionId)('SEND ► ' + JSON.stringify(messageObj, null, 2));
const result = new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => { this._rawSend(messageObj);
return new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
this._callbacks.set(id, {resolve, reject, error: new Error(), method}); this._callbacks.set(id, {resolve, reject, error: new Error(), method});
}); });
this._rawSend(messageObj);
return result;
} }
isDisposed(): boolean { isDisposed(): boolean {

View File

@ -120,21 +120,17 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
throw error; throw error;
} }
let callFunctionOnPromise; return this._session.send('Runtime.callFunctionOn', {
try { functionDeclaration: functionText + '\n' + suffix + '\n',
callFunctionOnPromise = this._session.send('Runtime.callFunctionOn', { objectId: thisObjectId,
functionDeclaration: functionText + '\n' + suffix + '\n', arguments: serializableArgs.map((arg: any) => this._convertArgument(arg)),
objectId: thisObjectId, returnByValue: false,
arguments: serializableArgs.map((arg: any) => this._convertArgument(arg)), emulateUserGesture: true
returnByValue: false, }).catch(err => {
emulateUserGesture: true
});
} catch (err) {
if (err instanceof TypeError && err.message.startsWith('Converting circular structure to JSON')) if (err instanceof TypeError && err.message.startsWith('Converting circular structure to JSON'))
err.message += ' Are you passing a nested JSHandle?'; err.message += ' Are you passing a nested JSHandle?';
throw err; throw err;
} }).then(response => {
return callFunctionOnPromise.then(response => {
if (response.result.type === 'object' && response.result.className === 'Promise') { if (response.result.type === 'object' && response.result.className === 'Promise') {
return Promise.race([ return Promise.race([
this._executionContextDestroyedPromise.then(() => contextDestroyedResult), this._executionContextDestroyedPromise.then(() => contextDestroyedResult),

View File

@ -95,10 +95,24 @@ export class WKPage implements PageDelegate {
// This method is called for provisional targets as well. The session passed as the parameter // This method is called for provisional targets as well. The session passed as the parameter
// may be different from the current session and may be destroyed without becoming current. // may be different from the current session and may be destroyed without becoming current.
async _initializeSession(session: WKSession, resourceTreeHandler: (r: Protocol.Page.getResourceTreeReturnValue) => void) { async _initializeSession(session: WKSession, resourceTreeHandler: (r: Protocol.Page.getResourceTreeReturnValue) => void) {
const promises : Promise<any>[] = [ await this._initializeSessionMayThrow(session, resourceTreeHandler).catch(e => {
if (session.isDisposed())
return;
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
if (this._session === session)
throw e;
});
}
private async _initializeSessionMayThrow(session: WKSession, resourceTreeHandler: (r: Protocol.Page.getResourceTreeReturnValue) => void) {
const [, frameTree] = await Promise.all([
// Page agent must be enabled before Runtime. // Page agent must be enabled before Runtime.
session.send('Page.enable'), session.send('Page.enable'),
session.send('Page.getResourceTree').then(resourceTreeHandler), session.send('Page.getResourceTree'),
]);
resourceTreeHandler(frameTree);
const promises : Promise<any>[] = [
// Resource tree should be received before first execution context. // Resource tree should be received before first execution context.
session.send('Runtime.enable'), session.send('Runtime.enable'),
session.send('Page.createUserWorld', { name: UTILITY_WORLD_NAME }).catch(_ => {}), // Worlds are per-process session.send('Page.createUserWorld', { name: UTILITY_WORLD_NAME }).catch(_ => {}), // Worlds are per-process
@ -129,19 +143,13 @@ export class WKPage implements PageDelegate {
promises.push(session.send('Network.setExtraHTTPHeaders', { headers: this._page._state.extraHTTPHeaders })); promises.push(session.send('Network.setExtraHTTPHeaders', { headers: this._page._state.extraHTTPHeaders }));
if (this._page._state.hasTouch) if (this._page._state.hasTouch)
promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: true })); promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: true }));
await Promise.all(promises).catch(e => { await Promise.all(promises);
if (session.isDisposed())
return;
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
if (this._session === session)
throw e;
});
} }
onProvisionalLoadStarted(provisionalSession: WKSession) { initializeProvisionalPage(provisionalSession: WKSession) : Promise<void> {
assert(!this._provisionalPage); assert(!this._provisionalPage);
this._provisionalPage = new WKProvisionalPage(provisionalSession, this); this._provisionalPage = new WKProvisionalPage(provisionalSession, this);
return this._provisionalPage.initializationPromise;
} }
onProvisionalLoadCommitted(session: WKSession) { onProvisionalLoadCommitted(session: WKSession) {

View File

@ -139,10 +139,13 @@ export class WKPageProxy {
} }
if (targetInfo.isProvisional) { if (targetInfo.isProvisional) {
(session as any)[isPovisionalSymbol] = true; (session as any)[isPovisionalSymbol] = true;
if (this._wkPage) if (this._wkPage) {
this._wkPage.onProvisionalLoadStarted(session); const provisionalPageInitialized = this._wkPage.initializeProvisionalPage(session);
if (targetInfo.isPaused) if (targetInfo.isPaused)
provisionalPageInitialized.then(() => this._resumeTarget(targetInfo.targetId));
} else if (targetInfo.isPaused) {
this._resumeTarget(targetInfo.targetId); this._resumeTarget(targetInfo.targetId);
}
} else if (this._pagePromise) { } else if (this._pagePromise) {
assert(!this._pagePausedOnStart); assert(!this._pagePausedOnStart);
// This is the first time page target is created, will resume // This is the first time page target is created, will resume

View File

@ -24,6 +24,7 @@ export class WKProvisionalPage {
private readonly _wkPage: WKPage; private readonly _wkPage: WKPage;
private _sessionListeners: RegisteredListener[] = []; private _sessionListeners: RegisteredListener[] = [];
private _mainFrameId: string | null = null; private _mainFrameId: string | null = null;
readonly initializationPromise: Promise<void>;
constructor(session: WKSession, page: WKPage) { constructor(session: WKSession, page: WKPage) {
this._session = session; this._session = session;
@ -47,7 +48,7 @@ export class WKProvisionalPage {
helper.addEventListener(session, 'Network.loadingFailed', overrideFrameId(e => wkPage._onLoadingFailed(e))), helper.addEventListener(session, 'Network.loadingFailed', overrideFrameId(e => wkPage._onLoadingFailed(e))),
]; ];
this._wkPage._initializeSession(session, ({frameTree}) => this._handleFrameTree(frameTree)); this.initializationPromise = this._wkPage._initializeSession(session, ({frameTree}) => this._handleFrameTree(frameTree));
} }
dispose() { dispose() {