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
3db1b5a49a

+ 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);
   }
 };
 
@@ -135,8 +135,9 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
   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) {
 }
 
@@ -36,6 +39,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);

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

@@ -27,14 +27,19 @@ content::RenderFrame* GetCurrentRenderFrame() {
 }  // namespace
 
 // static
-void RemoteObjectFreer::BindTo(
-    v8::Isolate* isolate, v8::Local<v8::Object> target, int object_id) {
-  new RemoteObjectFreer(isolate, target, object_id);
+void RemoteObjectFreer::BindTo(v8::Isolate* isolate,
+                               v8::Local<v8::Object> target,
+                               const std::string& context_id,
+                               int object_id) {
+  new RemoteObjectFreer(isolate, target, context_id, object_id);
 }
 
-RemoteObjectFreer::RemoteObjectFreer(
-    v8::Isolate* isolate, v8::Local<v8::Object> target, int 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();
@@ -55,6 +60,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));

+ 11 - 4
atom/common/api/remote_object_freer.h

@@ -5,23 +5,30 @@
 #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 {
 
 class RemoteObjectFreer : public ObjectLifeMonitor {
  public:
-  static void BindTo(
-      v8::Isolate* isolate, v8::Local<v8::Object> target, int object_id);
+  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, int object_id);
+  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)

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

@@ -55,7 +55,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') {
@@ -83,14 +83,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))
+    meta.members = value.map((el) => valueToMeta(sender, contextId, el))
   } 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') {
@@ -100,7 +100,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') {
@@ -168,7 +168,7 @@ const removeRemoteListenersAndLogWarning = (meta, args, 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) {
     let i, len, member, ref, returnValue
     switch (meta.type) {
@@ -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 Buffer.from(meta.value)
       case 'date':
@@ -203,26 +203,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(meta, args, 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
       }
@@ -235,19 +235,19 @@ 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) {
   let funcMarkedAsync, funcName, funcPassedCallback, ref, ret
   funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
   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 {
       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
@@ -258,45 +258,45 @@ 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(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(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(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(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) {
@@ -306,30 +306,30 @@ ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) {
     // Call new with array of arguments.
     // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
     let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
-    event.returnValue = valueToMeta(event.sender, obj)
+    event.returnValue = valueToMeta(event.sender, contextId, obj)
   } catch (error) {
     event.returnValue = exceptionToMeta(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(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) {
@@ -339,30 +339,30 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, a
     // Call new with array of arguments.
     let constructor = object[method]
     let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
-    event.returnValue = valueToMeta(event.sender, obj)
+    event.returnValue = valueToMeta(event.sender, contextId, obj)
   } catch (error) {
     event.returnValue = exceptionToMeta(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(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) {
@@ -376,7 +376,7 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) {
   }
 })
 
-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)
 
@@ -384,14 +384,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(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) => {
@@ -399,10 +399,10 @@ 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(error)
   }

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

@@ -11,6 +11,18 @@ const resolvePromise = Promise.resolve.bind(Promise)
 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) => {
@@ -109,7 +121,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)
       }
 
@@ -128,7 +140,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)
       }
 
@@ -136,7 +148,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
         }
@@ -166,7 +178,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)
   }
 
@@ -226,7 +238,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
@@ -239,7 +251,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
@@ -257,57 +269,51 @@ function metaToPlainObject (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 (initialContext) {
-  initialContext = parseInt(initialContext.substr(CONTEXT_ARG.length), 10)
-} else {
-  // In sandbox we need to pull this 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)
 }
 
@@ -324,7 +330,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)
 }