From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Michael Lippautz <mlippautz@chromium.org>
Date: Tue, 30 Jan 2024 16:05:33 +0000
Subject: bindings: Refactor DOMDataStore

Make DOMDataStore the entity that can set wrapper references in all
worlds, using inline storage where supported and otherwise fall back
to the wrapper map.

This is a refactoring that does not change any semantics but should
make fast paths more obvious.

Bug: chromium:1520621
Change-Id: Id8fd21d7598d818d3e820cc728f7f9357c1ec2d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5245546
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1253935}

diff --git a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
index c15355bf971294b99e9bb484faad84ff760398eb..770fd0a15c4a3521315b70e46a3646e361432508 100644
--- a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
+++ b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
@@ -122,9 +122,17 @@ void LocalWindowProxy::DisposeContext(Lifecycle next_status,
                ToScriptWrappable(global->GetPrototype().As<v8::Object>()));
     }
     V8DOMWrapper::ClearNativeInfo(GetIsolate(), global);
-    DOMWrapperWorld::ClearWrapperIfEqualTo(GetFrame()->DomWindow(), global);
+    script_state_->World().DomDataStore().ClearIfEqualTo(
+        GetFrame()->DomWindow(), global);
+#if DCHECK_IS_ON()
+    Vector<scoped_refptr<DOMWrapperWorld>> all_worlds;
+    DOMWrapperWorld::AllWorldsInIsolate(script_state_->GetIsolate(),
+                                        all_worlds);
+    for (auto& world : all_worlds) {
+      DCHECK(!world->DomDataStore().EqualTo(GetFrame()->DomWindow(), global));
+    }
+#endif  // DCHECK_IS_ON()
     script_state_->DetachGlobalObject();
-
 #if DCHECK_IS_ON()
     DidDetachGlobalObject();
 #endif
