Browse Source

fix double-freeing remote references

After the page does navigations, garbage collection can still happen in
the old context. This commit changes to store references to remote objects
by _pages_, instead of by _WebContents_.
Cheng Zhao 6 years ago
parent
commit
4cdb1b8fc3

+ 4 - 3
atom/common/api/atom_api_v8_util.cc

@@ -32,7 +32,7 @@ namespace std {
 template <typename Type1, typename Type2>
 struct hash<std::pair<Type1, Type2>> {
   std::size_t operator()(std::pair<Type1, Type2> value) const {
-    return base::HashInts<Type1, Type2>(value.first, value.second);
+    return base::HashInts(base::Hash(value.first), value.second);
   }
 };
 
@@ -137,8 +137,9 @@ void Initialize(v8::Local<v8::Object> exports,
   dict.SetMethod("setRemoteCallbackFreer", &atom::RemoteCallbackFreer::BindTo);
   dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo);
   dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap<int32_t>::Create);
-  dict.SetMethod("createDoubleIDWeakMap",
-                 &atom::api::KeyWeakMap<std::pair<int64_t, int32_t>>::Create);
+  dict.SetMethod(
+      "createDoubleIDWeakMap",
+      &atom::api::KeyWeakMap<std::pair<std::string, int32_t>>::Create);
   dict.SetMethod("requestGarbageCollectionForTesting",
                  &RequestGarbageCollectionForTesting);
   dict.SetMethod("isSameOrigin", &IsSameOrigin);

+ 5 - 1
atom/common/api/remote_callback_freer.cc

@@ -15,17 +15,20 @@ namespace atom {
 // static
 void RemoteCallbackFreer::BindTo(v8::Isolate* isolate,
                                  v8::Local<v8::Object> target,
+                                 const std::string& context_id,
                                  int object_id,
                                  content::WebContents* web_contents) {
-  new RemoteCallbackFreer(isolate, target, object_id, web_contents);
+  new RemoteCallbackFreer(isolate, target, context_id, object_id, web_contents);
 }
 
 RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
                                          v8::Local<v8::Object> target,
+                                         const std::string& context_id,
                                          int object_id,
                                          content::WebContents* web_contents)
     : ObjectLifeMonitor(isolate, target),
       content::WebContentsObserver(web_contents),
+      context_id_(context_id),
       object_id_(object_id) {}
 
 RemoteCallbackFreer::~RemoteCallbackFreer() {}
