diff --git a/filenames.gni b/filenames.gni index 15865a368448..246e22830544 100644 --- a/filenames.gni +++ b/filenames.gni @@ -559,8 +559,10 @@ filenames = { "shell/common/v8_value_converter.cc", "shell/common/v8_value_converter.h", "shell/common/world_ids.h", - "shell/renderer/api/context_bridge/render_frame_context_bridge_store.cc", - "shell/renderer/api/context_bridge/render_frame_context_bridge_store.h", + "shell/renderer/api/context_bridge/object_cache.cc", + "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.h", "shell/renderer/api/electron_api_renderer_ipc.cc", diff --git a/shell/renderer/api/context_bridge/object_cache.cc b/shell/renderer/api/context_bridge/object_cache.cc new file mode 100644 index 000000000000..fd060324d41a --- /dev/null +++ b/shell/renderer/api/context_bridge/object_cache.cc @@ -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 + +#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 from, + v8::Local proxy_value) { + if (from->IsObject() && !from->IsNullOrUndefined()) { + auto obj = v8::Local::Cast(from); + int hash = obj->GetIdentityHash(); + + auto* node = new ObjectCachePairNode(std::make_pair(from, proxy_value)); + proxy_map_[hash].Append(node); + } +} + +v8::MaybeLocal ObjectCache::GetCachedProxiedObject( + v8::Local from) const { + if (!from->IsObject() || from->IsNullOrUndefined()) + return v8::MaybeLocal(); + + auto obj = v8::Local::Cast(from); + int hash = obj->GetIdentityHash(); + auto iter = proxy_map_.find(hash); + if (iter == proxy_map_.end()) + return v8::MaybeLocal(); + + 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(); + return pair.second; + } + } + return v8::MaybeLocal(); +} + +} // namespace context_bridge + +} // namespace api + +} // namespace electron diff --git a/shell/renderer/api/context_bridge/object_cache.h b/shell/renderer/api/context_bridge/object_cache.h new file mode 100644 index 000000000000..ff420f736843 --- /dev/null +++ b/shell/renderer/api/context_bridge/object_cache.h @@ -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 +#include + +#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>; + +struct ObjectCachePairNode : public base::LinkNode { + explicit ObjectCachePairNode(ObjectCachePair&& pair); + ~ObjectCachePairNode(); + + ObjectCachePair pair; +}; + +class ObjectCache final { + public: + ObjectCache(); + ~ObjectCache(); + + void CacheProxiedObject(v8::Local from, + v8::Local proxy_value); + v8::MaybeLocal GetCachedProxiedObject( + v8::Local from) const; + + private: + // object_identity ==> [from_value, proxy_value] + std::map> proxy_map_; +}; + +} // namespace context_bridge + +} // namespace api + +} // namespace electron + +#endif // SHELL_RENDERER_API_CONTEXT_BRIDGE_OBJECT_CACHE_H_ diff --git a/shell/renderer/api/context_bridge/render_frame_context_bridge_store.cc b/shell/renderer/api/context_bridge/render_frame_context_bridge_store.cc deleted file mode 100644 index 16e14a3c15d5..000000000000 --- a/shell/renderer/api/context_bridge/render_frame_context_bridge_store.cc +++ /dev/null @@ -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 - -#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 target, - base::WeakPtr store, - WeakGlobalPairNode* node, - int hash) { - new CachedProxyLifeMonitor(isolate, target, store, node, hash); - } - - protected: - CachedProxyLifeMonitor(v8::Isolate* isolate, - v8::Local target, - base::WeakPtr 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 store_; - WeakGlobalPairNode* node_; - int hash_; -}; - -} // namespace - -std::map& GetStoreMap() { - static base::NoDestructor> - 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 from, - v8::Local proxy_value) { - if (from->IsObject() && !from->IsNullOrUndefined()) { - auto obj = v8::Local::Cast(from); - int hash = obj->GetIdentityHash(); - auto global_from = v8::Global(v8::Isolate::GetCurrent(), from); - auto global_proxy = - v8::Global(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::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 RenderFramePersistenceStore::GetCachedProxiedObject( - v8::Local from) { - if (!from->IsObject() || from->IsNullOrUndefined()) - return v8::MaybeLocal(); - - auto obj = v8::Local::Cast(from); - int hash = obj->GetIdentityHash(); - auto iter = proxy_map_.find(hash); - if (iter == proxy_map_.end()) - return v8::MaybeLocal(); - 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(); - return std::get<1>(target->pair).Get(v8::Isolate::GetCurrent()); - } - target = target->next; - } - return v8::MaybeLocal(); -} - -} // namespace context_bridge - -} // namespace api - -} // namespace electron diff --git a/shell/renderer/api/context_bridge/render_frame_context_bridge_store.h b/shell/renderer/api/context_bridge/render_frame_context_bridge_store.h deleted file mode 100644 index 3321785d59bb..000000000000 --- a/shell/renderer/api/context_bridge/render_frame_context_bridge_store.h +++ /dev/null @@ -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 -#include - -#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>; - -using WeakGlobalPair = std::tuple, v8::Global>; - -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& functions() { return functions_; } - std::map* proxy_map() { return &proxy_map_; } - - void CacheProxiedObject(v8::Local from, - v8::Local proxy_value); - v8::MaybeLocal GetCachedProxiedObject(v8::Local from); - - base::WeakPtr GetWeakPtr() { - return weak_factory_.GetWeakPtr(); - } - - private: - // func_id ==> { function, owning_context } - std::map 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 proxy_map_; - base::WeakPtrFactory weak_factory_{this}; -}; - -std::map& GetStoreMap(); - -} // namespace context_bridge - -} // namespace api - -} // namespace electron - -#endif // SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_ diff --git a/shell/renderer/api/context_bridge/render_frame_function_store.cc b/shell/renderer/api/context_bridge/render_frame_function_store.cc new file mode 100644 index 000000000000..804ccaf6fdfa --- /dev/null +++ b/shell/renderer/api/context_bridge/render_frame_function_store.cc @@ -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 + +#include "shell/common/api/remote/object_life_monitor.h" + +namespace electron { + +namespace api { + +namespace context_bridge { + +std::map& GetStoreMap() { + static base::NoDestructor> + 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 diff --git a/shell/renderer/api/context_bridge/render_frame_function_store.h b/shell/renderer/api/context_bridge/render_frame_function_store.h new file mode 100644 index 000000000000..e4b9ea436a0a --- /dev/null +++ b/shell/renderer/api/context_bridge/render_frame_function_store.h @@ -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 +#include + +#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>; + +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& functions() { return functions_; } + + base::WeakPtr GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + private: + // func_id ==> { function, owning_context } + std::map functions_; + size_t next_func_id_ = 1; + + const int32_t routing_id_; + + base::WeakPtrFactory weak_factory_{this}; +}; + +std::map& GetStoreMap(); + +} // namespace context_bridge + +} // namespace api + +} // namespace electron + +#endif // SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_FUNCTION_STORE_H_ diff --git a/shell/renderer/api/electron_api_context_bridge.cc b/shell/renderer/api/electron_api_context_bridge.cc index 8aa2283812f9..3c3eacf76409 100644 --- a/shell/renderer/api/electron_api_context_bridge.cc +++ b/shell/renderer/api/electron_api_context_bridge.cc @@ -21,7 +21,7 @@ #include "shell/common/gin_helper/promise.h" #include "shell/common/node_includes.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" namespace electron { @@ -47,11 +47,11 @@ content::RenderFrame* GetRenderFrame(const v8::Local& value) { return content::RenderFrame::FromWebFrame(frame); } -context_bridge::RenderFramePersistenceStore* GetOrCreateStore( +context_bridge::RenderFrameFunctionStore* GetOrCreateStore( content::RenderFrame* render_frame) { auto it = context_bridge::GetStoreMap().find(render_frame->GetRoutingID()); 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); return store; } @@ -113,7 +113,7 @@ class FunctionLifeMonitor final : public ObjectLifeMonitor { static void BindTo( v8::Isolate* isolate, v8::Local target, - base::WeakPtr store, + base::WeakPtr store, size_t func_id) { new FunctionLifeMonitor(isolate, target, store, func_id); } @@ -122,7 +122,7 @@ class FunctionLifeMonitor final : public ObjectLifeMonitor { FunctionLifeMonitor( v8::Isolate* isolate, v8::Local target, - base::WeakPtr store, + base::WeakPtr store, size_t func_id) : ObjectLifeMonitor(isolate, target), store_(store), func_id_(func_id) {} ~FunctionLifeMonitor() override = default; @@ -134,7 +134,7 @@ class FunctionLifeMonitor final : public ObjectLifeMonitor { } private: - base::WeakPtr store_; + base::WeakPtr store_; size_t func_id_; }; @@ -144,7 +144,8 @@ v8::MaybeLocal PassValueToOtherContext( v8::Local source_context, v8::Local destination_context, v8::Local value, - context_bridge::RenderFramePersistenceStore* store, + context_bridge::RenderFrameFunctionStore* store, + context_bridge::ObjectCache* object_cache, int recursion_depth) { if (recursion_depth >= kMaxRecursion) { v8::Context::Scope source_scope(source_context); @@ -158,7 +159,7 @@ v8::MaybeLocal PassValueToOtherContext( } } // Check Cache - auto cached_value = store->GetCachedProxiedObject(value); + auto cached_value = object_cache->GetCachedProxiedObject(value); if (!cached_value.IsEmpty()) { return cached_value; } @@ -182,7 +183,7 @@ v8::MaybeLocal PassValueToOtherContext( FunctionLifeMonitor::BindTo(destination_context->GetIsolate(), v8::Local::Cast(proxy_func), store->GetWeakPtr(), func_id); - store->CacheProxiedObject(value, proxy_func); + object_cache->CacheProxiedObject(value, proxy_func); return v8::MaybeLocal(proxy_func); } } @@ -202,11 +203,13 @@ v8::MaybeLocal PassValueToOtherContext( v8::Isolate* isolate, v8::Global global_source_context, v8::Global global_destination_context, - context_bridge::RenderFramePersistenceStore* store, + context_bridge::RenderFrameFunctionStore* store, v8::Local result) { - auto val = PassValueToOtherContext( - global_source_context.Get(isolate), - global_destination_context.Get(isolate), result, store, 0); + context_bridge::ObjectCache object_cache; + auto val = + PassValueToOtherContext(global_source_context.Get(isolate), + global_destination_context.Get(isolate), + result, store, &object_cache, 0); if (!val.IsEmpty()) proxied_promise->Resolve(val.ToLocalChecked()); delete proxied_promise; @@ -221,11 +224,13 @@ v8::MaybeLocal PassValueToOtherContext( v8::Isolate* isolate, v8::Global global_source_context, v8::Global global_destination_context, - context_bridge::RenderFramePersistenceStore* store, + context_bridge::RenderFrameFunctionStore* store, v8::Local result) { - auto val = PassValueToOtherContext( - global_source_context.Get(isolate), - global_destination_context.Get(isolate), result, store, 0); + context_bridge::ObjectCache object_cache; + auto val = + PassValueToOtherContext(global_source_context.Get(isolate), + global_destination_context.Get(isolate), + result, store, &object_cache, 0); if (!val.IsEmpty()) proxied_promise->Reject(val.ToLocalChecked()); delete proxied_promise; @@ -243,7 +248,7 @@ v8::MaybeLocal PassValueToOtherContext( v8::Local::Cast( gin::ConvertToV8(destination_context->GetIsolate(), catch_cb)))); - store->CacheProxiedObject(value, proxied_promise_handle); + object_cache->CacheProxiedObject(value, proxied_promise_handle); return v8::MaybeLocal(proxied_promise_handle); } } @@ -270,7 +275,7 @@ v8::MaybeLocal PassValueToOtherContext( for (size_t i = 0; i < length; i++) { auto value_for_array = PassValueToOtherContext( source_context, destination_context, - arr->Get(source_context, i).ToLocalChecked(), store, + arr->Get(source_context, i).ToLocalChecked(), store, object_cache, recursion_depth + 1); if (value_for_array.IsEmpty()) return v8::MaybeLocal(); @@ -280,7 +285,7 @@ v8::MaybeLocal PassValueToOtherContext( return v8::MaybeLocal(); } } - store->CacheProxiedObject(value, cloned_arr); + object_cache->CacheProxiedObject(value, cloned_arr); return v8::MaybeLocal(cloned_arr); } } @@ -290,7 +295,7 @@ v8::MaybeLocal PassValueToOtherContext( auto object_value = v8::Local::Cast(value); auto passed_value = CreateProxyForAPI(object_value, source_context, destination_context, - store, recursion_depth + 1); + store, object_cache, recursion_depth + 1); if (passed_value.IsEmpty()) return v8::MaybeLocal(); return v8::MaybeLocal(passed_value.ToLocalChecked()); @@ -311,13 +316,13 @@ v8::MaybeLocal PassValueToOtherContext( { v8::Local cloned_value = gin::ConvertToV8(destination_context->GetIsolate(), ret); - store->CacheProxiedObject(value, cloned_value); + object_cache->CacheProxiedObject(value, cloned_value); return v8::MaybeLocal(cloned_value); } } v8::Local ProxyFunctionWrapper( - context_bridge::RenderFramePersistenceStore* store, + context_bridge::RenderFrameFunctionStore* store, size_t func_id, gin_helper::Arguments* args) { // Context the proxy function was called from @@ -327,6 +332,7 @@ v8::Local ProxyFunctionWrapper( std::get<1>(store->functions()[func_id]).Get(args->isolate()); v8::Context::Scope func_owning_context_scope(func_owning_context); + context_bridge::ObjectCache object_cache; { v8::Local func = (std::get<0>(store->functions()[func_id])).Get(args->isolate()); @@ -337,7 +343,7 @@ v8::Local ProxyFunctionWrapper( for (auto value : original_args) { auto arg = PassValueToOtherContext(calling_context, func_owning_context, - value, store, 0); + value, store, &object_cache, 0); if (arg.IsEmpty()) return v8::Undefined(args->isolate()); proxied_args.push_back(arg.ToLocalChecked()); @@ -375,9 +381,9 @@ v8::Local ProxyFunctionWrapper( if (maybe_return_value.IsEmpty()) return v8::Undefined(args->isolate()); - auto ret = - PassValueToOtherContext(func_owning_context, calling_context, - maybe_return_value.ToLocalChecked(), store, 0); + auto ret = PassValueToOtherContext(func_owning_context, calling_context, + maybe_return_value.ToLocalChecked(), + store, &object_cache, 0); if (ret.IsEmpty()) return v8::Undefined(args->isolate()); return ret.ToLocalChecked(); @@ -388,12 +394,13 @@ v8::MaybeLocal CreateProxyForAPI( const v8::Local& api_object, const v8::Local& source_context, const v8::Local& destination_context, - context_bridge::RenderFramePersistenceStore* store, + context_bridge::RenderFrameFunctionStore* store, + context_bridge::ObjectCache* object_cache, int recursion_depth) { gin_helper::Dictionary api(source_context->GetIsolate(), api_object); gin_helper::Dictionary proxy = 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( source_context, static_cast(v8::ONLY_ENUMERABLE | v8::SKIP_SYMBOLS), @@ -419,7 +426,7 @@ v8::MaybeLocal CreateProxyForAPI( auto passed_value = PassValueToOtherContext(source_context, destination_context, value, - store, recursion_depth + 1); + store, object_cache, recursion_depth + 1); if (passed_value.IsEmpty()) return v8::MaybeLocal(); proxy.Set(key_str, passed_value.ToLocalChecked()); @@ -435,22 +442,6 @@ gin_helper::Dictionary DebugGC(gin_helper::Dictionary empty) { auto* store = GetOrCreateStore(render_frame); gin_helper::Dictionary ret = gin::Dictionary::CreateEmpty(empty.isolate()); 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; } #endif @@ -460,7 +451,7 @@ void ExposeAPIInMainWorld(const std::string& key, gin_helper::Arguments* args) { auto* render_frame = GetRenderFrame(api_object); CHECK(render_frame); - context_bridge::RenderFramePersistenceStore* store = + context_bridge::RenderFrameFunctionStore* store = GetOrCreateStore(render_frame); auto* frame = render_frame->GetWebFrame(); CHECK(frame); @@ -478,10 +469,11 @@ void ExposeAPIInMainWorld(const std::string& key, v8::Local isolated_context = frame->WorldScriptContext(args->isolate(), WorldIDs::ISOLATED_WORLD_ID); + context_bridge::ObjectCache object_cache; v8::Context::Scope main_context_scope(main_context); { - v8::MaybeLocal maybe_proxy = - CreateProxyForAPI(api_object, isolated_context, main_context, store, 0); + v8::MaybeLocal maybe_proxy = CreateProxyForAPI( + api_object, isolated_context, main_context, store, &object_cache, 0); if (maybe_proxy.IsEmpty()) return; auto proxy = maybe_proxy.ToLocalChecked(); diff --git a/shell/renderer/api/electron_api_context_bridge.h b/shell/renderer/api/electron_api_context_bridge.h index 9fe9861a0c80..11c11d9b0bf8 100644 --- a/shell/renderer/api/electron_api_context_bridge.h +++ b/shell/renderer/api/electron_api_context_bridge.h @@ -5,6 +5,7 @@ #ifndef 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" namespace gin_helper { @@ -16,11 +17,11 @@ namespace electron { namespace api { namespace context_bridge { -class RenderFramePersistenceStore; +class RenderFrameFunctionStore; } v8::Local ProxyFunctionWrapper( - context_bridge::RenderFramePersistenceStore* store, + context_bridge::RenderFrameFunctionStore* store, size_t func_id, gin_helper::Arguments* args); @@ -28,7 +29,8 @@ v8::MaybeLocal CreateProxyForAPI( const v8::Local& api_object, const v8::Local& source_context, const v8::Local& target_context, - context_bridge::RenderFramePersistenceStore* store, + context_bridge::RenderFrameFunctionStore* store, + context_bridge::ObjectCache* object_cache, int recursion_depth); } // namespace api diff --git a/spec-main/api-context-bridge-spec.ts b/spec-main/api-context-bridge-spec.ts index 17f971a0ffb9..4766cb127198 100644 --- a/spec-main/api-context-bridge-spec.ts +++ b/spec-main/api-context-bridge-spec.ts @@ -320,43 +320,6 @@ describe('contextBridge', () => { 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 if (!useSandbox) { 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) }) - - 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 () => {