Merge pull request #5293 from electron/native-gc

Dereference remote objects with native code
This commit is contained in:
Cheng Zhao 2016-04-26 19:39:48 +09:00
commit cdb4444caa
13 changed files with 246 additions and 78 deletions

View file

@ -2,9 +2,9 @@
// Use of this source code is governed by the MIT license that can be // Use of this source code is governed by the MIT license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "atom/browser/api/atom_api_web_contents.h"
#include "atom/browser/web_contents_preferences.h" #include "atom/browser/web_contents_preferences.h"
#include "atom/browser/web_view_manager.h" #include "atom/browser/web_view_manager.h"
#include "atom/common/native_mate_converters/content_converter.h"
#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/native_mate_converters/value_converter.h"
#include "atom/common/node_includes.h" #include "atom/common/node_includes.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
@ -12,22 +12,6 @@
using atom::WebContentsPreferences; using atom::WebContentsPreferences;
namespace mate {
template<>
struct Converter<content::WebContents*> {
static bool FromV8(v8::Isolate* isolate, v8::Local<v8::Value> val,
content::WebContents** out) {
atom::api::WebContents* contents;
if (!Converter<atom::api::WebContents*>::FromV8(isolate, val, &contents))
return false;
*out = contents->web_contents();
return true;
}
};
} // namespace mate
namespace { namespace {
atom::WebViewManager* GetWebViewManager(content::WebContents* web_contents) { atom::WebViewManager* GetWebViewManager(content::WebContents* web_contents) {

View file

@ -4,7 +4,9 @@
#include <string> #include <string>
#include "atom/common/api/object_life_monitor.h" #include "atom/common/api/remote_callback_freer.h"
#include "atom/common/api/remote_object_freer.h"
#include "atom/common/native_mate_converters/content_converter.h"
#include "atom/common/node_includes.h" #include "atom/common/node_includes.h"
#include "native_mate/dictionary.h" #include "native_mate/dictionary.h"
#include "v8/include/v8-profiler.h" #include "v8/include/v8-profiler.h"
@ -51,12 +53,6 @@ int32_t GetObjectHash(v8::Local<v8::Object> object) {
return object->GetIdentityHash(); return object->GetIdentityHash();
} }
void SetDestructor(v8::Isolate* isolate,
v8::Local<v8::Object> object,
v8::Local<v8::Function> callback) {
atom::ObjectLifeMonitor::BindTo(isolate, object, callback);
}
void TakeHeapSnapshot(v8::Isolate* isolate) { void TakeHeapSnapshot(v8::Isolate* isolate) {
isolate->GetHeapProfiler()->TakeHeapSnapshot(); isolate->GetHeapProfiler()->TakeHeapSnapshot();
} }
@ -68,8 +64,9 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
dict.SetMethod("setHiddenValue", &SetHiddenValue); dict.SetMethod("setHiddenValue", &SetHiddenValue);
dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue); dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue);
dict.SetMethod("getObjectHash", &GetObjectHash); dict.SetMethod("getObjectHash", &GetObjectHash);
dict.SetMethod("setDestructor", &SetDestructor);
dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot); dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot);
dict.SetMethod("setRemoteCallbackFreer", &atom::RemoteCallbackFreer::BindTo);
dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo);
} }
} // namespace } // namespace

View file