@@ -34,6 +37,7 @@ void RemoteCallbackFreer::RunDestructor() {
   base::string16 channel =
       base::ASCIIToUTF16("ELECTRON_RENDERER_RELEASE_CALLBACK");
   base::ListValue args;
+  args.AppendString(context_id_);
   args.AppendInteger(object_id_);
   auto* frame_host = web_contents()->GetMainFrame();
   if (frame_host) {

+ 6 - 0
atom/common/api/remote_callback_freer.h

@@ -4,6 +4,9 @@
 
 #ifndef ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_
 #define ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_
+
+#include <string>
+
 #include "atom/common/api/object_life_monitor.h"
 #include "content/public/browser/web_contents_observer.h"
 
@@ -14,12 +17,14 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
  public:
   static void BindTo(v8::Isolate* isolate,
                      v8::Local<v8::Object> target,
+                     const std::string& context_id,
                      int object_id,
                      content::WebContents* web_conents);
 
  protected:
   RemoteCallbackFreer(v8::Isolate* isolate,
                       v8::Local<v8::Object> target,
+                      const std::string& context_id,
                       int object_id,
                       content::WebContents* web_conents);
   ~RemoteCallbackFreer() override;
@@ -30,6 +35,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
   void RenderViewDeleted(content::RenderViewHost*) override;
 
  private:
+  std::string context_id_;
   int object_id_;
 
   DISALLOW_COPY_AND_ASSIGN(RemoteCallbackFreer);

+ 5 - 1
atom/common/api/remote_object_freer.cc

@@ -29,14 +29,17 @@ content::RenderFrame* GetCurrentRenderFrame() {
 // static
 void RemoteObjectFreer::BindTo(v8::Isolate* isolate,
                                v8::Local<v8::Object> target,
+                               const std::string& context_id,
                                int object_id) {
-  new RemoteObjectFreer(isolate, target, object_id);
+  new RemoteObjectFreer(isolate, target, context_id, object_id);
 }
 
 RemoteObjectFreer::RemoteObjectFreer(v8::Isolate* isolate,
                                      v8::Local<v8::Object> target,
+                                     const std::string& context_id,
                                      int object_id)
     : ObjectLifeMonitor(isolate, target),
+      context_id_(context_id),
       object_id_(object_id),
       routing_id_(MSG_ROUTING_NONE) {
   content::RenderFrame* render_frame = GetCurrentRenderFrame();
@@ -56,6 +59,7 @@ void RemoteObjectFreer::RunDestructor() {
   base::string16 channel = base::ASCIIToUTF16("ipc-message");
   base::ListValue args;
   args.AppendString("ELECTRON_BROWSER_DEREFERENCE");
+  args.AppendString(context_id_);
   args.AppendInteger(object_id_);
   render_frame->Send(new AtomFrameHostMsg_Message(render_frame->GetRoutingID(),
                                                   channel, args));

+ 5 - 0
atom/common/api/remote_object_freer.h

@@ -5,6 +5,8 @@
 #ifndef ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_
 #define ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_
 
+#include <string>
+
 #include "atom/common/api/object_life_monitor.h"
 
 namespace atom {
@@ -13,17 +15,20 @@ class RemoteObjectFreer : public ObjectLifeMonitor {
  public:
   static void BindTo(v8::Isolate* isolate,
                      v8::Local<v8::Object> target,
+                     const std::string& context_id,
                      int object_id);
 
  protected:
   RemoteObjectFreer(v8::Isolate* isolate,
                     v8::Local<v8::Object> target,
+                    const std::string& context_id,
                     int object_id);
   ~RemoteObjectFreer() override;
 
   void RunDestructor() override;
 
  private:
+  std::string context_id_;
   int object_id_;
   int routing_id_;
 

+ 16 - 16
lib/browser/objects-registry.js

@@ -17,16 +17,15 @@ class ObjectsRegistry {
 
   // Register a new object and return its assigned ID. If the object is already
   // registered then the already assigned ID would be returned.
-  add (webContents, obj) {
+  add (webContents, contextId, obj) {
     // Get or assign an ID to the object.
     const id = this.saveToStorage(obj)
 
     // Add object to the set of referenced objects.
-    const webContentsId = webContents.getId()
-    let owner = this.owners[webContentsId]
+    let owner = this.owners[contextId]
     if (!owner) {
-      owner = this.owners[webContentsId] = new Set()
-      this.registerDeleteListener(webContents, webContentsId)
+      owner = this.owners[contextId] = new Set()
+      this.registerDeleteListener(webContents, contextId)
     }
     if (!owner.has(id)) {
       owner.add(id)
@@ -43,25 +42,26 @@ class ObjectsRegistry {
   }
 
   // Dereference an object according to its ID.
-  remove (webContentsId, id) {
-    // Dereference from the storage.
-    this.dereference(id)
-
-    // Also remove the reference in owner.
-    let owner = this.owners[webContentsId]
+  // Note that an object may be double-freed (cleared when page is reloaded, and
+  // then garbage collected in old page).
+  remove (contextId, id) {
+    let owner = this.owners[contextId]
     if (owner) {
+      // Remove the reference in owner.
       owner.delete(id)
+      // Dereference from the storage.
+      this.dereference(id)
     }
   }
 
   // Clear all references to objects refrenced by the WebContents.
-  clear (webContentsId) {
-    let owner = this.owners[webContentsId]
+  clear (contextId) {
+    let owner = this.owners[contextId]
     if (!owner) return
 
     for (let id of owner) this.dereference(id)
 
-    delete this.owners[webContentsId]
+    delete this.owners[contextId]
   }
 
   // Private: Saves the object into storage and assigns an ID for it.
@@ -92,12 +92,12 @@ class ObjectsRegistry {
   }
 
   // Private: Clear the storage when webContents is reloaded/navigated.
-  registerDeleteListener (webContents, webContentsId) {
+  registerDeleteListener (webContents, contextId) {
     const processId = webContents.getProcessId()
     const listener = (event, deletedProcessId) => {
       if (deletedProcessId === processId) {
         webContents.removeListener('render-view-deleted', listener)
-        this.clear(webContentsId)
+        this.clear(contextId)
       }
     }
     webContents.on('render-view-deleted', listener)

+ 59 - 59
lib/browser/rpc-server.js

@@ -56,7 +56,7 @@ let getObjectPrototype = function (object) {
 }
 
 // Convert a real value into meta data.
-let valueToMeta = function (sender, value, optimizeSimpleObject = false) {
+let valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
   // Determine the type of value.
   const meta = { type: typeof value }
   if (meta.type === 'object') {
@@ -84,14 +84,14 @@ let valueToMeta = function (sender, value, optimizeSimpleObject = false) {
 
   // Fill the meta object according to value's type.
   if (meta.type === 'array') {
-    meta.members = value.map((el) => valueToMeta(sender, el, optimizeSimpleObject))
+    meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
   } else if (meta.type === 'object' || meta.type === 'function') {
     meta.name = value.constructor ? value.constructor.name : ''
 
     // Reference the original value if it's an object, because when it's
     // passed to renderer we would assume the renderer keeps a reference of
     // it.
-    meta.id = objectsRegistry.add(sender, value)
+    meta.id = objectsRegistry.add(sender, contextId, value)
     meta.members = getObjectMembers(value)
     meta.proto = getObjectPrototype(value)
   } else if (meta.type === 'buffer') {
@@ -101,7 +101,7 @@ let valueToMeta = function (sender, value, optimizeSimpleObject = false) {
     // Instead they should appear in the renderer process
     value.then(function () {}, function () {})
 
-    meta.then = valueToMeta(sender, function (onFulfilled, onRejected) {
+    meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
       value.then(onFulfilled, onRejected)
     })
   } else if (meta.type === 'error') {
@@ -132,12 +132,12 @@ const plainObjectToMeta = function (obj) {
 }
 
 // Convert Error into meta data.
-const exceptionToMeta = function (sender, error) {
+const exceptionToMeta = function (sender, contextId, error) {
   return {
     type: 'exception',
     message: error.message,
     stack: error.stack || error,
-    cause: valueToMeta(sender, error.cause)
+    cause: valueToMeta(sender, contextId, error.cause)
   }
 }
 
@@ -169,7 +169,7 @@ const removeRemoteListenersAndLogWarning = (sender, meta, callIntoRenderer) => {
 }
 
 // Convert array of meta data from renderer into array of real values.
-const unwrapArgs = function (sender, args) {
+const unwrapArgs = function (sender, contextId, args) {
   const metaToValue = function (meta) {
     switch (meta.type) {
       case 'value':
@@ -177,7 +177,7 @@ const unwrapArgs = function (sender, args) {
       case 'remote-object':
         return objectsRegistry.get(meta.id)
       case 'array':
-        return unwrapArgs(sender, meta.value)
+        return unwrapArgs(sender, contextId, meta.value)
       case 'buffer':
         return bufferUtils.metaToBuffer(meta.value)
       case 'date':
@@ -201,26 +201,26 @@ const unwrapArgs = function (sender, args) {
           return returnValue
         }
       case 'function': {
-        // Merge webContentsId and meta.id, since meta.id can be the same in
+        // Merge contextId and meta.id, since meta.id can be the same in
         // different webContents.
-        const webContentsId = sender.getId()
-        const objectId = [webContentsId, meta.id]
+        const objectId = [contextId, meta.id]
 
         // Cache the callbacks in renderer.
         if (rendererFunctions.has(objectId)) {
           return rendererFunctions.get(objectId)
         }
 
+        const webContentsId = sender.getId()
         let callIntoRenderer = function (...args) {
           if (!sender.isDestroyed() && webContentsId === sender.getId()) {
-            sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args))
+            sender.send('ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
           } else {
             removeRemoteListenersAndLogWarning(this, meta, callIntoRenderer)
           }
         }
         Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
 
-        v8Util.setRemoteCallbackFreer(callIntoRenderer, meta.id, sender)
+        v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
         rendererFunctions.set(objectId, callIntoRenderer)
         return callIntoRenderer
       }
@@ -233,18 +233,18 @@ 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) {
+const callFunction = function (event, contextId, func, caller, args) {
   const funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
   const funcPassedCallback = typeof args[args.length - 1] === 'function'
   try {
     if (funcMarkedAsync && !funcPassedCallback) {
       args.push(function (ret) {
-        event.returnValue = valueToMeta(event.sender, ret, true)
+        event.returnValue = valueToMeta(event.sender, contextId, ret, true)
       })
       func.apply(caller, args)
     } else {
       const ret = func.apply(caller, args)
-      event.returnValue = valueToMeta(event.sender, ret, true)
+      event.returnValue = valueToMeta(event.sender, contextId, ret, true)
     }
   } catch (error) {
     // Catch functions thrown further down in function invocation and wrap
@@ -257,105 +257,105 @@ const callFunction = function (event, func, caller, args) {
   }
 }
 
-ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, module) {
+ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, contextId, module) {
   try {
-    event.returnValue = valueToMeta(event.sender, process.mainModule.require(module))
+    event.returnValue = valueToMeta(event.sender, contextId, process.mainModule.require(module))
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) {
+ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, module) {
   try {
-    event.returnValue = valueToMeta(event.sender, electron[module])
+    event.returnValue = valueToMeta(event.sender, contextId, electron[module])
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, name) {
+ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, contextId, name) {
   try {
-    event.returnValue = valueToMeta(event.sender, global[name])
+    event.returnValue = valueToMeta(event.sender, contextId, global[name])
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event) {
+ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
   try {
-    event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow())
+    event.returnValue = valueToMeta(event.sender, contextId, event.sender.getOwnerBrowserWindow())
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event) {
-  event.returnValue = valueToMeta(event.sender, event.sender)
+ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
+  event.returnValue = valueToMeta(event.sender, contextId, event.sender)
 })
 
-ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) {
+ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
   try {
-    args = unwrapArgs(event.sender, args)
+    args = unwrapArgs(event.sender, contextId, args)
     let constructor = objectsRegistry.get(id)
 
     if (constructor == null) {
       throwRPCError(`Cannot call constructor on missing remote object ${id}`)
     }
 
-    event.returnValue = valueToMeta(event.sender, new constructor(...args))
+    event.returnValue = valueToMeta(event.sender, contextId, new constructor(...args))
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, id, args) {
+ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
   try {
-    args = unwrapArgs(event.sender, args)
+    args = unwrapArgs(event.sender, contextId, args)
     let func = objectsRegistry.get(id)
 
     if (func == null) {
       throwRPCError(`Cannot call function on missing remote object ${id}`)
     }
 
-    callFunction(event, func, global, args)
+    callFunction(event, contextId, func, global, args)
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, args) {
+ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
   try {
-    args = unwrapArgs(event.sender, args)
+    args = unwrapArgs(event.sender, contextId, args)
     let object = objectsRegistry.get(id)
 
     if (object == null) {
       throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
     }
 
-    event.returnValue = valueToMeta(event.sender, new object[method](...args))
+    event.returnValue = valueToMeta(event.sender, contextId, new object[method](...args))
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, id, method, args) {
+ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
   try {
-    args = unwrapArgs(event.sender, args)
+    args = unwrapArgs(event.sender, contextId, args)
     let obj = objectsRegistry.get(id)
 
     if (obj == null) {
       throwRPCError(`Cannot call function '${method}' on missing remote object ${id}`)
     }
 
-    callFunction(event, obj[method], obj, args)
+    callFunction(event, contextId, obj[method], obj, args)
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) {
+ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
   try {
-    args = unwrapArgs(event.sender, args)
+    args = unwrapArgs(event.sender, contextId, args)
     let obj = objectsRegistry.get(id)
 
     if (obj == null) {
@@ -365,11 +365,11 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) {
     obj[name] = args[0]
     event.returnValue = null
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) {
+ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
   try {
     let obj = objectsRegistry.get(id)
 
@@ -377,14 +377,14 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) {
       throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
     }
 
-    event.returnValue = valueToMeta(event.sender, obj[name])
+    event.returnValue = valueToMeta(event.sender, contextId, obj[name])
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, id) {
-  objectsRegistry.remove(event.sender.getId(), id)
+ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id) {
+  objectsRegistry.remove(contextId, id)
 })
 
 ipcMain.on('ELECTRON_BROWSER_CONTEXT_RELEASE', (e, contextId) => {
@@ -392,16 +392,16 @@ ipcMain.on('ELECTRON_BROWSER_CONTEXT_RELEASE', (e, contextId) => {
   e.returnValue = null
 })
 
-ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, guestInstanceId) {
+ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
   try {
     let guestViewManager = require('./guest-view-manager')
-    event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId))
+    event.returnValue = valueToMeta(event.sender, contextId, guestViewManager.getGuest(guestInstanceId))
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 
-ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, requestId, guestInstanceId, method, ...args) {
+ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, contextId, requestId, guestInstanceId, method, ...args) {
   try {
     let guestViewManager = require('./guest-view-manager')
     let guest = guestViewManager.getGuest(guestInstanceId)
@@ -413,7 +413,7 @@ ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, request
     }
     guest[method].apply(guest, args)
   } catch (error) {
-    event.returnValue = exceptionToMeta(event.sender, error)
+    event.returnValue = exceptionToMeta(event.sender, contextId, error)
   }
 })
 

+ 34 - 31
lib/renderer/api/remote.js

@@ -9,6 +9,18 @@ const bufferUtils = require('../../common/buffer-utils')
 const callbacksRegistry = new CallbacksRegistry()
 const remoteObjectCache = v8Util.createIDWeakMap()
 
+// An unique ID that can represent current context.
+const contextId = v8Util.getContextId()
+
+// Notify the main process when current context is going to be released.
+// Note that when the renderer process is destroyed, the message may not be
+// sent, we also listen to the "render-view-deleted" event in the main process
+// to guard that situation.
+process.on('exit', () => {
+  const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
+  ipcRenderer.sendSync(command, contextId)
+})
+
 // Convert the arguments object into an array of meta data.
 function wrapArgs (args, visited = new Set()) {
   const valueToMeta = (value) => {
@@ -107,7 +119,7 @@ function setObjectMembers (ref, object, metaId, members) {
         } else {
           command = 'ELECTRON_BROWSER_MEMBER_CALL'
         }
-        const ret = ipcRenderer.sendSync(command, metaId, member.name, wrapArgs(args))
+        const ret = ipcRenderer.sendSync(command, contextId, metaId, member.name, wrapArgs(args))
         return metaToValue(ret)
       }
 
@@ -126,7 +138,7 @@ function setObjectMembers (ref, object, metaId, members) {
     } else if (member.type === 'get') {
       descriptor.get = () => {
         const command = 'ELECTRON_BROWSER_MEMBER_GET'
-        const meta = ipcRenderer.sendSync(command, metaId, member.name)
+        const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name)
         return metaToValue(meta)
       }
 
@@ -134,7 +146,7 @@ function setObjectMembers (ref, object, metaId, members) {
         descriptor.set = (value) => {
           const args = wrapArgs([value])
           const command = 'ELECTRON_BROWSER_MEMBER_SET'
-          const meta = ipcRenderer.sendSync(command, metaId, member.name, args)
+          const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name, args)
           if (meta != null) metaToValue(meta)
           return value
         }
@@ -164,7 +176,7 @@ function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
     if (loaded) return
     loaded = true
     const command = 'ELECTRON_BROWSER_MEMBER_GET'
-    const meta = ipcRenderer.sendSync(command, metaId, name)
+    const meta = ipcRenderer.sendSync(command, contextId, metaId, name)
     setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members)
   }
 
@@ -224,7 +236,7 @@ function metaToValue (meta) {
         } else {
           command = 'ELECTRON_BROWSER_FUNCTION_CALL'
         }
-        const obj = ipcRenderer.sendSync(command, meta.id, wrapArgs(args))
+        const obj = ipcRenderer.sendSync(command, contextId, meta.id, wrapArgs(args))
         return metaToValue(obj)
       }
       ret = remoteFunction
@@ -237,7 +249,7 @@ function metaToValue (meta) {
     Object.defineProperty(ret.constructor, 'name', { value: meta.name })
 
     // Track delegate obj's lifetime & tell browser to clean up when object is GCed.
-    v8Util.setRemoteObjectFreer(ret, meta.id)
+    v8Util.setRemoteObjectFreer(ret, contextId, meta.id)
     v8Util.setHiddenValue(ret, 'atomId', meta.id)
     remoteObjectCache.set(meta.id, ret)
     return ret
@@ -264,60 +276,51 @@ function metaToException (meta) {
 }
 
 // Browser calls a callback in renderer.
-ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', (event, id, args) => {
+ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', (event, passedContextId, id, args) => {
+  if (passedContextId !== contextId) {
+    // The invoked callback belongs to an old page in this renderer.
+    return
+  }
   callbacksRegistry.apply(id, metaToValue(args))
 })
 
 // A callback in browser is released.
-ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', (event, id) => {
+ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', (event, passedContextId, id) => {
+  if (passedContextId !== contextId) {
+    // The freed callback belongs to an old page in this renderer.
+    return
+  }
   callbacksRegistry.remove(id)
 })
 
-process.on('exit', () => {
-  const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
-  ipcRenderer.sendSync(command, initialContext)
-})
-
 exports.require = (module) => {
   const command = 'ELECTRON_BROWSER_REQUIRE'
-  const meta = ipcRenderer.sendSync(command, module)
+  const meta = ipcRenderer.sendSync(command, contextId, module)
   return metaToValue(meta)
 }
 
 // Alias to remote.require('electron').xxx.
 exports.getBuiltin = (module) => {
   const command = 'ELECTRON_BROWSER_GET_BUILTIN'
-  const meta = ipcRenderer.sendSync(command, module)
+  const meta = ipcRenderer.sendSync(command, contextId, module)
   return metaToValue(meta)
 }
 
 exports.getCurrentWindow = () => {
   const command = 'ELECTRON_BROWSER_CURRENT_WINDOW'
-  const meta = ipcRenderer.sendSync(command)
+  const meta = ipcRenderer.sendSync(command, contextId)
   return metaToValue(meta)
 }
 
 // Get current WebContents object.
 exports.getCurrentWebContents = () => {
-  return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS'))
-}
-
-const CONTEXT_ARG = '--context-id='
-let initialContext = process.argv.find(arg => arg.startsWith(CONTEXT_ARG))
-if (process.webContentsId) {
-  // set by sandbox renderer init script
-  initialContext = process.webContentsId
-} else if (initialContext) {
-  initialContext = parseInt(initialContext.substr(CONTEXT_ARG.length), 10)
-} else {
-  // if not available, pull from remote
-  initialContext = exports.getCurrentWebContents().getId()
+  return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', contextId))
 }
 
 // Get a global object in browser.
 exports.getGlobal = (name) => {
   const command = 'ELECTRON_BROWSER_GLOBAL'
-  const meta = ipcRenderer.sendSync(command, name)
+  const meta = ipcRenderer.sendSync(command, contextId, name)
   return metaToValue(meta)
 }
 
@@ -334,7 +337,7 @@ exports.createFunctionWithReturnValue = (returnValue) => {
 // Get the guest WebContents from guestInstanceId.
 exports.getGuestWebContents = (guestInstanceId) => {
   const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS'
-  const meta = ipcRenderer.sendSync(command, guestInstanceId)
+  const meta = ipcRenderer.sendSync(command, contextId, guestInstanceId)
   return metaToValue(meta)
 }
 

+ 5 - 2
lib/renderer/web-view/web-view.js

@@ -8,6 +8,9 @@ const webViewConstants = require('./web-view-constants')
 
 const hasProp = {}.hasOwnProperty
 
+// An unique ID that can represent current context.
+const contextId = v8Util.getContextId()
+
 // ID generator.
 let nextId = 0
 
@@ -396,7 +399,7 @@ const registerWebViewElement = function () {
   const createNonBlockHandler = function (m) {
     return function (...args) {
       const internal = v8Util.getHiddenValue(this, 'internal')
-      ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', null, internal.guestInstanceId, m, ...args)
+      ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', contextId, null, internal.guestInstanceId, m, ...args)
     }
   }
   for (const method of nonblockMethods) {
@@ -410,7 +413,7 @@ const registerWebViewElement = function () {
       hasUserGesture = false
     }
     const requestId = getNextId()
-    ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture)
+    ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', contextId, requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture)
     ipcRenderer.once(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, function (event, result) {
       if (callback) callback(result)
     })