Browse Source

refactor: remove the RenderFrameFunctionStore and use privates to memory manage (#23592) (#23612)

Samuel Attard 5 years ago
parent
commit
7290d77cdc

+ 0 - 2
filenames.gni

@@ -574,8 +574,6 @@ filenames = {
     "shell/common/skia_util.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",

+ 1 - 4
lib/renderer/api/context-bridge.ts

@@ -11,12 +11,9 @@ const contextBridge = {
   exposeInMainWorld: (key: string, api: Record<string, any>) => {
     checkContextIsolationEnabled();
     return binding.exposeAPIInMainWorld(key, api);
-  },
-  debugGC: () => binding._debugGCMaps({})
+  }
 };
 
-if (!binding._debugGCMaps) delete contextBridge.debugGC;
-
 export default contextBridge;
 
 export const internalContextBridge = {

+ 6 - 0
lib/sandboxed_renderer/init.js

@@ -114,6 +114,12 @@ function preloadRequire (module) {
 // Process command line arguments.
 const { hasSwitch } = process.electronBinding('command_line');
 
+// Similar to nodes --expose-internals flag, this exposes electronBinding so
+// that tests can call it to get access to some test only bindings
+if (hasSwitch('unsafely-expose-electron-internals-for-testing')) {
+  preloadProcess.electronBinding = process.electronBinding;
+}
+
 const contextIsolation = hasSwitch('context-isolation');
 const isHiddenPage = hasSwitch('hidden-page');
 const usesNativeWindowOpen = true;

+ 28 - 0
shell/common/api/electron_api_v8_util.cc

@@ -114,6 +114,29 @@ bool IsSameOrigin(const GURL& l, const GURL& r) {
   return url::Origin::Create(l).IsSameOriginWith(url::Origin::Create(r));
 }
 
+#ifdef DCHECK_IS_ON
+std::vector<v8::Global<v8::Value>> weakly_tracked_values;
+
+void WeaklyTrackValue(v8::Isolate* isolate, v8::Local<v8::Value> value) {
+  v8::Global<v8::Value> global_value(isolate, value);
+  global_value.SetWeak();
+  weakly_tracked_values.push_back(std::move(global_value));
+}
+
+void ClearWeaklyTrackedValues() {
+  weakly_tracked_values.clear();
+}
+
+std::vector<v8::Local<v8::Value>> GetWeaklyTrackedValues(v8::Isolate* isolate) {
+  std::vector<v8::Local<v8::Value>> locals;
+  for (size_t i = 0; i < weakly_tracked_values.size(); i++) {
+    if (!weakly_tracked_values[i].IsEmpty())
+      locals.push_back(weakly_tracked_values[i].Get(isolate));
+  }
+  return locals;
+}
+#endif
+
 void Initialize(v8::Local<v8::Object> exports,
                 v8::Local<v8::Value> unused,
                 v8::Local<v8::Context> context,
@@ -141,6 +164,11 @@ void Initialize(v8::Local<v8::Object> exports,
   dict.SetMethod("requestGarbageCollectionForTesting",
                  &RequestGarbageCollectionForTesting);
   dict.SetMethod("isSameOrigin", &IsSameOrigin);
+#ifdef DCHECK_IS_ON
+  dict.SetMethod("getWeaklyTrackedValues", &GetWeaklyTrackedValues);
+  dict.SetMethod("clearWeaklyTrackedValues", &ClearWeaklyTrackedValues);
+  dict.SetMethod("weaklyTrackValue", &WeaklyTrackValue);
+#endif
 }
 
 }  // namespace

+ 0 - 49
shell/renderer/api/context_bridge/render_frame_function_store.cc

@@ -1,49 +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_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;
-}
-
-void RenderFrameFunctionStore::WillReleaseScriptContext(
-    v8::Local<v8::Context> context,
-    int32_t world_id) {
-  base::EraseIf(functions_, [context](auto const& pair) {
-    v8::Local<v8::Context> func_owning_context =
-        std::get<1>(pair.second).Get(context->GetIsolate());
-    return func_owning_context == context;
-  });
-}
-
-}  // namespace context_bridge
-
-}  // namespace api
-
-}  // namespace electron