@ -10,30 +10,28 @@
namespace atom { namespace atom {
// static
void ObjectLifeMonitor::BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
v8::Local<v8::Function> destructor) {
new ObjectLifeMonitor(isolate, target, destructor);
}
ObjectLifeMonitor::ObjectLifeMonitor(v8::Isolate* isolate, ObjectLifeMonitor::ObjectLifeMonitor(v8::Isolate* isolate,
v8::Local<v8::Object> target, v8::Local<v8::Object> target)
v8::Local<v8::Function> destructor)
: isolate_(isolate), : isolate_(isolate),
context_(isolate, isolate->GetCurrentContext()), context_(isolate, isolate->GetCurrentContext()),
target_(isolate, target), target_(isolate, target),
destructor_(isolate, destructor),
weak_ptr_factory_(this) { weak_ptr_factory_(this) {
target_.SetWeak(this, OnObjectGC, v8::WeakCallbackType::kParameter); target_.SetWeak(this, OnObjectGC, v8::WeakCallbackType::kParameter);
} }
ObjectLifeMonitor::~ObjectLifeMonitor() {
if (target_.IsEmpty())
return;
target_.ClearWeak();
target_.Reset();
}
// static // static
void ObjectLifeMonitor::OnObjectGC( void ObjectLifeMonitor::OnObjectGC(
const v8::WeakCallbackInfo<ObjectLifeMonitor>& data) { const v8::WeakCallbackInfo<ObjectLifeMonitor>& data) {
ObjectLifeMonitor* self = data.GetParameter(); ObjectLifeMonitor* self = data.GetParameter();
self->target_.Reset(); self->target_.Reset();
self->RunCallback(); self->RunDestructor();
data.SetSecondPassCallback(Free); data.SetSecondPassCallback(Free);
} }
@ -43,13 +41,4 @@ void ObjectLifeMonitor::Free(
delete data.GetParameter(); delete data.GetParameter();
} }
void ObjectLifeMonitor::RunCallback() {
v8::HandleScope handle_scope(isolate_);
v8::Local<v8::Context> context = v8::Local<v8::Context>::New(
isolate_, context_);
v8::Context::Scope context_scope(context);
v8::Local<v8::Function>::New(isolate_, destructor_)->Call(
context->Global(), 0, nullptr);
}
} // namespace atom } // namespace atom

View file

