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;
}
send<T extends keyof Protocol.CommandParameters>(
async send<T extends keyof Protocol.CommandParameters>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
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 messageObj = { id, method, params };
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._rawSend(messageObj);
return result;
}
isDisposed(): boolean {

View File

@ -120,21 +120,17 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
throw error;
}
let callFunctionOnPromise;
try {
callFunctionOnPromise = this._session.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n',
objectId: thisObjectId,
arguments: serializableArgs.map((arg: any) => this._convertArgument(arg)),
returnByValue: false,
emulateUserGesture: true
});
} catch (err) {
return this._session.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n',
objectId: thisObjectId,
arguments: serializableArgs.map((arg: any) => this._convertArgument(arg)),
returnByValue: false,
emulateUserGesture: true
}).catch(err => {
if (err instanceof TypeError && err.message.startsWith('Converting circular structure to JSON'))
err.message += ' Are you passing a nested JSHandle?';
throw err;
}
return callFunctionOnPromise.then(response => {
}).then(response => {
if (response.result.type === 'object' && response.result.className === 'Promise') {
return Promise.race([
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
// may be different from the current session and may be destroyed without becoming current.
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.
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.
session.send('Runtime.enable'),
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 }));
if (this._page._state.hasTouch)
promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: true }));
await Promise.all(promises).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;
});
await Promise.all(promises);
}
onProvisionalLoadStarted(provisionalSession: WKSession) {
initializeProvisionalPage(provisionalSession: WKSession) : Promise<void> {
assert(!this._provisionalPage);
this._provisionalPage = new WKProvisionalPage(provisionalSession, this);
return this._provisionalPage.initializationPromise;
}
onProvisionalLoadCommitted(session: WKSession) {

View File

@ -139,10 +139,13 @@ export class WKPageProxy {
}
if (targetInfo.isProvisional) {
(session as any)[isPovisionalSymbol] = true;
if (this._wkPage)
this._wkPage.onProvisionalLoadStarted(session);
if (targetInfo.isPaused)
if (this._wkPage) {
const provisionalPageInitialized = this._wkPage.initializeProvisionalPage(session);
if (targetInfo.isPaused)
provisionalPageInitialized.then(() => this._resumeTarget(targetInfo.targetId));
} else if (targetInfo.isPaused) {
this._resumeTarget(targetInfo.targetId);
}
} else if (this._pagePromise) {
assert(!this._pagePausedOnStart);
// 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 _sessionListeners: RegisteredListener[] = [];
private _mainFrameId: string | null = null;
readonly initializationPromise: Promise<void>;
constructor(session: WKSession, page: WKPage) {
this._session = session;
@ -47,7 +48,7 @@ export class WKProvisionalPage {
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() {