@@ -534,7 +542,7 @@ void LocalWindowProxy::NamedItemAdded(HTMLDocument* document,
 
   ScriptState::Scope scope(script_state_);
   v8::Local<v8::Object> document_wrapper =
-      world_->DomDataStore().Get(document, GetIsolate());
+      world_->DomDataStore().Get(GetIsolate(), document).ToLocalChecked();
   // When a non-configurable own property (e.g. unforgeable attribute) already
   // exists, `SetAccessor` fails and throws. Ignore the exception because own
   // properties have priority over named properties.
@@ -561,7 +569,7 @@ void LocalWindowProxy::NamedItemRemoved(HTMLDocument* document,
     return;
   ScriptState::Scope scope(script_state_);
   v8::Local<v8::Object> document_wrapper =
-      world_->DomDataStore().Get(document, GetIsolate());
+      world_->DomDataStore().Get(GetIsolate(), document).ToLocalChecked();
   document_wrapper
       ->Delete(GetIsolate()->GetCurrentContext(), V8String(GetIsolate(), name))
       .ToChecked();
diff --git a/third_party/blink/renderer/bindings/core/v8/observable_array_exotic_object_impl.cc b/third_party/blink/renderer/bindings/core/v8/observable_array_exotic_object_impl.cc
index d53c06ed5d436bd22898470c3de297d80e6ebae3..f1bbf27750c6d269a3d02f08348d78a2bd4ee375 100644
--- a/third_party/blink/renderer/bindings/core/v8/observable_array_exotic_object_impl.cc
+++ b/third_party/blink/renderer/bindings/core/v8/observable_array_exotic_object_impl.cc
@@ -64,7 +64,7 @@ ObservableArrayExoticObjectImpl::ObservableArrayExoticObjectImpl(
 v8::Local<v8::Value> ObservableArrayExoticObjectImpl::Wrap(
     ScriptState* script_state) {
   v8::Isolate* isolate = script_state->GetIsolate();
-  DCHECK(!DOMDataStore::ContainsWrapper(this, isolate));
+  DCHECK(!DOMDataStore::ContainsWrapper(isolate, this));
 
   // The proxy target object must be a JS Array (v8::Array) by definition.
   // Especially it's important that IsArray(proxy) evaluates to true.
diff --git a/third_party/blink/renderer/bindings/core/v8/remote_window_proxy.cc b/third_party/blink/renderer/bindings/core/v8/remote_window_proxy.cc
index 3caa238f10adc7294cac000aedf326d097758362..89211fcb8cb37549446ea9bbb14e1ae3a21afe1c 100644
--- a/third_party/blink/renderer/bindings/core/v8/remote_window_proxy.cc
+++ b/third_party/blink/renderer/bindings/core/v8/remote_window_proxy.cc
@@ -75,10 +75,16 @@ void RemoteWindowProxy::DisposeContext(Lifecycle next_status,
     v8::HandleScope handle_scope(GetIsolate());
     v8::Local<v8::Object> global = global_proxy_.Get(GetIsolate());
     V8DOMWrapper::ClearNativeInfo(GetIsolate(), global);
-    DOMWrapperWorld::ClearWrapperIfEqualTo(GetFrame()->DomWindow(), global);
+    world_->DomDataStore().ClearIfEqualTo(GetFrame()->DomWindow(), global);
 #if DCHECK_IS_ON()
+    Vector<scoped_refptr<DOMWrapperWorld>> all_worlds;
+    DOMWrapperWorld::AllWorldsInIsolate(GetIsolate(), all_worlds);
+    for (auto& world : all_worlds) {
+      DCHECK(!world->DomDataStore().EqualTo(GetFrame()->DomWindow(), global));
+    }
+
     DidDetachGlobalObject();
-#endif
+#endif  // DCHECK_IS_ON()
   }
 
   DCHECK_EQ(lifecycle_, Lifecycle::kContextIsInitialized);
diff --git a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
index 30ddc5b219c7d6d7630557e90d209a90bd0e00cd..45f4c36c20cc2db00ff6769c08271e629de99634 100644
--- a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
+++ b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
@@ -240,9 +240,10 @@ namespace bindings {
     ScriptState* script_state,
     ScriptWrappable* script_wrappable) {
   CHECK(script_wrappable);
-  v8::Local<v8::Value> wrapper =
-      DOMDataStore::GetWrapper(script_wrappable, script_state->GetIsolate());
-  if (LIKELY(!wrapper.IsEmpty())) {
+  v8::Local<v8::Object> wrapper;
+  if (LIKELY(
+          DOMDataStore::GetWrapper(script_state->GetIsolate(), script_wrappable)
+              .ToLocal(&wrapper))) {
     return wrapper;
   }
 
@@ -255,9 +256,9 @@ namespace bindings {
     ScriptWrappable* script_wrappable,
     v8::Local<v8::Object> creation_context_object) {
   CHECK(script_wrappable);
-  v8::Local<v8::Value> wrapper =
-      DOMDataStore::GetWrapper(script_wrappable, isolate);
-  if (LIKELY(!wrapper.IsEmpty())) {
+  v8::Local<v8::Object> wrapper;
+  if (LIKELY(DOMDataStore::GetWrapper(isolate, script_wrappable)
+                 .ToLocal(&wrapper))) {
     return wrapper;
   }
 
diff --git a/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_impl.cc b/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_impl.cc
index a8d881630c81218d231e955eb1dabb266b81b309..e6ed0f24ea30453ae35a7d5e140085f6c5fdb432 100644
--- a/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_impl.cc
+++ b/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_impl.cc
@@ -15,6 +15,7 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_window.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/html/html_document.h"
+#include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
 #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
 #include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h"
 #include "third_party/blink/renderer/platform/bindings/v8_object_constructor.h"
@@ -245,9 +246,10 @@ void DeserializeInternalFieldCallback(v8::Local<v8::Object> object,
       V8DOMWrapper::SetNativeInfo(deserializer_data->isolate, object,
                                   V8HTMLDocument::GetWrapperTypeInfo(),
                                   deserializer_data->html_document);
-      bool result = deserializer_data->html_document->SetWrapper(
-          deserializer_data->isolate, V8HTMLDocument::GetWrapperTypeInfo(),
-          object);
+      const bool result =
+          DOMDataStore::SetWrapperInInlineStorage</*entered_context=*/false>(
+              deserializer_data->isolate, deserializer_data->html_document,
+              V8HTMLDocument::GetWrapperTypeInfo(), object);
       CHECK(result);
       break;
     }
diff --git a/third_party/blink/renderer/core/dom/text.cc b/third_party/blink/renderer/core/dom/text.cc
index fbbfa23941811c4dd6e56cd6a13244b77cc39783..f79f59a91bf66ce07b4401accab88f1f3dbc3e53 100644
--- a/third_party/blink/renderer/core/dom/text.cc
+++ b/third_party/blink/renderer/core/dom/text.cc
@@ -144,7 +144,7 @@ Text* Text::splitText(unsigned offset, ExceptionState& exception_state) {
 
   // [NewObject] must always create a new wrapper.  Check that a wrapper
   // does not exist yet.
-  DCHECK(DOMDataStore::GetWrapper(new_text, GetDocument().GetAgent().isolate())
+  DCHECK(DOMDataStore::GetWrapper(GetDocument().GetAgent().isolate(), new_text)
              .IsEmpty());
 
   return new_text;
diff --git a/third_party/blink/renderer/core/frame/dom_window.cc b/third_party/blink/renderer/core/frame/dom_window.cc
index 41dfa6067538064d4b7f21c143baaee21dae5528..db557dc5f118cb0190085f61a799709791f7a606 100644
--- a/third_party/blink/renderer/core/frame/dom_window.cc
+++ b/third_party/blink/renderer/core/frame/dom_window.cc
@@ -106,7 +106,8 @@ v8::Local<v8::Object> DOMWindow::AssociateWithWrapper(
     v8::Local<v8::Object> wrapper) {
   // Using the world directly avoids fetching it from a potentially
   // half-initialized context.
-  if (world->DomDataStore().Set(isolate, this, wrapper_type_info, wrapper)) {
+  if (world->DomDataStore().Set</*entered_context=*/false>(
+          isolate, this, wrapper_type_info, wrapper)) {
     V8DOMWrapper::SetNativeInfo(isolate, wrapper, wrapper_type_info, this);
     DCHECK(V8DOMWrapper::HasInternalFieldsSet(wrapper));
   }
diff --git a/third_party/blink/renderer/core/frame/location.cc b/third_party/blink/renderer/core/frame/location.cc
index f9dbd16a03d80224b0d3e28a46917fbbe337d8a2..31676b163494573f77e65f1362678385bb1bb68e 100644
--- a/third_party/blink/renderer/core/frame/location.cc
+++ b/third_party/blink/renderer/core/frame/location.cc
@@ -56,7 +56,7 @@ v8::Local<v8::Value> Location::Wrap(ScriptState* script_state) {
   // the cross-origin status changes by changing properties like
   // |document.domain|.
   if (IsA<RemoteDOMWindow>(dom_window_.Get())) {
-    DCHECK(!DOMDataStore::ContainsWrapper(this, script_state->GetIsolate()));
+    DCHECK(!DOMDataStore::ContainsWrapper(script_state->GetIsolate(), this));
 
     DOMWrapperWorld& world = script_state->World();
     v8::Isolate* isolate = script_state->GetIsolate();
diff --git a/third_party/blink/renderer/core/script/detect_javascript_frameworks.cc b/third_party/blink/renderer/core/script/detect_javascript_frameworks.cc
index 15efc675f87b855e1b868be80cf5b98338ee2bbe..4fcace96ccfa5ec5897590480099fe60ccdfd921 100644
--- a/third_party/blink/renderer/core/script/detect_javascript_frameworks.cc
+++ b/third_party/blink/renderer/core/script/detect_javascript_frameworks.cc
@@ -95,12 +95,14 @@ inline void CheckPropertyMatches(Element& element,
                                  v8::Local<v8::Context> context,
                                  v8::Isolate* isolate,
                                  JavaScriptFrameworkDetectionResult& result) {
-  v8::Local<v8::Object> v8_element = dom_data_store.Get(&element, isolate);
-  if (v8_element.IsEmpty())
+  v8::Local<v8::Object> v8_element;
+  if (!dom_data_store.Get(isolate, &element).ToLocal(&v8_element)) {
     return;
+  }
   v8::Local<v8::Array> property_names;
-  if (!v8_element->GetOwnPropertyNames(context).ToLocal(&property_names))
+  if (!v8_element->GetOwnPropertyNames(context).ToLocal(&property_names)) {
     return;
+  }
 
   DEFINE_STATIC_LOCAL(AtomicString, vue_string, ("__vue__"));
   DEFINE_STATIC_LOCAL(AtomicString, vue_app_string, ("__vue_app__"));
diff --git a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.cc b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.cc
index 69060a1e5f6937896d385b5ceb70f2d49ef8669c..44e2849c8e4dfc38834c88937dc561858c13e726 100644
--- a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.cc
+++ b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.cc
@@ -51,9 +51,12 @@ static void AccumulateArrayBuffersForAllWorlds(
   Vector<scoped_refptr<DOMWrapperWorld>> worlds;
   DOMWrapperWorld::AllWorldsInIsolate(isolate, worlds);
   for (const auto& world : worlds) {
-    v8::Local<v8::Object> wrapper = world->DomDataStore().Get(object, isolate);
-    if (!wrapper.IsEmpty())
+    v8::Local<v8::Object> wrapper;
+    if (world->DomDataStore()
+            .Get</*entered_context=*/false>(isolate, object)
+            .ToLocal(&wrapper)) {
       buffers.push_back(v8::Local<v8::ArrayBuffer>::Cast(wrapper));
+    }
   }
 }
 
@@ -237,7 +240,7 @@ DOMArrayBuffer* DOMArrayBuffer::CreateUninitializedOrNull(
 }
 
 v8::Local<v8::Value> DOMArrayBuffer::Wrap(ScriptState* script_state) {
-  DCHECK(!DOMDataStore::ContainsWrapper(this, script_state->GetIsolate()));
+  DCHECK(!DOMDataStore::ContainsWrapper(script_state->GetIsolate(), this));
 
   const WrapperTypeInfo* wrapper_type_info = GetWrapperTypeInfo();
 
diff --git a/third_party/blink/renderer/core/typed_arrays/dom_data_view.cc b/third_party/blink/renderer/core/typed_arrays/dom_data_view.cc
index 59d8e8553b3c43ea68f43019974703d6fc0e83b8..fe5f1104ea156361f397cfa69944e6c19eb4ceb3 100644
--- a/third_party/blink/renderer/core/typed_arrays/dom_data_view.cc
+++ b/third_party/blink/renderer/core/typed_arrays/dom_data_view.cc
@@ -50,7 +50,7 @@ DOMDataView* DOMDataView::Create(DOMArrayBufferBase* buffer,
 }
 
 v8::Local<v8::Value> DOMDataView::Wrap(ScriptState* script_state) {
-  DCHECK(!DOMDataStore::ContainsWrapper(this, script_state->GetIsolate()));
+  DCHECK(!DOMDataStore::ContainsWrapper(script_state->GetIsolate(), this));
 
   const WrapperTypeInfo* wrapper_type_info = GetWrapperTypeInfo();
   v8::Local<v8::Value> v8_buffer =
diff --git a/third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.cc b/third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.cc
index 62278876ae55934c878dc22b97c8c6df1731e350..4ad4a143e1b08cea59a79171a0f7521f4e5aaa72 100644
--- a/third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.cc
+++ b/third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.cc
@@ -38,7 +38,7 @@ const WrapperTypeInfo& DOMSharedArrayBuffer::wrapper_type_info_ =
 #endif
 
 v8::Local<v8::Value> DOMSharedArrayBuffer::Wrap(ScriptState* script_state) {
-  DCHECK(!DOMDataStore::ContainsWrapper(this, script_state->GetIsolate()));
+  DCHECK(!DOMDataStore::ContainsWrapper(script_state->GetIsolate(), this));
 
   const WrapperTypeInfo* wrapper_type_info = GetWrapperTypeInfo();
   v8::Local<v8::SharedArrayBuffer> wrapper;
diff --git a/third_party/blink/renderer/core/typed_arrays/dom_typed_array.cc b/third_party/blink/renderer/core/typed_arrays/dom_typed_array.cc
index 2d874ba75ab27369f34ee9e51d6989fbf2e6da46..ba88280f7a7931dd7c23949ab2e9dd9ec424c31a 100644
--- a/third_party/blink/renderer/core/typed_arrays/dom_typed_array.cc
+++ b/third_party/blink/renderer/core/typed_arrays/dom_typed_array.cc
@@ -12,7 +12,7 @@ namespace blink {
 template <typename T, typename V8TypedArray, bool clamped>
 v8::Local<v8::Value> DOMTypedArray<T, V8TypedArray, clamped>::Wrap(
     ScriptState* script_state) {
-  DCHECK(!DOMDataStore::ContainsWrapper(this, script_state->GetIsolate()));
+  DCHECK(!DOMDataStore::ContainsWrapper(script_state->GetIsolate(), this));
 
   const WrapperTypeInfo* wrapper_type_info = GetWrapperTypeInfo();
   DOMArrayBufferBase* buffer = BufferBase();
diff --git a/third_party/blink/renderer/platform/bindings/dom_data_store.cc b/third_party/blink/renderer/platform/bindings/dom_data_store.cc
index f6b574ba2460b149f45379117e52a9d82b3bcad4..5bc84b868dbf072e9e2cb8c4aeee22a5ed4a9c57 100644
--- a/third_party/blink/renderer/platform/bindings/dom_data_store.cc
+++ b/third_party/blink/renderer/platform/bindings/dom_data_store.cc
@@ -6,8 +6,8 @@
 
 namespace blink {
 
-DOMDataStore::DOMDataStore(v8::Isolate* isolate, bool is_main_world)
-    : is_main_world_(is_main_world) {}
+DOMDataStore::DOMDataStore(v8::Isolate* isolate, bool can_use_inline_storage)
+    : can_use_inline_storage_(can_use_inline_storage) {}
 
 void DOMDataStore::Dispose() {
   for (auto& it : wrapper_map_) {
diff --git a/third_party/blink/renderer/platform/bindings/dom_data_store.h b/third_party/blink/renderer/platform/bindings/dom_data_store.h
index db1c7e1592b01ed6b29a607a598b810b81aadde4..7153e8db04789ef2961b8de5e2a3d47fe747237a 100644
--- a/third_party/blink/renderer/platform/bindings/dom_data_store.h
+++ b/third_party/blink/renderer/platform/bindings/dom_data_store.h
@@ -44,175 +44,389 @@
 
 namespace blink {
 
-// Holds a map specialized to map between ScriptWrappable objects and their
-// wrappers and provides an API to perform common operations with this map and
-// manage wrappers in a single world. Each world (DOMWrapperWorld) holds a
-// single map instance to hold wrappers only for that world.
+// DOMDataStore is the entity responsible for managing wrapper references (C++
+// to JS) in different worlds and settings (get/set wrapper, set return value).
+// Such references are stored in two ways:
+// - Inline in ScriptWrappable (or CustomWrappable); or
+// - In an ephemeron map in the DOMDataStore instance that is tied to a context
+//   in a V8 Isolate.
+//
+// The methods on DOMDataStore generally come in two forms:
+// - static methods: Have fast paths for inline storage (e.g. main world on the
+//   main rendering thread) and otherwise consider the current world, i.e., the
+//   world that corresponds to the current context
+//   (`v8::Isolate::GetCurrentContext()`).
+// - instance methods: Have fast paths for inline storage and consider the
+//   world the methods are invoked on. The calls are faster than the static
+//   calls if the DOMDataStore is already around.
+//
+// Exceptions are methods that operate on all worlds instead of the current
+// world. The methods mention this fact explicitly.
 class DOMDataStore final : public GarbageCollected<DOMDataStore> {
  public:
   static DOMDataStore& Current(v8::Isolate* isolate) {
     return DOMWrapperWorld::Current(isolate).DomDataStore();
   }
 
-  static bool SetReturnValue(v8::ReturnValue<v8::Value> return_value,
-                             ScriptWrappable* object) {
-    if (CanUseMainWorldWrapper())
-      return object->SetReturnValue(return_value);
-    return Current(return_value.GetIsolate())
-        .SetReturnValueFrom(return_value, object);
-  }
+  // Sets the `return_value` from `value`. Can be used from any world. Will only
+  // consider the current world.
+  static inline bool SetReturnValue(v8::ReturnValue<v8::Value> return_value,
+                                    ScriptWrappable* value);
 
-  static bool SetReturnValueForMainWorld(
+  // Sets the `return_value` from `value` in a world that can use inline
+  // storage.
+  static inline bool SetReturnValueFromInlineStorage(
       v8::ReturnValue<v8::Value> return_value,
-      ScriptWrappable* object) {
-    return object->SetReturnValue(return_value);
-  }
-
-  static bool SetReturnValueFast(v8::ReturnValue<v8::Value> return_value,
-                                 ScriptWrappable* object,
-                                 v8::Local<v8::Object> holder,
-                                 const ScriptWrappable* wrappable) {
-    if (HolderContainsWrapperForMainWorld(holder, wrappable)) {
-      // Verify our assumptions about the main world.
-      DCHECK(Current(return_value.GetIsolate()).is_main_world_);
-      return object->SetReturnValue(return_value);
-    }
-    return Current(return_value.GetIsolate())
-        .SetReturnValueFrom(return_value, object);
-  }
+      const ScriptWrappable* value);
 
-  static v8::Local<v8::Object> GetWrapper(ScriptWrappable* object,
-                                          v8::Isolate* isolate) {
-    if (CanUseMainWorldWrapper())
-      return object->MainWorldWrapper(isolate);
-    return Current(isolate).Get(object, isolate);
-  }
+  // Sets the `return_value` from `value` if already wrapped and returns false
+  // otherwise. Will use the `v8_receiver` and `blink_receiver` to check whether
+  // inline storage can be used.  Can be used from any world. Will only consider
+  // the current world.
+  static inline bool SetReturnValueFast(v8::ReturnValue<v8::Value> return_value,
+                                        ScriptWrappable* value,
+                                        v8::Local<v8::Object> v8_receiver,
+                                        const ScriptWrappable* blink_receiver);
 
-  // Associates the given |object| with the given |wrapper| if the object is
-  // not yet associated with any wrapper.  Returns true if the given wrapper
-  // is associated with the object, or false if the object is already
-  // associated with a wrapper.  In the latter case, |wrapper| will be updated
-  // to the existing wrapper.
-  [[nodiscard]] static bool SetWrapper(v8::Isolate* isolate,
-                                       ScriptWrappable* object,
-                                       const WrapperTypeInfo* wrapper_type_info,
-                                       v8::Local<v8::Object>& wrapper) {
-    if (CanUseMainWorldWrapper())
-      return object->SetWrapper(isolate, wrapper_type_info, wrapper);
-    return Current(isolate).Set(isolate, object, wrapper_type_info, wrapper);
-  }
+  // Returns the wrapper for `object` in the world corresponding to `isolate`.
+  // Can be used from any world. Will only consider the current world.
+  static inline v8::MaybeLocal<v8::Object> GetWrapper(
+      v8::Isolate* isolate,
+      const ScriptWrappable* object);
 
-  static bool ContainsWrapper(const ScriptWrappable* object,
-                              v8::Isolate* isolate) {
-    return Current(isolate).ContainsWrapper(object);
-  }
+  // Associates the given `object` with the given `wrapper` if the object is not
+  // yet associated with any wrapper.  Returns true if the given wrapper is
+  // associated with the object, or false if the object is already associated
+  // with a wrapper.  In the latter case, `wrapper` will be updated to the
+  // existing wrapper. Can be used from any world. Will only consider the
+  // current world.
+  [[nodiscard]] static inline bool SetWrapper(
+      v8::Isolate* isolate,
+      ScriptWrappable* object,
+      const WrapperTypeInfo* wrapper_type_info,
+      v8::Local<v8::Object>& wrapper);
+
+  // Same as `SetWrapper()` with the difference that it only considers inline
+  // storage. Can be used from any world.
+  template <bool entered_context = true>
+  [[nodiscard]] static inline bool SetWrapperInInlineStorage(
+      v8::Isolate* isolate,
+      ScriptWrappable* object,
+      const WrapperTypeInfo* wrapper_type_info,
+      v8::Local<v8::Object>& wrapper);
+
+  // Checks for the wrapper pair in all worlds and clears the first found pair.
+  // This should only be needed for garbage collection. All other callsites
+  // should know their worlds.
+  template <typename HandleType>
+  static inline bool ClearWrapperInAnyWorldIfEqualTo(ScriptWrappable* object,
+                                                     const HandleType& handle);
 
-  DOMDataStore(v8::Isolate* isolate, bool is_main_world);
+  // Clears the inline storage wrapper if it is equal to `handle`. Can be used
+  // from any world.
+  template <typename HandleType>
+  static inline bool ClearInlineStorageWrapperIfEqualTo(
+      ScriptWrappable* object,
+      const HandleType& handle);
+
+  // Checks whether a wrapper for `object` exists. Can be used from any world.
+  // Will only consider the current world.
+  static inline bool ContainsWrapper(v8::Isolate* isolate,
+                                     const ScriptWrappable* object);
+
+  DOMDataStore(v8::Isolate* isolate, bool);
   DOMDataStore(const DOMDataStore&) = delete;
   DOMDataStore& operator=(const DOMDataStore&) = delete;
 
   // Clears all references.
   void Dispose();
 
-  v8::Local<v8::Object> Get(ScriptWrappable* object, v8::Isolate* isolate) {
-    if (is_main_world_)
-      return object->MainWorldWrapper(isolate);
-    auto it = wrapper_map_.find(object);
-    if (it != wrapper_map_.end())
-      return it->value.Get(isolate);
-    return v8::Local<v8::Object>();
-  }
-
-  [[nodiscard]] bool Set(v8::Isolate* isolate,
-                         ScriptWrappable* object,
-                         const WrapperTypeInfo* wrapper_type_info,
-                         v8::Local<v8::Object>& wrapper) {
-    DCHECK(object);
-    DCHECK(!wrapper.IsEmpty());
-    if (is_main_world_)
-      return object->SetWrapper(isolate, wrapper_type_info, wrapper);
-
-    auto result = wrapper_map_.insert(
-        object, wrapper_type_info->SupportsDroppingWrapper()
-                    ? TraceWrapperV8Reference<v8::Object>(
-                          isolate, wrapper,
-                          TraceWrapperV8Reference<v8::Object>::IsDroppable{})
-                    : TraceWrapperV8Reference<v8::Object>(isolate, wrapper));
-    // TODO(mlippautz): Check whether there's still recursive cases of
-    // Wrap()/AssociateWithWrapper() that can run into the case of an existing
-    // entry.
-    if (UNLIKELY(!result.is_new_entry)) {
-      DCHECK(!result.stored_value->value.IsEmpty());
-      wrapper = result.stored_value->value.Get(isolate);
-    }
-    return result.is_new_entry;
-  }
+  // Same as `GetWrapper()` but for a single world.
+  template <bool entered_context = true>
+  inline v8::MaybeLocal<v8::Object> Get(v8::Isolate* isolate,
+                                        const ScriptWrappable* object);
+
+  // Same as `SetWrapper()` but for a single world.
+  template <bool entered_context = true>
+  [[nodiscard]] inline bool Set(v8::Isolate* isolate,
+                                ScriptWrappable* object,
+                                const WrapperTypeInfo* wrapper_type_info,
+                                v8::Local<v8::Object>& wrapper);
+
+  // Same as `ContainsWrapper()` but for a single world.
+  inline bool Contains(const ScriptWrappable* object) const;
+
+  // Returns true if the pair {object, handle} exists in the current world.
+  template <typename HandleType>
+  inline bool EqualTo(const ScriptWrappable* object, const HandleType& handle);
 
+  // Clears the connection between object and handle in the current world.
   template <typename HandleType>
-  bool ClearWrapperIfEqualTo(ScriptWrappable* object,
+  inline bool ClearIfEqualTo(ScriptWrappable* object,
                              const HandleType& handle) {
-    DCHECK(!is_main_world_);
-    const auto& it = wrapper_map_.find(object);
-    if (it != wrapper_map_.end() && it->value == handle) {
-      it->value.Reset();
-      wrapper_map_.erase(it);
-      return true;
+    if (can_use_inline_storage_) {
+      return ClearInlineStorageWrapperIfEqualTo(object, handle);
     }
-    return false;
+    return ClearInMapIfEqualTo(object, handle);
   }
 
-  bool SetReturnValueFrom(v8::ReturnValue<v8::Value> return_value,
-                          ScriptWrappable* object) {
-    if (is_main_world_)
-      return object->SetReturnValue(return_value);
-    auto it = wrapper_map_.find(object);
-    if (it != wrapper_map_.end()) {
-      return_value.SetNonEmpty(it->value);
+  // Clears the connection between object and handle in the current world
+  // assuming no inline storage is available for this world.
+  template <typename HandleType>
+  inline bool ClearInMapIfEqualTo(const ScriptWrappable* object,
+                                  const HandleType& handle) {
+    DCHECK(!can_use_inline_storage_);
+    if (const auto& it = wrapper_map_.find(object);
+        it != wrapper_map_.end() && it->value == handle) {
+      it->value.Reset();
+      wrapper_map_.erase(it);
       return true;
     }
     return false;
   }
 
-  bool ContainsWrapper(const ScriptWrappable* object) {
-    if (is_main_world_)
-      return object->ContainsWrapper();
-    return base::Contains(wrapper_map_, object);
-  }
-
   virtual void Trace(Visitor*) const;
 
  private:
-  // We can use a wrapper stored in a ScriptWrappable when we're in the main
-  // world.  This method does the fast check if we're in the main world. If this
+  // We can use the inline storage in a ScriptWrappable when we're in the main
+  // world. This method does the fast check if we're in the main world. If this
   // method returns true, it is guaranteed that we're in the main world. On the
   // other hand, if this method returns false, nothing is guaranteed (we might
   // be in the main world).
-  static bool CanUseMainWorldWrapper() {
+  static bool CanUseInlineStorageForWrapper() {
     return !WTF::MayNotBeMainThread() &&
            !DOMWrapperWorld::NonMainWorldsExistInMainThread();
   }
 
-  static bool HolderContainsWrapperForMainWorld(
-      v8::Local<v8::Object> holder,
+  inline bool SetReturnValueFrom(v8::ReturnValue<v8::Value> return_value,
+                                 const ScriptWrappable* value);
+
+  using WrapperRefType = decltype(ScriptWrappable::wrapper_);
+
+  // Convenience methods for accessing the inlined storage.
+  static inline WrapperRefType& GetUncheckedInlineStorage(
+      ScriptWrappable* wrappable) {
+    return wrappable->wrapper_;
+  }
+  static inline const WrapperRefType& GetUncheckedInlineStorage(
       const ScriptWrappable* wrappable) {
-    // The first fastest way is to check that there is only the main world
-    // on the main thread.
-    if (CanUseMainWorldWrapper()) {
-      return true;
-    }
-    // The second fastest way to check if we're in the main world is to
-    // check if the wrappable's wrapper is the same as the holder.
-    DCHECK(wrappable);
-    return wrappable->IsEqualTo(holder);
+    return GetUncheckedInlineStorage(const_cast<ScriptWrappable*>(wrappable));
+  }
+  static inline WrapperRefType& GetInlineStorage(v8::Isolate* isolate,
+                                                 ScriptWrappable* wrappable) {
+    // The following will crash if no context is entered. This is by design. The
+    // validation can be skipped with `SetWrapperInInlineStorage<false>()`.
+    DCHECK(Current(isolate).can_use_inline_storage_);
+    return GetUncheckedInlineStorage(wrappable);
+  }
+  static inline const WrapperRefType& GetInlineStorage(
+      v8::Isolate* isolate,
+      const ScriptWrappable* wrappable) {
+    return GetInlineStorage(isolate, const_cast<ScriptWrappable*>(wrappable));
   }
 
-  bool is_main_world_;
+  // Specifies whether this data store is allowed to use inline storage of a
+  // ScriptWrappable and can avoid using the ephemeron map below.
+  const bool can_use_inline_storage_;
   // Ephemeron map: V8 wrapper will be kept alive as long as ScriptWrappable is.
   HeapHashMap<WeakMember<const ScriptWrappable>,
               TraceWrapperV8Reference<v8::Object>>
       wrapper_map_;
 };
 
+// static
+bool DOMDataStore::SetReturnValue(v8::ReturnValue<v8::Value> return_value,
+                                  ScriptWrappable* value) {
+  if (CanUseInlineStorageForWrapper()) {
+    return SetReturnValueFromInlineStorage(return_value, value);
+  }
+  return Current(return_value.GetIsolate())
+      .SetReturnValueFrom(return_value, value);
+}
+
+// static
+bool DOMDataStore::SetReturnValueFromInlineStorage(
+    v8::ReturnValue<v8::Value> return_value,
+    const ScriptWrappable* value) {
+  const auto& ref = GetInlineStorage(return_value.GetIsolate(), value);
+  if (!ref.IsEmpty()) {
+    return_value.SetNonEmpty(ref);
+    return true;
+  }
+  return false;
+}
+
+// static
+bool DOMDataStore::SetReturnValueFast(v8::ReturnValue<v8::Value> return_value,
+                                      ScriptWrappable* object,
+                                      v8::Local<v8::Object> v8_receiver,
+                                      const ScriptWrappable* blink_receiver) {
+  // Fast checks for using inline storage:
+  // 1. Fast check for inline storage.
+  // 2. If the receiver receiver's inline wrapper is the V8 receiver. In this
+  //    case we know we are in the world that can use inline storage.
+  DCHECK(blink_receiver);
+  DCHECK(!v8_receiver.IsEmpty());
+  if (CanUseInlineStorageForWrapper() ||
+      v8_receiver == GetUncheckedInlineStorage(blink_receiver)) {
+    return SetReturnValueFromInlineStorage(return_value, object);
+  }
+  return Current(return_value.GetIsolate())
+      .SetReturnValueFrom(return_value, object);
+}
+
+bool DOMDataStore::SetReturnValueFrom(v8::ReturnValue<v8::Value> return_value,
+                                      const ScriptWrappable* value) {
+  if (can_use_inline_storage_) {
+    return SetReturnValueFromInlineStorage(return_value, value);
+  }
+  if (const auto it = wrapper_map_.find(value); it != wrapper_map_.end()) {
+    return_value.SetNonEmpty(it->value);
+    return true;
+  }
+  return false;
+}
+
+// static
+v8::MaybeLocal<v8::Object> DOMDataStore::GetWrapper(
+    v8::Isolate* isolate,
+    const ScriptWrappable* object) {
+  if (CanUseInlineStorageForWrapper()) {
+    return GetInlineStorage(isolate, object).Get(isolate);
+  }
+  return Current(isolate).Get(isolate, object);
+}
+
+template <bool entered_context>
+v8::MaybeLocal<v8::Object> DOMDataStore::Get(v8::Isolate* isolate,
+                                             const ScriptWrappable* object) {
+  if (can_use_inline_storage_) {
+    return entered_context ? GetInlineStorage(isolate, object).Get(isolate)
+                           : GetUncheckedInlineStorage(object).Get(isolate);
+  }
+  if (const auto it = wrapper_map_.find(object); it != wrapper_map_.end()) {
+    return it->value.Get(isolate);
+  }
+  return {};
+}
+
+// static
+bool DOMDataStore::SetWrapper(v8::Isolate* isolate,
+                              ScriptWrappable* object,
+                              const WrapperTypeInfo* wrapper_type_info,
+                              v8::Local<v8::Object>& wrapper) {
+  if (CanUseInlineStorageForWrapper()) {
+    return SetWrapperInInlineStorage(isolate, object, wrapper_type_info,
+                                     wrapper);
+  }
+  return Current(isolate).Set(isolate, object, wrapper_type_info, wrapper);
+}
+
+// static
+template <bool entered_context>
+bool DOMDataStore::SetWrapperInInlineStorage(
+    v8::Isolate* isolate,
+    ScriptWrappable* object,
+    const WrapperTypeInfo* wrapper_type_info,
+    v8::Local<v8::Object>& wrapper) {
+  DCHECK(!wrapper.IsEmpty());
+  auto& ref = entered_context ? GetInlineStorage(isolate, object)
+                              : GetUncheckedInlineStorage(object);
+  if (UNLIKELY(!ref.IsEmpty())) {
+    wrapper = ref.Get(isolate);
+    return false;
+  }
+  if (wrapper_type_info->SupportsDroppingWrapper()) {
+    ref.Reset(isolate, wrapper,
+              TraceWrapperV8Reference<v8::Object>::IsDroppable{});
+  } else {
+    ref.Reset(isolate, wrapper);
+  }
+  DCHECK(!ref.IsEmpty());
+  return true;
+}
+
+template <bool entered_context>
+bool DOMDataStore::Set(v8::Isolate* isolate,
+                       ScriptWrappable* object,
+                       const WrapperTypeInfo* wrapper_type_info,
+                       v8::Local<v8::Object>& wrapper) {
+  DCHECK(object);
+  DCHECK(!wrapper.IsEmpty());
+  if (can_use_inline_storage_) {
+    return SetWrapperInInlineStorage<entered_context>(
+        isolate, object, wrapper_type_info, wrapper);
+  }
+  auto result = wrapper_map_.insert(
+      object, wrapper_type_info->SupportsDroppingWrapper()
+                  ? TraceWrapperV8Reference<v8::Object>(
+                        isolate, wrapper,
+                        TraceWrapperV8Reference<v8::Object>::IsDroppable{})
+                  : TraceWrapperV8Reference<v8::Object>(isolate, wrapper));
+  // TODO(mlippautz): Check whether there's still recursive cases of
+  // Wrap()/AssociateWithWrapper() that can run into the case of an existing
+  // entry.
+  if (UNLIKELY(!result.is_new_entry)) {
+    DCHECK(!result.stored_value->value.IsEmpty());
+    wrapper = result.stored_value->value.Get(isolate);
+  }
+  return result.is_new_entry;
+}
+
+//  static
+bool DOMDataStore::ContainsWrapper(v8::Isolate* isolate,
+                                   const ScriptWrappable* object) {
+  if (CanUseInlineStorageForWrapper()) {
+    return !GetInlineStorage(isolate, object).IsEmpty();
+  }
+  return Current(isolate).Contains(object);
+}
+
+// Same as `ContainsWrapper()` but for a single world.
+bool DOMDataStore::Contains(const ScriptWrappable* object) const {
+  if (can_use_inline_storage_) {
+    return !GetUncheckedInlineStorage(object).IsEmpty();
+  }
+  return base::Contains(wrapper_map_, object);
+}
+
+// static
+template <typename HandleType>
+bool DOMDataStore::ClearWrapperInAnyWorldIfEqualTo(ScriptWrappable* object,
+                                                   const HandleType& handle) {
+  if (LIKELY(ClearInlineStorageWrapperIfEqualTo(object, handle))) {
+    return true;
+  }
+  return DOMWrapperWorld::ClearWrapperInAnyNonInlineStorageWorldIfEqualTo(
+      object, handle);
+}
+
+// static
+template <typename HandleType>
+bool DOMDataStore::ClearInlineStorageWrapperIfEqualTo(
+    ScriptWrappable* object,
+    const HandleType& handle) {
+  auto& ref = GetUncheckedInlineStorage(object);
+  if (ref == handle) {
+    ref.Reset();
+    return true;
+  }
+  return false;
+}
+
+template <typename HandleType>
+bool DOMDataStore::EqualTo(const ScriptWrappable* object,
+                           const HandleType& handle) {
+  if (can_use_inline_storage_) {
+    return GetUncheckedInlineStorage(object) == handle;
+  }
+  if (const auto& it = wrapper_map_.find(object);
+      it != wrapper_map_.end() && it->value == handle) {
+    return true;
+  }
+  return false;
+}
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_DOM_DATA_STORE_H_
diff --git a/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc b/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc
index e330dd46f1ed0bee4f209ab0ce2c058ca52f4029..663d5ecbfe3e2b7440e34e6c90d9738f666a8905 100644
--- a/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc
+++ b/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc
@@ -291,12 +291,12 @@ int DOMWrapperWorld::GenerateWorldIdForType(WorldType world_type) {
 }
 
 // static
-bool DOMWrapperWorld::ClearNonMainWorldWrapperIfEqualTo(
+bool DOMWrapperWorld::ClearWrapperInAnyNonInlineStorageWorldIfEqualTo(
     ScriptWrappable* object,
     const v8::Local<v8::Object>& handle) {
   for (DOMWrapperWorld* world : GetWorldMap().Values()) {
     DOMDataStore& data_store = world->DomDataStore();
-    if (data_store.ClearWrapperIfEqualTo(object, handle)) {
+    if (data_store.ClearInMapIfEqualTo(object, handle)) {
       return true;
     }
   }
@@ -304,12 +304,12 @@ bool DOMWrapperWorld::ClearNonMainWorldWrapperIfEqualTo(
 }
 
 // static
-bool DOMWrapperWorld::ClearNonMainWorldWrapperIfEqualTo(
+bool DOMWrapperWorld::ClearWrapperInAnyNonInlineStorageWorldIfEqualTo(
     ScriptWrappable* object,
     const v8::TracedReference<v8::Object>& handle) {
   for (DOMWrapperWorld* world : GetWorldMap().Values()) {
     DOMDataStore& data_store = world->DomDataStore();
-    if (data_store.ClearWrapperIfEqualTo(object, handle)) {
+    if (data_store.ClearInMapIfEqualTo(object, handle)) {
       return true;
     }
   }
diff --git a/third_party/blink/renderer/platform/bindings/dom_wrapper_world.h b/third_party/blink/renderer/platform/bindings/dom_wrapper_world.h
index b8fccf776cb01c120ac589072f5183d0f4318bdf..ec51d8bbed1af2a192211d19a89ced7da643dda8 100644
--- a/third_party/blink/renderer/platform/bindings/dom_wrapper_world.h
+++ b/third_party/blink/renderer/platform/bindings/dom_wrapper_world.h
@@ -171,24 +171,17 @@ class PLATFORM_EXPORT DOMWrapperWorld : public RefCounted<DOMWrapperWorld> {
     return *v8_object_data_store_;
   }
 
-  // Clear the reference pointing from |object| to |handle| in any world.
-  template <typename HandleType>
-  inline static bool ClearWrapperIfEqualTo(ScriptWrappable* object,
-                                           const HandleType& handle);
-
-  // Clear the reference pointing from |object| to |handle| in the main world.
-  template <typename HandleType>
-  inline static bool ClearMainWorldWrapperIfEqualTo(ScriptWrappable* object,
-                                                    const HandleType& handle);
-
- private:
-  static bool ClearNonMainWorldWrapperIfEqualTo(
+  // Methods iterate all worlds and invokes the clearing methods on
+  // DOMDataStore. The WorldMap is only known to the DOMWrapperWorld and as such
+  // the iteration cannot be folded into DOMDataStore.
+  static bool ClearWrapperInAnyNonInlineStorageWorldIfEqualTo(
       ScriptWrappable* object,
       const v8::Local<v8::Object>& handle);
-  static bool ClearNonMainWorldWrapperIfEqualTo(
+  static bool ClearWrapperInAnyNonInlineStorageWorldIfEqualTo(
       ScriptWrappable* object,
       const v8::TracedReference<v8::Object>& handle);
 
+private:
   DOMWrapperWorld(v8::Isolate*, WorldType, int32_t world_id);
 
   static unsigned number_of_non_main_worlds_in_main_thread_;
@@ -204,24 +197,6 @@ class PLATFORM_EXPORT DOMWrapperWorld : public RefCounted<DOMWrapperWorld> {
   Persistent<V8ObjectDataStore> v8_object_data_store_;
 };
 
-// static
-template <typename HandleType>
-bool DOMWrapperWorld::ClearMainWorldWrapperIfEqualTo(ScriptWrappable* object,
-                                                     const HandleType& handle) {
-  return object->ClearMainWorldWrapperIfEqualTo(handle);
-}
-
-// static
-template <typename HandleType>
-bool DOMWrapperWorld::ClearWrapperIfEqualTo(ScriptWrappable* object,
-                                            const HandleType& handle) {
-  if (ClearMainWorldWrapperIfEqualTo(object, handle)) {
-    return true;
-  }
-  // Slow path: |object| may point to |handle| in any non-main DOM world.
-  return DOMWrapperWorld::ClearNonMainWorldWrapperIfEqualTo(object, handle);
-}
-
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_DOM_WRAPPER_WORLD_H_
diff --git a/third_party/blink/renderer/platform/bindings/frozen_array_base.cc b/third_party/blink/renderer/platform/bindings/frozen_array_base.cc
index 4af0f5ef920e981e6cf0b7ff1e53d69d1bd41a29..5d01d17390f4de24190956f56e2784929266c21d 100644
--- a/third_party/blink/renderer/platform/bindings/frozen_array_base.cc
+++ b/third_party/blink/renderer/platform/bindings/frozen_array_base.cc
@@ -39,9 +39,9 @@ v8::Local<v8::Value> FrozenArrayBase::ToV8(ScriptState* script_state) const {
 }
 
 v8::Local<v8::Value> FrozenArrayBase::ToV8(ScriptState* script_state) {
-  v8::Local<v8::Object> wrapper = script_state->World().DomDataStore().Get(
-      this, script_state->GetIsolate());
-  if (LIKELY(!wrapper.IsEmpty())) {
+  v8::Local<v8::Object> wrapper;
+  if (LIKELY(DOMDataStore::GetWrapper(script_state->GetIsolate(), this)
+                 .ToLocal(&wrapper))) {
     return wrapper;
   }
 
@@ -49,7 +49,7 @@ v8::Local<v8::Value> FrozenArrayBase::ToV8(ScriptState* script_state) {
 }
 
 v8::Local<v8::Value> FrozenArrayBase::Wrap(ScriptState* script_state) {
-  DCHECK(!script_state->World().DomDataStore().ContainsWrapper(this));
+  DCHECK(!DOMDataStore::ContainsWrapper(script_state->GetIsolate(), this));
 
   v8::Local<v8::Value> wrapper = MakeV8ArrayToBeFrozen(script_state);
 
diff --git a/third_party/blink/renderer/platform/bindings/script_wrappable.cc b/third_party/blink/renderer/platform/bindings/script_wrappable.cc
index 92fa7df37915a3f456562f9cc0ef20436bfcad5f..9733be3a28b1eb315e7774dcb5a197143457cf8c 100644
--- a/third_party/blink/renderer/platform/bindings/script_wrappable.cc
+++ b/third_party/blink/renderer/platform/bindings/script_wrappable.cc
@@ -20,9 +20,9 @@ struct SameSizeAsScriptWrappable {
 ASSERT_SIZE(ScriptWrappable, SameSizeAsScriptWrappable);
 
 v8::Local<v8::Value> ScriptWrappable::ToV8(ScriptState* script_state) {
-  v8::Local<v8::Value> wrapper =
-      DOMDataStore::GetWrapper(this, script_state->GetIsolate());
-  if (LIKELY(!wrapper.IsEmpty())) {
+  v8::Local<v8::Object> wrapper;
+  if (LIKELY(DOMDataStore::GetWrapper(script_state->GetIsolate(), this)
+                 .ToLocal(&wrapper))) {
     return wrapper;
   }
   return Wrap(script_state);
@@ -31,8 +31,8 @@ v8::Local<v8::Value> ScriptWrappable::ToV8(ScriptState* script_state) {
 v8::Local<v8::Value> ScriptWrappable::ToV8(
     v8::Isolate* isolate,
     v8::Local<v8::Object> creation_context_object) {
-  v8::Local<v8::Value> wrapper = DOMDataStore::GetWrapper(this, isolate);
-  if (LIKELY(!wrapper.IsEmpty())) {
+  v8::Local<v8::Object> wrapper;
+  if (LIKELY(DOMDataStore::GetWrapper(isolate, this).ToLocal(&wrapper))) {
     return wrapper;
   }
   CHECK(!creation_context_object.IsEmpty());
@@ -44,7 +44,7 @@ v8::Local<v8::Value> ScriptWrappable::ToV8(
 v8::Local<v8::Value> ScriptWrappable::Wrap(ScriptState* script_state) {
   const WrapperTypeInfo* wrapper_type_info = GetWrapperTypeInfo();
 
-  DCHECK(!DOMDataStore::ContainsWrapper(this, script_state->GetIsolate()));
+  DCHECK(!DOMDataStore::ContainsWrapper(script_state->GetIsolate(), this));
 
   v8::Local<v8::Object> wrapper =
       V8DOMWrapper::CreateWrapper(script_state, wrapper_type_info);
@@ -61,7 +61,7 @@ v8::Local<v8::Object> ScriptWrappable::AssociateWithWrapper(
 }
 
 void ScriptWrappable::Trace(Visitor* visitor) const {
-  visitor->Trace(main_world_wrapper_);
+  visitor->Trace(wrapper_);
 }
 
 const char* ScriptWrappable::NameInHeapSnapshot() const {
diff --git a/third_party/blink/renderer/platform/bindings/script_wrappable.h b/third_party/blink/renderer/platform/bindings/script_wrappable.h
index ba1b49d06bd26f4f04767a953ffa2d918a5d93e9..858ae35448ba23ddc200cc8a44d3b77f47e69e35 100644
--- a/third_party/blink/renderer/platform/bindings/script_wrappable.h
+++ b/third_party/blink/renderer/platform/bindings/script_wrappable.h
@@ -42,6 +42,7 @@
 
 namespace blink {
 
+class DOMDataStore;
 class ScriptState;
 
 // ScriptWrappable provides a way to map from/to C++ DOM implementation to/from
@@ -174,78 +175,22 @@ class PLATFORM_EXPORT ScriptWrappable
       const WrapperTypeInfo*,
       v8::Local<v8::Object> wrapper);
 
-  // Associates this instance with the given |wrapper| if this instance is not
-  // yet associated with any wrapper.  Returns true if the given wrapper is
-  // associated with this instance, or false if this instance is already
-  // associated with a wrapper.  In the latter case, |wrapper| will be updated
-  // to the existing wrapper.
-  [[nodiscard]] bool SetWrapper(v8::Isolate* isolate,
-                                const WrapperTypeInfo* wrapper_type_info,
-                                v8::Local<v8::Object>& wrapper) {
-    DCHECK(!wrapper.IsEmpty());
-    if (UNLIKELY(ContainsWrapper())) {
-      wrapper = MainWorldWrapper(isolate);
-      return false;
-    }
-    if (wrapper_type_info->SupportsDroppingWrapper()) {
-      main_world_wrapper_.Reset(
-          isolate, wrapper, TraceWrapperV8Reference<v8::Object>::IsDroppable{});
-    } else {
-      main_world_wrapper_.Reset(isolate, wrapper);
-    }
-    DCHECK(ContainsWrapper());
-    return true;
-  }
-
-  bool IsEqualTo(const v8::Local<v8::Object>& other) const {
-    return main_world_wrapper_ == other;
-  }
-
-  bool SetReturnValue(v8::ReturnValue<v8::Value> return_value) {
-    const bool contains_wrapper = ContainsWrapper();
-    if (contains_wrapper) {
-      return_value.SetNonEmpty(main_world_wrapper_);
-    }
-    return contains_wrapper;
-  }
-
-
  protected:
   ScriptWrappable() = default;
 
  private:
-  bool ContainsWrapper() const { return !main_world_wrapper_.IsEmpty(); }
-
-  v8::Local<v8::Object> MainWorldWrapper(v8::Isolate* isolate) const {
-    return main_world_wrapper_.Get(isolate);
-  }
-
-  // Clear the main world wrapper if it is set to |handle|.
-  template <typename HandleType>
-  inline bool ClearMainWorldWrapperIfEqualTo(const HandleType& handle);
-
   static_assert(
       std::is_trivially_destructible<
           TraceWrapperV8Reference<v8::Object>>::value,
       "TraceWrapperV8Reference<v8::Object> should be trivially destructible.");
 
-  TraceWrapperV8Reference<v8::Object> main_world_wrapper_;
-
-  // These classes are exceptionally allowed to directly interact with the main
-  // world wrapper.
+  // Inline storage for the a single wrapper reference. Only
+  // `DOMDataStore::UncheckedInlineStorageForWrappable()` should access this
+  // field.
+  TraceWrapperV8Reference<v8::Object> wrapper_;
   friend class DOMDataStore;
-  friend class DOMWrapperWorld;
 };
 
-template <typename HandleType>
-bool ScriptWrappable::ClearMainWorldWrapperIfEqualTo(const HandleType& handle) {
-  if (main_world_wrapper_ == handle) {
-    main_world_wrapper_.Reset();
-    return true;
-  }
-  return false;
-}
-
 // Defines |GetWrapperTypeInfo| virtual method which returns the WrapperTypeInfo
 // of the instance. Also declares a static member of type WrapperTypeInfo, of
 // which the definition is given by the IDL code generator.
diff --git a/third_party/blink/renderer/platform/bindings/v8_set_return_value.h b/third_party/blink/renderer/platform/bindings/v8_set_return_value.h
index 6cf3278d7a186ad97b49841fb9bddd584582b9f7..2987c64bdbed2a7b797cd04a9b35a95c3f3f7aa9 100644
--- a/third_party/blink/renderer/platform/bindings/v8_set_return_value.h
+++ b/third_party/blink/renderer/platform/bindings/v8_set_return_value.h
@@ -50,16 +50,16 @@ struct V8ReturnValue {
   enum InterfaceObject { kInterfaceObject };
   enum NamespaceObject { kNamespaceObject };
 
-  // Selects the appropriate creation context.
-  static v8::Local<v8::Object> CreationContext(
+  // Selects the appropriate receiver from which e.g. the creation context can
+  // be retrieved.
+  static v8::Local<v8::Object> GetReceiver(
       const v8::FunctionCallbackInfo<v8::Value>& info) {
     return info.This();
   }
-  static v8::Local<v8::Object> CreationContext(
+  static v8::Local<v8::Object> GetReceiver(
       const v8::PropertyCallbackInfo<v8::Value>& info) {
     return info.Holder();
   }
-
   // Helper function for ScriptWrappable
   template <typename CallbackInfo>
   static void SetWrapper(const CallbackInfo& info,
@@ -290,12 +290,13 @@ void V8SetReturnValue(const CallbackInfo& info,
   if (UNLIKELY(!value))
     return info.GetReturnValue().SetNull();
   ScriptWrappable* wrappable = const_cast<ScriptWrappable*>(value);
-  if (DOMDataStore::SetReturnValueForMainWorld(info.GetReturnValue(),
-                                               wrappable))
+  if (DOMDataStore::SetReturnValueFromInlineStorage(info.GetReturnValue(),
+                                                    wrappable)) {
     return;
+  }
   V8ReturnValue::SetWrapper(
       info, wrappable,
-      V8ReturnValue::CreationContext(info)->GetCreationContextChecked());
+      V8ReturnValue::GetReceiver(info)->GetCreationContextChecked());
 }
 
 template <typename CallbackInfo>
@@ -304,12 +305,13 @@ void V8SetReturnValue(const CallbackInfo& info,
                       V8ReturnValue::MainWorld) {
   DCHECK(DOMWrapperWorld::Current(info.GetIsolate()).IsMainWorld());
   ScriptWrappable* wrappable = const_cast<ScriptWrappable*>(&value);
-  if (DOMDataStore::SetReturnValueForMainWorld(info.GetReturnValue(),
-                                               wrappable))
+  if (DOMDataStore::SetReturnValueFromInlineStorage(info.GetReturnValue(),
+                                                    wrappable)) {
     return;
+  }
   V8ReturnValue::SetWrapper(
       info, wrappable,
-      V8ReturnValue::CreationContext(info)->GetCreationContextChecked());
+      V8ReturnValue::GetReceiver(info)->GetCreationContextChecked());
 }
 
 template <typename CallbackInfo>
@@ -320,13 +322,13 @@ void V8SetReturnValue(const CallbackInfo& info,
     return info.GetReturnValue().SetNull();
   ScriptWrappable* wrappable = const_cast<ScriptWrappable*>(value);
   if (DOMDataStore::SetReturnValueFast(info.GetReturnValue(), wrappable,
-                                       V8ReturnValue::CreationContext(info),
+                                       V8ReturnValue::GetReceiver(info),
                                        receiver)) {
     return;
   }
   V8ReturnValue::SetWrapper(
       info, wrappable,
-      V8ReturnValue::CreationContext(info)->GetCreationContextChecked());
+      V8ReturnValue::GetReceiver(info)->GetCreationContextChecked());
 }
 
 template <typename CallbackInfo>
@@ -335,13 +337,13 @@ void V8SetReturnValue(const CallbackInfo& info,
                       const ScriptWrappable* receiver) {
   ScriptWrappable* wrappable = const_cast<ScriptWrappable*>(&value);
   if (DOMDataStore::SetReturnValueFast(info.GetReturnValue(), wrappable,
-                                       V8ReturnValue::CreationContext(info),
+                                       V8ReturnValue::GetReceiver(info),
                                        receiver)) {
     return;
   }
   V8ReturnValue::SetWrapper(
       info, wrappable,
-      V8ReturnValue::CreationContext(info)->GetCreationContextChecked());
+      V8ReturnValue::GetReceiver(info)->GetCreationContextChecked());
 }
 
 template <typename CallbackInfo>
@@ -353,7 +355,7 @@ void V8SetReturnValue(const CallbackInfo& info,
     return info.GetReturnValue().SetNull();
   ScriptWrappable* wrappable = const_cast<ScriptWrappable*>(value);
   if (DOMDataStore::SetReturnValueFast(info.GetReturnValue(), wrappable,
-                                       V8ReturnValue::CreationContext(info),
+                                       V8ReturnValue::GetReceiver(info),
                                        receiver)) {
     return;
   }
@@ -369,7 +371,7 @@ void V8SetReturnValue(const CallbackInfo& info,
   //    with the current context will still have the correct v8::Isolate and
   //    DOMWrapperWorld.
   v8::Local<v8::Context> context;
-  if (!V8ReturnValue::CreationContext(info)->GetCreationContext().ToLocal(
+  if (!V8ReturnValue::GetReceiver(info)->GetCreationContext().ToLocal(
           &context)) {
     context = info.GetIsolate()->GetCurrentContext();
   }
@@ -383,7 +385,7 @@ void V8SetReturnValue(const CallbackInfo& info,
                       V8ReturnValue::MaybeCrossOrigin) {
   ScriptWrappable* wrappable = const_cast<ScriptWrappable*>(&value);
   if (DOMDataStore::SetReturnValueFast(info.GetReturnValue(), wrappable,
-                                       V8ReturnValue::CreationContext(info),
+                                       V8ReturnValue::GetReceiver(info),
                                        receiver)) {
     return;
   }
@@ -399,7 +401,7 @@ void V8SetReturnValue(const CallbackInfo& info,
   //    with the current context will still have the correct v8::Isolate and
   //    DOMWrapperWorld.
   v8::Local<v8::Context> context;
-  if (!V8ReturnValue::CreationContext(info)->GetCreationContext().ToLocal(
+  if (!V8ReturnValue::GetReceiver(info)->GetCreationContext().ToLocal(
           &context)) {
     context = info.GetIsolate()->GetCurrentContext();
   }
diff --git a/third_party/blink/renderer/platform/heap/thread_state.cc b/third_party/blink/renderer/platform/heap/thread_state.cc
index 55434e8c3a6d57ff8088821e7c4777334d9119b8..f68b52c7fd23f2b296abf8493fa52b216d68fe98 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.cc
+++ b/third_party/blink/renderer/platform/heap/thread_state.cc
@@ -10,6 +10,7 @@
 #include "base/functional/callback.h"
 #include "base/notreached.h"
 #include "gin/public/v8_platform.h"
+#include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
 #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/bindings/wrapper_type_info.h"
@@ -46,7 +47,7 @@ class BlinkRootsHandler final : public v8::EmbedderRootsHandler {
   // generation garbage collections.
   void ResetRoot(const v8::TracedReference<v8::Value>& handle) final {
     const v8::TracedReference<v8::Object>& traced = handle.As<v8::Object>();
-    bool success = DOMWrapperWorld::ClearWrapperIfEqualTo(
+    bool success = DOMDataStore::ClearWrapperInAnyWorldIfEqualTo(
         ToScriptWrappable(traced), traced);
     // Since V8 found a handle, Blink needs to find it as well when trying to
     // remove it.
@@ -55,7 +56,7 @@ class BlinkRootsHandler final : public v8::EmbedderRootsHandler {
 
   bool TryResetRoot(const v8::TracedReference<v8::Value>& handle) final {
     const v8::TracedReference<v8::Object>& traced = handle.As<v8::Object>();
-    return DOMWrapperWorld::ClearMainWorldWrapperIfEqualTo(
+    return DOMDataStore::ClearInlineStorageWrapperIfEqualTo(
         ToScriptWrappable(traced), traced);
   }
 };
