fix: disable contextBridge object identity caching (#21803)

* fix: disable contextBridge object identity caching

* cleanup

* chore: make non-const references raw pointers

* fix: zero-param constructors are not explicit

* refactor: use base::LinkedList

Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
This commit is contained in:
Samuel Attard 2020-03-03 23:18:22 -08:00 committed by GitHub
parent a53a2aaa45
commit 2563681583
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 266 additions and 453 deletions

View file

@ -559,8 +559,10 @@ filenames = {
"shell/common/v8_value_converter.cc", "shell/common/v8_value_converter.cc",
"shell/common/v8_value_converter.h", "shell/common/v8_value_converter.h",
"shell/common/world_ids.h", "shell/common/world_ids.h",
"shell/renderer/api/context_bridge/render_frame_context_bridge_store.cc", "shell/renderer/api/context_bridge/object_cache.cc",
"shell/renderer/api/context_bridge/render_frame_context_bridge_store.h", "shell/renderer/api/context_bridge/object_cache.h",
"shell/renderer/api/context_bridge/render_frame_function_store.cc",
"shell/renderer/api/context_bridge/render_frame_function_store.h",
"shell/renderer/api/electron_api_context_bridge.cc", "shell/renderer/api/electron_api_context_bridge.cc",
"shell/renderer/api/electron_api_context_bridge.h", "shell/renderer/api/electron_api_context_bridge.h",
"shell/renderer/api/electron_api_renderer_ipc.cc", "shell/renderer/api/electron_api_renderer_ipc.cc",

View file

@ -0,0 +1,65 @@
// Copyright (c) 2020 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/renderer/api/context_bridge/object_cache.h"
#include <utility>
#include "shell/common/api/remote/object_life_monitor.h"
namespace electron {
namespace api {
namespace context_bridge {
ObjectCachePairNode::ObjectCachePairNode(ObjectCachePair&& pair) {
this->pair = std::move(pair);
}
ObjectCachePairNode::~ObjectCachePairNode() = default;
ObjectCache::ObjectCache() {}
ObjectCache::~ObjectCache() = default;
void ObjectCache::CacheProxiedObject(v8::Local<v8::Value> from,
v8::Local<v8::Value> proxy_value) {
if (from->IsObject() && !from->IsNullOrUndefined()) {
auto obj = v8::Local<v8::Object>::Cast(from);
int hash = obj->GetIdentityHash();
auto* node = new ObjectCachePairNode(std::make_pair(from, proxy_value));
proxy_map_[hash].Append(node);
}
}
v8::MaybeLocal<v8::Value> ObjectCache::GetCachedProxiedObject(
v8::Local<v8::Value> from) const {
if (!from->IsObject() || from->IsNullOrUndefined())
return v8::MaybeLocal<v8::Value>();
auto obj = v8::Local<v8::Object>::Cast(from);
int hash = obj->GetIdentityHash();
auto iter = proxy_map_.find(hash);
if (iter == proxy_map_.end())
return v8::MaybeLocal<v8::Value>();
auto& list = iter->second;
for (auto* node = list.head(); node != list.end(); node = node->next()) {
auto& pair = node->value()->pair;
auto from_cmp = pair.first;
if (from_cmp == from) {
if (pair.second.IsEmpty())
return v8::MaybeLocal<v8::Value>();
return pair.second;
}
}
return v8::MaybeLocal<v8::Value>();
}
} // namespace context_bridge
} // namespace api
} // namespace electron

View file

@ -0,0 +1,53 @@
// Copyright (c) 2020 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_RENDERER_API_CONTEXT_BRIDGE_OBJECT_CACHE_H_
#define SHELL_RENDERER_API_CONTEXT_BRIDGE_OBJECT_CACHE_H_
#include <map>
#include <utility>
#include "base/containers/linked_list.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "shell/renderer/electron_render_frame_observer.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace electron {
namespace api {
namespace context_bridge {
using ObjectCachePair = std::pair<v8::Local<v8::Value>, v8::Local<v8::Value>>;
struct ObjectCachePairNode : public base::LinkNode<ObjectCachePairNode> {
explicit ObjectCachePairNode(ObjectCachePair&& pair);
~ObjectCachePairNode();
ObjectCachePair pair;
};
class ObjectCache final {
public:
ObjectCache();
~ObjectCache();
void CacheProxiedObject(v8::Local<v8::Value> from,
v8::Local<v8::Value> proxy_value);
v8::MaybeLocal<v8::Value> GetCachedProxiedObject(
v8::Local<v8::Value> from) const;
private:
// object_identity ==> [from_value, proxy_value]
std::map<int, base::LinkedList<ObjectCachePairNode>> proxy_map_;
};
} // namespace context_bridge
} // namespace api
} // namespace electron
#endif // SHELL_RENDERER_API_CONTEXT_BRIDGE_OBJECT_CACHE_H_

View file

@ -1,155 +0,0 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/renderer/api/context_bridge/render_frame_context_bridge_store.h"
#include <utility>
#include "shell/common/api/remote/object_life_monitor.h"
namespace electron {
namespace api {
namespace context_bridge {
namespace {
class CachedProxyLifeMonitor final : public ObjectLifeMonitor {
public:
static void BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
base::WeakPtr<RenderFramePersistenceStore> store,
WeakGlobalPairNode* node,
int hash) {
new CachedProxyLifeMonitor(isolate, target, store, node, hash);
}
protected:
CachedProxyLifeMonitor(v8::Isolate* isolate,
v8::Local<v8::Object> target,
base::WeakPtr<RenderFramePersistenceStore> store,
WeakGlobalPairNode* node,
int hash)
: ObjectLifeMonitor(isolate, target),
store_(store),
node_(node),
hash_(hash) {}
void RunDestructor() override {
if (!store_)
return;
if (node_->detached) {
delete node_;
return;
}
if (node_->prev) {
node_->prev->next = node_->next;
node_->prev = nullptr;
}
if (node_->next) {
node_->next->prev = node_->prev;
node_->next = nullptr;
}
if (!node_->prev && !node_->next) {
// Must be a single length linked list
store_->proxy_map()->erase(hash_);
}
node_->detached = true;
}
private:
base::WeakPtr<RenderFramePersistenceStore> store_;
WeakGlobalPairNode* node_;
int hash_;
};
} // namespace
std::map<int32_t, RenderFramePersistenceStore*>& GetStoreMap() {
static base::NoDestructor<std::map<int32_t, RenderFramePersistenceStore*>>
store_map;
return *store_map;
}
WeakGlobalPairNode::WeakGlobalPairNode(WeakGlobalPair pair) {
this->pair = std::move(pair);
}
WeakGlobalPairNode::~WeakGlobalPairNode() {}
RenderFramePersistenceStore::RenderFramePersistenceStore(
content::RenderFrame* render_frame)
: content::RenderFrameObserver(render_frame),
routing_id_(render_frame->GetRoutingID()) {}
RenderFramePersistenceStore::~RenderFramePersistenceStore() = default;
void RenderFramePersistenceStore::OnDestruct() {
GetStoreMap().erase(routing_id_);
delete this;
}
void RenderFramePersistenceStore::CacheProxiedObject(
v8::Local<v8::Value> from,
v8::Local<v8::Value> proxy_value) {
if (from->IsObject() && !from->IsNullOrUndefined()) {
auto obj = v8::Local<v8::Object>::Cast(from);
int hash = obj->GetIdentityHash();
auto global_from = v8::Global<v8::Value>(v8::Isolate::GetCurrent(), from);
auto global_proxy =
v8::Global<v8::Value>(v8::Isolate::GetCurrent(), proxy_value);
// Do not retain
global_from.SetWeak();
global_proxy.SetWeak();
auto iter = proxy_map_.find(hash);
auto* node = new WeakGlobalPairNode(
std::make_tuple(std::move(global_from), std::move(global_proxy)));
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(), obj,
weak_factory_.GetWeakPtr(), node, hash);
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(),
v8::Local<v8::Object>::Cast(proxy_value),
weak_factory_.GetWeakPtr(), node, hash);
if (iter == proxy_map_.end()) {
proxy_map_.emplace(hash, node);
} else {
WeakGlobalPairNode* target = iter->second;
while (target->next) {
target = target->next;
}
target->next = node;
node->prev = target;
}
}
}
v8::MaybeLocal<v8::Value> RenderFramePersistenceStore::GetCachedProxiedObject(
v8::Local<v8::Value> from) {
if (!from->IsObject() || from->IsNullOrUndefined())
return v8::MaybeLocal<v8::Value>();
auto obj = v8::Local<v8::Object>::Cast(from);
int hash = obj->GetIdentityHash();
auto iter = proxy_map_.find(hash);
if (iter == proxy_map_.end())
return v8::MaybeLocal<v8::Value>();
WeakGlobalPairNode* target = iter->second;
while (target) {
auto from_cmp = std::get<0>(target->pair).Get(v8::Isolate::GetCurrent());
if (from_cmp == from) {
if (std::get<1>(target->pair).IsEmpty())
return v8::MaybeLocal<v8::Value>();
return std::get<1>(target->pair).Get(v8::Isolate::GetCurrent());
}
target = target->next;
}
return v8::MaybeLocal<v8::Value>();
}
} // namespace context_bridge
} // namespace api
} // namespace electron