+ 0 - 61
shell/renderer/api/context_bridge/render_frame_function_store.h

@@ -1,61 +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_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;
-  void WillReleaseScriptContext(v8::Local<v8::Context> context,
-                                int32_t world_id) 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_

+ 94 - 119
shell/renderer/api/electron_api_context_bridge.cc

@@ -18,13 +18,20 @@
 #include "shell/common/native_mate_converters/once_callback.h"
 #include "shell/common/node_includes.h"
 #include "shell/common/promise_util.h"
-#include "shell/renderer/api/context_bridge/render_frame_function_store.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 
 namespace electron {
 
 namespace api {
 
+namespace context_bridge {
+
+const char* const kProxyFunctionPrivateKey = "electron_contextBridge_proxy_fn";
+const char* const kSupportsDynamicPropertiesPrivateKey =
+    "electron_contextBridge_supportsDynamicProperties";
+
+}  // namespace context_bridge
+
 namespace {
 
 static int kMaxRecursion = 1000;
@@ -39,17 +46,6 @@ content::RenderFrame* GetRenderFrame(const v8::Local<v8::Object>& value) {
   return content::RenderFrame::FromWebFrame(frame);
 }
 
-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::RenderFrameFunctionStore(render_frame);
-    context_bridge::GetStoreMap().emplace(render_frame->GetRoutingID(), store);
-    return store;
-  }
-  return it->second;
-}
-
 // Sourced from "extensions/renderer/v8_schema_registry.cc"
 // Recursively freezes every v8 object on |object|.
 bool DeepFreeze(const v8::Local<v8::Object>& object,
@@ -101,35 +97,27 @@ bool IsPlainArray(const v8::Local<v8::Value>& arr) {
   return !arr->IsTypedArray();
 }
 
-class FunctionLifeMonitor final : public ObjectLifeMonitor {
- public:
-  static void BindTo(
-      v8::Isolate* isolate,
-      v8::Local<v8::Object> target,
-      base::WeakPtr<context_bridge::RenderFrameFunctionStore> store,
-      size_t func_id) {
-    new FunctionLifeMonitor(isolate, target, store, func_id);
-  }
-
- protected:
-  FunctionLifeMonitor(
-      v8::Isolate* isolate,
-      v8::Local<v8::Object> target,
-      base::WeakPtr<context_bridge::RenderFrameFunctionStore> store,
-      size_t func_id)
-      : ObjectLifeMonitor(isolate, target), store_(store), func_id_(func_id) {}
-  ~FunctionLifeMonitor() override = default;
-
-  void RunDestructor() override {
-    if (!store_)
-      return;
-    store_->functions().erase(func_id_);
-  }
+void SetPrivate(v8::Local<v8::Context> context,
+                v8::Local<v8::Object> target,
+                const std::string& key,
+                v8::Local<v8::Value> value) {
+  target
+      ->SetPrivate(
+          context,
+          v8::Private::ForApi(context->GetIsolate(),
+                              gin::StringToV8(context->GetIsolate(), key)),
+          value)
+      .Check();
+}
 
- private:
-  base::WeakPtr<context_bridge::RenderFrameFunctionStore> store_;
-  size_t func_id_;
-};
+v8::MaybeLocal<v8::Value> GetPrivate(v8::Local<v8::Context> context,
+                                     v8::Local<v8::Object> target,
+                                     const std::string& key) {
+  return target->GetPrivate(
+      context,
+      v8::Private::ForApi(context->GetIsolate(),
+                          gin::StringToV8(context->GetIsolate(), key)));
+}
 
 }  // namespace
 
@@ -147,7 +135,6 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
     v8::Local<v8::Context> source_context,
     v8::Local<v8::Context> destination_context,
     v8::Local<v8::Value> value,
-    context_bridge::RenderFrameFunctionStore* store,
     context_bridge::ObjectCache* object_cache,
     bool support_dynamic_properties,
     int recursion_depth) {
@@ -172,22 +159,21 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
   // the global handle at the right time.
   if (value->IsFunction()) {
     auto func = v8::Local<v8::Function>::Cast(value);
-    v8::Global<v8::Function> global_func(source_context->GetIsolate(), func);
-    v8::Global<v8::Context> global_source(source_context->GetIsolate(),
-                                          source_context);
 
-    size_t func_id = store->take_func_id();
-    store->functions()[func_id] =
-        std::make_tuple(std::move(global_func), std::move(global_source));
-    v8::Context::Scope destination_scope(destination_context);
     {
-      v8::Local<v8::Value> proxy_func = BindRepeatingFunctionToV8(
-          destination_context->GetIsolate(),
-          base::BindRepeating(&ProxyFunctionWrapper, store, func_id,
-                              support_dynamic_properties));
-      FunctionLifeMonitor::BindTo(destination_context->GetIsolate(),
-                                  v8::Local<v8::Object>::Cast(proxy_func),
-                                  store->GetWeakPtr(), func_id);
+      v8::Context::Scope destination_scope(destination_context);
+      v8::Local<v8::Object> state =
+          v8::Object::New(destination_context->GetIsolate());
+      SetPrivate(destination_context, state,
+                 context_bridge::kProxyFunctionPrivateKey, func);
+      SetPrivate(destination_context, state,
+                 context_bridge::kSupportsDynamicPropertiesPrivateKey,
+                 gin::ConvertToV8(destination_context->GetIsolate(),
+                                  support_dynamic_properties));
+      v8::Local<v8::Value> proxy_func;
+      if (!v8::Function::New(destination_context, ProxyFunctionWrapper, state)
+               .ToLocal(&proxy_func))
+        return v8::MaybeLocal<v8::Value>();
       object_cache->CacheProxiedObject(value, proxy_func);
       return v8::MaybeLocal<v8::Value>(proxy_func);
     }
@@ -219,7 +205,6 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
              v8::Isolate* isolate,
              v8::Global<v8::Context> global_source_context,
              v8::Global<v8::Context> global_destination_context,
-             context_bridge::RenderFrameFunctionStore* store,
              v8::Local<v8::Value> result) {
             if (global_source_context.IsEmpty() ||
                 global_destination_context.IsEmpty())
@@ -228,13 +213,13 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
             auto val =
                 PassValueToOtherContext(global_source_context.Get(isolate),
                                         global_destination_context.Get(isolate),
-                                        result, store, &object_cache, false, 0);
+                                        result, &object_cache, false, 0);
             if (!val.IsEmpty())
               proxied_promise->Resolve(val.ToLocalChecked());
           },
           proxied_promise, destination_context->GetIsolate(),
           std::move(global_then_source_context),