@ -12,25 +12,19 @@
namespace atom { namespace atom {
class ObjectLifeMonitor { class ObjectLifeMonitor {
public: protected:
static void BindTo(v8::Isolate* isolate, ObjectLifeMonitor(v8::Isolate* isolate, v8::Local<v8::Object> target);
v8::Local<v8::Object> target, virtual ~ObjectLifeMonitor();
v8::Local<v8::Function> destructor);
virtual void RunDestructor() = 0;
private: private:
ObjectLifeMonitor(v8::Isolate* isolate,
v8::Local<v8::Object> target,
v8::Local<v8::Function> destructor);
static void OnObjectGC(const v8::WeakCallbackInfo<ObjectLifeMonitor>& data); static void OnObjectGC(const v8::WeakCallbackInfo<ObjectLifeMonitor>& data);
static void Free(const v8::WeakCallbackInfo<ObjectLifeMonitor>& data); static void Free(const v8::WeakCallbackInfo<ObjectLifeMonitor>& data);
void RunCallback();
v8::Isolate* isolate_; v8::Isolate* isolate_;
v8::Global<v8::Context> context_; v8::Global<v8::Context> context_;
v8::Global<v8::Object> target_; v8::Global<v8::Object> target_;
v8::Global<v8::Function> destructor_;
base::WeakPtrFactory<ObjectLifeMonitor> weak_ptr_factory_; base::WeakPtrFactory<ObjectLifeMonitor> weak_ptr_factory_;

View file

@ -0,0 +1,47 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/common/api/remote_callback_freer.h"
#include "atom/common/api/api_messages.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
namespace atom {
// static
void RemoteCallbackFreer::BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
int object_id,
content::WebContents* web_contents) {
new RemoteCallbackFreer(isolate, target, object_id, web_contents);
}
RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
v8::Local<v8::Object> target,
int object_id,
content::WebContents* web_contents)
: ObjectLifeMonitor(isolate, target),
content::WebContentsObserver(web_contents),
object_id_(object_id) {
}
RemoteCallbackFreer::~RemoteCallbackFreer() {
}
void RemoteCallbackFreer::RunDestructor() {
base::string16 channel =
base::ASCIIToUTF16("ELECTRON_RENDERER_RELEASE_CALLBACK");
base::ListValue args;
args.AppendInteger(object_id_);
Send(new AtomViewMsg_Message(routing_id(), channel, args));
Observe(nullptr);
}
void RemoteCallbackFreer::RenderViewDeleted(content::RenderViewHost*) {
delete this;
}
} // namespace atom

View file

@ -0,0 +1,40 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_
#define ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_
#include "atom/common/api/object_life_monitor.h"
#include "content/public/browser/web_contents_observer.h"
namespace atom {
class RemoteCallbackFreer : public ObjectLifeMonitor,
public content::WebContentsObserver {
public:
static void BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
int object_id,
content::WebContents* web_conents);
protected:
RemoteCallbackFreer(v8::Isolate* isolate,
v8::Local<v8::Object> target,
int object_id,
content::WebContents* web_conents);
~RemoteCallbackFreer() override;
void RunDestructor() override;
// content::WebContentsObserver:
void RenderViewDeleted(content::RenderViewHost*) override;
private:
int object_id_;
DISALLOW_COPY_AND_ASSIGN(RemoteCallbackFreer);
};
} // namespace atom
#endif // ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_

View file

@ -0,0 +1,63 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/common/api/remote_object_freer.h"
#include "atom/common/api/api_messages.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "content/public/renderer/render_view.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebView.h"
using blink::WebLocalFrame;
using blink::WebView;
namespace atom {
namespace {
content::RenderView* GetCurrentRenderView() {
WebLocalFrame* frame = WebLocalFrame::frameForCurrentContext();
if (!frame)
return nullptr;
WebView* view = frame->view();
if (!view)
return nullptr; // can happen during closing.
return content::RenderView::FromWebView(view);
}
} // namespace
// static
void RemoteObjectFreer::BindTo(
v8::Isolate* isolate, v8::Local<v8::Object> target, int object_id) {
new RemoteObjectFreer(isolate, target, object_id);
}
RemoteObjectFreer::RemoteObjectFreer(
v8::Isolate* isolate, v8::Local<v8::Object> target, int object_id)
: ObjectLifeMonitor(isolate, target),
object_id_(object_id) {
}
RemoteObjectFreer::~RemoteObjectFreer() {
}
void RemoteObjectFreer::RunDestructor() {
content::RenderView* render_view = GetCurrentRenderView();
if (!render_view)
return;
base::string16 channel = base::ASCIIToUTF16("ipc-message");
base::ListValue args;
args.AppendString("ELECTRON_BROWSER_DEREFERENCE");
args.AppendInteger(object_id_);
render_view->Send(
new AtomViewHostMsg_Message(render_view->GetRoutingID(), channel, args));
}
} // namespace atom

View file

@ -0,0 +1,32 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_
#define ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_
#include "atom/common/api/object_life_monitor.h"
namespace atom {
class RemoteObjectFreer : public ObjectLifeMonitor {
public:
static void BindTo(
v8::Isolate* isolate, v8::Local<v8::Object> target, int object_id);
protected:
RemoteObjectFreer(
v8::Isolate* isolate, v8::Local<v8::Object> target, int object_id);
~RemoteObjectFreer() override;
void RunDestructor() override;
private:
int object_id_;
DISALLOW_COPY_AND_ASSIGN(RemoteObjectFreer);
};
} // namespace atom
#endif // ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_

View file

@ -180,4 +180,17 @@ v8::Local<v8::Value> Converter<content::WebContents*>::ToV8(
return atom::api::WebContents::CreateFrom(isolate, val).ToV8(); return atom::api::WebContents::CreateFrom(isolate, val).ToV8();
} }
// static
bool Converter<content::WebContents*>::FromV8(
v8::Isolate* isolate,
v8::Local<v8::Value> val,
content::WebContents** out) {
atom::api::WebContents* web_contents = nullptr;
if (!ConvertFromV8(isolate, val, &web_contents) || !web_contents)
return false;
*out = web_contents->web_contents();
return true;
}
} // namespace mate } // namespace mate

View file

@ -57,6 +57,8 @@ template<>
struct Converter<content::WebContents*> { struct Converter<content::WebContents*> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
content::WebContents* val); content::WebContents* val);
static bool FromV8(v8::Isolate* isolate, v8::Local<v8::Value> val,
content::WebContents** out);
}; };
} // namespace mate } // namespace mate

