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_.
This commit is contained in:
Cheng Zhao 2018-07-10 17:15:40 +09:00
parent 9cbbb2a6c4
commit 4cdb1b8fc3
9 changed files with 139 additions and 113 deletions

View file

@ -32,7 +32,7 @@ namespace std {
template <typename Type1, typename Type2> template <typename Type1, typename Type2>
struct hash<std::pair<Type1, Type2>> { struct hash<std::pair<Type1, Type2>> {
std::size_t operator()(std::pair<Type1, Type2> value) const { 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("setRemoteCallbackFreer", &atom::RemoteCallbackFreer::BindTo);
dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo); dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo);
dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap<int32_t>::Create); dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap<int32_t>::Create);
dict.SetMethod("createDoubleIDWeakMap", dict.SetMethod(
&atom::api::KeyWeakMap<std::pair<int64_t, int32_t>>::Create); "createDoubleIDWeakMap",
&atom::api::KeyWeakMap<std::pair<std::string, int32_t>>::Create);
dict.SetMethod("requestGarbageCollectionForTesting", dict.SetMethod("requestGarbageCollectionForTesting",
&RequestGarbageCollectionForTesting); &RequestGarbageCollectionForTesting);
dict.SetMethod("isSameOrigin", &IsSameOrigin); dict.SetMethod("isSameOrigin", &IsSameOrigin);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,6 +9,18 @@ const bufferUtils = require('../../common/buffer-utils')
const callbacksRegistry = new CallbacksRegistry() const callbacksRegistry = new CallbacksRegistry()
const remoteObjectCache = v8Util.createIDWeakMap() 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. // Convert the arguments object into an array of meta data.
function wrapArgs (args, visited = new Set()) { function wrapArgs (args, visited = new Set()) {
const valueToMeta = (value) => { const valueToMeta = (value) => {
@ -107,7 +119,7 @@ function setObjectMembers (ref, object, metaId, members) {
} else { } else {
command = 'ELECTRON_BROWSER_MEMBER_CALL' 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) return metaToValue(ret)
} }
@ -126,7 +138,7 @@ function setObjectMembers (ref, object, metaId, members) {
} else if (member.type === 'get') { } else if (member.type === 'get') {
descriptor.get = () => { descriptor.get = () => {
const command = 'ELECTRON_BROWSER_MEMBER_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) return metaToValue(meta)
} }
@ -134,7 +146,7 @@ function setObjectMembers (ref, object, metaId, members) {
descriptor.set = (value) => { descriptor.set = (value) => {
const args = wrapArgs([value]) const args = wrapArgs([value])
const command = 'ELECTRON_BROWSER_MEMBER_SET' 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) if (meta != null) metaToValue(meta)
return value return value
} }
@ -164,7 +176,7 @@ function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
if (loaded) return if (loaded) return
loaded = true loaded = true
const command = 'ELECTRON_BROWSER_MEMBER_GET' 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) setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members)
} }
@ -224,7 +236,7 @@ function metaToValue (meta) {
} else { } else {
command = 'ELECTRON_BROWSER_FUNCTION_CALL' 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) return metaToValue(obj)
} }
ret = remoteFunction ret = remoteFunction
@ -237,7 +249,7 @@ function metaToValue (meta) {
Object.defineProperty(ret.constructor, 'name', { value: meta.name }) Object.defineProperty(ret.constructor, 'name', { value: meta.name })
// Track delegate obj's lifetime & tell browser to clean up when object is GCed. // 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) v8Util.setHiddenValue(ret, 'atomId', meta.id)
remoteObjectCache.set(meta.id, ret) remoteObjectCache.set(meta.id, ret)
return ret return ret
@ -264,60 +276,51 @@ function metaToException (meta) {
} }
// Browser calls a callback in renderer. // 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)) callbacksRegistry.apply(id, metaToValue(args))
}) })
// A callback in browser is released. // 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) callbacksRegistry.remove(id)
}) })
process.on('exit', () => {
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
ipcRenderer.sendSync(command, initialContext)
})
exports.require = (module) => { exports.require = (module) => {
const command = 'ELECTRON_BROWSER_REQUIRE' const command = 'ELECTRON_BROWSER_REQUIRE'
const meta = ipcRenderer.sendSync(command, module) const meta = ipcRenderer.sendSync(command, contextId, module)
return metaToValue(meta) return metaToValue(meta)
} }
// Alias to remote.require('electron').xxx. // Alias to remote.require('electron').xxx.
exports.getBuiltin = (module) => { exports.getBuiltin = (module) => {
const command = 'ELECTRON_BROWSER_GET_BUILTIN' const command = 'ELECTRON_BROWSER_GET_BUILTIN'
const meta = ipcRenderer.sendSync(command, module) const meta = ipcRenderer.sendSync(command, contextId, module)
return metaToValue(meta) return metaToValue(meta)
} }
exports.getCurrentWindow = () => { exports.getCurrentWindow = () => {
const command = 'ELECTRON_BROWSER_CURRENT_WINDOW' const command = 'ELECTRON_BROWSER_CURRENT_WINDOW'
const meta = ipcRenderer.sendSync(command) const meta = ipcRenderer.sendSync(command, contextId)
return metaToValue(meta) return metaToValue(meta)
} }
// Get current WebContents object. // Get current WebContents object.
exports.getCurrentWebContents = () => { exports.getCurrentWebContents = () => {
return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS')) return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', contextId))
}
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()
} }
// Get a global object in browser. // Get a global object in browser.
exports.getGlobal = (name) => { exports.getGlobal = (name) => {
const command = 'ELECTRON_BROWSER_GLOBAL' const command = 'ELECTRON_BROWSER_GLOBAL'
const meta = ipcRenderer.sendSync(command, name) const meta = ipcRenderer.sendSync(command, contextId, name)
return metaToValue(meta) return metaToValue(meta)
} }
@ -334,7 +337,7 @@ exports.createFunctionWithReturnValue = (returnValue) => {
// Get the guest WebContents from guestInstanceId. // Get the guest WebContents from guestInstanceId.
exports.getGuestWebContents = (guestInstanceId) => { exports.getGuestWebContents = (guestInstanceId) => {
const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS' const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS'
const meta = ipcRenderer.sendSync(command, guestInstanceId) const meta = ipcRenderer.sendSync(command, contextId, guestInstanceId)
return metaToValue(meta) return metaToValue(meta)
} }

View file

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