From 95a19c74bcf5be308d596bd4ca32f4b9d46a6bf4 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 15 Nov 2018 14:51:34 -0800 Subject: [PATCH] fix(page): dispatch errors into page (#3550) Errors thrown on the node side of the `page.exposeFunction` callback should be dispatched into the page. Fixes #3549 --- lib/Page.js | 44 ++++++++++++++++++++++++++++++++++++++++---- test/page.spec.js | 27 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/lib/Page.js b/lib/Page.js index 2a8f85e1..1df9eafe 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -428,7 +428,7 @@ class Page extends EventEmitter { } const seq = (me['lastSeq'] || 0) + 1; me['lastSeq'] = seq; - const promise = new Promise(fulfill => callbacks.set(seq, fulfill)); + const promise = new Promise((resolve, reject) => callbacks.set(seq, {resolve, reject})); binding(JSON.stringify({name: bindingName, seq, args})); return promise; }; @@ -511,12 +511,48 @@ class Page extends EventEmitter { */ async _onBindingCalled(event) { const {name, seq, args} = JSON.parse(event.payload); - const result = await this._pageBindings.get(name)(...args); - const expression = helper.evaluationString(deliverResult, name, seq, result); + let expression = null; + try { + const result = await this._pageBindings.get(name)(...args); + expression = helper.evaluationString(deliverResult, name, seq, result); + } catch (error) { + if (error instanceof Error) + expression = helper.evaluationString(deliverError, name, seq, error.message, error.stack); + else + expression = helper.evaluationString(deliverErrorValue, name, seq, error); + } this._client.send('Runtime.evaluate', { expression, contextId: event.executionContextId }).catch(debugError); + /** + * @param {string} name + * @param {number} seq + * @param {*} result + */ function deliverResult(name, seq, result) { - window[name]['callbacks'].get(seq)(result); + window[name]['callbacks'].get(seq).resolve(result); + window[name]['callbacks'].delete(seq); + } + + /** + * @param {string} name + * @param {number} seq + * @param {string} message + * @param {string} stack + */ + function deliverError(name, seq, message, stack) { + const error = new Error(message); + error.stack = stack; + window[name]['callbacks'].get(seq).reject(error); + window[name]['callbacks'].delete(seq); + } + + /** + * @param {string} name + * @param {number} seq + * @param {*} value + */ + function deliverErrorValue(name, seq, value) { + window[name]['callbacks'].get(seq).reject(value); window[name]['callbacks'].delete(seq); } } diff --git a/test/page.spec.js b/test/page.spec.js index 15f95e08..a873194f 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -1033,6 +1033,33 @@ module.exports.addTests = function({testRunner, expect, headless}) { }); expect(result).toBe(36); }); + it('should throw exception in page context', async({page, server}) => { + await page.exposeFunction('woof', function() { + throw new Error('WOOF WOOF'); + }); + const {message, stack} = await page.evaluate(async() => { + try { + await woof(); + } catch (e) { + return {message: e.message, stack: e.stack}; + } + }); + expect(message).toBe('WOOF WOOF'); + expect(stack).toContain(__filename); + }); + it('should support throwing "null"', async({page, server}) => { + await page.exposeFunction('woof', function() { + throw null; + }); + const thrown = await page.evaluate(async() => { + try { + await woof(); + } catch (e) { + return e; + } + }); + expect(thrown).toBe(null); + }); it('should be callable from-inside evaluateOnNewDocument', async({page, server}) => { let called = false; await page.exposeFunction('woof', function() {