diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index a7946786c514..ee6ad94ba77a 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -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"); diff --git a/atom/browser/lib/objects-registry.js b/atom/browser/lib/objects-registry.js index e9d6d365fa5b..c3f70c976ecb 100644 --- a/atom/browser/lib/objects-registry.js +++ b/atom/browser/lib/objects-registry.js @@ -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]; diff --git a/atom/browser/lib/rpc-server.js b/atom/browser/lib/rpc-server.js index 64785879b3f2..20ee8fbdeae1 100644 --- a/atom/browser/lib/rpc-server.js +++ b/atom/browser/lib/rpc-server.js @@ -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); diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index c86335adb15e..f50d3485eba6 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -2,49 +2,15 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include #include #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 -struct LeakedPersistentTraits { - typedef v8::Persistent > LeakedPersistent; - static const bool kResetInDestructor = false; - template - static V8_INLINE void Copy(const v8::Persistent& source, - LeakedPersistent* dest) { - // do nothing, just allow copy - } -}; - -// The handles are leaked on purpose. -using FunctionTemplateHandle = - LeakedPersistentTraits::LeakedPersistent; -std::map function_templates_; - -v8::Local CreateObjectWithName(v8::Isolate* isolate, - const std::string& name) { - if (name == "Object") - return v8::Object::New(isolate); - - if (ContainsKey(function_templates_, name)) - return v8::Local::New( - isolate, function_templates_[name])->GetFunction()->NewInstance(); - - v8::Local t = v8::FunctionTemplate::New(isolate); - t->SetClassName(mate::StringToV8(isolate, name)); - function_templates_[name] = FunctionTemplateHandle(isolate, t); - return t->GetFunction()->NewInstance(); -} - v8::Local GetHiddenValue(v8::Local object, v8::Local key) { return object->GetHiddenValue(key); @@ -78,7 +44,6 @@ void TakeHeapSnapshot(v8::Isolate* isolate) { void Initialize(v8::Local exports, v8::Local unused, v8::Local 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); diff --git a/atom/renderer/api/lib/remote.js b/atom/renderer/api/lib/remote.js index d28ec6dfcbd0..a9798338faa6 100644 --- a/atom/renderer/api/lib/remote.js +++ b/atom/renderer/api/lib/remote.js @@ -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.