From 9c65abd7464226f9b9205b8e059b6c92806b838a Mon Sep 17 00:00:00 2001 From: Tatsuya Hiroishi Date: Tue, 24 Apr 2018 21:40:19 +0900 Subject: [PATCH] handle remote exception (#12694) * add cause property to exception in callFunction * update exceptionToMeta function * add sender argument * and cause property to return value * update exception convert in metaToValue function * add from and cause properties to the exception error * unit test for remote exception --- lib/browser/rpc-server.js | 35 +++++++++++++++++-------------- lib/renderer/api/remote.js | 11 +++++++++- spec/api-remote-spec.js | 22 +++++++++++++++++++ spec/fixtures/module/exception.js | 3 +++ 4 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 spec/fixtures/module/exception.js diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 20f62980c955..9b27c481cb97 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -131,11 +131,12 @@ const plainObjectToMeta = function (obj) { } // Convert Error into meta data. -const exceptionToMeta = function (error) { +const exceptionToMeta = function (sender, error) { return { type: 'exception', message: error.message, - stack: error.stack || error + stack: error.stack || error, + cause: valueToMeta(sender, error.cause) } } @@ -236,7 +237,7 @@ const unwrapArgs = function (sender, args) { // Call a function and send reply asynchronously if it's a an asynchronous // style function and the caller didn't pass a callback. const callFunction = function (event, func, caller, args) { - let funcMarkedAsync, funcName, funcPassedCallback, ref, ret + let err, funcMarkedAsync, funcName, funcPassedCallback, ref, ret funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous') funcPassedCallback = typeof args[args.length - 1] === 'function' try { @@ -254,7 +255,9 @@ const callFunction = function (event, func, caller, args) { // them with the function name so it's easier to trace things like // `Error processing argument -1.` funcName = ((ref = func.name) != null) ? ref : 'anonymous' - throw new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`) + err = new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`) + err.cause = error + throw err } } @@ -262,7 +265,7 @@ ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, module) { try { event.returnValue = valueToMeta(event.sender, process.mainModule.require(module)) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -270,7 +273,7 @@ ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) { try { event.returnValue = valueToMeta(event.sender, electron[module]) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -278,7 +281,7 @@ ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, name) { try { event.returnValue = valueToMeta(event.sender, global[name]) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -286,7 +289,7 @@ ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event) { try { event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow()) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -308,7 +311,7 @@ ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) { let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))() event.returnValue = valueToMeta(event.sender, obj) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -323,7 +326,7 @@ ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, id, args) { callFunction(event, func, global, args) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -341,7 +344,7 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, a let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))() event.returnValue = valueToMeta(event.sender, obj) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -356,7 +359,7 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, id, method, args) { callFunction(event, obj[method], obj, args) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -372,7 +375,7 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) { obj[name] = args[0] event.returnValue = null } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -386,7 +389,7 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) { event.returnValue = valueToMeta(event.sender, obj[name]) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -404,7 +407,7 @@ ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, guestInstance let guestViewManager = require('./guest-view-manager') event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId)) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) @@ -420,7 +423,7 @@ ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, request } guest[method].apply(guest, args) } catch (error) { - event.returnValue = exceptionToMeta(error) + event.returnValue = exceptionToMeta(event.sender, error) } }) diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index 3de99ecab255..15928d836c9e 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -206,7 +206,7 @@ function metaToValue (meta) { promise: () => resolvePromise({then: metaToValue(meta.then)}), error: () => metaToPlainObject(meta), date: () => new Date(meta.value), - exception: () => { throw new Error(`${meta.message}\n${meta.stack}`) } + exception: () => { throw metaToException(meta) } } if (meta.type in types) { @@ -256,6 +256,15 @@ function metaToPlainObject (meta) { return obj } +// Construct an exception error from the meta. +function metaToException (meta) { + const error = new Error(`${meta.message}\n${meta.stack}`) + const remoteProcess = exports.process + error.from = remoteProcess ? remoteProcess.type : null + error.cause = metaToValue(meta.cause) + return error +} + // Browser calls a callback in renderer. ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', (event, id, args) => { callbacksRegistry.apply(id, metaToValue(args)) diff --git a/spec/api-remote-spec.js b/spec/api-remote-spec.js index bf20e7feaef5..7c634e1740fa 100644 --- a/spec/api-remote-spec.js +++ b/spec/api-remote-spec.js @@ -370,4 +370,26 @@ describe('remote module', () => { assert.equal(method(), 'method') }) }) + + describe('remote exception', () => { + const throwFunction = remote.require(path.join(fixtures, 'module', 'exception.js')) + + it('throws errors from the main process', () => { + assert.throws(() => { + throwFunction() + }) + }) + + it('throws custom errors from the main process', () => { + let err = new Error('error') + err.cause = new Error('cause') + err.prop = 'error prop' + try { + throwFunction(err) + } catch (error) { + assert.ok(error.from) + assert.deepEqual(error.cause, err) + } + }) + }) }) diff --git a/spec/fixtures/module/exception.js b/spec/fixtures/module/exception.js new file mode 100644 index 000000000000..1465833ff8da --- /dev/null +++ b/spec/fixtures/module/exception.js @@ -0,0 +1,3 @@ +module.exports = function (error) { + throw error +}