Bug 1513108 - Remove the separate class extension hook for getting a weakmap key delegate, r=jonco
authorSteve Fink <sfink@mozilla.com>
Fri, 07 Dec 2018 14:38:01 -0800
changeset 450799 0429fe76b4cca9d11093dcfc79e01d7da1f91888
parent 450798 d1796e4fc9075101603c7ce49208119a04d8c38f
child 450800 5568f16f01914a95d8bb8a038457678f0b1b8e0d
push id35208
push usercsabou@mozilla.com
push dateSat, 15 Dec 2018 02:48:07 +0000
treeherdermozilla-central@d86d184dc7d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1513108
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1513108 - Remove the separate class extension hook for getting a weakmap key delegate, r=jonco Replace with just unwrapping the key, since there are no users that return anything else for a delegate.
dom/bindings/Codegen.py
dom/bindings/SimpleGlobalObject.cpp
js/public/Class.h
js/public/Proxy.h
js/public/Wrapper.h
js/rust/src/jsglue.cpp
js/src/builtin/MapObject.cpp
js/src/builtin/TypedObject.cpp
js/src/builtin/WeakMapObject-inl.h
js/src/devtools/rootAnalysis/annotations.js
js/src/gc/Verifier.cpp
js/src/gc/WeakMap-inl.h
js/src/gc/WeakMap.h
js/src/jsapi-tests/testGCGrayMarking.cpp
js/src/jsapi-tests/testWeakMap.cpp
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/proxy/BaseProxyHandler.cpp
js/src/proxy/Proxy.cpp
js/src/proxy/Wrapper.cpp
js/src/vm/ArgumentsObject.cpp
js/src/vm/ArrayBufferObject.cpp
js/src/vm/TypedArrayObject.cpp
js/xpconnect/src/Sandbox.cpp
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
xpcom/base/CycleCollectedJSRuntime.cpp
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -503,17 +503,16 @@ class CGDOMJSClass(CGThing):
               ${finalize}, /* finalize */
               ${call}, /* call */
               nullptr,               /* hasInstance */
               nullptr,               /* construct */
               ${trace}, /* trace */
             };
 
             static const js::ClassExtension sClassExtension = {
-              nullptr, /* weakmapKeyDelegateOp */
               ${objectMoved} /* objectMovedOp */
             };
 
             static const DOMJSClass sClass = {
               { "${name}",
                 ${flags},
                 &sClassOps,
                 JS_NULL_CLASS_SPEC,
--- a/dom/bindings/SimpleGlobalObject.cpp
+++ b/dom/bindings/SimpleGlobalObject.cpp
@@ -69,17 +69,17 @@ static const js::ClassOps SimpleGlobalCl
     SimpleGlobal_finalize,
     nullptr,
     nullptr,
     nullptr,
     JS_GlobalObjectTraceHook,
 };
 
 static const js::ClassExtension SimpleGlobalClassExtension = {
-    nullptr, SimpleGlobal_moved};
+    SimpleGlobal_moved};
 
 const js::Class SimpleGlobalClass = {
     "",
     JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE |
         JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_FOREGROUND_FINALIZE,
     &SimpleGlobalClassOps,
     JS_NULL_CLASS_SPEC,
     &SimpleGlobalClassExtension,
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -488,18 +488,16 @@ typedef bool (*JSHasInstanceOp)(JSContex
  * structures. The only exception for this rule is the case when the embedding
  * needs a tight integration with GC. In that case the embedding can check if
  * the traversal is a part of the marking phase through calling
  * JS_IsGCMarkingTracer and apply a special code like emptying caches or
  * marking its native structures.
  */
 typedef void (*JSTraceOp)(JSTracer* trc, JSObject* obj);
 
-typedef JSObject* (*JSWeakmapKeyDelegateOp)(JSObject* obj);
-
 typedef size_t (*JSObjectMovedOp)(JSObject* obj, JSObject* old);
 
 /* js::Class operation signatures. */
 
 namespace js {
 
 typedef bool (*LookupPropertyOp)(JSContext* cx, JS::HandleObject obj,
                                  JS::HandleId id, JS::MutableHandleObject objp,
@@ -683,29 +681,16 @@ struct MOZ_STATIC_CLASS ClassSpec {
   bool shouldDefineConstructor() const {
     MOZ_ASSERT(defined());
     return !(flags & DontDefineConstructor);
   }
 };
 
 struct MOZ_STATIC_CLASS ClassExtension {
   /**
-   * If an object is used as a key in a weakmap, it may be desirable for the
-   * garbage collector to keep that object around longer than it otherwise
-   * would. A common case is when the key is a wrapper around an object in
-   * another compartment, and we want to avoid collecting the wrapper (and
-   * removing the weakmap entry) as long as the wrapped object is alive. In
-   * that case, the wrapped object is returned by the wrapper's
-   * weakmapKeyDelegateOp hook. As long as the wrapper is used as a weakmap
-   * key, it will not be collected (and remain in the weakmap) until the
-   * wrapped object is collected.
-   */
-  JSWeakmapKeyDelegateOp weakmapKeyDelegateOp;
-
-  /**
    * Optional hook called when an object is moved by generational or
    * compacting GC.
    *
    * There may exist weak pointers to an object that are not traced through
    * when the normal trace APIs are used, for example objects in the wrapper
    * cache. This hook allows these pointers to be updated.
    *
    * Note that this hook can be called before JS_NewObject() returns if a GC
@@ -947,19 +932,16 @@ struct MOZ_STATIC_CLASS Class {
   }
   const JSPropertySpec* specPrototypeProperties() const {
     return spec ? spec->prototypeProperties : nullptr;
   }
   FinishClassInitOp specFinishInitHook() const {
     return spec ? spec->finishInit : nullptr;
   }
 
-  JSWeakmapKeyDelegateOp extWeakmapKeyDelegateOp() const {
-    return ext ? ext->weakmapKeyDelegateOp : nullptr;
-  }
   JSObjectMovedOp extObjectMovedOp() const {
     return ext ? ext->objectMovedOp : nullptr;
   }
 
   LookupPropertyOp getOpsLookupProperty() const {
     return oOps ? oOps->lookupProperty : nullptr;
   }
   DefinePropertyOp getOpsDefineProperty() const {
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -368,18 +368,16 @@ class JS_FRIEND_API BaseProxyHandler {
   //       We are not prepared to do this, as there's little const correctness
   //       in the external APIs that handle proxies.
   virtual bool isCallable(JSObject* obj) const;
   virtual bool isConstructor(JSObject* obj) const;
 
   virtual bool getElements(JSContext* cx, HandleObject proxy, uint32_t begin,
                            uint32_t end, ElementAdder* adder) const;
 
-  /* See comment for weakmapKeyDelegateOp in js/Class.h. */
-  virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const;
   virtual bool isScripted() const { return false; }
 };
 
 extern JS_FRIEND_DATA const js::Class ProxyClass;
 
 inline bool IsProxy(const JSObject* obj) {
   return GetObjectClass(obj)->isProxy();
 }
--- a/js/public/Wrapper.h
+++ b/js/public/Wrapper.h
@@ -128,17 +128,16 @@ class JS_FRIEND_API Wrapper : public For
 
  public:
   explicit constexpr Wrapper(unsigned aFlags, bool aHasPrototype = false,
                              bool aHasSecurityPolicy = false)
       : ForwardingProxyHandler(&family, aHasPrototype, aHasSecurityPolicy),
         mFlags(aFlags) {}
 
   virtual bool finalizeInBackground(const Value& priv) const override;
-  virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const override;
 
   using BaseProxyHandler::Action;
 
   enum Flags { CROSS_COMPARTMENT = 1 << 0, LAST_USED_FLAG = CROSS_COMPARTMENT };
 
   static JSObject* New(JSContext* cx, JSObject* obj, const Wrapper* handler,
                        const WrapperOptions& options = WrapperOptions());
 
--- a/js/rust/src/jsglue.cpp
+++ b/js/rust/src/jsglue.cpp
@@ -89,17 +89,16 @@ struct ProxyTraps {
   void (*finalize)(JSFreeOp* fop, JSObject* proxy);
   size_t (*objectMoved)(JSObject* proxy, JSObject* old);
 
   bool (*isCallable)(JSObject* obj);
   bool (*isConstructor)(JSObject* obj);
 
   // getElements
 
-  // weakmapKeyDelegate
   // isScripted
 };
 
 static int HandlerFamily;
 
 #define DEFER_TO_TRAP_OR_BASE_CLASS(_base)                                    \
                                                                               \
   /* Standard internal methods. */                                            \
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -136,17 +136,16 @@ static const ClassOps MapIteratorObjectC
                                                    nullptr, /* delProperty */
                                                    nullptr, /* enumerate */
                                                    nullptr, /* newEnumerate */
                                                    nullptr, /* resolve */
                                                    nullptr, /* mayResolve */
                                                    MapIteratorObject::finalize};
 
 static const ClassExtension MapIteratorObjectClassExtension = {
-    nullptr, /* weakmapKeyDelegateOp */
     MapIteratorObject::objectMoved};
 
 const Class MapIteratorObject::class_ = {
     "Map Iterator",
     JSCLASS_HAS_RESERVED_SLOTS(MapIteratorObject::SlotCount) |
         JSCLASS_FOREGROUND_FINALIZE | JSCLASS_SKIP_NURSERY_FINALIZE,
     &MapIteratorObjectClassOps, JS_NULL_CLASS_SPEC,
     &MapIteratorObjectClassExtension};
@@ -907,17 +906,16 @@ static const ClassOps SetIteratorObjectC
                                                    nullptr, /* delProperty */
                                                    nullptr, /* enumerate */
                                                    nullptr, /* newEnumerate */
                                                    nullptr, /* resolve */
                                                    nullptr, /* mayResolve */
                                                    SetIteratorObject::finalize};
 
 static const ClassExtension SetIteratorObjectClassExtension = {
-    nullptr, /* weakmapKeyDelegateOp */
     SetIteratorObject::objectMoved};
 
 const Class SetIteratorObject::class_ = {
     "Set Iterator",
     JSCLASS_HAS_RESERVED_SLOTS(SetIteratorObject::SlotCount) |
         JSCLASS_FOREGROUND_FINALIZE | JSCLASS_SKIP_NURSERY_FINALIZE,
     &SetIteratorObjectClassOps, JS_NULL_CLASS_SPEC,
     &SetIteratorObjectClassExtension};
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -2296,18 +2296,17 @@ const ObjectOps TypedObject::objectOps_ 
       nullptr, /* mayResolve  */                                           \
       nullptr, /* finalize    */                                           \
       nullptr, /* call        */                                           \
       nullptr, /* hasInstance */                                           \
       nullptr, /* construct   */                                           \
       Trace,                                                               \
   };                                                                       \
   static const ClassExtension Name##ClassExt = {                           \
-      nullptr, /* weakmapKeyDelegateOp */                                  \
-      Moved    /* objectMovedOp */                                         \
+      Moved /* objectMovedOp */                                            \
   };                                                                       \
   const Class Name::class_ = {                                             \
       #Name,           Class::NON_NATIVE | JSCLASS_DELAY_METADATA_BUILDER, \
       &Name##ClassOps, JS_NULL_CLASS_SPEC,                                 \
       &Name##ClassExt, &TypedObject::objectOps_}
 
 DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject,
                       OutlineTypedObject::obj_trace, nullptr);
