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 9431677e79
commit adf49daaac
9 changed files with 139 additions and 113 deletions

View file

@ -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);

View file

@ -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) {

View file

@ -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);

View file

@ -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));

View file

@ -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_;

View file

@ -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)

View file

@ -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)
}
})

View file

@ -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)
}

View file

@ -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)
})