View file

@ -1,80 +0,0 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
#define SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
#include <map>
#include <tuple>
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "shell/renderer/electron_render_frame_observer.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace electron {
namespace api {
namespace context_bridge {
using FunctionContextPair =
std::tuple<v8::Global<v8::Function>, v8::Global<v8::Context>>;
using WeakGlobalPair = std::tuple<v8::Global<v8::Value>, v8::Global<v8::Value>>;
struct WeakGlobalPairNode {
explicit WeakGlobalPairNode(WeakGlobalPair pair_);
~WeakGlobalPairNode();
WeakGlobalPair pair;
bool detached = false;
struct WeakGlobalPairNode* prev = nullptr;
struct WeakGlobalPairNode* next = nullptr;
};
class RenderFramePersistenceStore final : public content::RenderFrameObserver {
public:
explicit RenderFramePersistenceStore(content::RenderFrame* render_frame);
~RenderFramePersistenceStore() override;
// RenderFrameObserver implementation.
void OnDestruct() override;
size_t take_func_id() { return next_func_id_++; }
std::map<size_t, FunctionContextPair>& functions() { return functions_; }
std::map<int, WeakGlobalPairNode*>* proxy_map() { return &proxy_map_; }
void CacheProxiedObject(v8::Local<v8::Value> from,
v8::Local<v8::Value> proxy_value);
v8::MaybeLocal<v8::Value> GetCachedProxiedObject(v8::Local<v8::Value> from);
base::WeakPtr<RenderFramePersistenceStore> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
// func_id ==> { function, owning_context }
std::map<size_t, FunctionContextPair> functions_;
size_t next_func_id_ = 1;
// proxy maps are weak globals, i.e. these are not retained beyond
// there normal JS lifetime. You must check IsEmpty()
const int32_t routing_id_;
// object_identity ==> [from_value, proxy_value]
std::map<int, WeakGlobalPairNode*> proxy_map_;
base::WeakPtrFactory<RenderFramePersistenceStore> weak_factory_{this};
};
std::map<int32_t, RenderFramePersistenceStore*>& GetStoreMap();
} // namespace context_bridge
} // namespace api
} // namespace electron
#endif // SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_

