Browse Source

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
Tatsuya Hiroishi 7 years ago
parent
commit
9c65abd746
4 changed files with 54 additions and 17 deletions
  1. 19 16
      lib/browser/rpc-server.js
  2. 10 1
      lib/renderer/api/remote.js
  3. 22 0
      spec/api-remote-spec.js
  4. 3 0
      spec/fixtures/module/exception.js

+ 19 - 16
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)
   }
 })
 

+ 10 - 1
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))

+ 22 - 0
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)
+      }
+    })
+  })
 })

+ 3 - 0
spec/fixtures/module/exception.js

@@ -0,0 +1,3 @@
+module.exports = function (error) {
+  throw error
+}