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:
parent
9431677e79
commit
adf49daaac
9 changed files with 139 additions and 113 deletions
|
@ -32,7 +32,7 @@ namespace std {
|
|||
template <typename Type1, typename Type2>
|
||||
struct hash<std::pair<Type1, Type2>> {
|
||||
std::size_t operator()(std::pair<Type1, Type2> value) const {
|
||||
return base::HashInts<Type1, Type2>(value.first, value.second);
|
||||
return base::HashInts(base::Hash(value.first), value.second);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -137,8 +137,9 @@ void Initialize(v8::Local<v8::Object> exports,
|
|||
dict.SetMethod("setRemoteCallbackFreer", &atom::RemoteCallbackFreer::BindTo);
|
||||
dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo);
|
||||
dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap<int32_t>::Create);
|
||||
dict.SetMethod("createDoubleIDWeakMap",
|
||||
&atom::api::KeyWeakMap<std::pair<int64_t, int32_t>>::Create);
|
||||
dict.SetMethod(
|
||||
"createDoubleIDWeakMap",
|
||||
&atom::api::KeyWeakMap<std::pair<std::string, int32_t>>::Create);
|
||||
dict.SetMethod("requestGarbageCollectionForTesting",
|
||||
&RequestGarbageCollectionForTesting);
|
||||
dict.SetMethod("isSameOrigin", &IsSameOrigin);
|
||||
|
|
|
@ -15,17 +15,20 @@ namespace atom {
|
|||
// static
|
||||
void RemoteCallbackFreer::BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
content::WebContents* web_contents) {
|
||||
new RemoteCallbackFreer(isolate, target, object_id, web_contents);
|
||||
new RemoteCallbackFreer(isolate, target, context_id, object_id, web_contents);
|
||||
}
|
||||
|
||||
RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
content::WebContents* web_contents)
|
||||
: ObjectLifeMonitor(isolate, target),
|
||||
content::WebContentsObserver(web_contents),
|
||||
context_id_(context_id),
|
||||
object_id_(object_id) {}
|
||||
|
||||
RemoteCallbackFreer::~RemoteCallbackFreer() {}
|
||||
|
@ -34,6 +37,7 @@ void RemoteCallbackFreer::RunDestructor() {
|
|||
base::string16 channel =
|
||||
base::ASCIIToUTF16("ELECTRON_RENDERER_RELEASE_CALLBACK");
|
||||
base::ListValue args;
|
||||
args.AppendString(context_id_);
|
||||
args.AppendInteger(object_id_);
|
||||
auto* frame_host = web_contents()->GetMainFrame();
|
||||
if (frame_host) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -29,14 +29,17 @@ content::RenderFrame* GetCurrentRenderFrame() {
|
|||
// static
|
||||
void RemoteObjectFreer::BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
const std::string& context_id,
|
||||
int object_id) {
|
||||
new RemoteObjectFreer(isolate, target, object_id);
|
||||
new RemoteObjectFreer(isolate, target, context_id, object_id);
|
||||
}
|
||||
|
||||
RemoteObjectFreer::RemoteObjectFreer(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
const std::string& context_id,
|
||||
int object_id)
|
||||
: ObjectLifeMonitor(isolate, target),
|
||||
context_id_(context_id),
|
||||
object_id_(object_id),
|
||||
routing_id_(MSG_ROUTING_NONE) {
|
||||
content::RenderFrame* render_frame = GetCurrentRenderFrame();
|
||||
|
@ -56,6 +59,7 @@ void RemoteObjectFreer::RunDestructor() {
|
|||
base::string16 channel = base::ASCIIToUTF16("ipc-message");
|
||||
base::ListValue args;
|
||||
args.AppendString("ELECTRON_BROWSER_DEREFERENCE");
|
||||
args.AppendString(context_id_);
|
||||
args.AppendInteger(object_id_);
|
||||
render_frame->Send(new AtomFrameHostMsg_Message(render_frame->GetRoutingID(),
|
||||
channel, args));
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#ifndef ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_
|
||||
#define ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "atom/common/api/object_life_monitor.h"
|
||||
|
||||
namespace atom {
|
||||
|
@ -13,17 +15,20 @@ class RemoteObjectFreer : public ObjectLifeMonitor {
|
|||
public:
|
||||
static void BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
const std::string& context_id,
|
||||
int object_id);
|
||||
|
||||
protected:
|
||||
RemoteObjectFreer(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
const std::string& context_id,
|
||||
int object_id);
|
||||
~RemoteObjectFreer() override;
|
||||
|
||||
void RunDestructor() override;
|
||||
|
||||
private:
|
||||
std::string context_id_;
|
||||
int object_id_;
|
||||
int routing_id_;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
let i, len, member, ref, returnValue
|
||||
switch (meta.type) {
|
||||
|
@ -178,7 +178,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':
|
||||
|
@ -204,26 +204,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
|
||||
}
|
||||
|
@ -236,19 +236,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 err, 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
|
||||
|
@ -261,45 +261,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(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) {
|
||||
|
@ -309,30 +309,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(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) {
|
||||
|
@ -342,30 +342,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(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) {
|
||||
|
@ -375,11 +375,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)
|
||||
|
||||
|
@ -387,14 +387,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) => {
|
||||
|
@ -402,16 +402,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)
|
||||
|
@ -423,7 +423,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)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue