fix(webkit): do not swallow errors when returning by value (#2723)

We currently return undefined whenever we had an error trying
return the evaluation result by error. The most common error
is "execution context destroyed".

This produces very unexpected undefined from methods that do not
ever expect undefined. Instead, we should throw because we were
not able to return the result.
This commit is contained in:
Dmitry Gozman 2020-06-26 09:50:57 -07:00 committed by GitHub
parent 71618a9e2b
commit 064a0a1154
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 16 additions and 9 deletions

View file

@ -15,8 +15,7 @@
*/ */
export function parseEvaluationResultValue(value: any, handles: any[] = []): any { export function parseEvaluationResultValue(value: any, handles: any[] = []): any {
// { type: 'undefined' } does not even have value. if (value === undefined)
if (value === 'undefined')
return undefined; return undefined;
if (typeof value === 'object') { if (typeof value === 'object') {
if (value.v === 'undefined') if (value.v === 'undefined')

View file

@ -32,6 +32,9 @@ export default class UtilityScript {
} }
jsonValue(returnByValue: true, value: any) { jsonValue(returnByValue: true, value: any) {
// Special handling of undefined to work-around multi-step returnByValue handling in WebKit.
if (Object.is(value, undefined))
return undefined;
return serializeAsCallArgument(value, (value: any) => ({ fallThrough: value })); return serializeAsCallArgument(value, (value: any) => ({ fallThrough: value }));
} }

View file

@ -80,8 +80,13 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
throw new Error('Evaluation failed: ' + response.result.description); throw new Error('Evaluation failed: ' + response.result.description);
if (!returnByValue) if (!returnByValue)
return utilityScript._context.createHandle(response.result); return utilityScript._context.createHandle(response.result);
if (response.result.objectId) if (response.result.objectId) {
// Avoid protocol round trip for evaluates that do not return anything.
// Otherwise, we can fail with 'execution context destroyed' without any reason.
if (response.result.type === 'undefined')
return undefined;
return await this._returnObjectByValue(utilityScript, response.result.objectId); return await this._returnObjectByValue(utilityScript, response.result.objectId);
}
return parseEvaluationResultValue(response.result.value); return parseEvaluationResultValue(response.result.value);
} catch (error) { } catch (error) {
throw rewriteError(error); throw rewriteError(error);
@ -89,7 +94,6 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
} }
private async _returnObjectByValue(utilityScript: js.JSHandle<any>, objectId: Protocol.Runtime.RemoteObjectId): Promise<any> { private async _returnObjectByValue(utilityScript: js.JSHandle<any>, objectId: Protocol.Runtime.RemoteObjectId): Promise<any> {
// This is different from handleJSONValue in that it does not throw.
try { try {
const serializeResponse = await this._session.send('Runtime.callFunctionOn', { const serializeResponse = await this._session.send('Runtime.callFunctionOn', {
functionDeclaration: 'object => object' + sourceMap.generateSourceUrl(), functionDeclaration: 'object => object' + sourceMap.generateSourceUrl(),
@ -98,13 +102,10 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
returnByValue: true returnByValue: true
}); });
if (serializeResponse.wasThrown) if (serializeResponse.wasThrown)
return undefined; throw new Error('Evaluation failed: ' + serializeResponse.result.description);
return parseEvaluationResultValue(serializeResponse.result.value); return parseEvaluationResultValue(serializeResponse.result.value);
} catch (error) { } catch (error) {
return undefined; throw rewriteError(error);
// TODO: we should actually throw an error, but that breaks the common case of undefined
// that is for some reason reported as an object and cannot be accessed after navigation.
// throw rewriteError(error);
} }
} }

View file

@ -228,6 +228,10 @@ describe('Page.evaluate', function() {
it('should properly serialize undefined fields', async({page}) => { it('should properly serialize undefined fields', async({page}) => {
expect(await page.evaluate(() => ({a: undefined}))).toEqual({}); expect(await page.evaluate(() => ({a: undefined}))).toEqual({});
}); });
it('should return undefined properties', async({page}) => {
const value = await page.evaluate(() => ({a: undefined}));
expect('a' in value).toBe(true);
});
it('should properly serialize null arguments', async({page}) => { it('should properly serialize null arguments', async({page}) => {
expect(await page.evaluate(x => x, null)).toEqual(null); expect(await page.evaluate(x => x, null)).toEqual(null);
}); });