-          std::move(global_then_destination_context), store);
+          std::move(global_then_destination_context));
 
       v8::Global<v8::Context> global_catch_source_context(
           source_context->GetIsolate(), source_context);
@@ -248,7 +233,6 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
              v8::Isolate* isolate,
              v8::Global<v8::Context> global_source_context,
              v8::Global<v8::Context> global_destination_context,
-             context_bridge::RenderFrameFunctionStore* store,
              v8::Local<v8::Value> result) {
             if (global_source_context.IsEmpty() ||
                 global_destination_context.IsEmpty())
@@ -257,13 +241,13 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
             auto val =
                 PassValueToOtherContext(global_source_context.Get(isolate),
                                         global_destination_context.Get(isolate),
-                                        result, store, &object_cache, false, 0);
+                                        result, &object_cache, false, 0);
             if (!val.IsEmpty())
               proxied_promise->Reject(val.ToLocalChecked());
           },
           proxied_promise, destination_context->GetIsolate(),
           std::move(global_catch_source_context),
-          std::move(global_catch_destination_context), store);
+          std::move(global_catch_destination_context));
 
       ignore_result(source_promise->Then(
           source_context,
@@ -299,7 +283,7 @@ v8::MaybeLocal<v8::Value> 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, object_cache,
+            arr->Get(source_context, i).ToLocalChecked(), object_cache,
             support_dynamic_properties, recursion_depth + 1);
         if (value_for_array.IsEmpty())
           return v8::MaybeLocal<v8::Value>();
@@ -319,7 +303,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
   if (IsPlainObject(value)) {
     auto object_value = v8::Local<v8::Object>::Cast(value);
     auto passed_value = CreateProxyForAPI(
-        object_value, source_context, destination_context, store, object_cache,
+        object_value, source_context, destination_context, object_cache,
         support_dynamic_properties, recursion_depth + 1);
     if (passed_value.IsEmpty())
       return v8::MaybeLocal<v8::Value>();
@@ -346,33 +330,43 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
   }
 }
 