View file

@ -303,6 +303,10 @@
'atom/common/api/locker.h', 'atom/common/api/locker.h',
'atom/common/api/object_life_monitor.cc', 'atom/common/api/object_life_monitor.cc',
'atom/common/api/object_life_monitor.h', 'atom/common/api/object_life_monitor.h',
'atom/common/api/remote_callback_freer.cc',
'atom/common/api/remote_callback_freer.h',
'atom/common/api/remote_object_freer.cc',
'atom/common/api/remote_object_freer.h',
'atom/common/asar/archive.cc', 'atom/common/asar/archive.cc',
'atom/common/asar/archive.h', 'atom/common/asar/archive.h',
'atom/common/asar/asar_util.cc', 'atom/common/asar/asar_util.cc',

View file

@ -12,8 +12,19 @@ const FUNCTION_PROPERTIES = [
] ]
// The remote functions in renderer processes. // The remote functions in renderer processes.
// (webContentsId) => {id: Function} // id => Function
let rendererFunctions = {} let rendererFunctions = new IDWeakMap()
// Merge two IDs together.
let mergeIds = function (webContentsId, metaId) {
const PADDING_BITS = 20
if ((webContentsId << PADDING_BITS) < 0) {
throw new Error(`webContents ID is too large: ${webContentsId}`)
} else if (metaId > (1 << PADDING_BITS)) {
throw new Error(`Object ID is too large: ${metaId}`)
}
return (webContentsId << PADDING_BITS) + metaId
}
// Return the description of object's members: // Return the description of object's members:
let getObjectMembers = function (object) { let getObjectMembers = function (object) {
@ -165,32 +176,26 @@ var unwrapArgs = function (sender, args) {
return returnValue return returnValue
} }
case 'function': { case 'function': {
// Merge webContentsId and meta.id, since meta.id can be the same in
// different webContents.
const webContentsId = sender.getId()
const objectId = mergeIds(webContentsId, meta.id)
// Cache the callbacks in renderer. // Cache the callbacks in renderer.
let webContentsId = sender.getId() if (rendererFunctions.has(objectId)) {
let callbacks = rendererFunctions[webContentsId] return rendererFunctions.get(objectId)
if (!callbacks) {
callbacks = rendererFunctions[webContentsId] = new IDWeakMap()
sender.once('render-view-deleted', function (event, id) {
callbacks.clear()
delete rendererFunctions[id]
})
} }
if (callbacks.has(meta.id)) return callbacks.get(meta.id)
let callIntoRenderer = function (...args) { let callIntoRenderer = function (...args) {
if ((webContentsId in rendererFunctions) && !sender.isDestroyed()) { if (!sender.isDestroyed() && webContentsId === sender.getId()) {
sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args)) sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args))
} else { } else {
throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`) throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`)
} }
} }
v8Util.setDestructor(callIntoRenderer, function () {
if ((webContentsId in rendererFunctions) && !sender.isDestroyed()) { v8Util.setRemoteCallbackFreer(callIntoRenderer, meta.id, sender)
sender.send('ELECTRON_RENDERER_RELEASE_CALLBACK', meta.id) rendererFunctions.set(objectId, callIntoRenderer)
}
})
callbacks.set(meta.id, callIntoRenderer)
return callIntoRenderer return callIntoRenderer
} }
default: default:

View file

@ -210,9 +210,7 @@ let metaToValue = function (meta) {
// Track delegate object's life time, and tell the browser to clean up // Track delegate object's life time, and tell the browser to clean up
// when the object is GCed. // when the object is GCed.
v8Util.setDestructor(ret, function () { v8Util.setRemoteObjectFreer(ret, meta.id)
ipcRenderer.send('ELECTRON_BROWSER_DEREFERENCE', meta.id)
})
// Remember object's id. // Remember object's id.
v8Util.setHiddenValue(ret, 'atomId', meta.id) v8Util.setHiddenValue(ret, 'atomId', meta.id)