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 {
// { type: 'undefined' } does not even have value.
if (value === 'undefined')
if (value === undefined)
return undefined;
if (typeof value === 'object') {
if (value.v === 'undefined')

View file

@ -32,6 +32,9 @@ export default class UtilityScript {
}
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 }));
}

View file

@ -80,8 +80,13 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
throw new Error('Evaluation failed: ' + response.result.description);
if (!returnByValue)
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 parseEvaluationResultValue(response.result.value);
} catch (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> {
// This is different from handleJSONValue in that it does not throw.
try {
const serializeResponse = await this._session.send('Runtime.callFunctionOn', {
functionDeclaration: 'object => object' + sourceMap.generateSourceUrl(),
@ -98,13 +102,10 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
returnByValue: true
});
if (serializeResponse.wasThrown)
return undefined;
throw new Error('Evaluation failed: ' + serializeResponse.result.description);
return parseEvaluationResultValue(serializeResponse.result.value);
} catch (error) {
return undefined;
// 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);
throw rewriteError(error);
}
}

View file

@ -228,6 +228,10 @@ describe('Page.evaluate', function() {
it('should properly serialize undefined fields', async({page}) => {
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}) => {
expect(await page.evaluate(x => x, null)).toEqual(null);
});