--- a/js/src/builtin/WeakMapObject-inl.h
+++ b/js/src/builtin/WeakMapObject-inl.h
@@ -42,21 +42,19 @@ static MOZ_ALWAYS_INLINE bool WeakCollec
     obj->setPrivate(map);
   }
 
   // Preserve wrapped native keys to prevent wrapper optimization.
   if (!TryPreserveReflector(cx, key)) {
     return false;
   }
 
-  if (JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp()) {
-    RootedObject delegate(cx, op(key));
-    if (delegate && !TryPreserveReflector(cx, delegate)) {
-      return false;
-    }
+  RootedObject delegate(cx, UncheckedUnwrapWithoutExpose(key));
+  if (delegate && !TryPreserveReflector(cx, delegate)) {
+    return false;
   }
 
   MOZ_ASSERT(key->compartment() == obj->compartment());
   MOZ_ASSERT_IF(value.isObject(),
                 value.toObject().compartment() == obj->compartment());
   if (!map->put(key, value)) {
     JS_ReportOutOfMemory(cx);
     return false;
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -26,19 +26,16 @@ function indirectCallCannotGC(fullCaller
         return true;
 
     if (name == "mapper" && caller == "ptio.c:pt_MapError")
         return true;
 
     if (name == "params" && caller == "PR_ExplodeTime")
         return true;
 
-    if (name == "op" && /GetWeakmapKeyDelegate/.test(caller))
-        return true;
-
     // hook called during script finalization which cannot GC.
     if (/CallDestroyScriptHook/.test(caller))
         return true;
 
     // template method called during marking and hence cannot GC
     if (name == "op" && caller.includes("bool js::WeakMap<Key, Value, HashPolicy>::keyNeedsMark(JSObject*)"))
     {
         return true;
@@ -163,17 +160,16 @@ function isSuppressedVirtualMethod(csu, 
 var ignoreFunctions = {
     "ptio.c:pt_MapError" : true,
     "je_malloc_printf" : true,
     "malloc_usable_size" : true,
     "vprintf_stderr" : true,
     "PR_ExplodeTime" : true,
     "PR_ErrorInstallTable" : true,
     "PR_SetThreadPrivate" : true,
-    "JSObject* js::GetWeakmapKeyDelegate(JSObject*)" : true, // FIXME: mark with AutoSuppressGCAnalysis instead
     "uint8 NS_IsMainThread()" : true,
 
     // Has an indirect call under it by the name "__f", which seemed too
     // generic to ignore by itself.
     "void* std::_Locale_impl::~_Locale_impl(int32)" : true,
 
     // Bug 1056410 - devirtualization prevents the standard nsISupports::Release heuristic from working
     "uint32 nsXPConnect::Release()" : true,
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -746,23 +746,17 @@ static Zone* GetCellZone(Cell* cell) {
 }
 
 static JSObject* MaybeGetDelegate(Cell* cell) {
   if (!cell->is<JSObject>()) {
     return nullptr;
   }
 
   JSObject* object = cell->as<JSObject>();
-  JSWeakmapKeyDelegateOp op = object->getClass()->extWeakmapKeyDelegateOp();
-  if (!op) {
-    return nullptr;
-  }
-
-  JS::AutoSuppressGCAnalysis nogc; // Calling the delegate op cannot GC.
-  return op(object);
+  return js::UncheckedUnwrapWithoutExpose(object);
 }
 
 bool js::gc::CheckWeakMapEntryMarking(const WeakMapBase* map, Cell* key,
                                       Cell* value) {
   DebugOnly<Zone*> zone = map->zone();
   MOZ_ASSERT(zone->isGCMarking());
 
   JSObject* object = map->memberOf;
--- a/js/src/gc/WeakMap-inl.h
+++ b/js/src/gc/WeakMap-inl.h
@@ -163,30 +163,17 @@ bool WeakMap<K, V>::markIteratively(GCMa
     }
   }
 
   return markedAny;
 }
 
 template <class K, class V>
 inline JSObject* WeakMap<K, V>::getDelegate(JSObject* key) const {
-  JS::AutoSuppressGCAnalysis nogc;
-
-  JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp();
-  if (!op) {
-    return nullptr;
-  }
-
-  JSObject* obj = op(key);
-  if (!obj) {
-    return nullptr;
-  }
-
-  MOZ_ASSERT(obj->runtimeFromMainThread() == zone()->runtimeFromMainThread());
-  return obj;
+  return UncheckedUnwrapWithoutExpose(key);
 }
 
 template <class K, class V>
 inline JSObject* WeakMap<K, V>::getDelegate(JSScript* script) const {
   return nullptr;
 }
 
 template <class K, class V>
--- a/js/src/gc/WeakMap.h
+++ b/js/src/gc/WeakMap.h
@@ -178,16 +178,25 @@ class WeakMap
   void trace(JSTracer* trc) override;
 
  protected:
   static void addWeakEntry(GCMarker* marker, JS::GCCellPtr key,
                            const gc::WeakMarkable& markable);
 
   bool markIteratively(GCMarker* marker) override;
 
+  /**
+   * If a wrapper is used as a key in a weakmap, the garbage collector should
+   * keep that object around longer than it otherwise would. We want to avoid
+   * collecting the wrapper (and removing the weakmap entry) as long as the
+   * wrapped object is alive (because the object can be rewrapped and looked up
+   * again). As long as the wrapper is used as a weakmap key, it will not be
+   * collected (and remain in the weakmap) until the wrapped object is
+   * collected.
+   */
   JSObject* getDelegate(JSObject* key) const;
   JSObject* getDelegate(JSScript* script) const;
   JSObject* getDelegate(LazyScript* script) const;
 
  private:
   void exposeGCThingToActiveJS(const JS::Value& v) const {
     JS::ExposeValueToActiveJS(v);
   }
--- a/js/src/jsapi-tests/testGCGrayMarking.cpp
+++ b/js/src/jsapi-tests/testGCGrayMarking.cpp
@@ -3,16 +3,17 @@
  */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gc/Heap.h"
 #include "gc/WeakMap.h"
 #include "gc/Zone.h"
+#include "js/Proxy.h"
 #include "jsapi-tests/tests.h"
 
 using namespace js;
 using namespace js::gc;
 
 namespace js {
 
 struct GCManagedObjectWeakMap : public ObjectWeakMap {
@@ -222,17 +223,17 @@ bool TestWeakMaps() {
   JS_GC(cx);
   CHECK(IsMarkedBlack(weakMap));
   CHECK(IsMarkedBlack(key));
   CHECK(IsMarkedBlack(value));
 
   // Test that a weakmap key is marked gray if it has a gray delegate and the
   // map is either gray or black.
 
-  JSObject* delegate = AllocDelegateForKey(key);
+  JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
   blackRoot1 = weakMap;
   blackRoot2 = nullptr;
   grayRoots.grayRoot1 = delegate;
   grayRoots.grayRoot2 = nullptr;
   JS_GC(cx);
   CHECK(IsMarkedGray(delegate));
   CHECK(IsMarkedGray(key));
   CHECK(IsMarkedBlack(weakMap));
@@ -302,17 +303,16 @@ bool TestWeakMaps() {
   blackRoot2 = weakMap;
   grayRoots.grayRoot1 = nullptr;
   grayRoots.grayRoot2 = nullptr;
   JS_GC(cx);
   CHECK(IsMarkedBlack(key));
   CHECK(IsMarkedBlack(weakMap));
   CHECK(IsMarkedBlack(value));
 
-  CHECK(AllocDelegateForKey(key));
   blackRoot1 = nullptr;
   blackRoot2 = nullptr;
   grayRoots.grayRoot1 = weakMap;
   grayRoots.grayRoot2 = key;
   JS_GC(cx);
   CHECK(IsMarkedGray(key));
   CHECK(IsMarkedGray(weakMap));
   CHECK(IsMarkedGray(value));
@@ -366,17 +366,17 @@ bool TestUnassociatedWeakMaps() {
   grayRoots.grayRoot1 = nullptr;
   grayRoots.grayRoot2 = nullptr;
   JS_GC(cx);
   CHECK(IsMarkedBlack(key));
   CHECK(IsMarkedBlack(value));
 
   // Test that a weakmap key is marked gray if it has a gray delegate.
 
-  JSObject* delegate = AllocDelegateForKey(key);
+  JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
   blackRoot = nullptr;
   grayRoots.grayRoot1 = delegate;
   grayRoots.grayRoot2 = nullptr;
   JS_GC(cx);
   CHECK(IsMarkedGray(delegate));
   CHECK(IsMarkedGray(key));
   CHECK(IsMarkedGray(value));
 
@@ -404,17 +404,16 @@ bool TestUnassociatedWeakMaps() {
   delegate = nullptr;
   blackRoot = key;
   grayRoots.grayRoot1 = nullptr;
   grayRoots.grayRoot2 = nullptr;
   JS_GC(cx);
   CHECK(IsMarkedBlack(key));
   CHECK(IsMarkedBlack(value));
 
-  CHECK(AllocDelegateForKey(key));
   blackRoot = nullptr;
   grayRoots.grayRoot1 = key;
   grayRoots.grayRoot2 = nullptr;
   JS_GC(cx);
   CHECK(IsMarkedGray(key));
   CHECK(IsMarkedGray(value));
 
   blackRoot = nullptr;
@@ -647,44 +646,28 @@ JSObject* GetCrossCompartmentWrapper(JSO
   }
 
   EvictNursery();
 
   MOZ_ASSERT(obj->compartment() == global2->compartment());
   return obj;
 }
 
-static JSObject* GetKeyDelegate(JSObject* obj) {
-  return static_cast<JSObject*>(obj->as<NativeObject>().getPrivate());
-}
-
 JSObject* AllocWeakmapKeyObject() {
-  static const js::ClassExtension KeyClassExtension = {GetKeyDelegate};
-
-  static const js::Class KeyClass = {"keyWithDelegate",  JSCLASS_HAS_PRIVATE,
-                                     JS_NULL_CLASS_OPS,  JS_NULL_CLASS_SPEC,
-                                     &KeyClassExtension, JS_NULL_OBJECT_OPS};
-
-  JS::RootedObject key(cx, JS_NewObject(cx, Jsvalify(&KeyClass)));
-  if (!key) {
+  JS::RootedObject delegate(cx, JS_NewPlainObject(cx));
+  if (!delegate) {
     return nullptr;
   }
 
+  JS::RootedObject key(cx, js::Wrapper::New(cx, delegate, &js::Wrapper::singleton));
+
   EvictNursery();
   return key;
 }
 
-JSObject* AllocDelegateForKey(JSObject* key) {
-  JS::RootedObject obj(cx, JS_NewPlainObject(cx));
-  EvictNursery();
-
-  key->as<NativeObject>().setPrivate(obj);
-  return obj;
-}
-
 JSObject* AllocObjectChain(size_t length) {
   // Allocate a chain of linked JSObjects.
 
   // Use a unique property name so the shape is not shared with any other
   // objects.
   RootedString nextPropName(cx, JS_NewStringCopyZ(cx, "unique14142135"));
   RootedId nextId(cx);
   if (!JS_StringToId(cx, nextPropName, &nextId)) {
--- a/js/src/jsapi-tests/testWeakMap.cpp
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -65,21 +65,24 @@ BEGIN_TEST(testWeakMap_keyDelegates) {
   AutoLeaveZeal nozeal(cx);
 #endif /* JS_GC_ZEAL */
 
   JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
   JS_GC(cx);
   JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
   CHECK(map);
 
-  JS::RootedObject key(cx, newKey());
+  JS::RootedObject delegate(cx, newDelegate());
+  JS::RootedObject key(cx, delegate);
+  if (!JS_WrapObject(cx, &key)) {
+    return false;
+  }
   CHECK(key);
+  CHECK(delegate);
 
-  JS::RootedObject delegate(cx, newDelegate());
-  CHECK(delegate);
   keyDelegate = delegate;
 
   JS::RootedObject delegateRoot(cx);
   {
     JSAutoRealm ar(cx, delegate);
     delegateRoot = JS_NewPlainObject(cx);
     CHECK(delegateRoot);
     JS::RootedValue delegateValue(cx, JS::ObjectValue(*delegate));
@@ -141,39 +144,35 @@ static size_t DelegateObjectMoved(JSObje
     return 0;  // Object got moved before we set keyDelegate to point to it.
   }
 
   MOZ_RELEASE_ASSERT(keyDelegate == old);
   keyDelegate = obj;
   return 0;
 }
 
-static JSObject* GetKeyDelegate(JSObject* obj) { return keyDelegate; }
-
 JSObject* newKey() {
-  static const js::ClassExtension keyClassExtension = {GetKeyDelegate};
-
   static const js::Class keyClass = {
       "keyWithDelegate",  JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1),
       JS_NULL_CLASS_OPS,  JS_NULL_CLASS_SPEC,
-      &keyClassExtension, JS_NULL_OBJECT_OPS};
+      JS_NULL_CLASS_EXT, JS_NULL_OBJECT_OPS};
 
   JS::RootedObject key(cx, JS_NewObject(cx, Jsvalify(&keyClass)));
   if (!key) {
     return nullptr;
   }
 
   return key;
 }
 
 JSObject* newCCW(JS::HandleObject sourceZone, JS::HandleObject destZone) {
   /*
    * Now ensure that this zone will be swept first by adding a cross
-   * compartment wrapper to a new objct in the same zone as the
-   * delegate obejct.
+   * compartment wrapper to a new object in the same zone as the
+   * delegate object.
    */
   JS::RootedObject object(cx);
   {
     JSAutoRealm ar(cx, destZone);
     object = JS_NewPlainObject(cx);
     if (!object) {
       return nullptr;
     }
@@ -203,17 +202,17 @@ JSObject* newDelegate() {
       nullptr, /* finalize */
       nullptr, /* call */
       nullptr, /* hasInstance */
       nullptr, /* construct */
       JS_GlobalObjectTraceHook,
   };
 
   static const js::ClassExtension delegateClassExtension = {
-      nullptr, DelegateObjectMoved};
+      DelegateObjectMoved};
 
   static const js::Class delegateClass = {
       "delegate",
       JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1),
       &delegateClassOps,
       JS_NULL_CLASS_SPEC,
       &delegateClassExtension,
       JS_NULL_OBJECT_OPS};
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -570,23 +570,16 @@ JS_FRIEND_API void js::VisitGrayWrapperT
   for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
     for (Compartment::WrapperEnum e(comp); !e.empty(); e.popFront()) {
       e.front().mutableKey().applyToWrapped(
           VisitGrayCallbackFunctor(callback, closure));
     }
   }
 }
 
-JS_FRIEND_API JSObject* js::GetWeakmapKeyDelegate(JSObject* key) {
-  if (JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp()) {
-    return op(key);
-  }
-  return nullptr;
-}
-
 JS_FRIEND_API JSLinearString* js::StringToLinearStringSlow(JSContext* cx,
                                                            JSString* str) {
   return str->ensureLinear(cx);
 }
 
 JS_FRIEND_API void JS_SetAccumulateTelemetryCallback(
     JSContext* cx, JSAccumulateTelemetryDataCallback callback) {
   cx->runtime()->setTelemetryCallback(cx->runtime(), callback);
@@ -1041,17 +1034,17 @@ struct DumpHeapTracer : public JS::Callb
         js::WeakMapTracer(cx->runtime()),
         prefix(""),
         output(fp) {}
 
  private:
   void trace(JSObject* map, JS::GCCellPtr key, JS::GCCellPtr value) override {
     JSObject* kdelegate = nullptr;
     if (key.is<JSObject>()) {
-      kdelegate = js::GetWeakmapKeyDelegate(&key.as<JSObject>());
+      kdelegate = UncheckedUnwrapWithoutExpose(&key.as<JSObject>());
     }
 
     fprintf(output, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n", map,
             key.asCell(), kdelegate, value.asCell());
   }
 
   void onChild(const JS::GCCellPtr& thing) override;
 };
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -490,18 +490,16 @@ extern JS_FRIEND_API bool ZoneGlobalsAre
 extern JS_FRIEND_API bool IsObjectZoneSweepingOrCompacting(JSObject* obj);
 
 typedef void (*GCThingCallback)(void* closure, JS::GCCellPtr thing);
 
 extern JS_FRIEND_API void VisitGrayWrapperTargets(JS::Zone* zone,
                                                   GCThingCallback callback,
                                                   void* closure);
 
-extern JS_FRIEND_API JSObject* GetWeakmapKeyDelegate(JSObject* key);
-
 /**
  * Invoke cellCallback on every gray JSObject in the given zone.
  */
 extern JS_FRIEND_API void IterateGrayObjects(JS::Zone* zone,
                                              GCThingCallback cellCallback,
                                              void* data);
 
 /**
--- a/js/src/proxy/BaseProxyHandler.cpp
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -372,20 +372,16 @@ bool BaseProxyHandler::isArray(JSContext
 void BaseProxyHandler::trace(JSTracer* trc, JSObject* proxy) const {}
 
 void BaseProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const {}
 
 size_t BaseProxyHandler::objectMoved(JSObject* proxy, JSObject* old) const {
   return 0;
 }
 
-JSObject* BaseProxyHandler::weakmapKeyDelegate(JSObject* proxy) const {
-  return nullptr;
-}
-
 bool BaseProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
                                     MutableHandleObject protop) const {
   MOZ_CRASH("must override getPrototype with dynamic prototype");
 }
 
 bool BaseProxyHandler::setPrototype(JSContext* cx, HandleObject proxy,
                                     HandleObject proto,
                                     ObjectOpResult& result) const {
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -740,21 +740,16 @@ static bool proxy_DeleteProperty(JSConte
       continue;
     }
     TraceEdge(trc, proxy->reservedSlotPtr(i), "proxy_reserved");
   }
 
   Proxy::trace(trc, obj);
 }
 
-static JSObject* proxy_WeakmapKeyDelegate(JSObject* obj) {
-  MOZ_ASSERT(obj->is<ProxyObject>());
-  return obj->as<ProxyObject>().handler()->weakmapKeyDelegate(obj);
-}
-
 static void proxy_Finalize(FreeOp* fop, JSObject* obj) {
   // Suppress a bogus warning about finalize().
   JS::AutoSuppressGCAnalysis nogc;
 
   MOZ_ASSERT(obj->is<ProxyObject>());
   obj->as<ProxyObject>().handler()->finalize(fop, obj);
 
   if (!obj->as<ProxyObject>().usingInlineValueArray()) {
@@ -784,18 +779,17 @@ const ClassOps js::ProxyClassOps = {
     nullptr,            /* mayResolve  */
     proxy_Finalize,     /* finalize    */
     nullptr,            /* call        */
     Proxy::hasInstance, /* hasInstance */
     nullptr,            /* construct   */
     ProxyObject::trace, /* trace       */
 };
 
-const ClassExtension js::ProxyClassExtension = {proxy_WeakmapKeyDelegate,
-                                                proxy_ObjectMoved};
+const ClassExtension js::ProxyClassExtension = {proxy_ObjectMoved};
 
 const ObjectOps js::ProxyObjectOps = {
     proxy_LookupProperty, Proxy::defineProperty,
     Proxy::has,           Proxy::get,
     Proxy::set,           Proxy::getOwnPropertyDescriptor,
     proxy_DeleteProperty, Proxy::getElements,
     Proxy::fun_toString};
 
--- a/js/src/proxy/Wrapper.cpp
+++ b/js/src/proxy/Wrapper.cpp
@@ -275,21 +275,16 @@ bool ForwardingProxyHandler::isCallable(
   return target->isCallable();
 }
 
 bool ForwardingProxyHandler::isConstructor(JSObject* obj) const {
   JSObject* target = obj->as<ProxyObject>().target();
   return target->isConstructor();
 }
 
-JSObject* Wrapper::weakmapKeyDelegate(JSObject* proxy) const {
-  // This may be called during GC.
-  return UncheckedUnwrapWithoutExpose(proxy);
-}
-
 JSObject* Wrapper::New(JSContext* cx, JSObject* obj, const Wrapper* handler,
                        const WrapperOptions& options) {
   // If this is a cross-compartment wrapper allocate it in the compartment's
   // first realm. See Realm::realmForNewCCW.
   mozilla::Maybe<AutoRealmUnchecked> ar;
   if (handler->isCrossCompartmentWrapper()) {
     ar.emplace(cx, cx->compartment()->realmForNewCCW());
   }
@@ -335,18 +330,18 @@ JSObject* Wrapper::wrappedObject(JSObjec
 
 JS_FRIEND_API JSObject* js::UncheckedUnwrapWithoutExpose(JSObject* wrapped) {
   while (true) {
     if (!wrapped->is<WrapperObject>() || MOZ_UNLIKELY(IsWindowProxy(wrapped))) {
       break;
     }
     wrapped = wrapped->as<WrapperObject>().target();
 
-    // This can be called from Wrapper::weakmapKeyDelegate() on a wrapper
-    // whose referent has been moved while it is still unmarked.
+    // This can be called from when getting a weakmap key delegate() on a
+    // wrapper whose referent has been moved while it is still unmarked.
     if (wrapped) {
       wrapped = MaybeForwarded(wrapped);
     }
   }
   return wrapped;
 }
 
 JS_FRIEND_API JSObject* js::UncheckedUnwrap(JSObject* wrapped,
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -977,17 +977,16 @@ const ClassOps MappedArgumentsObject::cl
     ArgumentsObject::obj_mayResolve,
     ArgumentsObject::finalize,
     nullptr, /* call        */
     nullptr, /* hasInstance */
     nullptr, /* construct   */
     ArgumentsObject::trace};
 
 const js::ClassExtension MappedArgumentsObject::classExt_ = {
-    nullptr,                     /* weakmapKeyDelegateOp */
     ArgumentsObject::objectMoved /* objectMovedOp */
 };
 
 const ObjectOps MappedArgumentsObject::objectOps_ = {
     nullptr, /* lookupProperty */
     MappedArgumentsObject::obj_defineProperty};
 
 const Class MappedArgumentsObject::class_ = {
@@ -1014,17 +1013,16 @@ const ClassOps UnmappedArgumentsObject::
     ArgumentsObject::obj_mayResolve,
     ArgumentsObject::finalize,
     nullptr, /* call        */
     nullptr, /* hasInstance */
     nullptr, /* construct   */
     ArgumentsObject::trace};
 
 const js::ClassExtension UnmappedArgumentsObject::classExt_ = {
-    nullptr,                     /* weakmapKeyDelegateOp */
     ArgumentsObject::objectMoved /* objectMovedOp */
 };
 
 const Class UnmappedArgumentsObject::class_ = {
     "Arguments",
     JSCLASS_DELAY_METADATA_BUILDER |
         JSCLASS_HAS_RESERVED_SLOTS(UnmappedArgumentsObject::RESERVED_SLOTS) |
         JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -288,17 +288,16 @@ static const ClassSpec ArrayBufferObject
                              gc::AllocKind::FUNCTION>,
     GenericCreatePrototype<ArrayBufferObject>,
     arraybuffer_functions,
     arraybuffer_properties,
     arraybuffer_proto_functions,
     arraybuffer_proto_properties};
 
 static const ClassExtension ArrayBufferObjectClassExtension = {
-    nullptr, /* weakmapKeyDelegateOp */
     ArrayBufferObject::objectMoved};
 
 const Class ArrayBufferObject::class_ = {
     "ArrayBuffer",
     JSCLASS_DELAY_METADATA_BUILDER |
         JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
         JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) |
         JSCLASS_BACKGROUND_FINALIZE,
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1900,17 +1900,16 @@ static const ClassOps TypedArrayClassOps
     TypedArrayObject::finalize,   /* finalize    */
     nullptr,                      /* call        */
     nullptr,                      /* hasInstance */
     nullptr,                      /* construct   */
     ArrayBufferViewObject::trace, /* trace  */
 };
 
 static const ClassExtension TypedArrayClassExtension = {
-    nullptr,
     TypedArrayObject::objectMoved,
 };
 
 #define IMPL_TYPED_ARRAY_PROPERTIES(_type)                            \
   {                                                                   \
     JS_INT32_PS("BYTES_PER_ELEMENT", _type##Array::BYTES_PER_ELEMENT, \
                 JSPROP_READONLY | JSPROP_PERMANENT),                  \
         JS_PS_END                                                     \
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -443,17 +443,16 @@ static const js::ClassOps SandboxClassOp
     sandbox_finalize,
     nullptr,
     nullptr,
     nullptr,
     JS_GlobalObjectTraceHook,
 };
 
 static const js::ClassExtension SandboxClassExtension = {
-    nullptr,      /* weakmapKeyDelegateOp */
     sandbox_moved /* objectMovedOp */
 };
 
 static const js::Class SandboxClass = {
     "Sandbox",
     XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
     &SandboxClassOps,
     JS_NULL_CLASS_SPEC,
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -658,17 +658,16 @@ static const js::ClassOps XPC_WN_NoHelpe
     XPC_WN_NoHelper_Finalize,           // finalize
     nullptr,                            // call
     nullptr,                            // construct
     nullptr,                            // hasInstance
     XPCWrappedNative::Trace,            // trace
 };
 
 const js::ClassExtension XPC_WN_JSClassExtension = {
-    nullptr,  // weakmapKeyDelegateOp
     WrappedNativeObjectMoved};
 
 const js::Class XPC_WN_NoHelper_JSClass = {
     "XPCWrappedNative_NoHelper",
     XPC_WRAPPER_FLAGS | JSCLASS_IS_WRAPPED_NATIVE |
         JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_FOREGROUND_FINALIZE,
     &XPC_WN_NoHelper_JSClassOps,
     JS_NULL_CLASS_SPEC,
@@ -1109,17 +1108,16 @@ static const js::ClassOps XPC_WN_Proto_J
     XPC_WN_Proto_Finalize,                    // finalize
     nullptr,                                  // call
     nullptr,                                  // construct
     nullptr,                                  // hasInstance
     XPC_WN_Proto_Trace,                       // trace
 };
 
 static const js::ClassExtension XPC_WN_Proto_ClassExtension = {
-    nullptr, /* weakmapKeyDelegateOp */
     XPC_WN_Proto_ObjectMoved};
 
 const js::Class XPC_WN_Proto_JSClass = {
     "XPC_WN_Proto_JSClass",       XPC_WRAPPER_FLAGS,
     &XPC_WN_Proto_JSClassOps,     JS_NULL_CLASS_SPEC,
     &XPC_WN_Proto_ClassExtension, JS_NULL_OBJECT_OPS};
 
 /***************************************************************************/
@@ -1199,17 +1197,16 @@ static const js::ClassOps XPC_WN_Tearoff
     XPC_WN_TearOff_Finalize,            // finalize
     nullptr,                            // call
     nullptr,                            // construct
     nullptr,                            // hasInstance
     nullptr,                            // trace
 };
 
 static const js::ClassExtension XPC_WN_Tearoff_JSClassExtension = {
-    nullptr,  // weakmapKeyDelegateOp
     XPC_WN_TearOff_ObjectMoved};
 
 const js::Class XPC_WN_Tearoff_JSClass = {
     "WrappedNative_TearOff",
     XPC_WRAPPER_FLAGS |
         JSCLASS_HAS_RESERVED_SLOTS(XPC_WN_TEAROFF_RESERVED_SLOTS),
     &XPC_WN_Tearoff_JSClassOps, JS_NULL_CLASS_SPEC,
     &XPC_WN_Tearoff_JSClassExtension};
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -200,17 +200,17 @@ void NoteWeakMapsTracer::trace(JSObject*
   // can cause leaks, but is preferable to ignoring the binding, which could
   // cause the cycle collector to free live objects.
   if (!JS::IsCCTraceKind(aKey.kind())) {
     aKey = nullptr;
   }
 
   JSObject* kdelegate = nullptr;
   if (aKey.is<JSObject>()) {
-    kdelegate = js::GetWeakmapKeyDelegate(&aKey.as<JSObject>());
+    kdelegate = js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
   }
 
   if (JS::IsCCTraceKind(aValue.kind())) {
     mCb.NoteWeakMapping(aMap, aKey, kdelegate, aValue);
   } else {
     mChildTracer.mTracedAny = false;
     mChildTracer.mMap = aMap;
     mChildTracer.mKey = aKey;
@@ -246,17 +246,17 @@ static void ShouldWeakMappingEntryBeBlac
     return;
   }
 
   if (!JS::IsCCTraceKind(aKey.kind())) {
     aKey = nullptr;
   }
 
   if (keyMightNeedMarking && aKey.is<JSObject>()) {
-    JSObject* kdelegate = js::GetWeakmapKeyDelegate(&aKey.as<JSObject>());
+    JSObject* kdelegate = js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
     if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) &&
         (!aMap || !JS::ObjectIsMarkedGray(aMap))) {
       *aKeyShouldBeBlack = true;
     }
   }
 
   if (aValue && JS::GCThingIsMarkedGray(aValue) &&
       (!aKey || !JS::GCThingIsMarkedGray(aKey)) &&