Bug 1261720 (part 1) - Separate js::ClassExtension from js::Class. r=jorendorff,bz.
authorNicholas Nethercote <nnethercote@mozilla.com>
Mon, 04 Apr 2016 08:45:07 +1000
changeset 329610 b9f349b8151b9bc165318bda2eaed0ede209d50e
parent 329594 9b307318175cf1864f30a5d31e6494fb356a7c45
child 329611 32598dbf8f233575c5dd492eb05dd324b53d1d6a
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, bz
bugs1261720
milestone48.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 1261720 (part 1) - Separate js::ClassExtension from js::Class. r=jorendorff,bz. js::ClassExtension is often all null. When it's not all null, it's often duplicated among classes. By pulling it out into its own struct, and using a (possibly null) pointer in js::Class, we can save 17 KiB per process on 64-bit, and half that on 32-bit.
dom/base/nsGlobalWindow.cpp
dom/bindings/Codegen.py
dom/bindings/SimpleGlobalObject.cpp
dom/plugins/base/nsJSNPRuntime.cpp
dom/workers/WorkerScope.cpp
js/public/Class.h
js/src/builtin/WeakMapObject.cpp
js/src/gc/Marking.cpp
js/src/jsapi-tests/testBug604087.cpp
js/src/jsapi-tests/testWeakMap.cpp
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsgc.cpp
js/src/jsweakmap.cpp
js/src/jsweakmap.h
js/src/proxy/Proxy.cpp
js/src/vm/ArrayBufferObject.cpp
js/src/vm/UnboxedObject.cpp
js/xpconnect/src/Sandbox.cpp
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcprivate.h
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -753,24 +753,25 @@ protected:
   GetSubframeWindow(JSContext *cx,
                     JS::Handle<JSObject*> proxy,
                     JS::Handle<jsid> id) const;
 
   bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy,
                                   JS::AutoIdVector &props) const;
 };
 
