diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index a7bf28a8e2cfd..7eda0e667c90c 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -12,17 +12,27 @@ #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/node_includes.h" #include "base/hash.h" +#include "base/process/process_handle.h" +#include "base/strings/stringprintf.h" #include "native_mate/dictionary.h" #include "url/origin.h" #include "v8/include/v8-profiler.h" +// This is defined in later versions of Chromium, remove this if you see +// compiler complaining duplicate defines. +#if defined(OS_WIN) || defined(OS_FUCHSIA) +#define CrPRIdPid "ld" +#else +#define CrPRIdPid "d" +#endif + namespace std { // The hash function used by DoubleIDWeakMap. template struct hash> { std::size_t operator()(std::pair value) const { - return base::HashInts(value.first, value.second); + return base::HashInts(base::Hash(value.first), value.second); } }; @@ -90,6 +100,16 @@ int32_t GetObjectHash(v8::Local object) { return object->GetIdentityHash(); } +std::string GetContextID(v8::Isolate* isolate) { + // When a page is reloaded, V8 and blink may have optimizations that do not + // free blink::WebLocalFrame and v8::Context and reuse them for the new page, + // while we always recreate node::Environment when a page is loaded. + // So the only reliable way to return an identity for a page, is to return the + // address of the node::Environment instance. + node::Environment* env = node::Environment::GetCurrent(isolate); + return base::StringPrintf("%" CrPRIdPid "-%p", base::GetCurrentProcId(), env); +} + void TakeHeapSnapshot(v8::Isolate* isolate) { isolate->GetHeapProfiler()->TakeHeapSnapshot(); } @@ -112,12 +132,14 @@ void Initialize(v8::Local exports, dict.SetMethod("setHiddenValue", &SetHiddenValue); dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue); dict.SetMethod("getObjectHash", &GetObjectHash); + dict.SetMethod("getContextId", &GetContextID); dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot); dict.SetMethod("setRemoteCallbackFreer", &atom::RemoteCallbackFreer::BindTo); dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo); dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap::Create); - dict.SetMethod("createDoubleIDWeakMap", - &atom::api::KeyWeakMap>::Create); + dict.SetMethod( + "createDoubleIDWeakMap", + &atom::api::KeyWeakMap>::Create); dict.SetMethod("requestGarbageCollectionForTesting", &RequestGarbageCollectionForTesting); dict.SetMethod("isSameOrigin", &IsSameOrigin); diff --git a/atom/common/api/remote_callback_freer.cc b/atom/common/api/remote_callback_freer.cc index 3e3e68cf5ab73..eba828591353d 100644 --- a/atom/common/api/remote_callback_freer.cc +++ b/atom/common/api/remote_callback_freer.cc @@ -15,17 +15,20 @@ namespace atom { // static void RemoteCallbackFreer::BindTo(v8::Isolate* isolate, v8::Local 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 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) { diff --git a/atom/common/api/remote_callback_freer.h b/atom/common/api/remote_callback_freer.h index 8fe80c8d4774c..d0a05d9fa812c 100644 --- a/atom/common/api/remote_callback_freer.h +++ b/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 + #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 target, + const std::string& context_id, int object_id, content::WebContents* web_conents); protected: RemoteCallbackFreer(v8::Isolate* isolate, v8::Local 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); diff --git a/atom/common/api/remote_object_freer.cc b/atom/common/api/remote_object_freer.cc index 142e6cb8622fa..8afe8b9a32bc4 100644 --- a/atom/common/api/remote_object_freer.cc +++ b/atom/common/api/remote_object_freer.cc @@ -29,14 +29,17 @@ content::RenderFrame* GetCurrentRenderFrame() { // static void RemoteObjectFreer::BindTo(v8::Isolate* isolate, v8::Local 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 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)); diff --git a/atom/common/api/remote_object_freer.h b/atom/common/api/remote_object_freer.h index ece52122dbae7..f86de92d8fa06 100644 --- a/atom/common/api/remote_object_freer.h +++ b/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 + #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 target, + const std::string& context_id, int object_id); protected: RemoteObjectFreer(v8::Isolate* isolate, v8::Local 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_; diff --git a/lib/browser/objects-registry.js b/lib/browser/objects-registry.js index bfb9091e6584c..b2667454152c2 100644 --- a/lib/browser/objects-registry.js +++ b/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) diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 931e5525d5b39..31c38ed7ce14f 100644 --- a/lib/browser/rpc-server.js +++ b/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) } }) diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index 107ebfe470701..55359bad5c44e 100644 --- a/lib/renderer/api/remote.js +++ b/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) } diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index 544652e44756f..590991177595e 100644 --- a/lib/renderer/web-view/web-view.js +++ b/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) })