-v8::Local<v8::Value> ProxyFunctionWrapper(
-    context_bridge::RenderFrameFunctionStore* store,
-    size_t func_id,
-    bool support_dynamic_properties,
-    mate::Arguments* args) {
+void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info) {
+  CHECK(info.Data()->IsObject());
+  v8::Local<v8::Object> data = info.Data().As<v8::Object>();
+  bool support_dynamic_properties = false;
+  mate::Arguments args(info);
   // Context the proxy function was called from
-  v8::Local<v8::Context> calling_context = args->isolate()->GetCurrentContext();
-  // Context the function was created in
-  v8::Local<v8::Context> func_owning_context =
-      std::get<1>(store->functions()[func_id]).Get(args->isolate());
+  v8::Local<v8::Context> calling_context = args.isolate()->GetCurrentContext();
+
+  // Pull the original function and its context off of the data private key
+  v8::MaybeLocal<v8::Value> sdp_value =
+      GetPrivate(calling_context, data,
+                 context_bridge::kSupportsDynamicPropertiesPrivateKey);
+  v8::MaybeLocal<v8::Value> maybe_func = GetPrivate(
+      calling_context, data, context_bridge::kProxyFunctionPrivateKey);
+  v8::Local<v8::Value> func_value;
+  if (sdp_value.IsEmpty() || maybe_func.IsEmpty() ||
+      !gin::ConvertFromV8(args.isolate(), sdp_value.ToLocalChecked(),
+                          &support_dynamic_properties) ||
+      !maybe_func.ToLocal(&func_value))
+    return;
+
+  v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(func_value);
+  v8::Local<v8::Context> func_owning_context = func->CreationContext();
 
   v8::Context::Scope func_owning_context_scope(func_owning_context);
   context_bridge::ObjectCache object_cache;
   {
-    v8::Local<v8::Function> func =
-        (std::get<0>(store->functions()[func_id])).Get(args->isolate());
-
     std::vector<v8::Local<v8::Value>> original_args;
     std::vector<v8::Local<v8::Value>> proxied_args;
-    args->GetRemaining(&original_args);
+    args.GetRemaining(&original_args);
 
     for (auto value : original_args) {
-      auto arg = PassValueToOtherContext(calling_context, func_owning_context,
-                                         value, store, &object_cache,
-                                         support_dynamic_properties, 0);
+      auto arg =
+          PassValueToOtherContext(calling_context, func_owning_context, value,
+                                  &object_cache, support_dynamic_properties, 0);
       if (arg.IsEmpty())
-        return v8::Undefined(args->isolate());
+        return;
       proxied_args.push_back(arg.ToLocalChecked());
     }
 
@@ -380,7 +374,7 @@ v8::Local<v8::Value> ProxyFunctionWrapper(
     bool did_error = false;
     std::string error_message;
     {
-      v8::TryCatch try_catch(args->isolate());
+      v8::TryCatch try_catch(args.isolate());
       maybe_return_value = func->Call(func_owning_context, func,
                                       proxied_args.size(), proxied_args.data());
       if (try_catch.HasCaught()) {
@@ -388,7 +382,7 @@ v8::Local<v8::Value> ProxyFunctionWrapper(
         auto message = try_catch.Message();
 
         if (message.IsEmpty() ||
-            !mate::ConvertFromV8(args->isolate(), message->Get(),
+            !mate::ConvertFromV8(args.isolate(), message->Get(),
                                  &error_message)) {
           error_message =
               "An unknown exception occurred in the isolated context, an "
@@ -401,21 +395,22 @@ v8::Local<v8::Value> ProxyFunctionWrapper(
     if (did_error) {
       v8::Context::Scope calling_context_scope(calling_context);
       {
-        args->ThrowError(error_message);
-        return v8::Local<v8::Object>();
+        args.isolate()->ThrowException(v8::Exception::Error(
+            gin::StringToV8(args.isolate(), error_message)));
+        return;
       }
     }
 
     if (maybe_return_value.IsEmpty())
-      return v8::Undefined(args->isolate());
+      return;
 
     auto ret =
         PassValueToOtherContext(func_owning_context, calling_context,
-                                maybe_return_value.ToLocalChecked(), store,
+                                maybe_return_value.ToLocalChecked(),
                                 &object_cache, support_dynamic_properties, 0);
     if (ret.IsEmpty())
-      return v8::Undefined(args->isolate());
-    return ret.ToLocalChecked();
+      return;
+    info.GetReturnValue().Set(ret.ToLocalChecked());
   }
 }
 
@@ -423,7 +418,6 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
     const v8::Local<v8::Object>& api_object,
     const v8::Local<v8::Context>& source_context,
     const v8::Local<v8::Context>& destination_context,
-    context_bridge::RenderFrameFunctionStore* store,
     context_bridge::ObjectCache* object_cache,
     bool support_dynamic_properties,
     int recursion_depth) {
@@ -470,15 +464,13 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
             v8::Local<v8::Value> setter_proxy;
             if (!getter.IsEmpty()) {
               if (!PassValueToOtherContext(source_context, destination_context,
-                                           getter, store, object_cache, false,
-                                           1)
+                                           getter, object_cache, false, 1)
                        .ToLocal(&getter_proxy))
                 continue;
             }
             if (!setter.IsEmpty()) {
               if (!PassValueToOtherContext(source_context, destination_context,
-                                           setter, store, object_cache, false,
-                                           1)
+                                           setter, object_cache, false, 1)
                        .ToLocal(&setter_proxy))
                 continue;
             }
@@ -496,7 +488,7 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
         continue;
 
       auto passed_value = PassValueToOtherContext(
-          source_context, destination_context, value, store, object_cache,
+          source_context, destination_context, value, object_cache,
           support_dynamic_properties, recursion_depth + 1);
       if (passed_value.IsEmpty())
         return v8::MaybeLocal<v8::Object>();
@@ -507,23 +499,11 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
   }
 }
 
-#ifdef DCHECK_IS_ON
-mate::Dictionary DebugGC(mate::Dictionary empty) {
-  auto* render_frame = GetRenderFrame(empty.GetHandle());
-  auto* store = GetOrCreateStore(render_frame);
-  mate::Dictionary ret = mate::Dictionary::CreateEmpty(empty.isolate());
-  ret.Set("functionCount", store->functions().size());
-  return ret;
-}
-#endif
-
 void ExposeAPIInMainWorld(const std::string& key,
                           v8::Local<v8::Object> api_object,
                           mate::Arguments* args) {
   auto* render_frame = GetRenderFrame(api_object);
   CHECK(render_frame);
-  context_bridge::RenderFrameFunctionStore* store =
-      GetOrCreateStore(render_frame);
   auto* frame = render_frame->GetWebFrame();
   CHECK(frame);
   v8::Local<v8::Context> main_context = frame->MainWorldScriptContext();
@@ -542,9 +522,8 @@ void ExposeAPIInMainWorld(const std::string& key,
   context_bridge::ObjectCache object_cache;
   v8::Context::Scope main_context_scope(main_context);
   {
-    v8::MaybeLocal<v8::Object> maybe_proxy =
-        CreateProxyForAPI(api_object, isolated_context, main_context, store,
-                          &object_cache, false, 0);
+    v8::MaybeLocal<v8::Object> maybe_proxy = CreateProxyForAPI(
+        api_object, isolated_context, main_context, &object_cache, false, 0);
     if (maybe_proxy.IsEmpty())
       return;
     auto proxy = maybe_proxy.ToLocalChecked();
@@ -573,8 +552,6 @@ void OverrideGlobalValueFromIsolatedWorld(
 
   auto* render_frame = GetRenderFrame(value);
   CHECK(render_frame);
-  context_bridge::RenderFrameFunctionStore* store =
-      GetOrCreateStore(render_frame);
   auto* frame = render_frame->GetWebFrame();
   CHECK(frame);
   v8::Local<v8::Context> main_context = frame->MainWorldScriptContext();
@@ -586,9 +563,9 @@ void OverrideGlobalValueFromIsolatedWorld(
   {
     v8::Context::Scope main_context_scope(main_context);
     context_bridge::ObjectCache object_cache;
-    v8::MaybeLocal<v8::Value> maybe_proxy = PassValueToOtherContext(
-        value->CreationContext(), main_context, value, store, &object_cache,
-        support_dynamic_properties, 1);
+    v8::MaybeLocal<v8::Value> maybe_proxy =
+        PassValueToOtherContext(value->CreationContext(), main_context, value,
+                                &object_cache, support_dynamic_properties, 1);
     DCHECK(!maybe_proxy.IsEmpty());
     auto proxy = maybe_proxy.ToLocalChecked();
 
@@ -606,8 +583,6 @@ bool OverrideGlobalPropertyFromIsolatedWorld(
 
   auto* render_frame = GetRenderFrame(getter);
   CHECK(render_frame);
-  context_bridge::RenderFrameFunctionStore* store =
-      GetOrCreateStore(render_frame);
   auto* frame = render_frame->GetWebFrame();
   CHECK(frame);
   v8::Local<v8::Context> main_context = frame->MainWorldScriptContext();
@@ -625,14 +600,14 @@ bool OverrideGlobalPropertyFromIsolatedWorld(
     if (!getter->IsNullOrUndefined()) {
       v8::MaybeLocal<v8::Value> maybe_getter_proxy =
           PassValueToOtherContext(getter->CreationContext(), main_context,
-                                  getter, store, &object_cache, false, 1);
+                                  getter, &object_cache, false, 1);
       DCHECK(!maybe_getter_proxy.IsEmpty());
       getter_proxy = maybe_getter_proxy.ToLocalChecked();
     }
     if (!setter->IsNullOrUndefined() && setter->IsObject()) {
       v8::MaybeLocal<v8::Value> maybe_setter_proxy =
           PassValueToOtherContext(getter->CreationContext(), main_context,
-                                  setter, store, &object_cache, false, 1);
+                                  setter, &object_cache, false, 1);
       DCHECK(!maybe_setter_proxy.IsEmpty());
       setter_proxy = maybe_setter_proxy.ToLocalChecked();
     }
@@ -686,7 +661,7 @@ void Initialize(v8::Local<v8::Object> exports,
   dict.SetMethod("_isCalledFromIsolatedWorld",
                  &electron::api::IsCalledFromIsolatedWorld);
 #ifdef DCHECK_IS_ON
-  dict.SetMethod("_debugGCMaps", &electron::api::DebugGC);
+  dict.Set("_isDebug", true);
 #endif
 }
 

+ 1 - 10
shell/renderer/api/electron_api_context_bridge.h

@@ -14,21 +14,12 @@ namespace electron {
 
 namespace api {
 
-namespace context_bridge {
-class RenderFrameFunctionStore;
-}
-
-v8::Local<v8::Value> ProxyFunctionWrapper(
-    context_bridge::RenderFrameFunctionStore* store,
-    size_t func_id,
-    bool support_dynamic_properties,
-    mate::Arguments* args);
+void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info);
 
 v8::MaybeLocal<v8::Object> CreateProxyForAPI(
     const v8::Local<v8::Object>& api_object,
     const v8::Local<v8::Context>& source_context,
     const v8::Local<v8::Context>& target_context,
-    context_bridge::RenderFrameFunctionStore* store,
     context_bridge::ObjectCache* object_cache,
     bool support_dynamic_properties,
     int recursion_depth);

+ 39 - 22
spec-main/api-context-bridge-spec.ts

@@ -77,7 +77,8 @@ describe('contextBridge', () => {
             contextIsolation: true,
             nodeIntegration: true,
             sandbox: useSandbox,
-            preload: path.resolve(tmpDir, 'preload.js')
+            preload: path.resolve(tmpDir, 'preload.js'),
+            additionalArguments: ['--unsafely-expose-electron-internals-for-testing']
           }
         })
         await w.loadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}`)
@@ -88,15 +89,20 @@ describe('contextBridge', () => {
       }
     
       const getGCInfo = async (): Promise<{
-        functionCount: number
-        objectCount: number
-        liveFromValues: number
-        liveProxyValues: number
+        trackedValues: number;
       }> => {
-        const [,info] = await emittedOnce(ipcMain, 'gc-info', () => w.webContents.send('get-gc-info'))
-        return info
-      }
-    
+        const [, info] = await emittedOnce(ipcMain, 'gc-info', () => w.webContents.send('get-gc-info'));
+        return info;
+      };
+
+      const forceGCOnWindow = async () => {
+        w.webContents.debugger.attach();
+        await w.webContents.debugger.sendCommand('HeapProfiler.enable');
+        await w.webContents.debugger.sendCommand('HeapProfiler.collectGarbage');
+        await w.webContents.debugger.sendCommand('HeapProfiler.disable');
+        w.webContents.debugger.detach();
+      };
+
       it('should proxy numbers', async () => {
         await makeBindingWindow(() => {
           contextBridge.exposeInMainWorld('example', {
@@ -337,45 +343,56 @@ describe('contextBridge', () => {
       if (!useSandbox) {
         it('should release the global hold on methods sent across contexts', async () => {
           await makeBindingWindow(() => {
-            require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
+            require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: process.electronBinding('v8_util').getWeaklyTrackedValues().length }));
+            const { weaklyTrackValue } = process.electronBinding('v8_util');
             contextBridge.exposeInMainWorld('example', {
-              getFunction: () => () => 123
+              getFunction: () => () => 123,
+              track: weaklyTrackValue
             });
           });
           await callWithBindings(async (root: any) => {
             root.GCRunner.run();
           });
-          const baseValue = (await getGCInfo()).functionCount;
+          expect((await getGCInfo()).trackedValues).to.equal(0);
           await callWithBindings(async (root: any) => {
-            root.x = [root.example.getFunction()];
+            const fn = root.example.getFunction();
+            root.example.track(fn);
+            root.x = [fn];
           });
-          expect((await getGCInfo()).functionCount).to.equal(baseValue + 1);
+          expect((await getGCInfo()).trackedValues).to.equal(1);
           await callWithBindings(async (root: any) => {
             root.x = [];
             root.GCRunner.run();
           });
-          expect((await getGCInfo()).functionCount).to.equal(baseValue);
+          expect((await getGCInfo()).trackedValues).to.equal(0);
         });
       }
 
       if (useSandbox) {
         it('should not leak the global hold on methods sent across contexts when reloading a sandboxed renderer', async () => {
           await makeBindingWindow(() => {
-            require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
+            require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: process.electronBinding('v8_util').getWeaklyTrackedValues().length }));
+            const { weaklyTrackValue } = process.electronBinding('v8_util');
             contextBridge.exposeInMainWorld('example', {
-              getFunction: () => () => 123
+              getFunction: () => () => 123,
+              track: weaklyTrackValue
             });
             require('electron').ipcRenderer.send('window-ready-for-tasking');
           });
           const loadPromise = emittedOnce(ipcMain, 'window-ready-for-tasking');
-          const baseValue = (await getGCInfo()).functionCount;
+          expect((await getGCInfo()).trackedValues).to.equal(0);
           await callWithBindings((root: any) => {
-            root.location.reload()
-          })
-          await loadPromise
+            root.example.track(root.example.getFunction());
+          });
+          expect((await getGCInfo()).trackedValues).to.equal(1);
+          await callWithBindings((root: any) => {
+            root.location.reload();
+          });
+          await loadPromise;
+          await forceGCOnWindow();
           // If this is ever "2" it means we leaked the exposed function and
           // therefore the entire context after a reload
-          expect((await getGCInfo()).functionCount).to.equal(baseValue);
+          expect((await getGCInfo()).trackedValues).to.equal(0);
         });
       }
 

+ 3 - 0
typings/internal-ambient.d.ts

@@ -31,6 +31,9 @@ declare namespace NodeJS {
     requestGarbageCollectionForTesting(): void;
     createDoubleIDWeakMap(): any;
     setRemoteCallbackFreer(fn: Function, frameId: number, contextId: String, id: number, sender: any): void
+    weaklyTrackValue(value: any): void;
+    clearWeaklyTrackedValues(): void;
+    getWeaklyTrackedValues(): any[];
   }
 
   interface Process {