Browse Source

Fix missing remote object error when calling remote function created in preload script (4-0-x) (#15446)

* fix: report wrong context error based on contextId

* fix: destroyed remote renderer warning is now async
Cheng Zhao 6 years ago
parent
commit
d07115e1dc

+ 15 - 5
lib/browser/rpc-server.js

@@ -151,9 +151,10 @@ const throwRPCError = function (message) {
   throw error
 }
 
-const removeRemoteListenersAndLogWarning = (sender, meta, callIntoRenderer) => {
+const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
+  const location = v8Util.getHiddenValue(callIntoRenderer, 'location')
   let message = `Attempting to call a function in a renderer window that has been closed or released.` +
-    `\nFunction provided here: ${meta.location}`
+    `\nFunction provided here: ${location}`
 
   if (sender instanceof EventEmitter) {
     const remoteEvents = sender.eventNames().filter((eventName) => {
@@ -213,14 +214,14 @@ const unwrapArgs = function (sender, contextId, args) {
           return rendererFunctions.get(objectId)
         }
 
-        const processId = sender.getProcessId()
         const callIntoRenderer = function (...args) {
-          if (!sender.isDestroyed() && processId === sender.getProcessId()) {
+          if (!sender.isDestroyed()) {
             sender._sendInternal('ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
           } else {
-            removeRemoteListenersAndLogWarning(this, meta, callIntoRenderer)
+            removeRemoteListenersAndLogWarning(this, callIntoRenderer)
           }
         }
+        v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location)
         Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
 
         v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
@@ -280,6 +281,15 @@ const handleRemoteCommand = function (channel, handler) {
   })
 }
 
+handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
+  const objectId = [passedContextId, id]
+  if (!rendererFunctions.has(objectId)) {
+    // Do nothing if the error has already been reported before.
+    return
+  }
+  removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId))
+})
+
 handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, module) {
   return valueToMeta(event.sender, contextId, process.mainModule.require(module))
 })

+ 5 - 2
lib/renderer/api/remote.js

@@ -270,9 +270,12 @@ function metaToPlainObject (meta) {
 }
 
 function handleMessage (channel, handler) {
-  ipcRenderer.on(channel, (event, passedContextId, ...args) => {
+  ipcRenderer.on(channel, (event, passedContextId, id, ...args) => {
     if (passedContextId === contextId) {
-      handler(...args)
+      handler(id, ...args)
+    } else {
+      // Message sent to an un-exist context, notify the error to main process.
+      ipcRenderer.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id)
     }
   })
 }

+ 20 - 0
spec/api-remote-spec.js

@@ -6,6 +6,7 @@ const { closeWindow } = require('./window-helpers')
 const { resolveGetters } = require('./assert-helpers')
 
 const { remote } = require('electron')
+const { ipcMain, BrowserWindow } = remote
 
 const comparePaths = (path1, path2) => {
   if (process.platform === 'win32') {
@@ -485,4 +486,23 @@ describe('remote module', () => {
       }
     })
   })
+
+  describe('remote function in renderer', () => {
+    afterEach(() => {
+      ipcMain.removeAllListeners('done')
+    })
+
+    it('works when created in preload script', (done) => {
+      ipcMain.once('done', () => w.close())
+      const preload = path.join(fixtures, 'module', 'preload-remote-function.js')
+      w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          preload: preload
+        }
+      })
+      w.once('closed', () => done())
+      w.loadURL('about:blank')
+    })
+  })
 })

+ 5 - 0
spec/fixtures/module/preload-remote-function.js

@@ -0,0 +1,5 @@
+const { remote, ipcRenderer } = require('electron')
+remote.getCurrentWindow().rendererFunc = () => {
+  ipcRenderer.send('done')
+}
+remote.getCurrentWindow().rendererFunc()

+ 9 - 14
spec/static/main.js

@@ -332,26 +332,21 @@ ipcMain.on('disable-preload-on-next-will-attach-webview', (event, id) => {
 
 ipcMain.on('try-emit-web-contents-event', (event, id, eventName) => {
   const consoleWarn = console.warn
-  let warningMessage = null
   const contents = webContents.fromId(id)
   const listenerCountBefore = contents.listenerCount(eventName)
 
-  try {
-    console.warn = (message) => {
-      warningMessage = message
-    }
-    contents.emit(eventName, { sender: contents })
-  } finally {
+  console.warn = (warningMessage) => {
     console.warn = consoleWarn
-  }
-
-  const listenerCountAfter = contents.listenerCount(eventName)
 
-  event.returnValue = {
-    warningMessage,
-    listenerCountBefore,
-    listenerCountAfter
+    const listenerCountAfter = contents.listenerCount(eventName)
+    event.returnValue = {
+      warningMessage,
+      listenerCountBefore,
+      listenerCountAfter
+    }
   }
+
+  contents.emit(eventName, { sender: contents })
 })
 
 ipcMain.on('handle-uncaught-exception', (event, message) => {