View file

@ -0,0 +1,39 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/renderer/api/context_bridge/render_frame_function_store.h"
#include <utility>
#include "shell/common/api/remote/object_life_monitor.h"
namespace electron {
namespace api {
namespace context_bridge {
std::map<int32_t, RenderFrameFunctionStore*>& GetStoreMap() {
static base::NoDestructor<std::map<int32_t, RenderFrameFunctionStore*>>
store_map;
return *store_map;
}
RenderFrameFunctionStore::RenderFrameFunctionStore(
content::RenderFrame* render_frame)
: content::RenderFrameObserver(render_frame),
routing_id_(render_frame->GetRoutingID()) {}
RenderFrameFunctionStore::~RenderFrameFunctionStore() = default;
void RenderFrameFunctionStore::OnDestruct() {
GetStoreMap().erase(routing_id_);
delete this;
}
} // namespace context_bridge
} // namespace api
} // namespace electron

View file

@ -0,0 +1,59 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_FUNCTION_STORE_H_
#define SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_FUNCTION_STORE_H_
#include <map>
#include <tuple>
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "shell/renderer/electron_render_frame_observer.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace electron {
namespace api {
namespace context_bridge {
using FunctionContextPair =
std::tuple<v8::Global<v8::Function>, v8::Global<v8::Context>>;
class RenderFrameFunctionStore final : public content::RenderFrameObserver {
public:
explicit RenderFrameFunctionStore(content::RenderFrame* render_frame);
~RenderFrameFunctionStore() override;
// RenderFrameObserver implementation.
void OnDestruct() override;
size_t take_func_id() { return next_func_id_++; }
std::map<size_t, FunctionContextPair>& functions() { return functions_; }
base::WeakPtr<RenderFrameFunctionStore> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
// func_id ==> { function, owning_context }
std::map<size_t, FunctionContextPair> functions_;
size_t next_func_id_ = 1;
const int32_t routing_id_;
base::WeakPtrFactory<RenderFrameFunctionStore> weak_factory_{this};
};
std::map<int32_t, RenderFrameFunctionStore*>& GetStoreMap();
} // namespace context_bridge
} // namespace api
} // namespace electron
#endif // SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_FUNCTION_STORE_H_

