Repository: electron/electron
Pull request: perf: use v8::Local<v8::Object> as the key in ObjectCache

#### Description of Change

Followup to #27639

This cache is a `v8::Object`-to-`v8::Local<v8::Value>` lookup.

We're currently using `v8::Local<v8::Object>` as the key in a two-step process: first we hash the object as a `std::unordered_map` key. Second, we walk through a linked list of previously-cached `v8::Local<v8::Object>`s looking for one that  matches our key. Handles check for equality via `v8::HandleHelper::EqualHandles()`, so this is basically just testing pointers for equality (`a.ptr() == b.ptr()`).

This PR skips the middle steps and just uses a ~~`v8::Object*`~~ `v8::Local<v8::Object>` as the key

#### Checklist

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: none.

Changed files:
filename: shell/renderer/api/context_bridge/object_cache.cc
patch: @@ -12,37 +12,22 @@ namespace electron::api::context_bridge {
 ObjectCache::ObjectCache() = default;
 ObjectCache::~ObjectCache() = default;
 
-void ObjectCache::CacheProxiedObject(v8::Local<v8::Value> from,
+void ObjectCache::CacheProxiedObject(const v8::Local<v8::Value> from,
                                      v8::Local<v8::Value> proxy_value) {
-  if (from->IsObject() && !from->IsNullOrUndefined()) {
-    auto obj = from.As<v8::Object>();
-    int hash = obj->GetIdentityHash();
-
-    proxy_map_[hash].emplace_front(from, proxy_value);
-  }
+  if (from->IsObject() && !from->IsNullOrUndefined())
+    proxy_map_.insert_or_assign(from.As<v8::Object>(), proxy_value);
 }
 
 v8::MaybeLocal<v8::Value> ObjectCache::GetCachedProxiedObject(
-    v8::Local<v8::Value> from) const {
+    const v8::Local<v8::Value> from) const {
   if (!from->IsObject() || from->IsNullOrUndefined())
-    return v8::MaybeLocal<v8::Value>();
-
-  auto obj = from.As<v8::Object>();
-  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 (const auto& pair : list) {
-    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>();
+    return {};
+
+  const auto iter = proxy_map_.find(from.As<v8::Object>());
+  if (iter == proxy_map_.end() || iter->second.IsEmpty())
+    return {};
+
+  return iter->second;
 }
 
 }  // namespace electron::api::context_bridge

filename: shell/renderer/api/context_bridge/object_cache.h
patch: @@ -5,19 +5,18 @@
 #ifndef ELECTRON_SHELL_RENDERER_API_CONTEXT_BRIDGE_OBJECT_CACHE_H_
 #define ELECTRON_SHELL_RENDERER_API_CONTEXT_BRIDGE_OBJECT_CACHE_H_
 
-#include <forward_list>
 #include <unordered_map>
-#include <utility>
 
-#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"
+#include "v8/include/v8-local-handle.h"
+#include "v8/include/v8-object.h"
 
 namespace electron::api::context_bridge {
 
-using ObjectCachePair = std::pair<v8::Local<v8::Value>, v8::Local<v8::Value>>;
-
+/**
+ * NB: This is designed for context_bridge. Beware using it elsewhere!
+ * Since it's a v8::Local-to-v8::Local cache, be careful to destroy it
+ * before destroying the HandleScope that keeps the locals alive.
+ */
 class ObjectCache final {
  public:
   ObjectCache();
@@ -29,8 +28,15 @@ class ObjectCache final {
       v8::Local<v8::Value> from) const;
 
  private:
-  // object_identity ==> [from_value, proxy_value]
-  std::unordered_map<int, std::forward_list<ObjectCachePair>> proxy_map_;
+  struct Hash {
+    std::size_t operator()(const v8::Local<v8::Object>& obj) const {
+      return obj->GetIdentityHash();
+    }
+  };
+
+  // from_object ==> proxy_value
+  std::unordered_map<v8::Local<v8::Object>, v8::Local<v8::Value>, Hash>
+      proxy_map_;
 };
 
 }  // namespace electron::api::context_bridge