Merge pull request #4570 from atom/cleanup-remote

Cleanup code of remote module
This commit is contained in:
Cheng Zhao 2016-02-22 16:10:55 +08:00
commit 537ead8917
5 changed files with 78 additions and 192 deletions

View file

@ -296,6 +296,7 @@ WebContents::~WebContents() {
// The WebContentsDestroyed will not be called automatically because we
// unsubscribe from webContents before destroying it. So we have to manually
// call it here to make sure "destroyed" event is emitted.
RenderViewDeleted(web_contents()->GetRenderViewHost());
WebContentsDestroyed();
}
}
@ -485,17 +486,7 @@ void WebContents::BeforeUnloadFired(const base::TimeTicks& proceed_time) {
}
void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) {
int process_id = render_view_host->GetProcess()->GetID();
Emit("render-view-deleted", process_id);
// process.emit('ATOM_BROWSER_RELEASE_RENDER_VIEW', processId);
// Tell the rpc server that a render view has been deleted and we need to
// release all objects owned by it.
v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
node::Environment* env = node::Environment::GetCurrent(isolate());
mate::EmitEvent(isolate(), env->process_object(),
"ATOM_BROWSER_RELEASE_RENDER_VIEW", process_id);
Emit("render-view-deleted", render_view_host->GetProcess()->GetID());
}
void WebContents::RenderProcessGone(base::TerminationStatus status) {
@ -675,9 +666,6 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) {
// be destroyed on close, and WebContentsDestroyed would be called for it, so
// we need to make sure the api::WebContents is also deleted.
void WebContents::WebContentsDestroyed() {
// The RenderViewDeleted was not called when the WebContents is destroyed.
RenderViewDeleted(web_contents()->GetRenderViewHost());
// This event is only for internal use, which is emitted when WebContents is
// being destroyed.
Emit("will-destroy");

View file

@ -1,13 +1,9 @@
'use strict';
const EventEmitter = require('events').EventEmitter;
const v8Util = process.atomBinding('v8_util');
class ObjectsRegistry extends EventEmitter {
class ObjectsRegistry {
constructor() {
super();
this.setMaxListeners(Number.MAX_VALUE);
this.nextId = 0;
// Stores all objects by ref-counting.
@ -15,70 +11,61 @@ class ObjectsRegistry extends EventEmitter {
this.storage = {};
// Stores the IDs of objects referenced by WebContents.
// (webContentsId) => {(id) => (count)}
// (webContentsId) => [id]
this.owners = {};
}
// Register a new object, the object would be kept referenced until you release
// it explicitly.
add(webContentsId, obj) {
var base, base1, id;
id = this.saveToStorage(obj);
// 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) {
// Get or assign an ID to the object.
let id = this.saveToStorage(obj);
// Remember the owner.
if ((base = this.owners)[webContentsId] == null) {
base[webContentsId] = {};
// Add object to the set of referenced objects.
let webContentsId = webContents.getId();
let owner = this.owners[webContentsId];
if (!owner) {
owner = this.owners[webContentsId] = new Set();
// Clear the storage when webContents is reloaded/navigated.
webContents.once('render-view-deleted', (event, id) => {
this.clear(id);
});
}
if ((base1 = this.owners[webContentsId])[id] == null) {
base1[id] = 0;
if (!owner.has(id)) {
owner.add(id);
// Increase reference count if not referenced before.
this.storage[id].count++;
}
this.owners[webContentsId][id]++;
// Returns object's id
return id;
}
// Get an object according to its ID.
get(id) {
var ref;
return (ref = this.storage[id]) != null ? ref.object : void 0;
return this.storage[id].object;
}
// Dereference an object according to its ID.
remove(webContentsId, id) {
var pointer;
this.dereference(id, 1);
// Dereference from the storage.
this.dereference(id);
// Also reduce the count in owner.
pointer = this.owners[webContentsId];
if (pointer == null) {
return;
}
--pointer[id];
if (pointer[id] === 0) {
return delete pointer[id];
}
// Also remove the reference in owner.
this.owners[webContentsId].delete(id);
}
// Clear all references to objects refrenced by the WebContents.
clear(webContentsId) {
var count, id, ref;
this.emit("clear-" + webContentsId);
if (this.owners[webContentsId] == null) {
let owner = this.owners[webContentsId];
if (!owner)
return;
}
ref = this.owners[webContentsId];
for (id in ref) {
count = ref[id];
this.dereference(id, count);
}
return delete this.owners[webContentsId];
for (let id of owner)
this.dereference(id);
delete this.owners[webContentsId];
}
// Private: Saves the object into storage and assigns an ID for it.
saveToStorage(object) {
var id;
id = v8Util.getHiddenValue(object, 'atomId');
let id = v8Util.getHiddenValue(object, 'atomId');
if (!id) {
id = ++this.nextId;
this.storage[id] = {
@ -87,18 +74,16 @@ class ObjectsRegistry extends EventEmitter {
};
v8Util.setHiddenValue(object, 'atomId', id);
}
++this.storage[id].count;
return id;
}
// Private: Dereference the object from store.
dereference(id, count) {
var pointer;
pointer = this.storage[id];
dereference(id) {
let pointer = this.storage[id];
if (pointer == null) {
return;
}
pointer.count -= count;
pointer.count -= 1;
if (pointer.count === 0) {
v8Util.deleteHiddenValue(pointer.object, 'atomId');
return delete this.storage[id];

View file

@ -11,7 +11,9 @@ const FUNCTION_PROPERTIES = [
'length', 'name', 'arguments', 'caller', 'prototype',
];
var slice = [].slice;
// The remote functions in renderer processes.
// (webContentsId) => {id: Function}
let rendererFunctions = {};
// Return the description of object's members:
let getObjectMemebers = function(object) {
@ -98,7 +100,7 @@ var valueToMeta = function(sender, value, optimizeSimpleObject) {
// 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.getId(), value);
meta.id = objectsRegistry.add(sender, value);
meta.members = getObjectMemebers(value);
meta.proto = getObjectPrototype(value);
} else if (meta.type === 'buffer') {
@ -145,7 +147,7 @@ var exceptionToMeta = function(error) {
var unwrapArgs = function(sender, args) {
var metaToValue;
metaToValue = function(meta) {
var i, len, member, ref, rendererReleased, returnValue;
var i, len, member, ref, returnValue;
switch (meta.type) {
case 'value':
return meta.value;
@ -162,7 +164,9 @@ var unwrapArgs = function(sender, args) {
then: metaToValue(meta.then)
});
case 'object': {
let ret = v8Util.createObjectWithName(meta.name);
let ret = {};
Object.defineProperty(ret.constructor, 'name', { value: meta.name });
ref = meta.members;
for (i = 0, len = ref.length; i < len; i++) {
member = ref[i];
@ -177,32 +181,30 @@ var unwrapArgs = function(sender, args) {
};
case 'function': {
// Cache the callbacks in renderer.
if (!sender.callbacks) {
sender.callbacks = new IDWeakMap;
sender.on('render-view-deleted', function() {
return this.callbacks.clear();
let webContentsId = sender.getId();
let callbacks = rendererFunctions[webContentsId];
if (!callbacks) {
callbacks = rendererFunctions[webContentsId] = new IDWeakMap;
sender.once('render-view-deleted', function(event, id) {
callbacks.clear();
delete rendererFunctions[id];
});
}
if (sender.callbacks.has(meta.id))
return sender.callbacks.get(meta.id);
// Prevent the callback from being called when its page is gone.
rendererReleased = false;
sender.once('render-view-deleted', function() {
rendererReleased = true;
});
if (callbacks.has(meta.id))
return callbacks.get(meta.id);
let callIntoRenderer = function(...args) {
if (rendererReleased || sender.isDestroyed())
if ((webContentsId in rendererFunctions) && !sender.isDestroyed())
sender.send('ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args));
else
throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`);
sender.send('ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args));
};
v8Util.setDestructor(callIntoRenderer, function() {
if (!rendererReleased && !sender.isDestroyed())
if ((webContentsId in rendererFunctions) && !sender.isDestroyed())
sender.send('ATOM_RENDERER_RELEASE_CALLBACK', meta.id);
});
sender.callbacks.set(meta.id, callIntoRenderer);
callbacks.set(meta.id, callIntoRenderer);
return callIntoRenderer;
}
default:
@ -237,11 +239,6 @@ var callFunction = function(event, func, caller, args) {
}
};
// Send by BrowserWindow when its render view is deleted.
process.on('ATOM_BROWSER_RELEASE_RENDER_VIEW', function(id) {
return objectsRegistry.clear(id);
});
ipcMain.on('ATOM_BROWSER_REQUIRE', function(event, module) {
try {
return event.returnValue = valueToMeta(event.sender, process.mainModule.require(module));
@ -279,14 +276,13 @@ ipcMain.on('ATOM_BROWSER_CURRENT_WEB_CONTENTS', function(event) {
});
ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function(event, id, args) {
var constructor, obj;
try {
args = unwrapArgs(event.sender, args);
constructor = objectsRegistry.get(id);
let constructor = objectsRegistry.get(id);
// Call new with array of arguments.
// http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)));
let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)));
return event.returnValue = valueToMeta(event.sender, obj);
} catch (error) {
return event.returnValue = exceptionToMeta(error);
@ -294,10 +290,9 @@ ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function(event, id, args) {
});
ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function(event, id, args) {
var func;
try {
args = unwrapArgs(event.sender, args);
func = objectsRegistry.get(id);
let func = objectsRegistry.get(id);
return callFunction(event, func, global, args);
} catch (error) {
return event.returnValue = exceptionToMeta(error);
@ -305,13 +300,12 @@ ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function(event, id, args) {
});
ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function(event, id, method, args) {
var constructor, obj;
try {
args = unwrapArgs(event.sender, args);
constructor = objectsRegistry.get(id)[method];
let constructor = objectsRegistry.get(id)[method];
// Call new with array of arguments.
obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)));
let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)));
return event.returnValue = valueToMeta(event.sender, obj);
} catch (error) {
return event.returnValue = exceptionToMeta(error);
@ -319,10 +313,9 @@ ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function(event, id, method, args)
});
ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function(event, id, method, args) {
var obj;
try {
args = unwrapArgs(event.sender, args);
obj = objectsRegistry.get(id);
let obj = objectsRegistry.get(id);
return callFunction(event, obj[method], obj, args);
} catch (error) {
return event.returnValue = exceptionToMeta(error);
@ -330,9 +323,8 @@ ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function(event, id, method, args) {
});
ipcMain.on('ATOM_BROWSER_MEMBER_SET', function(event, id, name, value) {
var obj;
try {
obj = objectsRegistry.get(id);
let obj = objectsRegistry.get(id);
obj[name] = value;
return event.returnValue = null;
} catch (error) {
@ -341,9 +333,8 @@ ipcMain.on('ATOM_BROWSER_MEMBER_SET', function(event, id, name, value) {
});
ipcMain.on('ATOM_BROWSER_MEMBER_GET', function(event, id, name) {
var obj;
try {
obj = objectsRegistry.get(id);
let obj = objectsRegistry.get(id);
return event.returnValue = valueToMeta(event.sender, obj[name]);
} catch (error) {
return event.returnValue = exceptionToMeta(error);
@ -355,21 +346,18 @@ ipcMain.on('ATOM_BROWSER_DEREFERENCE', function(event, id) {
});
ipcMain.on('ATOM_BROWSER_GUEST_WEB_CONTENTS', function(event, guestInstanceId) {
var guestViewManager;
try {
guestViewManager = require('./guest-view-manager');
let guestViewManager = require('./guest-view-manager');
return event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId));
} catch (error) {
return event.returnValue = exceptionToMeta(error);
}
});
ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function() {
var args, event, guest, guestInstanceId, guestViewManager, method;
event = arguments[0], guestInstanceId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : [];
ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function(event, guestInstanceId, method, ...args) {
try {
guestViewManager = require('./guest-view-manager');
guest = guestViewManager.getGuest(guestInstanceId);
let guestViewManager = require('./guest-view-manager');
let guest = guestViewManager.getGuest(guestInstanceId);
return guest[method].apply(guest, args);
} catch (error) {
return event.returnValue = exceptionToMeta(error);

View file

@ -2,49 +2,15 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include <map>
#include <string>
#include "atom/common/api/object_life_monitor.h"
#include "atom/common/node_includes.h"
#include "base/stl_util.h"
#include "native_mate/dictionary.h"
#include "v8/include/v8-profiler.h"
namespace {
// A Persistent that can be copied and will not free itself.
template<class T>
struct LeakedPersistentTraits {
typedef v8::Persistent<T, LeakedPersistentTraits<T> > LeakedPersistent;
static const bool kResetInDestructor = false;
template<class S, class M>
static V8_INLINE void Copy(const v8::Persistent<S, M>& source,
LeakedPersistent* dest) {
// do nothing, just allow copy
}
};
// The handles are leaked on purpose.
using FunctionTemplateHandle =
LeakedPersistentTraits<v8::FunctionTemplate>::LeakedPersistent;
std::map<std::string, FunctionTemplateHandle> function_templates_;
v8::Local<v8::Object> CreateObjectWithName(v8::Isolate* isolate,
const std::string& name) {
if (name == "Object")
return v8::Object::New(isolate);
if (ContainsKey(function_templates_, name))
return v8::Local<v8::FunctionTemplate>::New(
isolate, function_templates_[name])->GetFunction()->NewInstance();
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
t->SetClassName(mate::StringToV8(isolate, name));
function_templates_[name] = FunctionTemplateHandle(isolate, t);
return t->GetFunction()->NewInstance();
}
v8::Local<v8::Value> GetHiddenValue(v8::Local<v8::Object> object,
v8::Local<v8::String> key) {
return object->GetHiddenValue(key);
@ -78,7 +44,6 @@ void TakeHeapSnapshot(v8::Isolate* isolate) {
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
v8::Local<v8::Context> context, void* priv) {
mate::Dictionary dict(context->GetIsolate(), exports);
dict.SetMethod("createObjectWithName", &CreateObjectWithName);
dict.SetMethod("getHiddenValue", &GetHiddenValue);
dict.SetMethod("setHiddenValue", &SetHiddenValue);
dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue);

View file

@ -141,7 +141,7 @@ let setObjectPrototype = function(object, metaId, descriptor) {
};
// Convert meta data from browser into real value.
var metaToValue = function(meta) {
let metaToValue = function(meta) {
var el, i, len, ref1, results, ret;
switch (meta.type) {
case 'value':
@ -257,73 +257,33 @@ for (var name in browserModules) {
}
// Get remote module.
// (Just like node's require, the modules are cached permanently, note that this
// is safe leak since the object is not expected to get freed in browser)
var moduleCache = {};
exports.require = function(module) {
var meta;
if (moduleCache[module] != null) {
return moduleCache[module];
}
meta = ipcRenderer.sendSync('ATOM_BROWSER_REQUIRE', module);
return moduleCache[module] = metaToValue(meta);
return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_REQUIRE', module));
};
// Optimize require('electron').
moduleCache.electron = exports;
// Alias to remote.require('electron').xxx.
var builtinCache = {};
exports.getBuiltin = function(module) {
var meta;
if (builtinCache[module] != null) {
return builtinCache[module];
}
meta = ipcRenderer.sendSync('ATOM_BROWSER_GET_BUILTIN', module);
return builtinCache[module] = metaToValue(meta);
return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_GET_BUILTIN', module));
};
// Get current BrowserWindow object.
var windowCache = null;
// Get current BrowserWindow.
exports.getCurrentWindow = function() {
var meta;
if (windowCache != null) {
return windowCache;
}
meta = ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WINDOW');
return windowCache = metaToValue(meta);
return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WINDOW'));
};
// Get current WebContents object.
var webContentsCache = null;
exports.getCurrentWebContents = function() {
var meta;
if (webContentsCache != null) {
return webContentsCache;
}
meta = ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WEB_CONTENTS');
return webContentsCache = metaToValue(meta);
return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WEB_CONTENTS'));
};
// Get a global object in browser.
exports.getGlobal = function(name) {
var meta;
meta = ipcRenderer.sendSync('ATOM_BROWSER_GLOBAL', name);
return metaToValue(meta);
return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_GLOBAL', name));
};
// Get the process object in browser.
var processCache = null;
exports.__defineGetter__('process', function() {
if (processCache == null) {
processCache = exports.getGlobal('process');
}
return processCache;
return exports.getGlobal('process');
});
// Create a funtion that will return the specifed value when called in browser.