View file

@ -21,7 +21,7 @@
#include "shell/common/gin_helper/promise.h" #include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
#include "shell/common/world_ids.h" #include "shell/common/world_ids.h"
#include "shell/renderer/api/context_bridge/render_frame_context_bridge_store.h" #include "shell/renderer/api/context_bridge/render_frame_function_store.h"
#include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_local_frame.h"
namespace electron { namespace electron {
@ -47,11 +47,11 @@ content::RenderFrame* GetRenderFrame(const v8::Local<v8::Object>& value) {
return content::RenderFrame::FromWebFrame(frame); return content::RenderFrame::FromWebFrame(frame);
} }
context_bridge::RenderFramePersistenceStore* GetOrCreateStore( context_bridge::RenderFrameFunctionStore* GetOrCreateStore(
content::RenderFrame* render_frame) { content::RenderFrame* render_frame) {
auto it = context_bridge::GetStoreMap().find(render_frame->GetRoutingID()); auto it = context_bridge::GetStoreMap().find(render_frame->GetRoutingID());
if (it == context_bridge::GetStoreMap().end()) { if (it == context_bridge::GetStoreMap().end()) {
auto* store = new context_bridge::RenderFramePersistenceStore(render_frame); auto* store = new context_bridge::RenderFrameFunctionStore(render_frame);
context_bridge::GetStoreMap().emplace(render_frame->GetRoutingID(), store); context_bridge::GetStoreMap().emplace(render_frame->GetRoutingID(), store);
return store; return store;
} }
@ -113,7 +113,7 @@ class FunctionLifeMonitor final : public ObjectLifeMonitor {
static void BindTo( static void BindTo(
v8::Isolate* isolate, v8::Isolate* isolate,
v8::Local<v8::Object> target, v8::Local<v8::Object> target,
base::WeakPtr<context_bridge::RenderFramePersistenceStore> store, base::WeakPtr<context_bridge::RenderFrameFunctionStore> store,
size_t func_id) { size_t func_id) {
new FunctionLifeMonitor(isolate, target, store, func_id); new FunctionLifeMonitor(isolate, target, store, func_id);
} }
@ -122,7 +122,7 @@ class FunctionLifeMonitor final : public ObjectLifeMonitor {
FunctionLifeMonitor( FunctionLifeMonitor(
v8::Isolate* isolate, v8::Isolate* isolate,
v8::Local<v8::Object> target, v8::Local<v8::Object> target,
base::WeakPtr<context_bridge::RenderFramePersistenceStore> store, base::WeakPtr<context_bridge::RenderFrameFunctionStore> store,
size_t func_id) size_t func_id)
: ObjectLifeMonitor(isolate, target), store_(store), func_id_(func_id) {} : ObjectLifeMonitor(isolate, target), store_(store), func_id_(func_id) {}
~FunctionLifeMonitor() override = default; ~FunctionLifeMonitor() override = default;
@ -134,7 +134,7 @@ class FunctionLifeMonitor final : public ObjectLifeMonitor {
} }
private: private:
base::WeakPtr<context_bridge::RenderFramePersistenceStore> store_; base::WeakPtr<context_bridge::RenderFrameFunctionStore> store_;
size_t func_id_; size_t func_id_;
}; };
@ -144,7 +144,8 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Local<v8::Context> source_context, v8::Local<v8::Context> source_context,
v8::Local<v8::Context> destination_context, v8::Local<v8::Context> destination_context,
v8::Local<v8::Value> value, v8::Local<v8::Value> value,
context_bridge::RenderFramePersistenceStore* store, context_bridge::RenderFrameFunctionStore* store,
context_bridge::ObjectCache* object_cache,
int recursion_depth) { int recursion_depth) {
if (recursion_depth >= kMaxRecursion) { if (recursion_depth >= kMaxRecursion) {
v8::Context::Scope source_scope(source_context); v8::Context::Scope source_scope(source_context);
@ -158,7 +159,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
} }
} }
// Check Cache // Check Cache
auto cached_value = store->GetCachedProxiedObject(value); auto cached_value = object_cache->GetCachedProxiedObject(value);
if (!cached_value.IsEmpty()) { if (!cached_value.IsEmpty()) {
return cached_value; return cached_value;
} }
@ -182,7 +183,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
FunctionLifeMonitor::BindTo(destination_context->GetIsolate(), FunctionLifeMonitor::BindTo(destination_context->GetIsolate(),
v8::Local<v8::Object>::Cast(proxy_func), v8::Local<v8::Object>::Cast(proxy_func),
store->GetWeakPtr(), func_id); store->GetWeakPtr(), func_id);
store->CacheProxiedObject(value, proxy_func); object_cache->CacheProxiedObject(value, proxy_func);
return v8::MaybeLocal<v8::Value>(proxy_func); return v8::MaybeLocal<v8::Value>(proxy_func);
} }
} }
@ -202,11 +203,13 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Isolate* isolate, v8::Isolate* isolate,
v8::Global<v8::Context> global_source_context, v8::Global<v8::Context> global_source_context,
v8::Global<v8::Context> global_destination_context, v8::Global<v8::Context> global_destination_context,
context_bridge::RenderFramePersistenceStore* store, context_bridge::RenderFrameFunctionStore* store,
v8::Local<v8::Value> result) { v8::Local<v8::Value> result) {
auto val = PassValueToOtherContext( context_bridge::ObjectCache object_cache;
global_source_context.Get(isolate), auto val =
global_destination_context.Get(isolate), result, store, 0); PassValueToOtherContext(global_source_context.Get(isolate),
global_destination_context.Get(isolate),
result, store, &object_cache, 0);
if (!val.IsEmpty()) if (!val.IsEmpty())
proxied_promise->Resolve(val.ToLocalChecked()); proxied_promise->Resolve(val.ToLocalChecked());
delete proxied_promise; delete proxied_promise;
@ -221,11 +224,13 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Isolate* isolate, v8::Isolate* isolate,
v8::Global<v8::Context> global_source_context, v8::Global<v8::Context> global_source_context,
v8::Global<v8::Context> global_destination_context, v8::Global<v8::Context> global_destination_context,
context_bridge::RenderFramePersistenceStore* store, context_bridge::RenderFrameFunctionStore* store,
v8::Local<v8::Value> result) { v8::Local<v8::Value> result) {
auto val = PassValueToOtherContext( context_bridge::ObjectCache object_cache;
global_source_context.Get(isolate), auto val =
global_destination_context.Get(isolate), result, store, 0); PassValueToOtherContext(global_source_context.Get(isolate),
global_destination_context.Get(isolate),
result, store, &object_cache, 0);
if (!val.IsEmpty()) if (!val.IsEmpty())
proxied_promise->Reject(val.ToLocalChecked()); proxied_promise->Reject(val.ToLocalChecked());
delete proxied_promise; delete proxied_promise;
@ -243,7 +248,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Local<v8::Function>::Cast( v8::Local<v8::Function>::Cast(
gin::ConvertToV8(destination_context->GetIsolate(), catch_cb)))); gin::ConvertToV8(destination_context->GetIsolate(), catch_cb))));
store->CacheProxiedObject(value, proxied_promise_handle); object_cache->CacheProxiedObject(value, proxied_promise_handle);
return v8::MaybeLocal<v8::Value>(proxied_promise_handle); return v8::MaybeLocal<v8::Value>(proxied_promise_handle);
} }
} }
@ -270,7 +275,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
for (size_t i = 0; i < length; i++) { for (size_t i = 0; i < length; i++) {
auto value_for_array = PassValueToOtherContext( auto value_for_array = PassValueToOtherContext(
source_context, destination_context, source_context, destination_context,
arr->Get(source_context, i).ToLocalChecked(), store, arr->Get(source_context, i).ToLocalChecked(), store, object_cache,
recursion_depth + 1); recursion_depth + 1);
if (value_for_array.IsEmpty()) if (value_for_array.IsEmpty())
return v8::MaybeLocal<v8::Value>(); return v8::MaybeLocal<v8::Value>();
@ -280,7 +285,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
return v8::MaybeLocal<v8::Value>(); return v8::MaybeLocal<v8::Value>();
} }
} }
store->CacheProxiedObject(value, cloned_arr); object_cache->CacheProxiedObject(value, cloned_arr);
return v8::MaybeLocal<v8::Value>(cloned_arr); return v8::MaybeLocal<v8::Value>(cloned_arr);
} }
} }
@ -290,7 +295,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
auto object_value = v8::Local<v8::Object>::Cast(value); auto object_value = v8::Local<v8::Object>::Cast(value);
auto passed_value = auto passed_value =
CreateProxyForAPI(object_value, source_context, destination_context, CreateProxyForAPI(object_value, source_context, destination_context,
store, recursion_depth + 1); store, object_cache, recursion_depth + 1);
if (passed_value.IsEmpty()) if (passed_value.IsEmpty())
return v8::MaybeLocal<v8::Value>(); return v8::MaybeLocal<v8::Value>();
return v8::MaybeLocal<v8::Value>(passed_value.ToLocalChecked()); return v8::MaybeLocal<v8::Value>(passed_value.ToLocalChecked());
@ -311,13 +316,13 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
{ {
v8::Local<v8::Value> cloned_value = v8::Local<v8::Value> cloned_value =
gin::ConvertToV8(destination_context->GetIsolate(), ret); gin::ConvertToV8(destination_context->GetIsolate(), ret);
store->CacheProxiedObject(value, cloned_value); object_cache->CacheProxiedObject(value, cloned_value);
return v8::MaybeLocal<v8::Value>(cloned_value); return v8::MaybeLocal<v8::Value>(cloned_value);
} }
} }
v8::Local<v8::Value> ProxyFunctionWrapper( v8::Local<v8::Value> ProxyFunctionWrapper(
context_bridge::RenderFramePersistenceStore* store, context_bridge::RenderFrameFunctionStore* store,
size_t func_id, size_t func_id,
gin_helper::Arguments* args) { gin_helper::Arguments* args) {
// Context the proxy function was called from // Context the proxy function was called from
@ -327,6 +332,7 @@ v8::Local<v8::Value> ProxyFunctionWrapper(
std::get<1>(store->functions()[func_id]).Get(args->isolate()); std::get<1>(store->functions()[func_id]).Get(args->isolate());
v8::Context::Scope func_owning_context_scope(func_owning_context); v8::Context::Scope func_owning_context_scope(func_owning_context);
context_bridge::ObjectCache object_cache;
{ {
v8::Local<v8::Function> func = v8::Local<v8::Function> func =
(std::get<0>(store->functions()[func_id])).Get(args->isolate()); (std::get<0>(store->functions()[func_id])).Get(args->isolate());
@ -337,7 +343,7 @@ v8::Local<v8::Value> ProxyFunctionWrapper(
for (auto value : original_args) { for (auto value : original_args) {
auto arg = PassValueToOtherContext(calling_context, func_owning_context, auto arg = PassValueToOtherContext(calling_context, func_owning_context,
value, store, 0); value, store, &object_cache, 0);
if (arg.IsEmpty()) if (arg.IsEmpty())
return v8::Undefined(args->isolate()); return v8::Undefined(args->isolate());
proxied_args.push_back(arg.ToLocalChecked()); proxied_args.push_back(arg.ToLocalChecked());
@ -375,9 +381,9 @@ v8::Local<v8::Value> ProxyFunctionWrapper(
if (maybe_return_value.IsEmpty()) if (maybe_return_value.IsEmpty())
return v8::Undefined(args->isolate()); return v8::Undefined(args->isolate());
auto ret = auto ret = PassValueToOtherContext(func_owning_context, calling_context,
PassValueToOtherContext(func_owning_context, calling_context, maybe_return_value.ToLocalChecked(),
maybe_return_value.ToLocalChecked(), store, 0); store, &object_cache, 0);
if (ret.IsEmpty()) if (ret.IsEmpty())
return v8::Undefined(args->isolate()); return v8::Undefined(args->isolate());
return ret.ToLocalChecked(); return ret.ToLocalChecked();
@ -388,12 +394,13 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
const v8::Local<v8::Object>& api_object, const v8::Local<v8::Object>& api_object,
const v8::Local<v8::Context>& source_context, const v8::Local<v8::Context>& source_context,
const v8::Local<v8::Context>& destination_context, const v8::Local<v8::Context>& destination_context,
context_bridge::RenderFramePersistenceStore* store, context_bridge::RenderFrameFunctionStore* store,
context_bridge::ObjectCache* object_cache,
int recursion_depth) { int recursion_depth) {
gin_helper::Dictionary api(source_context->GetIsolate(), api_object); gin_helper::Dictionary api(source_context->GetIsolate(), api_object);
gin_helper::Dictionary proxy = gin_helper::Dictionary proxy =
gin::Dictionary::CreateEmpty(destination_context->GetIsolate()); gin::Dictionary::CreateEmpty(destination_context->GetIsolate());
store->CacheProxiedObject(api.GetHandle(), proxy.GetHandle()); object_cache->CacheProxiedObject(api.GetHandle(), proxy.GetHandle());
auto maybe_keys = api.GetHandle()->GetOwnPropertyNames( auto maybe_keys = api.GetHandle()->GetOwnPropertyNames(
source_context, source_context,
static_cast<v8::PropertyFilter>(v8::ONLY_ENUMERABLE | v8::SKIP_SYMBOLS), static_cast<v8::PropertyFilter>(v8::ONLY_ENUMERABLE | v8::SKIP_SYMBOLS),
@ -419,7 +426,7 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
auto passed_value = auto passed_value =
PassValueToOtherContext(source_context, destination_context, value, PassValueToOtherContext(source_context, destination_context, value,
store, recursion_depth + 1); store, object_cache, recursion_depth + 1);
if (passed_value.IsEmpty()) if (passed_value.IsEmpty())
return v8::MaybeLocal<v8::Object>(); return v8::MaybeLocal<v8::Object>();
proxy.Set(key_str, passed_value.ToLocalChecked()); proxy.Set(key_str, passed_value.ToLocalChecked());
@ -435,22 +442,6 @@ gin_helper::Dictionary DebugGC(gin_helper::Dictionary empty) {
auto* store = GetOrCreateStore(render_frame); auto* store = GetOrCreateStore(render_frame);
gin_helper::Dictionary ret = gin::Dictionary::CreateEmpty(empty.isolate()); gin_helper::Dictionary ret = gin::Dictionary::CreateEmpty(empty.isolate());
ret.Set("functionCount", store->functions().size()); ret.Set("functionCount", store->functions().size());
auto* proxy_map = store->proxy_map();
ret.Set("objectCount", proxy_map->size() * 2);
int live_from = 0;
int live_proxy = 0;
for (auto iter = proxy_map->begin(); iter != proxy_map->end(); iter++) {
auto* node = iter->second;
while (node) {
if (!std::get<0>(node->pair).IsEmpty())
live_from++;
if (!std::get<1>(node->pair).IsEmpty())
live_proxy++;
node = node->next;
}
}
ret.Set("liveFromValues", live_from);
ret.Set("liveProxyValues", live_proxy);
return ret; return ret;
} }
#endif #endif
@ -460,7 +451,7 @@ void ExposeAPIInMainWorld(const std::string& key,
gin_helper::Arguments* args) { gin_helper::Arguments* args) {
auto* render_frame = GetRenderFrame(api_object); auto* render_frame = GetRenderFrame(api_object);
CHECK(render_frame); CHECK(render_frame);
context_bridge::RenderFramePersistenceStore* store = context_bridge::RenderFrameFunctionStore* store =
GetOrCreateStore(render_frame); GetOrCreateStore(render_frame);
auto* frame = render_frame->GetWebFrame(); auto* frame = render_frame->GetWebFrame();
CHECK(frame); CHECK(frame);
@ -478,10 +469,11 @@ void ExposeAPIInMainWorld(const std::string& key,
v8::Local<v8::Context> isolated_context = v8::Local<v8::Context> isolated_context =
frame->WorldScriptContext(args->isolate(), WorldIDs::ISOLATED_WORLD_ID); frame->WorldScriptContext(args->isolate(), WorldIDs::ISOLATED_WORLD_ID);
context_bridge::ObjectCache object_cache;
v8::Context::Scope main_context_scope(main_context); v8::Context::Scope main_context_scope(main_context);
{ {
v8::MaybeLocal<v8::Object> maybe_proxy = v8::MaybeLocal<v8::Object> maybe_proxy = CreateProxyForAPI(
CreateProxyForAPI(api_object, isolated_context, main_context, store, 0); api_object, isolated_context, main_context, store, &object_cache, 0);
if (maybe_proxy.IsEmpty()) if (maybe_proxy.IsEmpty())
return; return;
auto proxy = maybe_proxy.ToLocalChecked(); auto proxy = maybe_proxy.ToLocalChecked();

View file

@ -5,6 +5,7 @@
#ifndef SHELL_RENDERER_API_ELECTRON_API_CONTEXT_BRIDGE_H_ #ifndef SHELL_RENDERER_API_ELECTRON_API_CONTEXT_BRIDGE_H_
#define SHELL_RENDERER_API_ELECTRON_API_CONTEXT_BRIDGE_H_ #define SHELL_RENDERER_API_ELECTRON_API_CONTEXT_BRIDGE_H_
#include "shell/renderer/api/context_bridge/object_cache.h"
#include "v8/include/v8.h" #include "v8/include/v8.h"
namespace gin_helper { namespace gin_helper {
@ -16,11 +17,11 @@ namespace electron {
namespace api { namespace api {
namespace context_bridge { namespace context_bridge {
class RenderFramePersistenceStore; class RenderFrameFunctionStore;
} }
v8::Local<v8::Value> ProxyFunctionWrapper( v8::Local<v8::Value> ProxyFunctionWrapper(
context_bridge::RenderFramePersistenceStore* store, context_bridge::RenderFrameFunctionStore* store,
size_t func_id, size_t func_id,
gin_helper::Arguments* args); gin_helper::Arguments* args);
@ -28,7 +29,8 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
const v8::Local<v8::Object>& api_object, const v8::Local<v8::Object>& api_object,
const v8::Local<v8::Context>& source_context, const v8::Local<v8::Context>& source_context,
const v8::Local<v8::Context>& target_context, const v8::Local<v8::Context>& target_context,
context_bridge::RenderFramePersistenceStore* store, context_bridge::RenderFrameFunctionStore* store,
context_bridge::ObjectCache* object_cache,
int recursion_depth); int recursion_depth);
} // namespace api } // namespace api

View file

@ -320,43 +320,6 @@ describe('contextBridge', () => {
expect(result).to.deep.equal([135, 135, 135]) expect(result).to.deep.equal([135, 135, 135])
}) })
it('it should follow expected simple rules of object identity', async () => {
await makeBindingWindow(() => {
const o: any = { value: 135 }
const sub = { thing: 7 }
o.a = sub
o.b = sub
contextBridge.exposeInMainWorld('example', {
o
})
})
const result = await callWithBindings((root: any) => {
return root.example.a === root.example.b
})
expect(result).to.equal(true)
})
it('it should follow expected complex rules of object identity', async () => {
await makeBindingWindow(() => {
let first: any = null
contextBridge.exposeInMainWorld('example', {
check: (arg: any) => {
if (first === null) {
first = arg
} else {
return first === arg
}
}
})
})
const result = await callWithBindings((root: any) => {
const o = { thing: 123 }
root.example.check(o)
return root.example.check(o)
})
expect(result).to.equal(true)
})
// Can only run tests which use the GCRunner in non-sandboxed environments // Can only run tests which use the GCRunner in non-sandboxed environments
if (!useSandbox) { if (!useSandbox) {
it('should release the global hold on methods sent across contexts', async () => { it('should release the global hold on methods sent across contexts', async () => {
@ -377,133 +340,6 @@ describe('contextBridge', () => {
}) })
expect((await getGCInfo()).functionCount).to.equal(2) expect((await getGCInfo()).functionCount).to.equal(2)
}) })
it('should release the global hold on objects sent across contexts when the object proxy is de-reffed', async () => {
await makeBindingWindow(() => {
require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
let myObj: any
contextBridge.exposeInMainWorld('example', {
setObj: (o: any) => {
myObj = o
},
getObj: () => myObj
})
})
await callWithBindings(async (root: any) => {
root.GCRunner.run()
})
// Initial Setup
let info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
// Create Reference
await callWithBindings(async (root: any) => {
root.x = { value: 123 }
root.example.setObj(root.x)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(4)
expect(info.liveProxyValues).to.equal(4)
expect(info.objectCount).to.equal(8)
// Release Reference
await callWithBindings(async (root: any) => {
root.example.setObj(null)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
})
it('should release the global hold on objects sent across contexts when the object source is de-reffed', async () => {
await makeBindingWindow(() => {
require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
let myObj: any
contextBridge.exposeInMainWorld('example', {
setObj: (o: any) => {
myObj = o
},
getObj: () => myObj
})
})
await callWithBindings(async (root: any) => {
root.GCRunner.run()
})
// Initial Setup
let info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
// Create Reference
await callWithBindings(async (root: any) => {
root.x = { value: 123 }
root.example.setObj(root.x)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(4)
expect(info.liveProxyValues).to.equal(4)
expect(info.objectCount).to.equal(8)
// Release Reference
await callWithBindings(async (root: any) => {
delete root.x
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
})
it('should not crash when the object source is de-reffed AND the object proxy is de-reffed', async () => {
await makeBindingWindow(() => {
require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
let myObj: any
contextBridge.exposeInMainWorld('example', {
setObj: (o: any) => {
myObj = o
},
getObj: () => myObj
})
})
await callWithBindings(async (root: any) => {
root.GCRunner.run()
})
// Initial Setup
let info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
// Create Reference
await callWithBindings(async (root: any) => {
root.x = { value: 123 }
root.example.setObj(root.x)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(4)
expect(info.liveProxyValues).to.equal(4)
expect(info.objectCount).to.equal(8)
// Release Reference
await callWithBindings(async (root: any) => {
delete root.x
root.example.setObj(null)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
})
} }
it('it should not let you overwrite existing exposed things', async () => { it('it should not let you overwrite existing exposed things', async () => {