-const js::Class OuterWindowProxyClass =
-    PROXY_CLASS_WITH_EXT(
-        "Proxy",
-        0, /* additional class flags */
-        PROXY_MAKE_EXT(
-            false,   /* isWrappedNative */
-            nsOuterWindowProxy::ObjectMoved
-        ));
+static const js::ClassExtension OuterWindowProxyClassExtension = PROXY_MAKE_EXT(
+    false,   /* isWrappedNative */
+    nsOuterWindowProxy::ObjectMoved
+);
+
+const js::Class OuterWindowProxyClass = PROXY_CLASS_WITH_EXT(
+    "Proxy",
+    0, /* additional class flags */
+    &OuterWindowProxyClassExtension);
 
 const char *
 nsOuterWindowProxy::className(JSContext *cx, JS::Handle<JSObject*> proxy) const
 {
     MOZ_ASSERT(js::IsProxy(proxy));
 
     return "Window";
 }
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -435,37 +435,39 @@ class CGDOMJSClass(CGThing):
             enumerateHook = "mozilla::dom::EnumerateGlobal"
         else:
             resolveHook = "nullptr"
             mayResolveHook = "nullptr"
             enumerateHook = "nullptr"
 
         return fill(
             """
+            static const js::ClassExtension sClassExtension = {
+              false,   /* isWrappedNative */
+              nullptr, /* weakmapKeyDelegateOp */
+              ${objectMoved} /* objectMovedOp */
+            };
+
             static const DOMJSClass sClass = {
               { "${name}",
                 ${flags},
                 ${addProperty}, /* addProperty */
                 nullptr,               /* delProperty */
                 nullptr,               /* getProperty */
                 nullptr,               /* setProperty */
                 ${enumerate}, /* enumerate */
                 ${resolve}, /* resolve */
                 ${mayResolve}, /* mayResolve */
                 ${finalize}, /* finalize */
                 ${call}, /* call */
                 nullptr,               /* hasInstance */
                 nullptr,               /* construct */
                 ${trace}, /* trace */
                 JS_NULL_CLASS_SPEC,
-                {
-                  false,   /* isWrappedNative */
-                  nullptr, /* weakmapKeyDelegateOp */
-                  ${objectMoved} /* objectMovedOp */
-                },
+                &sClassExtension,
                 JS_NULL_OBJECT_OPS
               },
               $*{descriptor}
             };
             static_assert(${instanceReservedSlots} == DOM_INSTANCE_RESERVED_SLOTS,
                           "Must have the right minimal number of reserved slots.");
             static_assert(${reservedSlots} >= ${slotCount},
                           "Must have enough reserved slots.");
@@ -502,21 +504,25 @@ class CGDOMProxyJSClass(CGThing):
         # We don't use an IDL annotation for JSCLASS_EMULATES_UNDEFINED because
         # we don't want people ever adding that to any interface other than
         # HTMLAllCollection.  So just hardcode it here.
         if self.descriptor.interface.identifier.name == "HTMLAllCollection":
             flags.append("JSCLASS_EMULATES_UNDEFINED")
         objectMovedHook = OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else 'nullptr'
         return fill(
             """
+            static const js::ClassExtension sClassExtension = PROXY_MAKE_EXT(
+                false,   /* isWrappedNative */
+                ${objectMoved}
+            );
+
             static const DOMJSClass sClass = {
               PROXY_CLASS_WITH_EXT("${name}",
                                    ${flags},
-                                   PROXY_MAKE_EXT(false,   /* isWrappedNative */
-                                                  ${objectMoved})),
+                                   &sClassExtension),
               $*{descriptor}
             };
             """,
             name=self.descriptor.interface.identifier.name,
             flags=" | ".join(flags),
             objectMoved=objectMovedHook,
             descriptor=DOMClass(self.descriptor))
 
--- a/dom/bindings/SimpleGlobalObject.cpp
+++ b/dom/bindings/SimpleGlobalObject.cpp
@@ -65,36 +65,40 @@ SimpleGlobal_finalize(js::FreeOp *fop, J
 static void
 SimpleGlobal_moved(JSObject *obj, const JSObject *old)
 {
   SimpleGlobalObject* globalObject =
     static_cast<SimpleGlobalObject*>(JS_GetPrivate(obj));
   globalObject->UpdateWrapper(obj, old);
 }
 
+static const js::ClassExtension SimpleGlobalClassExtension = {
+  false,
+  nullptr,
+  SimpleGlobal_moved
+};
+
 const js::Class SimpleGlobalClass = {
     "",
     JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     SimpleGlobal_enumerate,
     SimpleGlobal_resolve,
     nullptr,
     SimpleGlobal_finalize,
     nullptr,
     nullptr,
     nullptr,
     JS_GlobalObjectTraceHook,
-    JS_NULL_CLASS_SPEC, {
-      false,
-      nullptr,
-      SimpleGlobal_moved
-    }, JS_NULL_OBJECT_OPS
+    JS_NULL_CLASS_SPEC,
+    &SimpleGlobalClassExtension,
+    JS_NULL_OBJECT_OPS
 };
 
 // static
 JSObject*
 SimpleGlobalObject::Create(GlobalType globalType, JS::Handle<JS::Value> proto)
 {
   JSContext* cx = nsContentUtils::GetDefaultJSContextForThread();
   JSAutoRequest ar(cx);
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -213,16 +213,22 @@ NPObjWrapper_Construct(JSContext *cx, un
 static bool
 NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
 
 static bool
 CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
                      JS::Handle<jsid> id,  NPVariant* getPropertyResult,
                      JS::MutableHandle<JS::Value> vp);
 
+const static js::ClassExtension sNPObjectJSWrapperClassExtension = {
+    false,                                                /* isWrappedNative */
+    nullptr,                                              /* weakmapKeyDelegateOp */
+    NPObjWrapper_ObjectMoved
+};
+
 const static js::ObjectOps sNPObjectJSWrapperObjectOps = {
     nullptr, // lookupProperty
     nullptr, // defineProperty
     nullptr, // hasProperty
     nullptr, // getProperty
     nullptr, // setProperty
     nullptr, // getOwnPropertyDescriptor
     nullptr, // deleteProperty
@@ -244,21 +250,17 @@ const static js::Class sNPObjectJSWrappe
     NPObjWrapper_Resolve,
     nullptr,                                                /* mayResolve */
     NPObjWrapper_Finalize,
     NPObjWrapper_Call,
     nullptr,                                                /* hasInstance */
     NPObjWrapper_Construct,
     nullptr,                                                /* trace */
     JS_NULL_CLASS_SPEC,
-    {
-      false,                                                /* isWrappedNative */
-      nullptr,                                              /* weakmapKeyDelegateOp */
-      NPObjWrapper_ObjectMoved
-    },
+    &sNPObjectJSWrapperClassExtension,
     &sNPObjectJSWrapperObjectOps
   };
 
 typedef struct NPObjectMemberPrivate {
     JS::Heap<JSObject *> npobjWrapper;
     JS::Heap<JS::Value> fieldValue;
     JS::Heap<jsid> methodName;
     NPP   npp;
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -706,17 +706,16 @@ WorkerDebuggerGlobalScope::GetGlobal(JSC
   WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
   if (!scope) {
     aRv.Throw(NS_ERROR_FAILURE);
   }
 
   aGlobal.set(scope->GetWrapper());
 }
 
-
 void
 WorkerDebuggerGlobalScope::CreateSandbox(JSContext* aCx, const nsAString& aName,
                                          JS::Handle<JSObject*> aPrototype,
                                          JS::MutableHandle<JSObject*> aResult,
                                          ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -620,17 +620,17 @@ struct ClassExtension
     JSObjectMovedOp objectMovedOp;
 };
 
 inline ClassObjectCreationOp DELEGATED_CLASSSPEC(const ClassSpec* spec) {
     return reinterpret_cast<ClassObjectCreationOp>(const_cast<ClassSpec*>(spec));
 }
 
 #define JS_NULL_CLASS_SPEC  nullptr
-#define JS_NULL_CLASS_EXT   {false,nullptr}
+#define JS_NULL_CLASS_EXT   nullptr
 
 struct ObjectOps
 {
     LookupPropertyOp lookupProperty;
     DefinePropertyOp defineProperty;
     HasPropertyOp    hasProperty;
     GetPropertyOp    getProperty;
     SetPropertyOp    setProperty;
@@ -649,17 +649,17 @@ struct ObjectOps
 
 // Classes, objects, and properties.
 
 typedef void (*JSClassInternal)();
 
 struct JSClass {
     JS_CLASS_MEMBERS(JSFinalizeOp);
 
-    void*               reserved[5];
+    void* reserved[3];
 };
 
 #define JSCLASS_HAS_PRIVATE             (1<<0)  // objects have private slot
 #define JSCLASS_DELAY_METADATA_BUILDER  (1<<1)  // class's initialization code
                                                 // will call
                                                 // SetNewObjectMetadata itself
 #define JSCLASS_PRIVATE_IS_NSISUPPORTS  (1<<3)  // private is (nsISupports*)
 #define JSCLASS_IS_DOMJSCLASS           (1<<4)  // objects are DOM
@@ -751,19 +751,19 @@ struct JSClass {
 #define JSCLASS_NO_INTERNAL_MEMBERS     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
 #define JSCLASS_NO_OPTIONAL_MEMBERS     0,0,0,0,0,JSCLASS_NO_INTERNAL_MEMBERS
 
 namespace js {
 
 struct Class
 {
     JS_CLASS_MEMBERS(FinalizeOp);
-    const ClassSpec*    spec;
-    ClassExtension      ext;
-    const ObjectOps*    ops;
+    const ClassSpec* spec;
+    const ClassExtension* ext;
+    const ObjectOps* ops;
 
     /*
      * Objects of this class aren't native objects. They don't have Shapes that
      * describe their properties and layout. Classes using this flag must
      * provide their own property behavior, either by being proxy classes (do
      * this) or by overriding all the ObjectOps except getElements, watch and
      * unwatch (don't do this).
      */
@@ -819,16 +819,22 @@ struct Class
                                const { return spec ? spec->constructorProperties()   : nullptr; }
     const JSFunctionSpec* specPrototypeFunctions()
                                const { return spec ? spec->prototypeFunctions()      : nullptr; }
     const JSPropertySpec* specPrototypeProperties()
                                const { return spec ? spec->prototypeProperties()     : nullptr; }
     FinishClassInitOp specFinishInitHook()
                                const { return spec ? spec->finishInitHook()          : nullptr; }
 
+    bool extIsWrappedNative()  const { return ext ? ext->isWrappedNative             : false; }
+    JSWeakmapKeyDelegateOp extWeakmapKeyDelegateOp()
+                               const { return ext ? ext->weakmapKeyDelegateOp        : nullptr; }
+    JSObjectMovedOp extObjectMovedOp()
+                               const { return ext ? ext->objectMovedOp               : nullptr; }
+
     LookupPropertyOp getOpsLookupProperty() const { return ops ? ops->lookupProperty : nullptr; }
     DefinePropertyOp getOpsDefineProperty() const { return ops ? ops->defineProperty : nullptr; }
     HasPropertyOp    getOpsHasProperty()    const { return ops ? ops->hasProperty    : nullptr; }
     GetPropertyOp    getOpsGetProperty()    const { return ops ? ops->getProperty    : nullptr; }
     SetPropertyOp    getOpsSetProperty()    const { return ops ? ops->setProperty    : nullptr; }
     GetOwnPropertyOp getOpsGetOwnPropertyDescriptor()
                                             const { return ops ? ops->getOwnPropertyDescriptor
                                                                                      : nullptr; }
--- a/js/src/builtin/WeakMapObject.cpp
+++ b/js/src/builtin/WeakMapObject.cpp
@@ -106,17 +106,17 @@ js::WeakMap_delete(JSContext* cx, unsign
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsWeakMap, WeakMap_delete_impl>(cx, args);
 }
 
 static bool
 TryPreserveReflector(JSContext* cx, HandleObject obj)
 {
-    if (obj->getClass()->ext.isWrappedNative ||
+    if (obj->getClass()->extIsWrappedNative() ||
         (obj->getClass()->flags & JSCLASS_IS_DOMJSCLASS) ||
         (obj->is<ProxyObject>() &&
          obj->as<ProxyObject>().handler()->family() == GetDOMProxyHandlerFamily()))
     {
         MOZ_ASSERT(cx->runtime()->preserveWrapperCallback);
         if (!cx->runtime()->preserveWrapperCallback(cx, obj)) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY);
             return false;
@@ -142,17 +142,17 @@ SetWeakMapEntryInternal(JSContext* cx, H
         map = newMap.release();
         mapObj->setPrivate(map);
     }
 
     // Preserve wrapped native keys to prevent wrapper optimization.
     if (!TryPreserveReflector(cx, key))
         return false;
 
-    if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) {
+    if (JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp()) {
         RootedObject delegate(cx, op(key));
         if (delegate && !TryPreserveReflector(cx, delegate))
             return false;
     }
 
     MOZ_ASSERT(key->compartment() == mapObj->compartment());
     MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment());
     if (!map->put(key, value)) {
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2265,17 +2265,17 @@ js::TenuringTracer::moveObjectToTenured(
 
     if (src->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE) {
         if (src->is<InlineTypedObject>()) {
             InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
         } else if (src->is<UnboxedArrayObject>()) {
             tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
         } else if (src->is<ArgumentsObject>()) {
             tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
-        } else if (JSObjectMovedOp op = dst->getClass()->ext.objectMovedOp) {
+        } else if (JSObjectMovedOp op = dst->getClass()->extObjectMovedOp()) {
             op(dst, src);
         } else {
             // Objects with JSCLASS_SKIP_NURSERY_FINALIZE need to be handled above
             // to ensure any additional nursery buffers they hold are moved.
             MOZ_CRASH("Unhandled JSCLASS_SKIP_NURSERY_FINALIZE Class");
         }
     }
 
--- a/js/src/jsapi-tests/testBug604087.cpp
+++ b/js/src/jsapi-tests/testBug604087.cpp
@@ -9,24 +9,25 @@
 
 #include "jsobj.h"
 #include "jswrapper.h"
 
 #include "jsapi-tests/tests.h"
 
 #include "vm/ProxyObject.h"
 
-const js::Class OuterWrapperClass =
-    PROXY_CLASS_WITH_EXT(
-        "Proxy",
-        0, /* additional class flags */
-        PROXY_MAKE_EXT(
-            false,   /* isWrappedNative */
-            nullptr  /* objectMoved */
-        ));
+static const js::ClassExtension OuterWrapperClassExtension = PROXY_MAKE_EXT(
+    false,   /* isWrappedNative */
+    nullptr  /* objectMoved */
+);
+
+const js::Class OuterWrapperClass = PROXY_CLASS_WITH_EXT(
+    "Proxy",
+    0, /* additional class flags */
+    &OuterWrapperClassExtension);
 
 static JSObject*
 wrap(JSContext* cx, JS::HandleObject toWrap, JS::HandleObject target)
 {
     JSAutoCompartment ac(cx, target);
     JS::RootedObject wrapper(cx, toWrap);
     if (!JS_WrapObject(cx, &wrapper))
         return nullptr;
--- a/js/src/jsapi-tests/testWeakMap.cpp
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -147,36 +147,38 @@ static void DelegateObjectMoved(JSObject
 
 static JSObject* GetKeyDelegate(JSObject* obj)
 {
     return keyDelegate;
 }
 
 JSObject* newKey()
 {
+    static const js::ClassExtension keyClassExtension = {
+        false,
+        GetKeyDelegate
+    };
+
     static const js::Class keyClass = {
-        "keyWithDelgate",
+        "keyWithDelegate",
         JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1),
         nullptr, /* addProperty */
         nullptr, /* delProperty */
         nullptr, /* getProperty */
         nullptr, /* setProperty */
         nullptr, /* enumerate */
         nullptr, /* resolve */
         nullptr, /* mayResolve */
         nullptr, /* finalize */
         nullptr, /* call */
         nullptr, /* hasInstance */
         nullptr, /* construct */
         nullptr, /* trace */
         JS_NULL_CLASS_SPEC,
-        {
-            false,
-            GetKeyDelegate
-        },
+        &keyClassExtension,
         JS_NULL_OBJECT_OPS
     };
 
     JS::RootedObject key(cx, JS_NewObject(cx, Jsvalify(&keyClass)));
     if (!key)
         return nullptr;
 
     return key;
@@ -201,37 +203,39 @@ JSObject* newCCW(JS::HandleObject source
         if (!JS_WrapObject(cx, &object))
             return nullptr;
     }
     return object;
 }
 
 JSObject* newDelegate()
 {
+    static const js::ClassExtension delegateClassExtension = {
+        false,
+        nullptr,
+        DelegateObjectMoved
+    };
+
     static const js::Class delegateClass = {
         "delegate",
         JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1),
         nullptr, /* addProperty */
         nullptr, /* delProperty */
         nullptr, /* getProperty */
         nullptr, /* setProperty */
         nullptr, /* enumerate */
         nullptr, /* resolve */
         nullptr, /* mayResolve */
         nullptr, /* finalize */
         nullptr, /* call */
         nullptr, /* hasInstance */
         nullptr, /* construct */
         JS_GlobalObjectTraceHook,
         JS_NULL_CLASS_SPEC,
-        {
-            false,
-            nullptr,
-            DelegateObjectMoved
-        },
+        &delegateClassExtension,
         JS_NULL_OBJECT_OPS
     };
 
     /* Create the global object. */
     JS::CompartmentOptions options;
     options.behaviors().setVersion(JSVERSION_LATEST);
 
     JS::RootedObject global(cx, JS_NewGlobalObject(cx, Jsvalify(&delegateClass), nullptr,
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -598,17 +598,17 @@ js::VisitGrayWrapperTargets(Zone* zone, 
                 callback(closure, JS::GCCellPtr(thing, thing->asTenured().getTraceKind()));
         }
     }
 }
 
 JS_FRIEND_API(JSObject*)
 js::GetWeakmapKeyDelegate(JSObject* key)
 {
-    if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp)
+    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);
@@ -1267,17 +1267,17 @@ js::ReportErrorWithId(JSContext* cx, con
     if (!bytes)
         return;
     JS_ReportError(cx, msg, bytes.ptr());
 }
 
 #ifdef DEBUG
 bool
 js::HasObjectMovedOp(JSObject* obj) {
-    return !!GetObjectClass(obj)->ext.objectMovedOp;
+    return !!GetObjectClass(obj)->extObjectMovedOp();
 }
 #endif
 
 JS_FRIEND_API(void)
 JS_StoreObjectPostBarrierCallback(JSContext* cx,
                                   void (*callback)(JSTracer* trc, JSObject* key, void* data),
                                   JSObject* key, void* data)
 {
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -309,32 +309,33 @@ struct JSFunctionSpecWithHelp {
 #define JS_FS_HELP_END                                                        \
     {nullptr, nullptr, 0, 0, nullptr, nullptr}
 
 extern JS_FRIEND_API(bool)
 JS_DefineFunctionsWithHelp(JSContext* cx, JS::HandleObject obj, const JSFunctionSpecWithHelp* fs);
 
 namespace js {
 
+extern JS_FRIEND_DATA(const js::ClassExtension) ProxyClassExtension;
 extern JS_FRIEND_DATA(const js::ObjectOps) ProxyObjectOps;
 
 /*
  * Helper Macros for creating JSClasses that function as proxies.
  *
  * NB: The macro invocation must be surrounded by braces, so as to
  *     allow for potential JSClass extensions.
  */
 #define PROXY_MAKE_EXT(isWrappedNative, objectMoved)                    \
     {                                                                   \
         isWrappedNative,                                                \
         js::proxy_WeakmapKeyDelegate,                                   \
         objectMoved                                                     \
     }
 
-#define PROXY_CLASS_WITH_EXT(name, flags, ext)                                          \
+#define PROXY_CLASS_WITH_EXT(name, flags, extPtr)                                       \
     {                                                                                   \
         name,                                                                           \
         js::Class::NON_NATIVE |                                                         \
             JSCLASS_IS_PROXY |                                                          \
             JSCLASS_DELAY_METADATA_BUILDER |                                            \
             flags,                                                                      \
         nullptr,                 /* addProperty */                                      \
         nullptr,                 /* delProperty */                                      \
@@ -344,26 +345,22 @@ extern JS_FRIEND_DATA(const js::ObjectOp
         nullptr,                 /* resolve */                                          \
         nullptr,                 /* mayResolve */                                       \
         js::proxy_Finalize,      /* finalize    */                                      \
         nullptr,                 /* call        */                                      \
         js::proxy_HasInstance,   /* hasInstance */                                      \
         nullptr,                 /* construct   */                                      \
         js::proxy_Trace,         /* trace       */                                      \
         JS_NULL_CLASS_SPEC,                                                             \
-        ext,                                                                            \
+        extPtr,                                                                         \
         &js::ProxyObjectOps                                                             \
     }
 
-#define PROXY_CLASS_DEF(name, flags)                                    \
-  PROXY_CLASS_WITH_EXT(name, flags,                                     \
-                       PROXY_MAKE_EXT(                                  \
-                         false,   /* isWrappedNative */                 \
-                         js::proxy_ObjectMoved                          \
-                       ))
+#define PROXY_CLASS_DEF(name, flags) \
+  PROXY_CLASS_WITH_EXT(name, flags, &js::ProxyClassExtension)
 
 /*
  * Proxy stubs, similar to JS_*Stub, for embedder proxy class definitions.
  *
  * NB: Should not be called directly.
  */
 
 extern JS_FRIEND_API(bool)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2194,17 +2194,17 @@ RelocateCell(Zone* zone, TenuredCell* sr
             if (srcNative->denseElementsAreCopyOnWrite()) {
                 HeapPtrNativeObject& owner = dstNative->getElementsHeader()->ownerObject();
                 if (owner == srcNative)
                     owner = dstNative;
             }
         }
 
         // Call object moved hook if present.
-        if (JSObjectMovedOp op = srcObj->getClass()->ext.objectMovedOp)
+        if (JSObjectMovedOp op = srcObj->getClass()->extObjectMovedOp())
             op(dstObj, srcObj);
 
         MOZ_ASSERT_IF(dstObj->isNative(),
                       !PtrIsInRange((const Value*)dstObj->as<NativeObject>().getDenseElements(),
                                     src, thingSize));
     }
 
     // Copy the mark bits.
--- a/js/src/jsweakmap.cpp
+++ b/js/src/jsweakmap.cpp
@@ -137,17 +137,17 @@ ObjectValueMap::findZoneEdges()
      * edge to ensure that the delegate zone finishes marking before the key
      * zone.
      */
     JS::AutoSuppressGCAnalysis nogc;
     for (Range r = all(); !r.empty(); r.popFront()) {
         JSObject* key = r.front().key();
         if (key->asTenured().isMarked(BLACK) && !key->asTenured().isMarked(GRAY))
             continue;
-        JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp;
+        JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp();
         if (!op)
             continue;
         JSObject* delegate = op(key);
         if (!delegate)
             continue;
         Zone* delegateZone = delegate->zone();
         if (delegateZone == zone)
             continue;
--- a/js/src/jsweakmap.h
+++ b/js/src/jsweakmap.h
@@ -277,17 +277,17 @@ class WeakMap : public HashMap<Key, Valu
         return markedAny;
     }
 
   private:
     void exposeGCThingToActiveJS(const JS::Value& v) const { JS::ExposeValueToActiveJS(v); }
     void exposeGCThingToActiveJS(JSObject* obj) const { JS::ExposeObjectToActiveJS(obj); }
 
     JSObject* getDelegate(JSObject* key) const {
-        JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp;
+        JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp();
         return op ? op(key) : nullptr;
     }
 
     JSObject* getDelegate(gc::Cell* cell) const {
         return nullptr;
     }
 
     bool keyNeedsMark(JSObject* key) const {
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -711,16 +711,21 @@ js::proxy_GetElements(JSContext* cx, Han
 }
 
 JSString*
 js::proxy_FunToString(JSContext* cx, HandleObject proxy, unsigned indent)
 {
     return Proxy::fun_toString(cx, proxy, indent);
 }
 
+const ClassExtension js::ProxyClassExtension = PROXY_MAKE_EXT(
+    false,   /* isWrappedNative */
+    js::proxy_ObjectMoved
+);
+
 const ObjectOps js::ProxyObjectOps = {
     js::proxy_LookupProperty,
     js::proxy_DefineProperty,
     js::proxy_HasProperty,
     js::proxy_GetProperty,
     js::proxy_SetProperty,
     js::proxy_GetOwnPropertyDescriptor,
     js::proxy_DeleteProperty,
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -91,16 +91,22 @@ js::ToClampedIndex(JSContext* cx, Handle
  * ArrayBufferObject (base)
  */
 
 const Class ArrayBufferObject::protoClass = {
     "ArrayBufferPrototype",
     JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer)
 };
 
+static const ClassExtension ArrayBufferObjectClassExtension = {
+    false,      /* isWrappedNative */
+    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,
     nullptr,                 /* addProperty */
     nullptr,                 /* delProperty */
@@ -110,21 +116,17 @@ const Class ArrayBufferObject::class_ = 
     nullptr,                 /* resolve */
     nullptr,                 /* mayResolve */
     ArrayBufferObject::finalize,
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
     ArrayBufferObject::trace,
     JS_NULL_CLASS_SPEC,
-    {
-        false,      /* isWrappedNative */
-        nullptr,    /* weakmapKeyDelegateOp */
-        ArrayBufferObject::objectMoved
-    }
+    &ArrayBufferObjectClassExtension
 };
 
 const JSFunctionSpec ArrayBufferObject::jsfuncs[] = {
     JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2,0),
     JS_FS_END
 };
 
 const JSFunctionSpec ArrayBufferObject::jsstaticfuncs[] = {
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -1586,16 +1586,22 @@ UnboxedArrayObject::obj_enumerate(JSCont
     }
 
     if (!enumerableOnly && !properties.append(NameToId(cx->names().length)))
         return false;
 
     return true;
 }
 
+static const ClassExtension UnboxedArrayObjectClassExtension = {
+    false,      /* isWrappedNative */
+    nullptr,    /* weakmapKeyDelegateOp */
+    UnboxedArrayObject::objectMoved
+};
+
 static const ObjectOps UnboxedArrayObjectObjectOps = {
     UnboxedArrayObject::obj_lookupProperty,
     UnboxedArrayObject::obj_defineProperty,
     UnboxedArrayObject::obj_hasProperty,
     UnboxedArrayObject::obj_getProperty,
     UnboxedArrayObject::obj_setProperty,
     UnboxedArrayObject::obj_getOwnPropertyDescriptor,
     UnboxedArrayObject::obj_deleteProperty,
@@ -1619,21 +1625,17 @@ const Class UnboxedArrayObject::class_ =
     nullptr,        /* resolve     */
     nullptr,        /* mayResolve  */
     UnboxedArrayObject::finalize,
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
     UnboxedArrayObject::trace,
     JS_NULL_CLASS_SPEC,
-    {
-        false,      /* isWrappedNative */
-        nullptr,    /* weakmapKeyDelegateOp */
-        UnboxedArrayObject::objectMoved
-    },
+    &UnboxedArrayObjectClassExtension,
     &UnboxedArrayObjectObjectOps
 };
 
 /////////////////////////////////////////////////////////////////////
 // API
 /////////////////////////////////////////////////////////////////////
 
 static bool
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -545,49 +545,47 @@ sandbox_addProperty(JSContext* cx, Handl
                                JS_PROPERTYOP_SETTER(writeToProto_setProperty)))
         return false;
 
     return true;
 }
 
 #define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)
 
+static const js::ClassExtension SandboxClassExtension = {
+    false,        /* isWrappedNative */
+    nullptr,      /* weakmapKeyDelegateOp */
+    sandbox_moved /* objectMovedOp */
+};
+
 static const js::Class SandboxClass = {
     "Sandbox",
     XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
     nullptr, nullptr, nullptr, nullptr,
     sandbox_enumerate, sandbox_resolve,
     nullptr,        /* mayResolve */
     sandbox_finalize,
     nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
     JS_NULL_CLASS_SPEC,
-    {
-      false,        /* isWrappedNative */
-      nullptr,      /* weakmapKeyDelegateOp */
-      sandbox_moved /* objectMovedOp */
-    },
+    &SandboxClassExtension,
     JS_NULL_OBJECT_OPS
 };
 
 // Note to whomever comes here to remove addProperty hooks: billm has promised
 // to do the work for this class.
 static const js::Class SandboxWriteToProtoClass = {
     "Sandbox",
     XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
     sandbox_addProperty, nullptr, nullptr, nullptr,
     sandbox_enumerate, sandbox_resolve,
     nullptr,        /* mayResolve */
     sandbox_finalize,
     nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
     JS_NULL_CLASS_SPEC,
-    {
-      false,        /* isWrappedNative */
-      nullptr,      /* weakmapKeyDelegateOp */
-      sandbox_moved /* objectMovedOp */
-    },
+    &SandboxClassExtension,
     JS_NULL_OBJECT_OPS
 };
 
 static const JSFunctionSpec SandboxFunctions[] = {
     JS_FS("dump",    SandboxDump,    1,0),
     JS_FS("debug",   SandboxDebug,   1,0),
     JS_FS("importFunction", SandboxImport, 1,0),
     JS_FS_END
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -615,16 +615,22 @@ XPC_WN_NoHelper_Resolve(JSContext* cx, H
                                  set, nullptr, nullptr, wrapper->GetScope(),
                                  true, wrapper, wrapper, nullptr,
                                  JSPROP_ENUMERATE |
                                  JSPROP_READONLY |
                                  JSPROP_PERMANENT,
                                  resolvedp);
 }
 
+static const js::ClassExtension XPC_WN_JSClassExtension = {
+    true,    // isWrappedNative
+    nullptr, // weakmapKeyDelegateOp
+    WrappedNativeObjectMoved
+};
+
 const js::Class XPC_WN_NoHelper_JSClass = {
     "XPCWrappedNative_NoHelper",    // name;
     WRAPPER_FLAGS |
     JSCLASS_PRIVATE_IS_NSISUPPORTS, // flags
 
     /* Mandatory non-null function pointer members. */
     XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty
     XPC_WN_CantDeletePropertyStub,     // delProperty
@@ -639,21 +645,17 @@ const js::Class XPC_WN_NoHelper_JSClass 
     /* Optionally non-null members start here. */
     nullptr,                         // call
     nullptr,                         // construct
     nullptr,                         // hasInstance
     XPCWrappedNative::Trace,         // trace
     JS_NULL_CLASS_SPEC,
 
     // ClassExtension
-    {
-        true,    // isWrappedNative
-        nullptr, // weakmapKeyDelegateOp
-        WrappedNativeObjectMoved
-    },
+    &XPC_WN_JSClassExtension,
 
     // ObjectOps
     JS_NULL_OBJECT_OPS
 };
 
 
 /***************************************************************************/
 
@@ -962,16 +964,18 @@ static const js::ObjectOps XPC_WN_Object
 
 XPCNativeScriptableShared::XPCNativeScriptableShared(uint32_t aFlags,
                                                      char* aName,
                                                      bool aPopulate)
     : mFlags(aFlags)
 {
     MOZ_COUNT_CTOR(XPCNativeScriptableShared);
 
+    // Initialize the js::Class.
+
     memset(&mJSClass, 0, sizeof(mJSClass));
     mJSClass.name = aName;  // take ownership
 
     if (!aPopulate)
         return;
 
     mJSClass.flags = WRAPPER_FLAGS | JSCLASS_PRIVATE_IS_NSISUPPORTS;
 
@@ -1028,34 +1032,37 @@ XPCNativeScriptableShared::XPCNativeScri
     // We have to figure out resolve strategy at call time
     mJSClass.resolve = XPC_WN_Helper_Resolve;
 
     if (mFlags.WantFinalize())
         mJSClass.finalize = XPC_WN_Helper_Finalize;
     else
         mJSClass.finalize = XPC_WN_NoHelper_Finalize;
 
-    if (mFlags.WantNewEnumerate())
-        mJSClass.ops = &XPC_WN_ObjectOpsWithEnumerate;
-
     if (mFlags.WantCall())
         mJSClass.call = XPC_WN_Helper_Call;
     if (mFlags.WantConstruct())
         mJSClass.construct = XPC_WN_Helper_Construct;
 
     if (mFlags.WantHasInstance())
         mJSClass.hasInstance = XPC_WN_Helper_HasInstance;
 
     if (mFlags.IsGlobalObject())
         mJSClass.trace = JS_GlobalObjectTraceHook;
     else
         mJSClass.trace = XPCWrappedNative::Trace;
 
-    mJSClass.ext.isWrappedNative = true;
-    mJSClass.ext.objectMovedOp = WrappedNativeObjectMoved;
+    // Initialize the js::ClassExtension.
+
+    mJSClass.ext = &XPC_WN_JSClassExtension;
+
+    // Initialize the js::ObjectOps.
+
+    if (mFlags.WantNewEnumerate())
+        mJSClass.ops = &XPC_WN_ObjectOpsWithEnumerate;
 }
 
 /***************************************************************************/
 /***************************************************************************/
 
 // Compatibility hack.
 //
 // XPConnect used to do all sorts of funny tricks to find the "correct"
@@ -1245,22 +1252,21 @@ XPC_WN_ModsAllowed_Proto_Resolve(JSConte
     XPCNativeScriptableInfo* si = self->GetScriptableInfo();
     return DefinePropertyIfFound(ccx, obj, id,
                                  self->GetSet(), nullptr, nullptr,
                                  self->GetScope(),
                                  true, nullptr, nullptr, si,
                                  JSPROP_ENUMERATE, resolvep);
 }
 
-#define XPC_WN_SHARED_PROTO_CLASS_EXT                                  \
-    {                                                                  \
-        false,      /* isWrappedNative */                              \
-        nullptr,    /* weakmapKeyDelegateOp */                         \
-        XPC_WN_Shared_Proto_ObjectMoved                                \
-    }
+static const js::ClassExtension XPC_WN_Shared_Proto_ClassExtension = {
+    false,      /* isWrappedNative */
+    nullptr,    /* weakmapKeyDelegateOp */
+    XPC_WN_Shared_Proto_ObjectMoved
+};
 
 const js::Class XPC_WN_ModsAllowed_WithCall_Proto_JSClass = {
     "XPC_WN_ModsAllowed_WithCall_Proto_JSClass", // name;
     WRAPPER_FLAGS, // flags;
 
     /* Function pointer members. */
     nullptr,                        // addProperty;
     nullptr,                        // delProperty;
@@ -1273,17 +1279,17 @@ const js::Class XPC_WN_ModsAllowed_WithC
 
     /* Optionally non-null members start here. */
     nullptr,                        // call;
     nullptr,                        // construct;
     nullptr,                        // hasInstance;
     XPC_WN_Shared_Proto_Trace,      // trace;
 
     JS_NULL_CLASS_SPEC,
-    XPC_WN_SHARED_PROTO_CLASS_EXT,
+    &XPC_WN_Shared_Proto_ClassExtension,
     XPC_WN_WithCall_ObjectOps
 };
 
 const js::Class XPC_WN_ModsAllowed_NoCall_Proto_JSClass = {
     "XPC_WN_ModsAllowed_NoCall_Proto_JSClass", // name;
     WRAPPER_FLAGS,                  // flags;
 
     /* Function pointer members. */
@@ -1298,17 +1304,17 @@ const js::Class XPC_WN_ModsAllowed_NoCal
 
     /* Optionally non-null members start here. */
     nullptr,                         // call;
     nullptr,                         // construct;
     nullptr,                         // hasInstance;
     XPC_WN_Shared_Proto_Trace,      // trace;
 
     JS_NULL_CLASS_SPEC,
-    XPC_WN_SHARED_PROTO_CLASS_EXT,
+    &XPC_WN_Shared_Proto_ClassExtension,
     XPC_WN_NoCall_ObjectOps
 };
 
 /***************************************************************************/
 
 static bool
 XPC_WN_OnlyIWrite_Proto_AddPropertyStub(JSContext* cx, HandleObject obj, HandleId id,
                                         HandleValue v)
@@ -1376,17 +1382,17 @@ const js::Class XPC_WN_NoMods_WithCall_P
 
     /* Optionally non-null members start here. */
     nullptr,                         // call;
     nullptr,                         // construct;
     nullptr,                         // hasInstance;
     XPC_WN_Shared_Proto_Trace,      // trace;
 
     JS_NULL_CLASS_SPEC,
-    XPC_WN_SHARED_PROTO_CLASS_EXT,
+    &XPC_WN_Shared_Proto_ClassExtension,
     XPC_WN_WithCall_ObjectOps
 };
 
 const js::Class XPC_WN_NoMods_NoCall_Proto_JSClass = {
     "XPC_WN_NoMods_NoCall_Proto_JSClass",      // name;
     WRAPPER_FLAGS,                             // flags;
 
     /* Mandatory non-null function pointer members. */
@@ -1401,17 +1407,17 @@ const js::Class XPC_WN_NoMods_NoCall_Pro
 
     /* Optionally non-null members start here. */
     nullptr,                         // call;
     nullptr,                         // construct;
     nullptr,                         // hasInstance;
     XPC_WN_Shared_Proto_Trace,      // trace;
 
     JS_NULL_CLASS_SPEC,
-    XPC_WN_SHARED_PROTO_CLASS_EXT,
+    &XPC_WN_Shared_Proto_ClassExtension,
     XPC_WN_NoCall_ObjectOps
 };
 
 /***************************************************************************/
 
 static bool
 XPC_WN_TearOff_Enumerate(JSContext* cx, HandleObject obj)
 {
@@ -1476,16 +1482,22 @@ XPC_WN_TearOff_ObjectMoved(JSObject* obj
 }
 
 // Make sure WRAPPER_FLAGS has no reserved slots, so our XPC_WN_TEAROFF_RESERVED_SLOTS value is OK.
 
 static_assert(((WRAPPER_FLAGS >> JSCLASS_RESERVED_SLOTS_SHIFT) &
                JSCLASS_RESERVED_SLOTS_MASK) == 0,
               "WRAPPER_FLAGS should not include any reserved slots");
 
+static const js::ClassExtension XPC_WN_Tearoff_JSClassExtension = {
+    false,                                 // isWrappedNative
+    nullptr,                               // weakmapKeyDelegateOp
+    XPC_WN_TearOff_ObjectMoved
+};
+
 const js::Class XPC_WN_Tearoff_JSClass = {
     "WrappedNative_TearOff",                   // name;
     WRAPPER_FLAGS |
     JSCLASS_HAS_RESERVED_SLOTS(XPC_WN_TEAROFF_RESERVED_SLOTS), // flags;
     XPC_WN_OnlyIWrite_AddPropertyStub,         // addProperty;
     XPC_WN_CantDeletePropertyStub,             // delProperty;
     nullptr,                                   // getProperty;
     nullptr,                                   // setProperty;
@@ -1497,14 +1509,10 @@ const js::Class XPC_WN_Tearoff_JSClass =
     /* Optionally non-null members start here. */
     nullptr,                                   // call
     nullptr,                                   // construct
     nullptr,                                   // hasInstance
     nullptr,                                   // trace
     JS_NULL_CLASS_SPEC,
 
     // ClassExtension
-    {
-        false,                                 // isWrappedNative
-        nullptr,                               // weakmapKeyDelegateOp
-        XPC_WN_TearOff_ObjectMoved
-    },
+    &XPC_WN_Tearoff_JSClassExtension
 };
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -380,17 +380,17 @@ CreateGlobalObject(JSContext* cx, const 
     // The constructor automatically attaches the scope to the compartment private
     // of |global|.
     (void) new XPCWrappedNativeScope(cx, global);
 
 #ifdef DEBUG
     // Verify that the right trace hook is called. Note that this doesn't
     // work right for wrapped globals, since the tracing situation there is
     // more complicated. Manual inspection shows that they do the right thing.
-    if (!((const js::Class*)clasp)->ext.isWrappedNative)
+    if (!((const js::Class*)clasp)->extIsWrappedNative())
     {
         VerifyTraceProtoAndIfaceCacheCalledTracer trc(JS_GetRuntime(cx));
         TraceChildren(&trc, GCCellPtr(global.get()));
         MOZ_ASSERT(trc.ok, "Trace hook on global needs to call TraceXPCGlobal for XPConnect compartments.");
     }
 #endif
 
     if (clasp->flags & JSCLASS_DOM_GLOBAL) {
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -214,17 +214,17 @@ extern const char XPC_XPCONNECT_CONTRACT
 #define WRAPPER_FLAGS JSCLASS_HAS_PRIVATE
 
 #define INVALID_OBJECT ((JSObject*)1)
 
 // If IS_WN_CLASS for the JSClass of an object is true, the object is a
 // wrappednative wrapper, holding the XPCWrappedNative in its private slot.
 static inline bool IS_WN_CLASS(const js::Class* clazz)
 {
-    return clazz->ext.isWrappedNative;
+    return clazz->extIsWrappedNative();
 }
 
 static inline bool IS_WN_REFLECTOR(JSObject* obj)
 {
     return IS_WN_CLASS(js::GetObjectClass(obj));
 }
 
 /***************************************************************************