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:
parent
71618a9e2b
commit
064a0a1154
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue