Bug 1261723 (part 2) - Separate class ops from js::Class. code=njn,h4writer. r=efaust,bz.
authorNicholas Nethercote <nnethercote@mozilla.com>
Fri, 01 Apr 2016 11:00:01 +1100
changeset 332805 aa88b0d0cd4adf9e2fde0288a0347b91af32c69a
parent 332804 8246956e2368cc19f4b731ca3a2daca57bcb27f1
child 332806 fb5f7f7e9c5c72b63708e4a44dd3c337b56d47c3
push id1146
push userCallek@gmail.com
push dateMon, 25 Jul 2016 16:35:44 +0000
treeherdermozilla-release@a55778f9cd5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust, bz
bugs1261723
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 1261723 (part 2) - Separate class ops from js::Class. code=njn,h4writer. r=efaust,bz. js::Class op are often all null. And when they're not all null, they're often duplicated among classes. By pulling them out into their own struct, and using a (possibly null) pointer in js::Class, we can save 114 KiB per process on 64-bit, and half that on 32-bit. * * * imported patch separate-ClassOps-2
devtools/shared/heapsnapshot/tests/gtest/DevTools.h
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/SimpleGlobalObject.cpp
dom/indexedDB/ActorsParent.cpp
dom/plugins/base/nsJSNPRuntime.cpp
dom/xbl/nsXBLBinding.cpp
js/public/Class.h
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/builtin/Intl.cpp
js/src/builtin/MapObject.cpp
js/src/builtin/MapObject.h
js/src/builtin/ModuleObject.cpp
js/src/builtin/ModuleObject.h
js/src/builtin/Object.cpp
js/src/builtin/Promise.cpp
js/src/builtin/SIMD.cpp
js/src/builtin/TestingFunctions.cpp
js/src/builtin/TypedObject.cpp
js/src/builtin/WeakMapObject.cpp
js/src/ctypes/CTypes.cpp
js/src/ctypes/Library.cpp
js/src/devtools/rootAnalysis/annotations.js
js/src/gc/Marking.cpp
js/src/gc/Nursery.cpp
js/src/gdb/gdb-tests.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/IonCaches.cpp
js/src/jit/SharedIC.cpp
js/src/jsapi-tests/testAddPropertyPropcache.cpp
js/src/jsapi-tests/testChromeBuffer.cpp
js/src/jsapi-tests/testClassGetter.cpp
js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
js/src/jsapi-tests/testLookup.cpp
js/src/jsapi-tests/testNewObject.cpp
js/src/jsapi-tests/testPersistentRooted.cpp
js/src/jsapi-tests/testPropCache.cpp
js/src/jsapi-tests/testResolveRecursion.cpp
js/src/jsapi-tests/testSetProperty.cpp
js/src/jsapi-tests/testWeakMap.cpp
js/src/jsapi-tests/tests.h
js/src/jsapi.cpp
js/src/jsarray.cpp
js/src/jsdate.cpp
js/src/jsexn.cpp
js/src/jsfriendapi.h
js/src/jsfun.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsiter.cpp
js/src/jsiter.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsstr.cpp
js/src/perf/jsperf.cpp
js/src/proxy/BaseProxyHandler.cpp
js/src/proxy/Proxy.cpp
js/src/shell/OSObject.cpp
js/src/shell/js.cpp
js/src/vm/ArgumentsObject.cpp
js/src/vm/ArgumentsObject.h
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
js/src/vm/ArrayObject-inl.h
js/src/vm/Debugger-inl.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/GlobalObject.cpp
js/src/vm/HelperThreads.cpp
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/NativeObject-inl.h
js/src/vm/NativeObject.cpp
js/src/vm/NativeObject.h
js/src/vm/PIC.cpp
js/src/vm/PIC.h
js/src/vm/ProxyObject.h
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpStatics.cpp
js/src/vm/SavedStacks.cpp
js/src/vm/ScopeObject.cpp
js/src/vm/SelfHosting.cpp
js/src/vm/SharedArrayObject.cpp
js/src/vm/TypeInference.cpp
js/src/vm/TypedArrayObject.cpp
js/src/vm/UnboxedObject.cpp
js/xpconnect/src/Sandbox.cpp
js/xpconnect/src/XPCShellImpl.cpp
js/xpconnect/src/XPCWrappedJSClass.cpp
js/xpconnect/src/XPCWrappedNative.cpp
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/wrappers/XrayWrapper.cpp
netwerk/base/ProxyAutoConfig.cpp
toolkit/components/finalizationwitness/FinalizationWitnessService.cpp
xpcom/base/CycleCollectedJSRuntime.cpp
xpcom/glue/tests/gtest/TestGCPostBarriers.cpp
--- a/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
+++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
@@ -93,23 +93,26 @@ struct DevTools : public ::testing::Test
             message);
   }
 
   JSContext* createContext() {
     return JS_NewContext(rt, 8192);
   }
 
   static const JSClass* getGlobalClass() {
-    static const JSClass globalClass = {
-      "global", JSCLASS_GLOBAL_FLAGS,
+    static const JSClassOps globalClassOps = {
       nullptr, nullptr, nullptr, nullptr,
       nullptr, nullptr, nullptr, nullptr,
       nullptr, nullptr, nullptr,
       JS_GlobalObjectTraceHook
     };
+    static const JSClass globalClass = {
+      "global", JSCLASS_GLOBAL_FLAGS,
+      &globalClassOps
+    };
     return &globalClass;
   }
 
   JSObject* createGlobal()
   {
     /* Create the global object. */
     JS::RootedObject newGlobal(cx);
     JS::CompartmentOptions options;
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1801,16 +1801,31 @@ NativePropertyHooks sEmptyNativeProperty
     nullptr,
     nullptr
   },
   prototypes::id::_ID_Count,
   constructors::id::_ID_Count,
   nullptr
 };
 
+const js::ClassOps sBoringInterfaceObjectClassClassOps = {
+    nullptr,               /* addProperty */
+    nullptr,               /* delProperty */
+    nullptr,               /* getProperty */
+    nullptr,               /* setProperty */
+    nullptr,               /* enumerate */
+    nullptr,               /* resolve */
+    nullptr,               /* mayResolve */
+    nullptr,               /* finalize */
+    ThrowingConstructor,   /* call */
+    InterfaceHasInstance,  /* hasInstance */
+    ThrowingConstructor,   /* construct */
+    nullptr,               /* trace */
+};
+
 const js::ObjectOps sInterfaceObjectClassObjectOps = {
   nullptr, /* lookupProperty */
   nullptr, /* defineProperty */
   nullptr, /* hasProperty */
   nullptr, /* getProperty */
   nullptr, /* setProperty */
   nullptr, /* getOwnPropertyDescriptor */
   nullptr, /* deleteProperty */
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2503,16 +2503,18 @@ XrayGetNativeProto(JSContext* cx, JS::Ha
     }
   }
 
   return JS_WrapObject(cx, protop);
 }
 
 extern NativePropertyHooks sEmptyNativePropertyHooks;
 
+extern const js::ClassOps sBoringInterfaceObjectClassClassOps;
+
 extern const js::ObjectOps sInterfaceObjectClassObjectOps;
 
 // We use one constructor JSNative to represent all DOM interface objects (so
 // we can easily detect when we need to wrap them in an Xray wrapper). We store
 // the real JSNative in the mNative member of a JSNativeHolder in the
 // CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction object for a
 // specific interface object. We also store the NativeProperties in the
 // JSNativeHolder.
@@ -2535,17 +2537,17 @@ UseDOMXray(JSObject* obj)
          IsDOMIfaceAndProtoClass(clasp);
 }
 
 #ifdef DEBUG
 inline bool
 HasConstructor(JSObject* obj)
 {
   return JS_IsNativeFunction(obj, Constructor) ||
-         js::GetObjectClass(obj)->construct;
+         js::GetObjectClass(obj)->getConstruct();
 }
  #endif
 
 // Helpers for creating a const version of a type.
 template<typename T>
 const T& Constify(T& arg)
 {
   return arg;
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -435,36 +435,40 @@ class CGDOMJSClass(CGThing):
             enumerateHook = "mozilla::dom::EnumerateGlobal"
         else:
             resolveHook = "nullptr"
             mayResolveHook = "nullptr"
             enumerateHook = "nullptr"
 
         return fill(
             """
+            static const js::ClassOps sClassOps = {
+              ${addProperty}, /* addProperty */
+              nullptr,               /* delProperty */
+              nullptr,               /* getProperty */
+              nullptr,               /* setProperty */
+              ${enumerate}, /* enumerate */
+              ${resolve}, /* resolve */
+              ${mayResolve}, /* mayResolve */
+              ${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},
-                ${addProperty}, /* addProperty */
-                nullptr,               /* delProperty */
-                nullptr,               /* getProperty */
-                nullptr,               /* setProperty */
-                ${enumerate}, /* enumerate */
-                ${resolve}, /* resolve */
-                ${mayResolve}, /* mayResolve */
-                ${finalize}, /* finalize */
-                ${call}, /* call */
-                nullptr,               /* hasInstance */
-                nullptr,               /* construct */
-                ${trace}, /* trace */
+                &sClassOps,
                 JS_NULL_CLASS_SPEC,
                 &sClassExtension,
                 JS_NULL_OBJECT_OPS
               },
               $*{descriptor}
             };
             static_assert(${instanceReservedSlots} == DOM_INSTANCE_RESERVED_SLOTS,
                           "Must have the right minimal number of reserved slots.");
@@ -589,28 +593,17 @@ class CGPrototypeJSClass(CGThing):
         (protoGetter, _) = InterfacePrototypeObjectProtoGetter(self.descriptor)
         type = "eGlobalInterfacePrototype" if self.descriptor.isGlobal() else "eInterfacePrototype"
         return fill(
             """
             static const DOMIfaceAndProtoJSClass sPrototypeClass = {
               {
                 "${name}Prototype",
                 JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
-                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_OPS,
                 JS_NULL_CLASS_SPEC,
                 JS_NULL_CLASS_EXT,
                 JS_NULL_OBJECT_OPS
               },
               ${type},
               ${prototypeID},
               ${depth},
               ${hooks},
@@ -679,55 +672,71 @@ class CGInterfaceObjectJSClass(CGThing):
             hasinstance = "nullptr"
         prototypeID, depth = PrototypeIDAndDepth(self.descriptor)
         slotCount = "DOM_INTERFACE_SLOTS_BASE"
         if len(self.descriptor.interface.namedConstructors) > 0:
             slotCount += (" + %i /* slots for the named constructors */" %
                           len(self.descriptor.interface.namedConstructors))
         (protoGetter, _) = InterfaceObjectProtoGetter(self.descriptor)
 
-        return fill(
+        if ctorname == "ThrowingConstructor" and hasinstance == "InterfaceHasInstance":
+            ret = ""
+            classOpsPtr = "&sBoringInterfaceObjectClassClassOps"
+        else:
+            ret = fill(
+                """
+                static const js::ClassOps sInterfaceObjectClassOps = {
+                    nullptr,               /* addProperty */
+                    nullptr,               /* delProperty */
+                    nullptr,               /* getProperty */
+                    nullptr,               /* setProperty */
+                    nullptr,               /* enumerate */
+                    nullptr,               /* resolve */
+                    nullptr,               /* mayResolve */
+                    nullptr,               /* finalize */
+                    ${ctorname}, /* call */
+                    ${hasInstance}, /* hasInstance */
+                    ${ctorname}, /* construct */
+                    nullptr,               /* trace */
+                };
+
+                """,
+                ctorname=ctorname,
+                hasInstance=hasinstance)
+            classOpsPtr = "&sInterfaceObjectClassOps"
+
+        ret = ret + fill(
             """
             static const DOMIfaceAndProtoJSClass sInterfaceObjectClass = {
               {
                 "Function",
                 JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
-                nullptr,               /* addProperty */
-                nullptr,               /* delProperty */
-                nullptr,               /* getProperty */
-                nullptr,               /* setProperty */
-                nullptr,               /* enumerate */
-                nullptr,               /* resolve */
-                nullptr,               /* mayResolve */
-                nullptr,               /* finalize */
-                ${ctorname}, /* call */
-                ${hasInstance}, /* hasInstance */
-                ${ctorname}, /* construct */
-                nullptr,               /* trace */
+                ${classOpsPtr},
                 JS_NULL_CLASS_SPEC,
                 JS_NULL_CLASS_EXT,
                 &sInterfaceObjectClassObjectOps
               },
               eInterface,
               ${prototypeID},
               ${depth},
               ${hooks},
               "function ${name}() {\\n    [native code]\\n}",
               ${protoGetter}
             };
             """,
             slotCount=slotCount,
             ctorname=ctorname,
             hasInstance=hasinstance,
+            classOpsPtr=classOpsPtr,
             hooks=NativePropertyHooks(self.descriptor),
             name=self.descriptor.interface.identifier.name,
             prototypeID=prototypeID,
             depth=depth,
             protoGetter=protoGetter)
-
+        return ret
 
 class CGList(CGThing):
     """
     Generate code for a list of GCThings.  Just concatenates them together, with
     an optional joiner string.  "\n" is a common joiner.
     """
     def __init__(self, children, joiner=""):
         CGThing.__init__(self)
--- 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 = {
-  nullptr,
-  SimpleGlobal_moved
-};
-
-const js::Class SimpleGlobalClass = {
-    "",
-    JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS,
+static const js::ClassOps SimpleGlobalClassOps = {
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     SimpleGlobal_enumerate,
     SimpleGlobal_resolve,
     nullptr,
     SimpleGlobal_finalize,
     nullptr,
     nullptr,
     nullptr,
     JS_GlobalObjectTraceHook,
+};
+
+static const js::ClassExtension SimpleGlobalClassExtension = {
+  nullptr,
+  SimpleGlobal_moved
+};
+
+const js::Class SimpleGlobalClass = {
+    "",
+    JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS,
+    &SimpleGlobalClassOps,
     JS_NULL_CLASS_SPEC,
     &SimpleGlobalClassExtension,
     JS_NULL_OBJECT_OPS
 };
 
 // static
 JSObject*
 SimpleGlobalObject::Create(GlobalType globalType, JS::Handle<JS::Value> proto)
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -7787,17 +7787,17 @@ private:
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
 class NormalJSRuntime
 {
   friend class nsAutoPtr<NormalJSRuntime>;
 
-  static const JSClass kGlobalClass;
+  static const JSClass sGlobalClass;
   static const uint32_t kRuntimeHeapSize = 768 * 1024;
 
   JSRuntime* mRuntime;
   JSContext* mContext;
   JSObject* mGlobal;
 
 public:
   static NormalJSRuntime*
@@ -23756,33 +23756,37 @@ CreateIndexOp::DoDatabaseWork(DatabaseCo
   rv = autoSave.Commit();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-const JSClass NormalJSRuntime::kGlobalClass = {
-  "IndexedDBTransactionThreadGlobal",
-  JSCLASS_GLOBAL_FLAGS,
+static const JSClassOps sNormalJSRuntimeGlobalClassOps = {
   /* addProperty */ nullptr,
   /* delProperty */ nullptr,
   /* getProperty */ nullptr,
   /* setProperty */ nullptr,
   /* enumerate */ nullptr,
   /* resolve */ nullptr,
   /* mayResolve */ nullptr,
   /* finalize */ nullptr,
   /* call */ nullptr,
   /* hasInstance */ nullptr,
   /* construct */ nullptr,
   /* trace */ JS_GlobalObjectTraceHook
 };
 
+const JSClass NormalJSRuntime::sGlobalClass = {
+  "IndexedDBTransactionThreadGlobal",
+  JSCLASS_GLOBAL_FLAGS,
+  &sNormalJSRuntimeGlobalClassOps
+};
+
 bool
 NormalJSRuntime::Init()
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
 
   mRuntime = JS_NewRuntime(kRuntimeHeapSize);
   if (NS_WARN_IF(!mRuntime)) {
     return false;
@@ -23794,17 +23798,17 @@ NormalJSRuntime::Init()
   mContext = JS_NewContext(mRuntime, 0);
   if (NS_WARN_IF(!mContext)) {
     return false;
   }
 
   JSAutoRequest ar(mContext);
 
   JS::CompartmentOptions options;
-  mGlobal = JS_NewGlobalObject(mContext, &kGlobalClass, nullptr,
+  mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
                                JS::FireOnNewGlobalHook, options);
   if (NS_WARN_IF(!mGlobal)) {
     return false;
   }
 
   return true;
 }
 
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -213,18 +213,33 @@ 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::ClassOps sNPObjectJSWrapperClassOps = {
+    NPObjWrapper_AddProperty,
+    NPObjWrapper_DelProperty,
+    NPObjWrapper_GetProperty,
+    NPObjWrapper_SetProperty,
+    nullptr,
+    NPObjWrapper_Resolve,
+    nullptr,                    /* mayResolve */
+    NPObjWrapper_Finalize,
+    NPObjWrapper_Call,
+    nullptr,                    /* hasInstance */
+    NPObjWrapper_Construct,
+    nullptr,                    /* trace */
+};
+
 const static js::ClassExtension sNPObjectJSWrapperClassExtension = {
-    nullptr,                                              /* weakmapKeyDelegateOp */
+    nullptr,                    /* weakmapKeyDelegateOp */
     NPObjWrapper_ObjectMoved
 };
 
 const static js::ObjectOps sNPObjectJSWrapperObjectOps = {
     nullptr, // lookupProperty
     nullptr, // defineProperty
     nullptr, // hasProperty
     nullptr, // getProperty
@@ -232,36 +247,24 @@ const static js::ObjectOps sNPObjectJSWr
     nullptr, // getOwnPropertyDescriptor
     nullptr, // deleteProperty
     nullptr, nullptr, // watch/unwatch
     nullptr, // getElements
     NPObjWrapper_Enumerate,
     nullptr,
 };
 
-const static js::Class sNPObjectJSWrapperClass =
-  {
+const static js::Class sNPObjectJSWrapperClass = {
     NPRUNTIME_JSCLASS_NAME,
     JSCLASS_HAS_PRIVATE,
-    NPObjWrapper_AddProperty,
-    NPObjWrapper_DelProperty,
-    NPObjWrapper_GetProperty,
-    NPObjWrapper_SetProperty,
-    nullptr,
-    NPObjWrapper_Resolve,
-    nullptr,                                                /* mayResolve */
-    NPObjWrapper_Finalize,
-    NPObjWrapper_Call,
-    nullptr,                                                /* hasInstance */
-    NPObjWrapper_Construct,
-    nullptr,                                                /* trace */
+    &sNPObjectJSWrapperClassOps,
     JS_NULL_CLASS_SPEC,
     &sNPObjectJSWrapperClassExtension,
     &sNPObjectJSWrapperObjectOps
-  };
+};
 
 typedef struct NPObjectMemberPrivate {
     JS::Heap<JSObject *> npobjWrapper;
     JS::Heap<JS::Value> fieldValue;
     JS::Heap<jsid> methodName;
     NPP   npp;
 } NPObjectMemberPrivate;
 
@@ -276,24 +279,27 @@ static bool
 NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp);
 
 static void
 NPObjectMember_Trace(JSTracer *trc, JSObject *obj);
 
 static bool
 NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
 
-static const JSClass sNPObjectMemberClass =
-  {
-    "NPObject Ambiguous Member class", JSCLASS_HAS_PRIVATE,
-    nullptr, nullptr, NPObjectMember_GetProperty, nullptr,
-    nullptr, nullptr, nullptr,
-    NPObjectMember_Finalize, NPObjectMember_Call,
-    nullptr, nullptr, NPObjectMember_Trace
-  };
+static const JSClassOps sNPObjectMemberClassOps = {
+  nullptr, nullptr, NPObjectMember_GetProperty, nullptr,
+  nullptr, nullptr, nullptr,
+  NPObjectMember_Finalize, NPObjectMember_Call,
+  nullptr, nullptr, NPObjectMember_Trace
+};
+
+static const JSClass sNPObjectMemberClass = {
+  "NPObject Ambiguous Member class", JSCLASS_HAS_PRIVATE,
+  &sNPObjectMemberClassOps
+};
 
 static void
 OnWrapperDestroyed();
 
 static void
 TraceJSObjWrappers(JSTracer *trc, void *data)
 {
   if (sJSObjWrappers.initialized()) {
--- a/dom/xbl/nsXBLBinding.cpp
+++ b/dom/xbl/nsXBLBinding.cpp
@@ -81,25 +81,29 @@ XBLEnumerate(JSContext *cx, JS::Handle<J
 {
   nsXBLPrototypeBinding* protoBinding =
     static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate());
   MOZ_ASSERT(protoBinding);
 
   return protoBinding->ResolveAllFields(cx, obj);
 }
 
+static const JSClassOps gPrototypeJSClassOps = {
+    nullptr, nullptr, nullptr, nullptr,
+    XBLEnumerate, nullptr,
+    nullptr, XBLFinalize,
+    nullptr, nullptr, nullptr, nullptr
+};
+
 static const JSClass gPrototypeJSClass = {
     "XBL prototype JSClass",
     JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS |
     // Our one reserved slot holds the relevant nsXBLPrototypeBinding
     JSCLASS_HAS_RESERVED_SLOTS(1),
-    nullptr, nullptr, nullptr, nullptr,
-    XBLEnumerate, nullptr,
-    nullptr, XBLFinalize,
-    nullptr, nullptr, nullptr, nullptr
+    &gPrototypeJSClassOps
 };
 
 // Implementation /////////////////////////////////////////////////////////////////
 
 // Constructors/Destructors
 nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
   : mMarkedForDeath(false)
   , mUsingContentXBLScope(false)
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -472,33 +472,65 @@ class JS_FRIEND_API(ElementAdder)
 
 typedef bool
 (* GetElementsOp)(JSContext* cx, JS::HandleObject obj, uint32_t begin, uint32_t end,
                   ElementAdder* adder);
 
 typedef void
 (* FinalizeOp)(FreeOp* fop, JSObject* obj);
 
-#define JS_CLASS_MEMBERS(FinalizeOpType)                                      \
-    const char*         name;                                                \
-    uint32_t            flags;                                                \
-                                                                              \
-    /* Function pointer members (may be null). */                             \
-    JSAddPropertyOp     addProperty;                                          \
-    JSDeletePropertyOp  delProperty;                                          \
-    JSGetterOp          getProperty;                                          \
-    JSSetterOp          setProperty;                                          \
-    JSEnumerateOp       enumerate;                                            \
-    JSResolveOp         resolve;                                              \
-    JSMayResolveOp      mayResolve;                                           \
-    FinalizeOpType      finalize;                                             \
-    JSNative            call;                                                 \
-    JSHasInstanceOp     hasInstance;                                          \
-    JSNative            construct;                                            \
-    JSTraceOp           trace
+// The special treatment of |finalize| and |trace| is necessary because if we
+// assign either of those hooks to a local variable and then call it -- as is
+// done with the other hooks -- the GC hazard analysis gets confused.
+#define JS_CLASS_MEMBERS(ClassOpsType, FinalizeOpType, FreeOpType) \
+    const char* name; \
+    uint32_t flags; \
+    const ClassOpsType* cOps; \
+    \
+    JSAddPropertyOp    getAddProperty() const { return cOps ? cOps->addProperty : nullptr; } \
+    JSDeletePropertyOp getDelProperty() const { return cOps ? cOps->delProperty : nullptr; } \
+    JSGetterOp         getGetProperty() const { return cOps ? cOps->getProperty : nullptr; } \
+    JSSetterOp         getSetProperty() const { return cOps ? cOps->setProperty : nullptr; } \
+    JSEnumerateOp      getEnumerate()   const { return cOps ? cOps->enumerate   : nullptr; } \
+    JSResolveOp        getResolve()     const { return cOps ? cOps->resolve     : nullptr; } \
+    JSMayResolveOp     getMayResolve()  const { return cOps ? cOps->mayResolve  : nullptr; } \
+    JSNative           getCall()        const { return cOps ? cOps->call        : nullptr; } \
+    JSHasInstanceOp    getHasInstance() const { return cOps ? cOps->hasInstance : nullptr; } \
+    JSNative           getConstruct()   const { return cOps ? cOps->construct   : nullptr; } \
+    \
+    bool hasFinalize() const { return cOps && cOps->finalize; } \
+    bool hasTrace()    const { return cOps && cOps->trace;    } \
+    \
+    bool isTrace(JSTraceOp trace) const { return cOps && cOps->trace == trace; } \
+    \
+    void doFinalize(FreeOpType* fop, JSObject* obj) const { \
+        MOZ_ASSERT(cOps && cOps->finalize); \
+        cOps->finalize(fop, obj); \
+    } \
+    void doTrace(JSTracer* trc, JSObject* obj) const { \
+        MOZ_ASSERT(cOps && cOps->trace); \
+        cOps->trace(trc, obj); \
+    }
+
+struct ClassOps
+{
+    /* Function pointer members (may be null). */
+    JSAddPropertyOp     addProperty;
+    JSDeletePropertyOp  delProperty;
+    JSGetterOp          getProperty;
+    JSSetterOp          setProperty;
+    JSEnumerateOp       enumerate;
+    JSResolveOp         resolve;
+    JSMayResolveOp      mayResolve;
+    FinalizeOp          finalize;
+    JSNative            call;
+    JSHasInstanceOp     hasInstance;
+    JSNative            construct;
+    JSTraceOp           trace;
+};
 
 /** Callback for the creation of constructor and prototype objects. */
 typedef JSObject* (*ClassObjectCreationOp)(JSContext* cx, JSProtoKey key);
 
 /** Callback for custom post-processing after class initialization via ClassSpec. */
 typedef bool (*FinishClassInitOp)(JSContext* cx, JS::HandleObject ctor,
                                   JS::HandleObject proto);
 
@@ -640,18 +672,37 @@ struct ObjectOps
 #define JS_NULL_OBJECT_OPS nullptr
 
 } // namespace js
 
 // Classes, objects, and properties.
 
 typedef void (*JSClassInternal)();
 
+struct JSClassOps
+{
+    /* Function pointer members (may be null). */
+    JSAddPropertyOp     addProperty;
+    JSDeletePropertyOp  delProperty;
+    JSGetterOp          getProperty;
+    JSSetterOp          setProperty;
+    JSEnumerateOp       enumerate;
+    JSResolveOp         resolve;
+    JSMayResolveOp      mayResolve;
+    JSFinalizeOp        finalize;
+    JSNative            call;
+    JSHasInstanceOp     hasInstance;
+    JSNative            construct;
+    JSTraceOp           trace;
+};
+
+#define JS_NULL_CLASS_OPS nullptr
+
 struct JSClass {
-    JS_CLASS_MEMBERS(JSFinalizeOp);
+    JS_CLASS_MEMBERS(JSClassOps, JSFinalizeOp, JSFreeOp);
 
     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
@@ -748,17 +799,17 @@ struct JSClass {
 // Initializer for unused members of statically initialized JSClass structs.
 #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);
+    JS_CLASS_MEMBERS(js::ClassOps, FinalizeOp, FreeOp);
     const ClassSpec* spec;
     const ClassExtension* ext;
     const ObjectOps* oOps;
 
     /*
      * 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
@@ -780,17 +831,17 @@ struct Class
     }
 
     bool isJSFunction() const {
         return this == js::FunctionClassPtr;
     }
 
     bool nonProxyCallable() const {
         MOZ_ASSERT(!isProxy());
-        return isJSFunction() || call;
+        return isJSFunction() || getCall();
     }
 
     bool isProxy() const {
         return flags & JSCLASS_IS_PROXY;
     }
 
     bool isDOMClass() const {
         return flags & JSCLASS_IS_DOMJSCLASS;
@@ -842,43 +893,48 @@ struct Class
     DeletePropertyOp getOpsDeleteProperty() const { return oOps ? oOps->deleteProperty : nullptr; }
     WatchOp          getOpsWatch()          const { return oOps ? oOps->watch          : nullptr; }
     UnwatchOp        getOpsUnwatch()        const { return oOps ? oOps->unwatch        : nullptr; }
     GetElementsOp    getOpsGetElements()    const { return oOps ? oOps->getElements    : nullptr; }
     JSNewEnumerateOp getOpsEnumerate()      const { return oOps ? oOps->enumerate      : nullptr; }
     JSFunToStringOp  getOpsFunToString()    const { return oOps ? oOps->funToString    : nullptr; }
 };
 
+static_assert(offsetof(JSClassOps, addProperty) == offsetof(ClassOps, addProperty),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, delProperty) == offsetof(ClassOps, delProperty),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, getProperty) == offsetof(ClassOps, getProperty),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, setProperty) == offsetof(ClassOps, setProperty),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, enumerate) == offsetof(ClassOps, enumerate),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, resolve) == offsetof(ClassOps, resolve),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, mayResolve) == offsetof(ClassOps, mayResolve),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, finalize) == offsetof(ClassOps, finalize),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, call) == offsetof(ClassOps, call),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, construct) == offsetof(ClassOps, construct),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, hasInstance) == offsetof(ClassOps, hasInstance),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(offsetof(JSClassOps, trace) == offsetof(ClassOps, trace),
+              "ClassOps and JSClassOps must be consistent");
+static_assert(sizeof(JSClassOps) == sizeof(ClassOps),
+              "ClassOps and JSClassOps must be consistent");
+
 static_assert(offsetof(JSClass, name) == offsetof(Class, name),
               "Class and JSClass must be consistent");
 static_assert(offsetof(JSClass, flags) == offsetof(Class, flags),
               "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, addProperty) == offsetof(Class, addProperty),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, delProperty) == offsetof(Class, delProperty),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, getProperty) == offsetof(Class, getProperty),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, setProperty) == offsetof(Class, setProperty),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, enumerate) == offsetof(Class, enumerate),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, resolve) == offsetof(Class, resolve),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, mayResolve) == offsetof(Class, mayResolve),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, finalize) == offsetof(Class, finalize),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, call) == offsetof(Class, call),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, construct) == offsetof(Class, construct),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, hasInstance) == offsetof(Class, hasInstance),
-              "Class and JSClass must be consistent");
-static_assert(offsetof(JSClass, trace) == offsetof(Class, trace),
+static_assert(offsetof(JSClass, cOps) == offsetof(Class, cOps),
               "Class and JSClass must be consistent");
 static_assert(sizeof(JSClass) == sizeof(Class),
               "Class and JSClass must be consistent");
 
 static MOZ_ALWAYS_INLINE const JSClass*
 Jsvalify(const Class* c)
 {
     return (const JSClass*)c;
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -1558,34 +1558,38 @@ Module::createText(JSContext* cx)
 const char*
 Module::profilingLabel(uint32_t funcIndex) const
 {
     MOZ_ASSERT(dynamicallyLinked_);
     MOZ_ASSERT(profilingEnabled_);
     return funcLabels_[funcIndex].get();
 }
 
-const Class WasmModuleObject::class_ = {
-    "WasmModuleObject",
-    JSCLASS_IS_ANONYMOUS | JSCLASS_DELAY_METADATA_BUILDER |
-    JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS),
+const ClassOps WasmModuleObject::classOps_ = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     WasmModuleObject::finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     WasmModuleObject::trace
 };
 
+const Class WasmModuleObject::class_ = {
+    "WasmModuleObject",
+    JSCLASS_IS_ANONYMOUS | JSCLASS_DELAY_METADATA_BUILDER |
+    JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS),
+    &WasmModuleObject::classOps_,
+};
+
 bool
 WasmModuleObject::hasModule() const
 {
     MOZ_ASSERT(is<WasmModuleObject>());
     return !getReservedSlot(MODULE_SLOT).isUndefined();
 }
 
 /* static */ void
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -652,16 +652,18 @@ ExportedFunctionToIndex(JSFunction* fun)
 // An WasmModuleObject is an internal object (i.e., not exposed directly to user
 // code) which traces and owns a wasm::Module. The WasmModuleObject is
 // referenced by the extended slots of exported JSFunctions and serves to keep
 // the wasm::Module alive until its last GC reference is dead.
 
 class WasmModuleObject : public NativeObject
 {
     static const unsigned MODULE_SLOT = 0;
+    static const ClassOps classOps_;
+
     bool hasModule() const;
     static void finalize(FreeOp* fop, JSObject* obj);
     static void trace(JSTracer* trc, JSObject* obj);
   public:
     static const unsigned RESERVED_SLOTS = 1;
     static WasmModuleObject* create(ExclusiveContext* cx);
     bool init(wasm::Module* module);
     wasm::Module& module() const;
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -623,29 +623,33 @@ static const size_t INITIAL_CHAR_BUFFER_
 
 /******************** Collator ********************/
 
 static void collator_finalize(FreeOp* fop, JSObject* obj);
 
 static const uint32_t UCOLLATOR_SLOT = 0;
 static const uint32_t COLLATOR_SLOTS_COUNT = 1;
 
-static const Class CollatorClass = {
-    js_Object_str,
-    JSCLASS_HAS_RESERVED_SLOTS(COLLATOR_SLOTS_COUNT),
+static const ClassOps CollatorClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     collator_finalize
 };
 
+static const Class CollatorClass = {
+    js_Object_str,
+    JSCLASS_HAS_RESERVED_SLOTS(COLLATOR_SLOTS_COUNT),
+    &CollatorClassOps
+};
+
 #if JS_HAS_TOSOURCE
 static bool
 collator_toSource(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setString(cx->names().Collator);
     return true;
 }
@@ -1117,29 +1121,33 @@ js::intl_CompareStrings(JSContext* cx, u
 
 /******************** NumberFormat ********************/
 
 static void numberFormat_finalize(FreeOp* fop, JSObject* obj);
 
 static const uint32_t UNUMBER_FORMAT_SLOT = 0;
 static const uint32_t NUMBER_FORMAT_SLOTS_COUNT = 1;
 
-static const Class NumberFormatClass = {
-    js_Object_str,
-    JSCLASS_HAS_RESERVED_SLOTS(NUMBER_FORMAT_SLOTS_COUNT),
+static const ClassOps NumberFormatClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     numberFormat_finalize
 };
 
+static const Class NumberFormatClass = {
+    js_Object_str,
+    JSCLASS_HAS_RESERVED_SLOTS(NUMBER_FORMAT_SLOTS_COUNT),
+    &NumberFormatClassOps
+};
+
 #if JS_HAS_TOSOURCE
 static bool
 numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setString(cx->names().NumberFormat);
     return true;
 }
@@ -1586,29 +1594,33 @@ js::intl_FormatNumber(JSContext* cx, uns
 
 /******************** DateTimeFormat ********************/
 
 static void dateTimeFormat_finalize(FreeOp* fop, JSObject* obj);
 
 static const uint32_t UDATE_FORMAT_SLOT = 0;
 static const uint32_t DATE_TIME_FORMAT_SLOTS_COUNT = 1;
 
-static const Class DateTimeFormatClass = {
-    js_Object_str,
-    JSCLASS_HAS_RESERVED_SLOTS(DATE_TIME_FORMAT_SLOTS_COUNT),
+static const ClassOps DateTimeFormatClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     dateTimeFormat_finalize
 };
 
+static const Class DateTimeFormatClass = {
+    js_Object_str,
+    JSCLASS_HAS_RESERVED_SLOTS(DATE_TIME_FORMAT_SLOTS_COUNT),
+    &DateTimeFormatClassOps
+};
+
 #if JS_HAS_TOSOURCE
 static bool
 dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setString(cx->names().DateTimeFormat);
     return true;
 }
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -99,29 +99,33 @@ HashableValue::mark(JSTracer* trc) const
 
 
 /*** MapIterator *********************************************************************************/
 
 namespace {
 
 } /* anonymous namespace */
 
-const Class MapIteratorObject::class_ = {
-    "Map Iterator",
-    JSCLASS_HAS_RESERVED_SLOTS(MapIteratorObject::SlotCount),
+static const ClassOps MapIteratorObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     MapIteratorObject::finalize
 };
 
+const Class MapIteratorObject::class_ = {
+    "Map Iterator",
+    JSCLASS_HAS_RESERVED_SLOTS(MapIteratorObject::SlotCount),
+    &MapIteratorObjectClassOps
+};
+
 const JSFunctionSpec MapIteratorObject::methods[] = {
     JS_SELF_HOSTED_FN("next", "MapIteratorNext", 0, 0),
     JS_FS_END
 };
 
 static inline ValueMap::Range*
 MapIteratorObjectRange(NativeObject* obj)
 {
@@ -254,34 +258,38 @@ MapIteratorObject::createResultPair(JSCo
     AddTypePropertyId(cx, resultPairObj, JSID_VOID, TypeSet::UnknownType());
 
     return resultPairObj;
 }
 
 
 /*** Map *****************************************************************************************/
 
-const Class MapObject::class_ = {
-    "Map",
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Map),
+const ClassOps MapObject::classOps_ = {
     nullptr, // addProperty
     nullptr, // delProperty
     nullptr, // getProperty
     nullptr, // setProperty
     nullptr, // enumerate
     nullptr, // resolve
     nullptr, // mayResolve
     finalize,
     nullptr, // call
     nullptr, // hasInstance
     nullptr, // construct
     mark
 };
 
+const Class MapObject::class_ = {
+    "Map",
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Map),
+    &MapObject::classOps_
+};
+
 const JSPropertySpec MapObject::properties[] = {
     JS_PSG("size", size, 0),
     JS_PS_END
 };
 
 const JSFunctionSpec MapObject::methods[] = {
     JS_FN("get", get, 1, 0),
     JS_FN("has", has, 1, 0),
@@ -865,29 +873,33 @@ class SetIteratorObject : public NativeO
     static inline bool is(HandleValue v);
     inline ValueSet::Range* range();
     inline SetObject::IteratorKind kind() const;
     static bool next_impl(JSContext* cx, const CallArgs& args);
 };
 
 } /* anonymous namespace */
 
-const Class SetIteratorObject::class_ = {
-    "Set Iterator",
-    JSCLASS_HAS_RESERVED_SLOTS(SetIteratorObject::SlotCount),
+static const ClassOps SetIteratorObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     SetIteratorObject::finalize
 };
 
+const Class SetIteratorObject::class_ = {
+    "Set Iterator",
+    JSCLASS_HAS_RESERVED_SLOTS(SetIteratorObject::SlotCount),
+    &SetIteratorObjectClassOps
+};
+
 const JSFunctionSpec SetIteratorObject::methods[] = {
     JS_FN("next", next, 0, 0),
     JS_FS_END
 };
 
 inline ValueSet::Range*
 SetIteratorObject::range()
 {
@@ -1001,34 +1013,38 @@ SetIteratorObject::next(JSContext* cx, u
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod(cx, is, next_impl, args);
 }
 
 
 /*** Set *****************************************************************************************/
 
-const Class SetObject::class_ = {
-    "Set",
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Set),
+const ClassOps SetObject::classOps_ = {
     nullptr, // addProperty
     nullptr, // delProperty
     nullptr, // getProperty
     nullptr, // setProperty
     nullptr, // enumerate
     nullptr, // resolve
     nullptr, // mayResolve
     finalize,
     nullptr, // call
     nullptr, // hasInstance
     nullptr, // construct
     mark
 };
 
+const Class SetObject::class_ = {
+    "Set",
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Set),
+    &SetObject::classOps_
+};
+
 const JSPropertySpec SetObject::properties[] = {
     JS_PSG("size", size, 0),
     JS_PS_END
 };
 
 const JSFunctionSpec SetObject::methods[] = {
     JS_FN("has", has, 1, 0),
     JS_FN("add", add, 1, 0),
--- a/js/src/builtin/MapObject.h
+++ b/js/src/builtin/MapObject.h
@@ -104,16 +104,18 @@ class MapObject : public NativeObject {
     // Set call for public JSAPI exposure. Does not actually return map object
     // as stated in spec, expects caller to return a value. for instance, with
     // webidl maplike/setlike, should return interface object.
     static bool set(JSContext *cx, HandleObject obj, HandleValue key, HandleValue val);
     static bool clear(JSContext *cx, HandleObject obj);
     static bool iterator(JSContext *cx, IteratorKind kind, HandleObject obj, MutableHandleValue iter);
 
   private:
+    static const ClassOps classOps_;
+
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static const JSPropertySpec staticProperties[];
     ValueMap* getData() { return static_cast<ValueMap*>(getPrivate()); }
     static ValueMap & extract(HandleObject o);
     static ValueMap & extract(CallReceiver call);
     static void mark(JSTracer* trc, JSObject* obj);
     static void finalize(FreeOp* fop, JSObject* obj);
@@ -186,19 +188,22 @@ class SetObject : public NativeObject {
     static SetObject* create(JSContext *cx, HandleObject proto = nullptr);
     static uint32_t size(JSContext *cx, HandleObject obj);
     static bool has(JSContext *cx, HandleObject obj, HandleValue key, bool* rval);
     static bool clear(JSContext *cx, HandleObject obj);
     static bool iterator(JSContext *cx, IteratorKind kind, HandleObject obj, MutableHandleValue iter);
     static bool delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval);
 
   private:
+    static const ClassOps classOps_;
+
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static const JSPropertySpec staticProperties[];
+
     ValueSet* getData() { return static_cast<ValueSet*>(getPrivate()); }
     static ValueSet & extract(HandleObject o);
     static ValueSet & extract(CallReceiver call);
     static void mark(JSTracer* trc, JSObject* obj);
     static void finalize(FreeOp* fop, JSObject* obj);
     static bool construct(JSContext* cx, unsigned argc, Value* vp);
 
     static bool is(HandleValue v);
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -524,35 +524,40 @@ void FunctionDeclaration::trace(JSTracer
 {
     TraceEdge(trc, &name, "FunctionDeclaration name");
     TraceEdge(trc, &fun, "FunctionDeclaration fun");
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // ModuleObject
 
-/* static */ const Class
-ModuleObject::class_ = {
-    "Module",
-    JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) |
-    JSCLASS_IS_ANONYMOUS,
+/* static */ const ClassOps
+ModuleObject::classOps_ = {
     nullptr,        /* addProperty */
     nullptr,        /* delProperty */
     nullptr,        /* getProperty */
     nullptr,        /* setProperty */
     nullptr,        /* enumerate   */
     nullptr,        /* resolve     */
     nullptr,        /* mayResolve  */
     ModuleObject::finalize,
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
     ModuleObject::trace
 };
 
+/* static */ const Class
+ModuleObject::class_ = {
+    "Module",
+    JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) |
+    JSCLASS_IS_ANONYMOUS,
+    &ModuleObject::classOps_
+};
+
 #define DEFINE_ARRAY_SLOT_ACCESSOR(cls, name, slot)                           \
     ArrayObject&                                                              \
     cls::name() const                                                         \
     {                                                                         \
         return getFixedSlot(cls::slot).toObject().as<ArrayObject>();          \
     }
 
 DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, requestedModules, RequestedModulesSlot)
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -259,16 +259,18 @@ class ModuleObject : public NativeObject
 
     void setEvaluated();
     static bool evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval);
 
     static ModuleNamespaceObject* createNamespace(JSContext* cx, HandleModuleObject self,
                                                   HandleObject exports);
 
   private:
+    static const ClassOps classOps_;
+
     static void trace(JSTracer* trc, JSObject* obj);
     static void finalize(js::FreeOp* fop, JSObject* obj);
 
     bool hasScript() const;
     bool hasImportBindings() const;
     FunctionDeclarationVector* functionDeclarations();
 };
 
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -1219,24 +1219,13 @@ static const ClassSpec PlainObjectClassS
     object_methods,
     object_properties,
     FinishObjectClassInit
 };
 
 const Class PlainObject::class_ = {
     js_Object_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
-    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_OPS,
     &PlainObjectClassSpec
 };
 
 const Class* const js::ObjectClassPtr = &PlainObject::class_;
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -404,28 +404,17 @@ static const ClassSpec PromiseObjectClas
     promise_static_properties,
     promise_methods
 };
 
 const Class PromiseObject::class_ = {
     "Promise",
     JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Promise) |
     JSCLASS_HAS_XRAYED_CONSTRUCTOR,
-    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_OPS,
     &PromiseObjectClassSpec
 };
 
 static const ClassSpec PromiseObjectProtoClassSpec = {
     DELEGATED_CLASSSPEC(PromiseObject::class_.spec),
     nullptr,
     nullptr,
     nullptr,
@@ -433,22 +422,11 @@ static const ClassSpec PromiseObjectProt
     nullptr,
     nullptr,
     ClassSpec::IsDelegated
 };
 
 const Class PromiseObject::protoClass_ = {
     "PromiseProto",
     JSCLASS_HAS_CACHED_PROTO(JSProto_Promise),
-    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_OPS,
     &PromiseObjectProtoClassSpec
 };
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -184,28 +184,32 @@ template bool js::ToSimdConstant<Bool32x
 template<typename Elem>
 static Elem
 TypedObjectMemory(HandleValue v)
 {
     TypedObject& obj = v.toObject().as<TypedObject>();
     return reinterpret_cast<Elem>(obj.typedMem());
 }
 
-const Class SimdTypeDescr::class_ = {
-    "SIMD",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+static const ClassOps SimdTypeDescrClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     TypeDescr::finalize,
-    call
+    SimdTypeDescr::call
+};
+
+const Class SimdTypeDescr::class_ = {
+    "SIMD",
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+    &SimdTypeDescrClassOps
 };
 
 namespace {
 
 // Define classes (Int8x16Defn, Int16x8Defn, etc.) to group together various
 // properties and so on.
 #define DEFINE_DEFN_(TypeName)                                       \
 class TypeName##Defn {                                               \
@@ -419,25 +423,29 @@ SimdTypeDescr::call(JSContext* cx, unsig
 #undef CASE_CALL_
     MOZ_CRASH("unexpected SIMD descriptor");
     return false;
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // SIMD class
 
-const Class SimdObject::class_ = {
-    "SIMD",
-    JSCLASS_HAS_RESERVED_SLOTS(uint32_t(SimdType::Count)),
+static const ClassOps SimdObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
-    resolve  /* resolve */
+    SimdObject::resolve
+};
+
+const Class SimdObject::class_ = {
+    "SIMD",
+    JSCLASS_HAS_RESERVED_SLOTS(uint32_t(SimdType::Count)),
+    &SimdObjectClassOps
 };
 
 bool
 GlobalObject::initSimdObject(JSContext* cx, Handle<GlobalObject*> global)
 {
     // SIMD relies on the TypedObject module being initialized.
     // In particular, the self-hosted code for array() wants
     // to be able to call GetTypedObjectModule(). It is NOT necessary
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1405,28 +1405,32 @@ SettleFakePromise(JSContext* cx, unsigne
 static unsigned finalizeCount = 0;
 
 static void
 finalize_counter_finalize(JSFreeOp* fop, JSObject* obj)
 {
     ++finalizeCount;
 }
 
-static const JSClass FinalizeCounterClass = {
-    "FinalizeCounter", JSCLASS_IS_ANONYMOUS,
+static const JSClassOps FinalizeCounterClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     finalize_counter_finalize
 };
 
+static const JSClass FinalizeCounterClass = {
+    "FinalizeCounter", JSCLASS_IS_ANONYMOUS,
+    &FinalizeCounterClassOps
+};
+
 static bool
 MakeFinalizeObserver(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSObject* obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, nullptr);
     if (!obj)
         return false;
@@ -2108,26 +2112,30 @@ class CloneBufferObject : public NativeO
         return CallNonGenericMethod<is, getCloneBuffer_impl>(cx, args);
     }
 
     static void Finalize(FreeOp* fop, JSObject* obj) {
         obj->as<CloneBufferObject>().discard();
     }
 };
 
-const Class CloneBufferObject::class_ = {
-    "CloneBuffer", JSCLASS_HAS_RESERVED_SLOTS(CloneBufferObject::NUM_SLOTS),
+static const ClassOps CloneBufferObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
-    Finalize
+    CloneBufferObject::Finalize
+};
+
+const Class CloneBufferObject::class_ = {
+    "CloneBuffer", JSCLASS_HAS_RESERVED_SLOTS(CloneBufferObject::NUM_SLOTS),
+    &CloneBufferObjectClassOps
 };
 
 const JSPropertySpec CloneBufferObject::props_[] = {
     JS_PSGS("clonebuffer", getCloneBuffer, setCloneBuffer, 0),
     JS_PS_END
 };
 
 static bool
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -210,30 +210,34 @@ const Class js::TypedProto::class_ = {
  * Scalar type objects
  *
  * Scalar type objects like `uint8`, `uint16`, are all instances of
  * the ScalarTypeDescr class. Like all type objects, they have a reserved
  * slot pointing to a TypeRepresentation object, which is used to
  * distinguish which scalar type object this actually is.
  */
 
-const Class js::ScalarTypeDescr::class_ = {
-    "Scalar",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+static const ClassOps ScalarTypeDescrClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     TypeDescr::finalize,
     ScalarTypeDescr::call
 };
 
+const Class js::ScalarTypeDescr::class_ = {
+    "Scalar",
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+    &ScalarTypeDescrClassOps
+};
+
 const JSFunctionSpec js::ScalarTypeDescr::typeObjectMethods[] = {
     JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
     JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, JSFUN_HAS_REST),
     JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0),
     JS_FS_END
 };
 
 int32_t
@@ -307,30 +311,34 @@ ScalarTypeDescr::call(JSContext* cx, uns
  *
  * Reference type objects like `Any` or `Object` basically work the
  * same way that the scalar type objects do. There is one class with
  * many instances, and each instance has a reserved slot with a
  * TypeRepresentation object, which is used to distinguish which
  * reference type object this actually is.
  */
 
-const Class js::ReferenceTypeDescr::class_ = {
-    "Reference",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+static const ClassOps ReferenceTypeDescrClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     TypeDescr::finalize,
     ReferenceTypeDescr::call
 };
 
+const Class js::ReferenceTypeDescr::class_ = {
+    "Reference",
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+    &ReferenceTypeDescrClassOps
+};
+
 const JSFunctionSpec js::ReferenceTypeDescr::typeObjectMethods[] = {
     JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
     {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
     {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
     JS_FS_END
 };
 
 static const int32_t ReferenceSizes[] = {
@@ -492,32 +500,36 @@ CreatePrototypeObjectForComplexTypeInsta
 {
     RootedObject ctorPrototypePrototype(cx, GetPrototype(cx, ctorPrototype));
     if (!ctorPrototypePrototype)
         return nullptr;
 
     return NewObjectWithGivenProto<TypedProto>(cx, ctorPrototypePrototype, SingletonObject);
 }
 
-const Class ArrayTypeDescr::class_ = {
-    "ArrayType",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+static const ClassOps ArrayTypeDescrClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     TypeDescr::finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     TypedObject::construct
 };
 
+const Class ArrayTypeDescr::class_ = {
+    "ArrayType",
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+    &ArrayTypeDescrClassOps
+};
+
 const JSPropertySpec ArrayMetaTypeDescr::typeObjectProperties[] = {
     JS_PS_END
 };
 
 const JSFunctionSpec ArrayMetaTypeDescr::typeObjectMethods[] = {
     {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
     JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
     {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
@@ -717,32 +729,36 @@ js::IsTypedObjectArray(JSObject& obj)
     TypeDescr& d = obj.as<TypedObject>().typeDescr();
     return d.is<ArrayTypeDescr>();
 }
 
 /*********************************
  * StructType class
  */
 
-const Class StructTypeDescr::class_ = {
-    "StructType",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+static const ClassOps StructTypeDescrClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     TypeDescr::finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     TypedObject::construct
 };
 
+const Class StructTypeDescr::class_ = {
+    "StructType",
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+    &StructTypeDescrClassOps
+};
+
 const JSPropertySpec StructMetaTypeDescr::typeObjectProperties[] = {
     JS_PS_END
 };
 
 const JSFunctionSpec StructMetaTypeDescr::typeObjectMethods[] = {
     {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
     JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
     {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
@@ -2256,31 +2272,34 @@ const ObjectOps TypedObject::objectOps_ 
     TypedObject::obj_deleteProperty,
     nullptr, nullptr, /* watch/unwatch */
     nullptr,   /* getElements */
     TypedObject::obj_enumerate,
     nullptr, /* thisValue */
 };
 
 #define DEFINE_TYPEDOBJ_CLASS(Name, Trace, flag)         \
-    const Class Name::class_ = {                         \
-        # Name,                                          \
-        Class::NON_NATIVE | flag,                        \
+    static const ClassOps Name##ClassOps = {             \
         nullptr,        /* addProperty */                \
         nullptr,        /* delProperty */                \
         nullptr,        /* getProperty */                \
         nullptr,        /* setProperty */                \
         nullptr,        /* enumerate   */                \
         nullptr,        /* resolve     */                \
         nullptr,        /* mayResolve  */                \
         nullptr,        /* finalize    */                \
         nullptr,        /* call        */                \
         nullptr,        /* hasInstance */                \
         nullptr,        /* construct   */                \
         Trace,                                           \
+    };                                                   \
+    const Class Name::class_ = {                         \
+        # Name,                                          \
+        Class::NON_NATIVE | flag,                        \
+        &Name##ClassOps,                                 \
         JS_NULL_CLASS_SPEC,                              \
         JS_NULL_CLASS_EXT,                               \
         &TypedObject::objectOps_                         \
     }
 
 DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace, 0);
 DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject,      OutlineTypedObject::obj_trace, 0);
 DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject,  InlineTypedObject::obj_trace,
--- a/js/src/builtin/WeakMapObject.cpp
+++ b/js/src/builtin/WeakMapObject.cpp
@@ -378,34 +378,38 @@ WeakMap_construct(JSContext* cx, unsigne
             }
         }
     }
 
     args.rval().setObject(*obj);
     return true;
 }
 
-const Class WeakMapObject::class_ = {
-    "WeakMap",
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap),
+static const ClassOps WeakMapObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     WeakMap_finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     WeakMap_mark
 };
 
+const Class WeakMapObject::class_ = {
+    "WeakMap",
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap),
+    &WeakMapObjectClassOps
+};
+
 static const JSFunctionSpec weak_map_methods[] = {
     JS_FN("has",    WeakMap_has, 1, 0),
     JS_FN("get",    WeakMap_get, 1, 0),
     JS_FN("delete", WeakMap_delete, 1, 0),
     JS_FN("set",    WeakMap_set, 2, 0),
     JS_FS_END
 };
 
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -541,75 +541,90 @@ static const JSClass sCTypesGlobalClass 
 static const JSClass sCABIClass = {
   "CABI",
   JSCLASS_HAS_RESERVED_SLOTS(CABI_SLOTS)
 };
 
 // Class representing ctypes.{C,Pointer,Array,Struct,Function}Type.prototype.
 // This exists to give said prototypes a class of "CType", and to provide
 // reserved slots for stashing various other prototype objects.
+static const JSClassOps sCTypeProtoClassOps = {
+  nullptr, nullptr, nullptr, nullptr,
+  nullptr, nullptr, nullptr, nullptr,
+  ConstructAbstract, nullptr, ConstructAbstract
+};
 static const JSClass sCTypeProtoClass = {
   "CType",
   JSCLASS_HAS_RESERVED_SLOTS(CTYPEPROTO_SLOTS),
-  nullptr, nullptr, nullptr, nullptr,
-  nullptr, nullptr, nullptr, nullptr,
-  ConstructAbstract, nullptr, ConstructAbstract
+  &sCTypeProtoClassOps
 };
 
 // Class representing ctypes.CData.prototype and the 'prototype' properties
 // of CTypes. This exists to give said prototypes a class of "CData".
 static const JSClass sCDataProtoClass = {
   "CData",
   0
 };
 
-static const JSClass sCTypeClass = {
-  "CType",
-  JSCLASS_HAS_RESERVED_SLOTS(CTYPE_SLOTS),
+static const JSClassOps sCTypeClassOps = {
   nullptr, nullptr, nullptr, nullptr,
   nullptr, nullptr, nullptr, CType::Finalize,
   CType::ConstructData, CType::HasInstance, CType::ConstructData,
   CType::Trace
 };
-
-static const JSClass sCDataClass = {
-  "CData",
-  JSCLASS_HAS_RESERVED_SLOTS(CDATA_SLOTS),
+static const JSClass sCTypeClass = {
+  "CType",
+  JSCLASS_HAS_RESERVED_SLOTS(CTYPE_SLOTS),
+  &sCTypeClassOps
+};
+
+static const JSClassOps sCDataClassOps = {
   nullptr, nullptr, ArrayType::Getter, ArrayType::Setter,
   nullptr, nullptr, nullptr, CData::Finalize,
   FunctionType::Call, nullptr, FunctionType::Call
 };
-
+static const JSClass sCDataClass = {
+  "CData",
+  JSCLASS_HAS_RESERVED_SLOTS(CDATA_SLOTS),
+  &sCDataClassOps
+};
+
+static const JSClassOps sCClosureClassOps = {
+  nullptr, nullptr, nullptr, nullptr,
+  nullptr, nullptr, nullptr, CClosure::Finalize,
+  nullptr, nullptr, nullptr, CClosure::Trace
+};
 static const JSClass sCClosureClass = {
   "CClosure",
   JSCLASS_HAS_RESERVED_SLOTS(CCLOSURE_SLOTS),
-  nullptr, nullptr, nullptr, nullptr,
-  nullptr, nullptr, nullptr, CClosure::Finalize,
-  nullptr, nullptr, nullptr, CClosure::Trace
+  &sCClosureClassOps
 };
 
 /*
  * Class representing the prototype of CDataFinalizer.
  */
 static const JSClass sCDataFinalizerProtoClass = {
   "CDataFinalizer",
   0
 };
 
 /*
  * Class representing instances of CDataFinalizer.
  *
  * Instances of CDataFinalizer have both private data (with type
  * |CDataFinalizer::Private|) and slots (see |CDataFinalizerSlots|).
  */
+static const JSClassOps sCDataFinalizerClassOps = {
+  nullptr, nullptr, nullptr, nullptr,
+  nullptr, nullptr, nullptr, CDataFinalizer::Finalize
+};
 static const JSClass sCDataFinalizerClass = {
   "CDataFinalizer",
   JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(CDATAFINALIZER_SLOTS),
-  nullptr, nullptr, nullptr, nullptr,
-  nullptr, nullptr, nullptr, CDataFinalizer::Finalize
+  &sCDataFinalizerClassOps
 };
 
 
 #define CTYPESFN_FLAGS \
   (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)
 
 #define CTYPESCTOR_FLAGS \
   (CTYPESFN_FLAGS | JSFUN_CONSTRUCTOR)
@@ -781,28 +796,31 @@ static const JSClass sInt64ProtoClass = 
   0
 };
 
 static const JSClass sUInt64ProtoClass = {
   "UInt64",
   0
 };
 
+static const JSClassOps sInt64ClassOps = {
+  nullptr, nullptr, nullptr, nullptr,
+  nullptr, nullptr, nullptr, Int64Base::Finalize
+};
+
 static const JSClass sInt64Class = {
   "Int64",
   JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS),
-  nullptr, nullptr, nullptr, nullptr,
-  nullptr, nullptr, nullptr, Int64Base::Finalize
+  &sInt64ClassOps
 };
 
 static const JSClass sUInt64Class = {
   "UInt64",
   JSCLASS_HAS_RESERVED_SLOTS(INT64_SLOTS),
-  nullptr, nullptr, nullptr, nullptr,
-  nullptr, nullptr, nullptr, Int64Base::Finalize
+  &sInt64ClassOps
 };
 
 static const JSFunctionSpec sInt64StaticFunctions[] = {
   JS_FN("compare", Int64::Compare, 2, CTYPESFN_FLAGS),
   JS_FN("lo", Int64::Lo, 1, CTYPESFN_FLAGS),
   JS_FN("hi", Int64::Hi, 1, CTYPESFN_FLAGS),
   // "join" is defined specially; see InitInt64Class.
   JS_FS_END
--- a/js/src/ctypes/Library.cpp
+++ b/js/src/ctypes/Library.cpp
@@ -27,21 +27,25 @@ namespace Library
 } // namespace Library
 
 /*******************************************************************************
 ** JSObject implementation
 *******************************************************************************/
 
 typedef Rooted<JSFlatString*>    RootedFlatString;
 
+static const JSClassOps sLibraryClassOps = {
+  nullptr, nullptr, nullptr, nullptr,
+  nullptr, nullptr, nullptr, Library::Finalize
+};
+
 static const JSClass sLibraryClass = {
   "Library",
   JSCLASS_HAS_RESERVED_SLOTS(LIBRARY_SLOTS),
-  nullptr, nullptr, nullptr, nullptr,
-  nullptr, nullptr, nullptr, Library::Finalize
+  &sLibraryClassOps
 };
 
 #define CTYPESFN_FLAGS \
   (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)
 
 static const JSFunctionSpec sLibraryFunctions[] = {
   JS_FN("close",   Library::Close,   0, CTYPESFN_FLAGS),
   JS_FN("declare", Library::Declare, 0, CTYPESFN_FLAGS),
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -63,18 +63,18 @@ var ignoreClasses = {
     "_MD_IOVector" : true,
     "malloc_table_t": true, // replace_malloc
     "malloc_hook_table_t": true, // replace_malloc
 };
 
 // Ignore calls through TYPE.FIELD, where TYPE is the class or struct name containing
 // a function pointer field named FIELD.
 var ignoreCallees = {
-    "js::Class.trace" : true,
-    "js::Class.finalize" : true,
+    "js::ClassOps.trace" : true,
+    "js::ClassOps.finalize" : true,
     "JSRuntime.destroyPrincipals" : true,
     "icu_50::UObject.__deleting_dtor" : true, // destructors in ICU code can't cause GC
     "mozilla::CycleCollectedJSRuntime.DescribeCustomObjects" : true, // During tracing, cannot GC.
     "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
     "PLDHashTableOps.hashKey" : true,
     "z_stream_s.zfree" : true,
     "z_stream_s.zalloc" : true,
     "GrGLInterface.fCallback" : true,
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1263,20 +1263,20 @@ enum class CheckGeneration { DoChecks, N
 template <typename Functor, typename... Args>
 static inline NativeObject*
 CallTraceHook(Functor f, JSTracer* trc, JSObject* obj, CheckGeneration check, Args&&... args)
 {
     const Class* clasp = obj->getClass();
     MOZ_ASSERT(clasp);
     MOZ_ASSERT(obj->isNative() == clasp->isNative());
 
-    if (!clasp->trace)
+    if (!clasp->hasTrace())
         return &obj->as<NativeObject>();
 
-    if (clasp->trace == InlineTypedObject::obj_trace) {
+    if (clasp->isTrace(InlineTypedObject::obj_trace)) {
         Shape** pshape = obj->as<InlineTypedObject>().addressOfShapeFromGC();
         f(pshape, mozilla::Forward<Args>(args)...);
 
         InlineTypedObject& tobj = obj->as<InlineTypedObject>();
         if (tobj.typeDescr().hasTraceList()) {
             VisitTraceList(f, tobj.typeDescr().traceList(), tobj.inlineTypedMem(),
                            mozilla::Forward<Args>(args)...);
         }
@@ -1296,17 +1296,17 @@ CallTraceHook(Functor f, JSTracer* trc, 
         if (layout.traceList()) {
             VisitTraceList(f, layout.traceList(), unboxed.data(),
                            mozilla::Forward<Args>(args)...);
         }
 
         return nullptr;
     }
 
-    clasp->trace(trc, obj);
+    clasp->doTrace(trc, obj);
 
     if (!clasp->isNative())
         return nullptr;
     return &obj->as<NativeObject>();
 }
 
 template <typename F, typename... Args>
 static void
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -170,17 +170,17 @@ js::Nursery::allocateObject(JSContext* c
     MOZ_ASSERT(size >= sizeof(RelocationOverlay));
 
     /*
      * Classes with JSCLASS_SKIP_NURSERY_FINALIZE will not have their finalizer
      * called if they are nursery allocated and not promoted to the tenured
      * heap. The finalizers for these classes must do nothing except free data
      * which was allocated via Nursery::allocateBuffer.
      */
-    MOZ_ASSERT_IF(clasp->finalize, clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE);
+    MOZ_ASSERT_IF(clasp->hasFinalize(), clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE);
 
     /* Make the object allocation. */
     JSObject* obj = static_cast<JSObject*>(allocate(size));
     if (!obj)
         return nullptr;
 
     /* If we want external slots, add them. */
     HeapSlot* slots = nullptr;
--- a/js/src/gdb/gdb-tests.cpp
+++ b/js/src/gdb/gdb-tests.cpp
@@ -8,25 +8,29 @@
 
 #include "gdb-tests.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/Initialization.h"
 
 using namespace JS;
 
-/* The class of the global object. */
-const JSClass global_class = {
-    "global", JSCLASS_GLOBAL_FLAGS,
+static const JSClassOps global_classOps = {
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr,
     JS_GlobalObjectTraceHook
 };
 
+/* The class of the global object. */
+static const JSClass global_class = {
+    "global", JSCLASS_GLOBAL_FLAGS,
+    &global_classOps
+};
+
 template<typename T>
 static inline T*
 checkPtr(T* ptr)
 {
   if (! ptr)
     abort();
   return ptr;
 }
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -922,17 +922,17 @@ IsCacheableSetPropAddSlot(JSContext* cx,
         !propertyShape->hasDefaultSetter() ||
         !propertyShape->writable())
     {
         return false;
     }
 
     // Watch out for resolve or addProperty hooks.
     if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj) ||
-        obj->getClass()->addProperty)
+        obj->getClass()->getAddProperty())
     {
         return false;
     }
 
     size_t chainDepth = 0;
     // Walk up the object prototype chain and ensure that all prototypes are
     // native, and that all prototypes have no setter defined on the property.
     for (JSObject* proto = obj->getProto(); proto; proto = proto->getProto()) {
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -10694,29 +10694,38 @@ void
 CodeGenerator::visitIsCallable(LIsCallable* ins)
 {
     Register object = ToRegister(ins->object());
     Register output = ToRegister(ins->output());
 
     OutOfLineIsCallable* ool = new(alloc()) OutOfLineIsCallable(ins);
     addOutOfLineCode(ool, ins->mir());
 
-    Label notFunction, done;
+    Label notFunction, hasCOps, done;
     masm.loadObjClass(object, output);
 
     // Just skim proxies off. Their notion of isCallable() is more complicated.
     masm.branchTestClassIsProxy(true, output, ool->entry());
 
-    // An object is callable iff (is<JSFunction>() || getClass()->call.
+    // An object is callable iff:
+    //   is<JSFunction>() || (getClass()->cOps && getClass()->cOps->call).
     masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), &notFunction);
     masm.move32(Imm32(1), output);
     masm.jump(&done);
 
     masm.bind(&notFunction);
-    masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::Class, call)), ImmPtr(nullptr), output);
+    masm.branchPtr(Assembler::NonZero, Address(output, offsetof(js::Class, cOps)),
+                   ImmPtr(nullptr), &hasCOps);
+    masm.move32(Imm32(0), output);
+    masm.jump(&done);
+
+    masm.bind(&hasCOps);
+    masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, call)),
+                   ImmPtr(nullptr), output);
+
     masm.bind(&done);
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitOutOfLineIsCallable(OutOfLineIsCallable* ool)
 {
     LIsCallable* ins = ool->ins();
@@ -10756,37 +10765,45 @@ void
 CodeGenerator::visitIsConstructor(LIsConstructor* ins)
 {
     Register object = ToRegister(ins->object());
     Register output = ToRegister(ins->output());
 
     OutOfLineIsConstructor* ool = new(alloc()) OutOfLineIsConstructor(ins);
     addOutOfLineCode(ool, ins->mir());
 
-    Label notFunction, notConstructor, done;
+    Label notFunction, notConstructor, hasCOps, done;
     masm.loadObjClass(object, output);
 
     // Just skim proxies off. Their notion of isConstructor() is more complicated.
     masm.branchTestClassIsProxy(true, output, ool->entry());
 
     // An object is constructor iff
     //  ((is<JSFunction>() && as<JSFunction>().isConstructor) ||
-    //   getClass()->construct).
+    //   (getClass()->cOps && getClass()->cOps->construct)).
     masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), &notFunction);
     masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output);
     masm.and32(Imm32(JSFunction::CONSTRUCTOR), output);
     masm.branchTest32(Assembler::Zero, output, output, &notConstructor);
     masm.move32(Imm32(1), output);
     masm.jump(&done);
     masm.bind(&notConstructor);
     masm.move32(Imm32(0), output);
     masm.jump(&done);
 
     masm.bind(&notFunction);
-    masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::Class, construct)), ImmPtr(nullptr), output);
+    masm.branchPtr(Assembler::NonZero, Address(output, offsetof(js::Class, cOps)),
+                   ImmPtr(nullptr), &hasCOps);
+    masm.move32(Imm32(0), output);
+    masm.jump(&done);
+
+    masm.bind(&hasCOps);
+    masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, construct)),
+                   ImmPtr(nullptr), output);
+
     masm.bind(&done);
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitOutOfLineIsConstructor(OutOfLineIsConstructor* ool)
 {
     LIsConstructor* ins = ool->ins();
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -479,17 +479,17 @@ IsCacheableNoProperty(JSObject* obj, JSO
 {
     if (shape)
         return false;
 
     MOZ_ASSERT(!holder);
 
     // Just because we didn't find the property on the object doesn't mean it
     // won't magically appear through various engine hacks.
-    if (obj->getClass()->getProperty)
+    if (obj->getClass()->getGetProperty())
         return false;
 
     // Don't generate missing property ICs if we skipped a non-native object, as
     // lookups may extend beyond the prototype chain (e.g. for DOMProxy
     // proxies).
     JSObject* obj2 = obj;
     while (obj2) {
         if (!obj2->isNative())
@@ -3275,17 +3275,17 @@ IsPropertyAddInlineable(JSContext* cx, N
     // the shape must be the one we just added.
     MOZ_ASSERT(shape == obj->lastProperty());
 
     // Watch out for resolve hooks.
     if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj))
         return false;
 
     // Likewise for an addProperty hook, since we'll need to invoke it.
-    if (obj->getClass()->addProperty)
+    if (obj->getClass()->getAddProperty())
         return false;
 
     if (!obj->nonProxyIsExtensible() || !shape->writable())
         return false;
 
     if (PrototypeChainShadowsPropertyAdd(cx, obj, id))
         return false;
 
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -2739,17 +2739,17 @@ CheckHasNoSuchProperty(JSContext* cx, JS
     JSObject* curObj = obj;
     while (curObj) {
         if (curObj->isNative()) {
             // Don't handle proto chains with resolve hooks.
             if (ClassMayResolveId(cx->names(), curObj->getClass(), NameToId(name), curObj))
                 return false;
             if (curObj->as<NativeObject>().contains(cx, NameToId(name)))
                 return false;
-            if (curObj->getClass()->getProperty)
+            if (curObj->getClass()->getGetProperty())
                 return false;
         } else if (curObj != obj) {
             // Non-native objects are only handled as the original receiver.
             return false;
         } else if (curObj->is<UnboxedPlainObject>()) {
             if (curObj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, NameToId(name)))
                 return false;
         } else if (curObj->is<UnboxedArrayObject>()) {
--- a/js/src/jsapi-tests/testAddPropertyPropcache.cpp
+++ b/js/src/jsapi-tests/testAddPropertyPropcache.cpp
@@ -11,20 +11,24 @@ static int callCount = 0;
 
 static bool
 AddProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue v)
 {
     callCount++;
     return true;
 }
 
+static const JSClassOps AddPropertyClassOps = {
+    AddProperty
+};
+
 static const JSClass AddPropertyClass = {
     "AddPropertyTester",
     0,
-    AddProperty
+    &AddPropertyClassOps
 };
 
 BEGIN_TEST(testAddPropertyHook)
 {
     /*
      * Do the test a bunch of times, because sometimes we seem to randomly
      * miss the propcache.
      */
--- a/js/src/jsapi-tests/testChromeBuffer.cpp
+++ b/js/src/jsapi-tests/testChromeBuffer.cpp
@@ -3,33 +3,37 @@
  * 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 "jsapi-tests/tests.h"
 
 static TestJSPrincipals system_principals(1);
 
-static const JSClass global_class = {
-    "global",
-    JSCLASS_IS_GLOBAL | JSCLASS_GLOBAL_FLAGS,
+static const JSClassOps global_classOps = {
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     JS_GlobalObjectTraceHook
 };
 
+static const JSClass global_class = {
+    "global",
+    JSCLASS_IS_GLOBAL | JSCLASS_GLOBAL_FLAGS,
+    &global_classOps
+};
+
 static JS::PersistentRootedObject trusted_glob;
 static JS::PersistentRootedObject trusted_fun;
 
 static bool
 CallTrusted(JSContext* cx, unsigned argc, JS::Value* vp)
 {
     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
 
--- a/js/src/jsapi-tests/testClassGetter.cpp
+++ b/js/src/jsapi-tests/testClassGetter.cpp
@@ -16,25 +16,29 @@ static bool test_prop_get( JSContext* cx
 {
     called_test_prop_get++;
     return true;
 }
 
 static bool
 PTest(JSContext* cx, unsigned argc, JS::Value* vp);
 
-static const JSClass ptestClass = {
-    "PTest",
-    JSCLASS_HAS_PRIVATE,
+static const JSClassOps ptestClassOps = {
     nullptr, // addProperty
     nullptr, // delProperty
     test_prop_get,
     nullptr // setProperty
 };
 
+static const JSClass ptestClass = {
+    "PTest",
+    JSCLASS_HAS_PRIVATE,
+    &ptestClassOps
+};
+
 static bool
 PTest(JSContext* cx, unsigned argc, JS::Value* vp)
 {
     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     JSObject* obj = JS_NewObjectForConstructor(cx, &ptestClass, args);
     if (!obj)
         return false;
     args.rval().setObject(*obj);
--- a/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
+++ b/js/src/jsapi-tests/testFreshGlobalEvalRedefinition.cpp
@@ -16,24 +16,28 @@ GlobalEnumerate(JSContext* cx, JS::Handl
 static bool
 GlobalResolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp)
 {
     return JS_ResolveStandardClass(cx, obj, id, resolvedp);
 }
 
 BEGIN_TEST(testRedefineGlobalEval)
 {
-    static const JSClass cls = {
-        "global", JSCLASS_GLOBAL_FLAGS,
+    static const JSClassOps clsOps = {
         nullptr, nullptr, nullptr, nullptr,
         GlobalEnumerate, GlobalResolve, nullptr, nullptr,
         nullptr, nullptr, nullptr,
         JS_GlobalObjectTraceHook
     };
 
+    static const JSClass cls = {
+        "global", JSCLASS_GLOBAL_FLAGS,
+        &clsOps
+    };
+
     /* Create the global object. */
     JS::CompartmentOptions options;
     JS::Rooted<JSObject*> g(cx, JS_NewGlobalObject(cx, &cls, nullptr, JS::FireOnNewGlobalHook, options));
     if (!g)
         return false;
 
     JSAutoCompartment ac(cx, g);
     JS::Rooted<JS::Value> v(cx);
--- a/js/src/jsapi-tests/testLookup.cpp
+++ b/js/src/jsapi-tests/testLookup.cpp
@@ -68,20 +68,24 @@ document_resolve(JSContext* cx, JS::Hand
             return true;
         }
     }
 
     *resolvedp = false;
     return true;
 }
 
+static const JSClassOps document_classOps = {
+    nullptr, nullptr, nullptr, nullptr,
+    nullptr, document_resolve, nullptr
+};
+
 static const JSClass document_class = {
     "document", 0,
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, document_resolve, nullptr
+    &document_classOps
 };
 
 BEGIN_TEST(testLookup_bug570195)
 {
     JS::RootedObject obj(cx, JS_NewObject(cx, &document_class));
     CHECK(obj);
     CHECK(JS_DefineProperty(cx, global, "document", obj, 0));
     JS::RootedValue v(cx);
--- a/js/src/jsapi-tests/testNewObject.cpp
+++ b/js/src/jsapi-tests/testNewObject.cpp
@@ -93,22 +93,25 @@ BEGIN_TEST(testNewObject_1)
     CHECK(JS_IsArrayObject(cx, obj, &isArray));
     CHECK(isArray);
     CHECK(JS_GetArrayLength(cx, obj, &len));
     CHECK_EQUAL(len, N);
     CHECK(JS_GetElement(cx, obj, N - 1, &v));
     CHECK(v.isInt32(N - 1));
 
     // With JSClass.construct.
+    static const JSClassOps clsOps = {
+        nullptr, nullptr, nullptr, nullptr,
+        nullptr, nullptr, nullptr, nullptr,
+        nullptr, nullptr, constructHook
+    };
     static const JSClass cls = {
         "testNewObject_1",
         0,
-        nullptr, nullptr, nullptr, nullptr,
-        nullptr, nullptr, nullptr, nullptr,
-        nullptr, nullptr, constructHook
+        &clsOps
     };
     JS::RootedObject ctor(cx, JS_NewObject(cx, &cls));
     CHECK(ctor);
     JS::RootedValue rt2(cx, JS::ObjectValue(*ctor));
     obj = JS_New(cx, ctor, JS::HandleValueArray::subarray(argv, 0, 3));
     CHECK(obj);
     CHECK(JS_GetElement(cx, ctor, 0, &v));
     CHECK(v.isInt32(0));
--- a/js/src/jsapi-tests/testPersistentRooted.cpp
+++ b/js/src/jsapi-tests/testPersistentRooted.cpp
@@ -15,31 +15,35 @@ struct BarkWhenTracedClass {
     static void finalize(JSFreeOp* fop, JSObject* obj) { finalizeCount++; }
     static void trace(JSTracer* trc, JSObject* obj) { traceCount++; }
     static void reset() { finalizeCount = 0; traceCount = 0; }
 };
 
 int BarkWhenTracedClass::finalizeCount;
 int BarkWhenTracedClass::traceCount;
 
-const JSClass BarkWhenTracedClass::class_ = {
-    "BarkWhenTracedClass",
-    0,
+static const JSClassOps BarkWhenTracedClassClassOps = {
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
-    finalize,
+    BarkWhenTracedClass::finalize,
     nullptr,
     nullptr,
     nullptr,
-    trace
+    BarkWhenTracedClass::trace
+};
+
+const JSClass BarkWhenTracedClass::class_ = {
+    "BarkWhenTracedClass",
+    0,
+    &BarkWhenTracedClassClassOps
 };
 
 struct Kennel {
     PersistentRootedObject obj;
     Kennel() { }
     explicit Kennel(JSContext* cx) : obj(cx) { }
     Kennel(JSContext* cx, const HandleObject& woof) : obj(cx, woof) { }
     void init(JSContext* cx, const HandleObject& woof) {
--- a/js/src/jsapi-tests/testPropCache.cpp
+++ b/js/src/jsapi-tests/testPropCache.cpp
@@ -11,20 +11,24 @@ static int g_counter;
 
 static bool
 CounterAdd(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue v)
 {
     g_counter++;
     return true;
 }
 
+static const JSClassOps CounterClassOps = {
+    CounterAdd
+};
+
 static const JSClass CounterClass = {
     "Counter",  /* name */
     0,  /* flags */
-    CounterAdd
+    &CounterClassOps
 };
 
 BEGIN_TEST(testPropCache_bug505798)
 {
     g_counter = 0;
     EXEC("var x = {};");
     CHECK(JS_DefineObject(cx, global, "y", &CounterClass, JSPROP_ENUMERATE));
     EXEC("var arr = [x, y];\n"
--- a/js/src/jsapi-tests/testResolveRecursion.cpp
+++ b/js/src/jsapi-tests/testResolveRecursion.cpp
@@ -8,27 +8,31 @@
 #include "jsapi-tests/tests.h"
 
 /*
  * Test that resolve hook recursion for the same object and property is
  * prevented.
  */
 BEGIN_TEST(testResolveRecursion)
 {
-    static const JSClass my_resolve_class = {
-        "MyResolve",
-        JSCLASS_HAS_PRIVATE,
+    static const JSClassOps my_resolve_classOps = {
         nullptr, // add
         nullptr, // delete
         nullptr, // get
         nullptr, // set
         nullptr, // enumerate
         my_resolve
     };
 
+    static const JSClass my_resolve_class = {
+        "MyResolve",
+        JSCLASS_HAS_PRIVATE,
+        &my_resolve_classOps
+    };
+
     obj1.init(cx, JS_NewObject(cx, &my_resolve_class));
     CHECK(obj1);
     obj2.init(cx, JS_NewObject(cx, &my_resolve_class));
     CHECK(obj2);
     JS_SetPrivate(obj1, this);
     JS_SetPrivate(obj2, this);
 
     JS::RootedValue obj1Val(cx, JS::ObjectValue(*obj1));
@@ -145,33 +149,37 @@ END_TEST(testResolveRecursion)
  */
 BEGIN_TEST(testResolveRecursion_InitStandardClasses)
 {
     CHECK(JS_InitStandardClasses(cx, global));
     return true;
 }
 
 const JSClass* getGlobalClass() override {
-    static const JSClass myGlobalClass = {
-        "testResolveRecursion_InitStandardClasses_myGlobalClass",
-        JSCLASS_GLOBAL_FLAGS,
+    static const JSClassOps myGlobalClassOps = {
         nullptr, // add
         nullptr, // delete
         nullptr, // get
         nullptr, // set
         nullptr, // enumerate
         my_resolve,
         nullptr, // mayResolve
         nullptr, // finalize
         nullptr, // call
         nullptr, // hasInstance
         nullptr, // construct
         JS_GlobalObjectTraceHook
     };
 
+    static const JSClass myGlobalClass = {
+        "testResolveRecursion_InitStandardClasses_myGlobalClass",
+        JSCLASS_GLOBAL_FLAGS,
+        &myGlobalClassOps
+    };
+
     return &myGlobalClass;
 }
 
 static bool
 my_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp)
 {
     MOZ_ASSERT_UNREACHABLE("resolve hook should not be called from InitStandardClasses");
     JS_ReportError(cx, "FAIL");
--- a/js/src/jsapi-tests/testSetProperty.cpp
+++ b/js/src/jsapi-tests/testSetProperty.cpp
@@ -63,17 +63,17 @@ NativeGet(JSContext* cx, JS::HandleObjec
 }
 END_TEST(testSetProperty_NativeGetterStubSetter)
 
 BEGIN_TEST(testSetProperty_InheritedGlobalSetter)
 {
     // This is a JSAPI test because jsapi-test globals do not have a resolve
     // hook and therefore can use the property cache in some cases where the
     // shell can't.
-    MOZ_RELEASE_ASSERT(!JS_GetClass(global)->resolve);
+    MOZ_RELEASE_ASSERT(!JS_GetClass(global)->getResolve());
 
     CHECK(JS_DefineProperty(cx, global, "HOTLOOP", 8, 0));
     EXEC("var n = 0;\n"
          "var global = this;\n"
          "function f() { n++; }\n"
          "Object.defineProperty(Object.prototype, 'x', {set: f});\n"
          "for (var i = 0; i < HOTLOOP; i++)\n"
          "    global.x = i;\n");
--- a/js/src/jsapi-tests/testWeakMap.cpp
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -154,28 +154,17 @@ JSObject* newKey()
 {
     static const js::ClassExtension keyClassExtension = {
         GetKeyDelegate
     };
 
     static const js::Class keyClass = {
         "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_OPS,
         JS_NULL_CLASS_SPEC,
         &keyClassExtension,
         JS_NULL_OBJECT_OPS
     };
 
     JS::RootedObject key(cx, JS_NewObject(cx, Jsvalify(&keyClass)));
     if (!key)
         return nullptr;
@@ -202,36 +191,40 @@ JSObject* newCCW(JS::HandleObject source
         if (!JS_WrapObject(cx, &object))
             return nullptr;
     }
     return object;
 }
 
 JSObject* newDelegate()
 {
-    static const js::ClassExtension delegateClassExtension = {
-        nullptr,
-        DelegateObjectMoved
-    };
-
-    static const js::Class delegateClass = {
-        "delegate",
-        JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1),
+    static const js::ClassOps delegateClassOps = {
         nullptr, /* addProperty */
         nullptr, /* delProperty */
         nullptr, /* getProperty */
         nullptr, /* setProperty */
         nullptr, /* enumerate */
         nullptr, /* resolve */
         nullptr, /* mayResolve */
         nullptr, /* finalize */
         nullptr, /* call */
         nullptr, /* hasInstance */
         nullptr, /* construct */
         JS_GlobalObjectTraceHook,
+    };
+
+    static const js::ClassExtension delegateClassExtension = {
+        nullptr,
+        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
     };
 
     /* Create the global object. */
     JS::CompartmentOptions options;
     options.behaviors().setVersion(JSVERSION_LATEST);
--- a/js/src/jsapi-tests/tests.h
+++ b/js/src/jsapi-tests/tests.h
@@ -221,23 +221,26 @@ class JSAPITest
         fprintf(stderr, "%s:%d:%.*s\n", filename, lineno, (int) msg.length(), msg.begin());
         msgs += msg;
         return false;
     }
 
     JSAPITestString messages() const { return msgs; }
 
     static const JSClass * basicGlobalClass() {
-        static const JSClass c = {
-            "global", JSCLASS_GLOBAL_FLAGS,
+        static const JSClassOps cOps = {
             nullptr, nullptr, nullptr, nullptr,
             nullptr, nullptr, nullptr, nullptr,
             nullptr, nullptr, nullptr,
             JS_GlobalObjectTraceHook
         };
+        static const JSClass c = {
+            "global", JSCLASS_GLOBAL_FLAGS,
+            &cOps
+        };
         return &c;
     }
 
   protected:
     static bool
     print(JSContext* cx, unsigned argc, JS::Value* vp)
     {
         JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2191,19 +2191,19 @@ DefinePropertyById(JSContext* cx, Handle
 
     // In most places throughout the engine, a property with null getter and
     // not JSPROP_GETTER/SETTER/SHARED has no getter, and the same for setters:
     // it's just a plain old data property. However the JS_Define* APIs use
     // null getter and setter to mean "default to the Class getProperty and
     // setProperty ops".
     if (!(attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
         if (!getter)
-            getter = obj->getClass()->getProperty;
+            getter = obj->getClass()->getGetProperty();
         if (!setter)
-            setter = obj->getClass()->setProperty;
+            setter = obj->getClass()->getSetProperty();
     }
     if (getter == JS_PropertyStub)
         getter = nullptr;
     if (setter == JS_StrictPropertyStub)
         setter = nullptr;
     return DefineProperty(cx, obj, id, value, getter, setter, attrs);
 }
 
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -493,17 +493,18 @@ array_length_setter(JSContext* cx, Handl
                     ObjectOpResult& result)
 {
     if (!obj->is<ArrayObject>()) {
         // This array .length property was found on the prototype
         // chain. Ideally the setter should not have been called, but since
         // we're here, do an impression of SetPropertyByDefining.
         const Class* clasp = obj->getClass();
         return DefineProperty(cx, obj, cx->names().length, vp,
-                              clasp->getProperty, clasp->setProperty, JSPROP_ENUMERATE, result);
+                              clasp->getGetProperty(), clasp->getSetProperty(),
+                              JSPROP_ENUMERATE, result);
     }
 
     Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
     MOZ_ASSERT(arr->lengthIsWritable(),
                "setter shouldn't be called if property is non-writable");
 
     return ArraySetLength(cx, arr, id, JSPROP_PERMANENT, vp, result);
 }
@@ -2695,17 +2696,17 @@ GetIndexedPropertiesInRange(JSContext* c
                             Vector<uint32_t>& indexes, bool* success)
 {
     *success = false;
 
     // First, look for proxies or class hooks that can introduce extra
     // properties.
     JSObject* pobj = obj;
     do {
-        if (!pobj->isNative() || pobj->getClass()->resolve || pobj->getOpsLookupProperty())
+        if (!pobj->isNative() || pobj->getClass()->getResolve() || pobj->getOpsLookupProperty())
             return true;
     } while ((pobj = pobj->getProto()));
 
     // Collect indexed property names.
     pobj = obj;
     do {
         // Append dense elements.
         NativeObject* nativeObj = &pobj->as<NativeObject>();
@@ -3280,41 +3281,45 @@ array_proto_finish(JSContext* cx, JS::Ha
         return false;
     }
 
     RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().get(JS::SymbolCode::unscopables)));
     value.setObject(*unscopables);
     return DefineProperty(cx, proto, id, value, nullptr, nullptr, JSPROP_READONLY);
 }
 
+static const ClassOps ArrayObjectClassOps = {
+    array_addProperty,
+    nullptr, /* delProperty */
+    nullptr, /* getProperty */
+    nullptr, /* setProperty */
+    nullptr, /* enumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    nullptr, /* finalize */
+    nullptr, /* call */
+    nullptr, /* hasInstance */
+    nullptr, /* construct */
+    nullptr, /* trace */
+};
+
 static const ClassSpec ArrayObjectClassSpec = {
     GenericCreateConstructor<ArrayConstructor, 1, AllocKind::FUNCTION, &jit::JitInfo_Array>,
     CreateArrayPrototype,
     array_static_methods,
     array_static_props,
     array_methods,
     nullptr,
     array_proto_finish
 };
 
 const Class ArrayObject::class_ = {
     "Array",
     JSCLASS_HAS_CACHED_PROTO(JSProto_Array) | JSCLASS_DELAY_METADATA_BUILDER,
-    array_addProperty,
-    nullptr, /* delProperty */
-    nullptr, /* getProperty */
-    nullptr, /* setProperty */
-    nullptr, /* enumerate */
-    nullptr, /* resolve */
-    nullptr, /* mayResolve */
-    nullptr, /* finalize */
-    nullptr, /* call */
-    nullptr, /* hasInstance */
-    nullptr, /* construct */
-    nullptr, /* trace */
+    &ArrayObjectClassOps,
     &ArrayObjectClassSpec
 };
 
 /*
  * Array allocation functions.
  */
 
 static inline bool
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -3281,28 +3281,17 @@ static const ClassSpec DateObjectClassSp
     nullptr,
     FinishDateClassInit
 };
 
 const Class DateObject::class_ = {
     js_Date_str,
     JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
-    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_OPS,
     &DateObjectClassSpec
 };
 
 static const ClassSpec DateObjectProtoClassSpec = {
     DELEGATED_CLASSSPEC(DateObject::class_.spec),
     nullptr,
     nullptr,
     nullptr,
@@ -3310,28 +3299,17 @@ static const ClassSpec DateObjectProtoCl
     nullptr,
     nullptr,
     ClassSpec::IsDelegated
 };
 
 const Class DateObject::protoClass_ = {
     js_Object_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
-    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_OPS,
     &DateObjectProtoClassSpec
 };
 
 JSObject*
 js::NewDateObjectMsec(JSContext* cx, ClippedTime t, HandleObject proto /* = nullptr */)
 {
     JSObject* obj = NewObjectWithClassProto(cx, &DateObject::class_, proto);
     if (!obj)
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -60,33 +60,37 @@ static const JSPropertySpec exception_pr
 static const JSFunctionSpec exception_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str, exn_toSource, 0, 0),
 #endif
     JS_SELF_HOSTED_FN(js_toString_str, "ErrorToString", 0,0),
     JS_FS_END
 };
 
+static const ClassOps ErrorObjectClassOps = {
+    nullptr,                 /* addProperty */
+    nullptr,                 /* delProperty */
+    nullptr,                 /* getProperty */
+    nullptr,                 /* setProperty */
+    nullptr,                 /* enumerate */
+    nullptr,                 /* resolve */
+    nullptr,                 /* mayResolve */
+    exn_finalize,
+    nullptr,                 /* call        */
+    nullptr,                 /* hasInstance */
+    nullptr,                 /* construct   */
+    nullptr,                 /* trace       */
+};
+
 #define IMPLEMENT_ERROR_CLASS(name, classSpecPtr) \
     { \
         js_Error_str, /* yes, really */ \
         JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
         JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS), \
-        nullptr,                 /* addProperty */ \
-        nullptr,                 /* delProperty */ \
-        nullptr,                 /* getProperty */ \
-        nullptr,                 /* setProperty */ \
-        nullptr,                 /* enumerate */ \
-        nullptr,                 /* resolve */ \
-        nullptr,                 /* mayResolve */ \
-        exn_finalize, \
-        nullptr,                 /* call        */ \
-        nullptr,                 /* hasInstance */ \
-        nullptr,                 /* construct   */ \
-        nullptr,                 /* trace       */ \
+        &ErrorObjectClassOps, \
         classSpecPtr \
     }
 
 const ClassSpec
 ErrorObject::errorClassSpec_ = {
     ErrorObject::createConstructor,
     ErrorObject::createProto,
     nullptr,
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -309,16 +309,17 @@ 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::ClassOps) ProxyClassOps;
 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.
@@ -331,28 +332,17 @@ extern JS_FRIEND_DATA(const js::ObjectOp
 
 #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 */                                      \
-        nullptr,                 /* getProperty */                                      \
-        nullptr,                 /* setProperty */                                      \
-        nullptr,                 /* enumerate */                                        \
-        nullptr,                 /* resolve */                                          \
-        nullptr,                 /* mayResolve */                                       \
-        js::proxy_Finalize,      /* finalize    */                                      \
-        nullptr,                 /* call        */                                      \
-        js::proxy_HasInstance,   /* hasInstance */                                      \
-        nullptr,                 /* construct   */                                      \
-        js::proxy_Trace,         /* trace       */                                      \
+        &js::ProxyClassOps,                                                             \
         JS_NULL_CLASS_SPEC,                                                             \
         extPtr,                                                                         \
         &js::ProxyObjectOps                                                             \
     }
 
 #define PROXY_CLASS_DEF(name, flags) \
   PROXY_CLASS_WITH_EXT(name, flags, &js::ProxyClassExtension)
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -829,40 +829,44 @@ CreateFunctionPrototype(JSContext* cx, J
     if (!throwTypeError || !PreventExtensions(cx, throwTypeError))
         return nullptr;
 
     self->setThrowTypeError(throwTypeError);
 
     return functionProto;
 }
 
+static const ClassOps JSFunctionClassOps = {
+    nullptr,                 /* addProperty */
+    nullptr,                 /* delProperty */
+    nullptr,                 /* getProperty */
+    nullptr,                 /* setProperty */
+    fun_enumerate,
+    fun_resolve,
+    fun_mayResolve,
+    nullptr,                 /* finalize    */
+    nullptr,                 /* call        */
+    fun_hasInstance,
+    nullptr,                 /* construct   */
+    fun_trace,
+};
+
 static const ClassSpec JSFunctionClassSpec = {
     CreateFunctionConstructor,
     CreateFunctionPrototype,
     nullptr,
     nullptr,
     function_methods,
     function_properties
 };
 
 const Class JSFunction::class_ = {
     js_Function_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_Function),
-    nullptr,                 /* addProperty */
-    nullptr,                 /* delProperty */
-    nullptr,                 /* getProperty */
-    nullptr,                 /* setProperty */
-    fun_enumerate,
-    fun_resolve,
-    fun_mayResolve,
-    nullptr,                 /* finalize    */
-    nullptr,                 /* call        */
-    fun_hasInstance,
-    nullptr,                 /* construct   */
-    fun_trace,
+    &JSFunctionClassOps,
     &JSFunctionClassSpec
 };
 
 const Class* const js::FunctionClassPtr = &JSFunction::class_;
 
 /* Find the body of a function (not including braces). */
 bool
 js::FindBody(JSContext* cx, HandleFunction fun, HandleLinearString src, size_t* bodyStart,
@@ -2105,18 +2109,18 @@ js::DefineFunction(JSContext* cx, Handle
          * the defined property's attributes. This allows us to encode another,
          * internal flag using the same bit, JSFUN_EXPR_CLOSURE -- see jsfun.h
          * for more on this.
          */
         flags &= ~JSFUN_STUB_GSOPS;
         gop = nullptr;
         sop = nullptr;
     } else {
-        gop = obj->getClass()->getProperty;
-        sop = obj->getClass()->setProperty;
+        gop = obj->getClass()->getGetProperty();
+        sop = obj->getClass()->getSetProperty();
         MOZ_ASSERT(gop != JS_PropertyStub);
         MOZ_ASSERT(sop != JS_StrictPropertyStub);
     }
 
     RootedAtom atom(cx, IdToFunctionName(cx, id));
     if (!atom)
         return nullptr;
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -7195,17 +7195,18 @@ AutoDisableProxyCheck::~AutoDisableProxy
 {
     gc.enableStrictProxyChecking();
 }
 
 JS_FRIEND_API(void)
 JS::AssertGCThingMustBeTenured(JSObject* obj)
 {
     MOZ_ASSERT(obj->isTenured() &&
-               (!IsNurseryAllocable(obj->asTenured().getAllocKind()) || obj->getClass()->finalize));
+               (!IsNurseryAllocable(obj->asTenured().getAllocKind()) ||
+                obj->getClass()->hasFinalize()));
 }
 
 JS_FRIEND_API(void)
 JS::AssertGCThingIsNotAnObjectSubclass(Cell* cell)
 {
     MOZ_ASSERT(cell);
     MOZ_ASSERT(cell->getTraceKind() != JS::TraceKind::Object);
 }
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -150,17 +150,17 @@ CanBeFinalizedInBackground(AllocKind kin
     /* If the class has no finalizer or a finalizer that is safe to call on
      * a different thread, we change the alloc kind. For example,
      * AllocKind::OBJECT0 calls the finalizer on the main thread,
      * AllocKind::OBJECT0_BACKGROUND calls the finalizer on the gcHelperThread.
      * IsBackgroundFinalized is called to prevent recursively incrementing
      * the alloc kind; kind may already be a background finalize kind.
      */
     return (!IsBackgroundFinalized(kind) &&
-            (!clasp->finalize || (clasp->flags & JSCLASS_BACKGROUND_FINALIZE)));
+            (!clasp->hasFinalize() || (clasp->flags & JSCLASS_BACKGROUND_FINALIZE)));
 }
 
 /* Capacity for slotsToThingKind */
 const size_t SLOTS_TO_THING_KIND_LIMIT = 17;
 
 extern const AllocKind slotsToThingKind[];
 
 /* Get the best kind to use when making an object with the given slot count. */
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -376,17 +376,17 @@ Snapshot(JSContext* cx, HandleObject pob
 
                 if (pobj->isNative()) {
                     if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
                         return false;
                 }
             }
         } else if (pobj->isNative()) {
             // Give the object a chance to resolve all lazy properties
-            if (JSEnumerateOp enumerate = pobj->getClass()->enumerate) {
+            if (JSEnumerateOp enumerate = pobj->getClass()->getEnumerate()) {
                 if (!enumerate(cx, pobj.as<NativeObject>()))
                     return false;
             }
             if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
                 return false;
         } else if (pobj->is<ProxyObject>()) {
             AutoIdVector proxyProps(cx);
             if (flags & JSITER_HIDDEN || flags & JSITER_SYMBOLS) {
@@ -793,17 +793,17 @@ static inline bool
 CanCacheIterableObject(JSContext* cx, JSObject* obj)
 {
     if (!CanCompareIterableObjectToCache(obj))
         return false;
     if (obj->isNative()) {
         if (obj->is<TypedArrayObject>() ||
             obj->hasUncacheableProto() ||
             obj->getOpsEnumerate() ||
-            obj->getClass()->enumerate ||
+            obj->getClass()->getEnumerate() ||
             obj->as<NativeObject>().containsPure(cx->names().iteratorIntrinsic))
         {
             return false;
         }
     }
     return true;
 }
 
@@ -1079,35 +1079,39 @@ PropertyIteratorObject::trace(JSTracer* 
 
 void
 PropertyIteratorObject::finalize(FreeOp* fop, JSObject* obj)
 {
     if (NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator())
         fop->free_(ni);
 }
 
-const Class PropertyIteratorObject::class_ = {
-    "Iterator",
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator) |
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_BACKGROUND_FINALIZE,
+const ClassOps PropertyIteratorObject::classOps_ = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     finalize,
     nullptr, /* call        */
     nullptr, /* hasInstance */
     nullptr, /* construct   */
     trace
 };
 
+const Class PropertyIteratorObject::class_ = {
+    "Iterator",
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator) |
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_BACKGROUND_FINALIZE,
+    &PropertyIteratorObject::classOps_
+};
+
 static const Class ArrayIteratorPrototypeClass = {
     "Array Iterator",
     0
 };
 
 enum {
     ArrayIteratorSlotIteratedObject,
     ArrayIteratorSlotNextIndex,
@@ -1420,31 +1424,35 @@ js::IteratorMore(JSContext* cx, HandleOb
 
 static bool
 stopiter_hasInstance(JSContext* cx, HandleObject obj, MutableHandleValue v, bool* bp)
 {
     *bp = JS_IsStopIteration(v);
     return true;
 }
 
-const Class StopIterationObject::class_ = {
-    "StopIteration",
-    JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration),
+static const ClassOps StopIterationObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     nullptr, /* finalize */
     nullptr, /* call */
     stopiter_hasInstance
 };
 
+const Class StopIterationObject::class_ = {
+    "StopIteration",
+    JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration),
+    &StopIterationObjectClassOps
+};
+
 static const JSFunctionSpec iterator_proto_methods[] = {
     JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0),
     JS_FS_END
 };
 
 /* static */ bool
 GlobalObject::initIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
 {
--- a/js/src/jsiter.h
+++ b/js/src/jsiter.h
@@ -115,16 +115,18 @@ struct NativeIterator
 
     static void destroy(NativeIterator* iter) {
         js_free(iter);
     }
 };
 
 class PropertyIteratorObject : public NativeObject
 {
+    static const ClassOps classOps_;
+
   public:
     static const Class class_;
 
     NativeIterator* getNativeIterator() const {
         return static_cast<js::NativeIterator*>(getPrivate());
     }
     void setNativeIterator(js::NativeIterator* ni) {
         setPrivate(ni);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1850,18 +1850,18 @@ js::InitClass(JSContext* cx, HandleObjec
               const Class* clasp, Native constructor, unsigned nargs,
               const JSPropertySpec* ps, const JSFunctionSpec* fs,
               const JSPropertySpec* static_ps, const JSFunctionSpec* static_fs,
               NativeObject** ctorp, AllocKind ctorKind)
 {
     RootedObject protoProto(cx, protoProto_);
 
     /* Check function pointer members. */
-    MOZ_ASSERT(clasp->getProperty != JS_PropertyStub);
-    MOZ_ASSERT(clasp->setProperty != JS_StrictPropertyStub);
+    MOZ_ASSERT(clasp->getGetProperty() != JS_PropertyStub);
+    MOZ_ASSERT(clasp->getSetProperty() != JS_StrictPropertyStub);
 
     RootedAtom atom(cx, Atomize(cx, clasp->name, strlen(clasp->name)));
     if (!atom)
         return nullptr;
 
     /*
      * All instances of the class will inherit properties from the prototype
      * object we are about to create (in DefineConstructorAndPrototype), which
@@ -2107,34 +2107,34 @@ JSObject::isConstructor() const
     return constructHook() != nullptr;
 }
 
 JSNative
 JSObject::callHook() const
 {
     const js::Class* clasp = getClass();
 
-    if (clasp->call)
-        return clasp->call;
+    if (JSNative call = clasp->getCall())
+        return call;
 
     if (is<js::ProxyObject>()) {
         const js::ProxyObject& p = as<js::ProxyObject>();
         if (p.handler()->isCallable(const_cast<JSObject*>(this)))
             return js::proxy_Call;
     }
     return nullptr;
 }
 
 JSNative
 JSObject::constructHook() const
 {
     const js::Class* clasp = getClass();
 
-    if (clasp->construct)
-        return clasp->construct;
+    if (JSNative construct = clasp->getConstruct())
+        return construct;
 
     if (is<js::ProxyObject>()) {
         const js::ProxyObject& p = as<js::ProxyObject>();
         if (p.handler()->isConstructor(const_cast<JSObject*>(this)))
             return js::proxy_Construct;
     }
     return nullptr;
 }
@@ -2939,18 +2939,18 @@ DefineFunctionFromSpec(JSContext* cx, Ha
     SetterOp sop;
     if (flags & JSFUN_STUB_GSOPS) {
         // JSFUN_STUB_GSOPS is a request flag only, not stored in fun->flags or
         // the defined property's attributes.
         flags &= ~JSFUN_STUB_GSOPS;
         gop = nullptr;
         sop = nullptr;
     } else {
-        gop = obj->getClass()->getProperty;
-        sop = obj->getClass()->setProperty;
+        gop = obj->getClass()->getGetProperty();
+        sop = obj->getClass()->getSetProperty();
         MOZ_ASSERT(gop != JS_PropertyStub);
         MOZ_ASSERT(sop != JS_StrictPropertyStub);
     }
 
     RootedId id(cx);
     if (!PropertySpecNameToId(cx, fs->name, &id))
         return false;
 
@@ -3904,18 +3904,18 @@ JSObject::traceChildren(JSTracer* trc)
                        nobj->getDenseInitializedLength(),
                        static_cast<HeapSlot*>(nobj->getDenseElementsAllowCopyOnWrite()),
                        "objectElements");
         } while (false);
     }
 
     // Call the trace hook at the end so that during a moving GC the trace hook
     // will see updated fields and slots.
-    if (clasp->trace)
-        clasp->trace(trc, this);
+    if (clasp->hasTrace())
+        clasp->doTrace(trc, this);
 }
 
 static JSAtom*
 displayAtomFromObjectGroup(ObjectGroup& group)
 {
     TypeNewScript* script = group.newScript();
     if (!script)
         return nullptr;
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1093,17 +1093,17 @@ extern const char js_lookupSetter_str[];
 
 namespace js {
 
 inline gc::InitialHeap
 GetInitialHeap(NewObjectKind newKind, const Class* clasp)
 {
     if (newKind != GenericObject)
         return gc::TenuredHeap;
-    if (clasp->finalize && !(clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE))
+    if (clasp->hasFinalize() && !(clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE))
         return gc::TenuredHeap;
     return gc::DefaultHeap;
 }
 
 bool
 NewObjectWithTaggedProtoIsCachable(ExclusiveContext* cxArg, Handle<TaggedProto> proto,
                                    NewObjectKind newKind, const Class* clasp);
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -73,18 +73,18 @@ JSObject::finalize(js::FreeOp* fop)
     MOZ_ASSERT(isTenured());
     if (!IsBackgroundFinalized(asTenured().getAllocKind())) {
         /* Assert we're on the main thread. */
         MOZ_ASSERT(CurrentThreadCanAccessRuntime(fop->runtime()));
     }
 #endif
 
     const js::Class* clasp = getClass();
-    if (clasp->finalize)
-        clasp->finalize(fop, this);
+    if (clasp->hasFinalize())
+        clasp->doFinalize(fop, this);
 
     if (!clasp->isNative())
         return;
 
     js::NativeObject* nobj = &as<js::NativeObject>();
 
     if (nobj->hasDynamicSlots())
         fop->free_(nobj->slots_);
@@ -316,17 +316,17 @@ JSObject::create(js::ExclusiveContext* c
 {
     MOZ_ASSERT(shape && group);
     MOZ_ASSERT(group->clasp() == shape->getObjectClass());
     MOZ_ASSERT(group->clasp() != &js::ArrayObject::class_);
     MOZ_ASSERT_IF(!js::ClassCanHaveFixedData(group->clasp()),
                   js::gc::GetGCKindSlots(kind, group->clasp()) == shape->numFixedSlots());
     MOZ_ASSERT_IF(group->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE,
                   IsBackgroundFinalized(kind));
-    MOZ_ASSERT_IF(group->clasp()->finalize,
+    MOZ_ASSERT_IF(group->clasp()->hasFinalize(),
                   heap == js::gc::TenuredHeap ||
                   (group->clasp()->flags & JSCLASS_SKIP_NURSERY_FINALIZE));
     MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(),
                   heap == js::gc::TenuredHeap);
     MOZ_ASSERT(!cx->compartment()->hasObjectPendingMetadata());
 
     // Non-native classes cannot have reserved slots or private data, and the
     // objects can't have any fixed slots, for compatibility with
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1697,32 +1697,36 @@ ScriptSourceObject::finalize(FreeOp* fop
     // source object.
     if (fop->runtime()->lcovOutput.isEnabled())
         sso->compartment()->lcovOutput.collectSourceFile(sso->compartment(), sso);
 
     sso->source()->decref();
     sso->setReservedSlot(SOURCE_SLOT, PrivateValue(nullptr));
 }
 
-const Class ScriptSourceObject::class_ = {
-    "ScriptSource",
-    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
-    JSCLASS_IS_ANONYMOUS,
+static const ClassOps ScriptSourceObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
-    finalize,
+    ScriptSourceObject::finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
-    trace
+    ScriptSourceObject::trace
+};
+
+const Class ScriptSourceObject::class_ = {
+    "ScriptSource",
+    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
+    JSCLASS_IS_ANONYMOUS,
+    &ScriptSourceObjectClassOps
 };
 
 ScriptSourceObject*
 ScriptSourceObject::create(ExclusiveContext* cx, ScriptSource* source)
 {
     RootedObject object(cx, NewObjectWithGivenProto(cx, &class_, nullptr));
     if (!object)
         return nullptr;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -875,16 +875,18 @@ struct CompressedSourceHasher
                !memcmp(a->compressedData(), b->compressedData(), a->compressedBytes());
     }
 };
 
 typedef HashSet<ScriptSource*, CompressedSourceHasher, SystemAllocPolicy> CompressedSourceSet;
 
 class ScriptSourceObject : public NativeObject
 {
+    static const ClassOps classOps_;
+
   public:
     static const Class class_;
 
     static void trace(JSTracer* trc, JSObject* obj);
     static void finalize(FreeOp* fop, JSObject* obj);
     static ScriptSourceObject* create(ExclusiveContext* cx, ScriptSource* source);
 
     // Initialize those properties of this ScriptSourceObject whose values
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -425,29 +425,33 @@ str_resolve(JSContext* cx, HandleObject 
         {
             return false;
         }
         *resolvedp = true;
     }
     return true;
 }
 
-const Class StringObject::class_ = {
-    js_String_str,
-    JSCLASS_HAS_RESERVED_SLOTS(StringObject::RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_String),
+static const ClassOps StringObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     str_enumerate,
     str_resolve,
     str_mayResolve
 };
 
+const Class StringObject::class_ = {
+    js_String_str,
+    JSCLASS_HAS_RESERVED_SLOTS(StringObject::RESERVED_SLOTS) |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_String),
+    &StringObjectClassOps
+};
+
 /*
  * Returns a JSString * for the |this| value associated with 'call', or throws
  * a TypeError if |this| is null or undefined.  This algorithm is the same as
  * calling CheckObjectCoercible(this), then returning ToString(this), as all
  * String.prototype.* methods do (other than toString and valueOf).
  */
 static MOZ_ALWAYS_INLINE JSString*
 ThisToStringForStringProto(JSContext* cx, CallReceiver call)
--- a/js/src/perf/jsperf.cpp
+++ b/js/src/perf/jsperf.cpp
@@ -154,20 +154,31 @@ static const struct pm_const {
     { 0, PerfMeasurement::EventMask(0) }
 };
 
 #undef CONSTANT
 
 static bool pm_construct(JSContext* cx, unsigned argc, Value* vp);
 static void pm_finalize(JSFreeOp* fop, JSObject* obj);
 
+static const JSClassOps pm_classOps = {
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    pm_finalize
+};
+
 static const JSClass pm_class = {
-    "PerfMeasurement", JSCLASS_HAS_PRIVATE,
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, pm_finalize
+    "PerfMeasurement",
+    JSCLASS_HAS_PRIVATE,
+    &pm_classOps
 };
 
 // Constructor and destructor
 
 static bool
 pm_construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
--- a/js/src/proxy/BaseProxyHandler.cpp
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -223,20 +223,20 @@ js::SetPropertyIgnoringNamedGetter(JSCon
         unsigned attrs =
             existingDescriptor.object()
             ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
             : JSPROP_ENUMERATE;
 
         // A very old nonstandard SpiderMonkey extension: default to the Class
         // getter and setter ops.
         const Class* clasp = receiverObj->getClass();
-        MOZ_ASSERT(clasp->getProperty != JS_PropertyStub);
-        MOZ_ASSERT(clasp->setProperty != JS_StrictPropertyStub);
-        return DefineProperty(cx, receiverObj, id, v, clasp->getProperty, clasp->setProperty,
-                              attrs, result);
+        MOZ_ASSERT(clasp->getGetProperty() != JS_PropertyStub);
+        MOZ_ASSERT(clasp->getSetProperty() != JS_StrictPropertyStub);
+        return DefineProperty(cx, receiverObj, id, v,
+                              clasp->getGetProperty(), clasp->getSetProperty(), attrs, result);
     }
 
     // Step 6.
     MOZ_ASSERT(ownDesc.isAccessorDescriptor());
     RootedObject setter(cx);
     if (ownDesc.hasSetterObject())
         setter = ownDesc.setterObject();
     if (!setter)
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -711,16 +711,31 @@ js::proxy_GetElements(JSContext* cx, Han
 }
 
 JSString*
 js::proxy_FunToString(JSContext* cx, HandleObject proxy, unsigned indent)
 {
     return Proxy::fun_toString(cx, proxy, indent);
 }
 
+const ClassOps js::ProxyClassOps = {
+    nullptr,                 /* addProperty */
+    nullptr,                 /* delProperty */
+    nullptr,                 /* getProperty */
+    nullptr,                 /* setProperty */
+    nullptr,                 /* enumerate   */
+    nullptr,                 /* resolve     */
+    nullptr,                 /* mayResolve  */
+    js::proxy_Finalize,      /* finalize    */
+    nullptr,                 /* call        */
+    js::proxy_HasInstance,   /* hasInstance */
+    nullptr,                 /* construct   */
+    js::proxy_Trace,         /* trace       */
+};
+
 const ClassExtension js::ProxyClassExtension = PROXY_MAKE_EXT(
     js::proxy_ObjectMoved
 );
 
 const ObjectOps js::ProxyObjectOps = {
     js::proxy_LookupProperty,
     js::proxy_DefineProperty,
     js::proxy_HasProperty,
--- a/js/src/shell/OSObject.cpp
+++ b/js/src/shell/OSObject.cpp
@@ -390,33 +390,37 @@ class FileObject : public JSObject {
 
   private:
 
     void setRCFile(RCFile* file) {
         js::SetReservedSlot(this, FILE_SLOT, PrivateValue(file));
     }
 };
 
-const js::Class FileObject::class_ = {
-    "File",
-    JSCLASS_HAS_RESERVED_SLOTS(FileObject::NUM_SLOTS),
+static const js::ClassOps FileObjectClassOps = {
     nullptr,               /* addProperty */
     nullptr,               /* delProperty */
     nullptr,               /* getProperty */
     nullptr,               /* setProperty */
     nullptr,               /* enumerate */
     nullptr,               /* resolve */
     nullptr,               /* mayResolve */
     FileObject::finalize,  /* finalize */
     nullptr,               /* call */
     nullptr,               /* hasInstance */
     nullptr,               /* construct */
     nullptr                /* trace */
 };
 
+const js::Class FileObject::class_ = {
+    "File",
+    JSCLASS_HAS_RESERVED_SLOTS(FileObject::NUM_SLOTS),
+    &FileObjectClassOps
+};
+
 static FileObject*
 redirect(JSContext* cx, HandleString relFilename, RCFile** globalFile)
 {
     RootedString filename(cx, ResolvePath(cx, relFilename, RootRelative));
     if (!filename)
         return nullptr;
     JSAutoByteString filenameABS(cx, filename);
     if (!filenameABS)
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2741,26 +2741,30 @@ sandbox_resolve(JSContext* cx, HandleObj
     if (!JS_GetProperty(cx, obj, "lazy", &v))
         return false;
 
     if (ToBoolean(v))
         return JS_ResolveStandardClass(cx, obj, id, resolvedp);
     return true;
 }
 
-static const JSClass sandbox_class = {
-    "sandbox",
-    JSCLASS_GLOBAL_FLAGS,
+static const JSClassOps sandbox_classOps = {
     nullptr, nullptr, nullptr, nullptr,
     sandbox_enumerate, sandbox_resolve,
     nullptr, nullptr,
     nullptr, nullptr, nullptr,
     JS_GlobalObjectTraceHook
 };
 
+static const JSClass sandbox_class = {
+    "sandbox",
+    JSCLASS_GLOBAL_FLAGS,
+    &sandbox_classOps
+};
+
 static void
 SetStandardCompartmentOptions(JS::CompartmentOptions& options)
 {
     options.behaviors().setVersion(JSVERSION_DEFAULT);
     options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory);
 }
 
 static JSObject*
@@ -6021,25 +6025,29 @@ global_resolve(JSContext* cx, HandleObje
 }
 
 static bool
 global_mayResolve(const JSAtomState& names, jsid id, JSObject* maybeObj)
 {
     return JS_MayResolveStandardClass(names, id, maybeObj);
 }
 
-static const JSClass global_class = {
-    "global", JSCLASS_GLOBAL_FLAGS,
+static const JSClassOps global_classOps = {
     nullptr, nullptr, nullptr, nullptr,
     global_enumerate, global_resolve, global_mayResolve,
     nullptr,
     nullptr, nullptr, nullptr,
     JS_GlobalObjectTraceHook
 };
 
+static const JSClass global_class = {
+    "global", JSCLASS_GLOBAL_FLAGS,
+    &global_classOps
+};
+
 /*
  * Define a FakeDOMObject constructor. It returns an object with a getter,
  * setter and method with attached JitInfo. This object can be used to test
  * IonMonkey DOM optimizations in the shell.
  */
 static const uint32_t DOM_OBJECT_SLOT = 0;
 
 static bool
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -670,53 +670,61 @@ ArgumentsObject::objectMovedDuringMinorG
 }
 
 /*
  * The classes below collaborate to lazily reflect and synchronize actual
  * argument values, argument count, and callee function object stored in a
  * stack frame with their corresponding property values in the frame's
  * arguments object.
  */
-const Class MappedArgumentsObject::class_ = {
-    "Arguments",
-    JSCLASS_DELAY_METADATA_BUILDER |
-    JSCLASS_HAS_RESERVED_SLOTS(MappedArgumentsObject::RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
-    JSCLASS_SKIP_NURSERY_FINALIZE |
-    JSCLASS_BACKGROUND_FINALIZE,
+const ClassOps MappedArgumentsObject::classOps_ = {
     nullptr,                 /* addProperty */
     ArgumentsObject::obj_delProperty,
     nullptr,                 /* getProperty */
     nullptr,                 /* setProperty */
     MappedArgumentsObject::obj_enumerate,
     MappedArgumentsObject::obj_resolve,
     nullptr,                 /* mayResolve  */
     ArgumentsObject::finalize,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     ArgumentsObject::trace
 };
 
+const Class MappedArgumentsObject::class_ = {
+    "Arguments",
+    JSCLASS_DELAY_METADATA_BUILDER |
+    JSCLASS_HAS_RESERVED_SLOTS(MappedArgumentsObject::RESERVED_SLOTS) |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
+    JSCLASS_SKIP_NURSERY_FINALIZE |
+    JSCLASS_BACKGROUND_FINALIZE,
+    &MappedArgumentsObject::classOps_
+};
+
 /*
  * Unmapped arguments is significantly less magical than mapped arguments, so
  * it is represented by a different class while sharing some functionality.
  */
-const Class UnmappedArgumentsObject::class_ = {
-    "Arguments",
-    JSCLASS_DELAY_METADATA_BUILDER |
-    JSCLASS_HAS_RESERVED_SLOTS(UnmappedArgumentsObject::RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
-    JSCLASS_SKIP_NURSERY_FINALIZE |
-    JSCLASS_BACKGROUND_FINALIZE,
+const ClassOps UnmappedArgumentsObject::classOps_ = {
     nullptr,                 /* addProperty */
     ArgumentsObject::obj_delProperty,
     nullptr,                 /* getProperty */
     nullptr,                 /* setProperty */
     UnmappedArgumentsObject::obj_enumerate,
     UnmappedArgumentsObject::obj_resolve,
     nullptr,                 /* mayResolve  */
     ArgumentsObject::finalize,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     ArgumentsObject::trace
 };
+
+const Class UnmappedArgumentsObject::class_ = {
+    "Arguments",
+    JSCLASS_DELAY_METADATA_BUILDER |
+    JSCLASS_HAS_RESERVED_SLOTS(UnmappedArgumentsObject::RESERVED_SLOTS) |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
+    JSCLASS_SKIP_NURSERY_FINALIZE |
+    JSCLASS_BACKGROUND_FINALIZE,
+    &UnmappedArgumentsObject::classOps_
+};
--- a/js/src/vm/ArgumentsObject.h
+++ b/js/src/vm/ArgumentsObject.h
@@ -324,16 +324,18 @@ class ArgumentsObject : public NativeObj
     static void MaybeForwardToCallObject(AbstractFramePtr frame, ArgumentsObject* obj,
                                          ArgumentsData* data);
     static void MaybeForwardToCallObject(jit::JitFrameLayout* frame, HandleObject callObj,
                                          ArgumentsObject* obj, ArgumentsData* data);
 };
 
 class MappedArgumentsObject : public ArgumentsObject
 {
+    static const ClassOps classOps_;
+
   public:
     static const Class class_;
 
     /*
      * Stores arguments.callee, or MagicValue(JS_ARGS_HOLE) if the callee has
      * been cleared.
      */
     const js::Value& callee() const {
@@ -347,16 +349,18 @@ class MappedArgumentsObject : public Arg
 
   private:
     static bool obj_enumerate(JSContext* cx, HandleObject obj);
     static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp);
 };
 
 class UnmappedArgumentsObject : public ArgumentsObject
 {
+    static const ClassOps classOps_;
+
   public:
     static const Class class_;
 
   private:
     static bool obj_enumerate(JSContext* cx, HandleObject obj);
     static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp);
 };
 
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -91,39 +91,43 @@ js::ToClampedIndex(JSContext* cx, Handle
  * ArrayBufferObject (base)
  */
 
 const Class ArrayBufferObject::protoClass = {
     "ArrayBufferPrototype",
     JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer)
 };
 
+const ClassOps ArrayBufferObject::classOps_ = {
+    nullptr,        /* addProperty */
+    nullptr,        /* delProperty */
+    nullptr,        /* getProperty */
+    nullptr,        /* setProperty */
+    nullptr,        /* enumerate */
+    nullptr,        /* resolve */
+    nullptr,        /* mayResolve */
+    ArrayBufferObject::finalize,
+    nullptr,        /* call        */
+    nullptr,        /* hasInstance */
+    nullptr,        /* construct   */
+    ArrayBufferObject::trace,
+};
+
 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,
-    nullptr,                 /* addProperty */
-    nullptr,                 /* delProperty */
-    nullptr,                 /* getProperty */
-    nullptr,                 /* setProperty */
-    nullptr,                 /* enumerate */
-    nullptr,                 /* resolve */
-    nullptr,                 /* mayResolve */
-    ArrayBufferObject::finalize,
-    nullptr,        /* call        */
-    nullptr,        /* hasInstance */
-    nullptr,        /* construct   */
-    ArrayBufferObject::trace,
+    &ArrayBufferObject::classOps_,
     JS_NULL_CLASS_SPEC,
     &ArrayBufferObjectClassExtension
 };
 
 const JSFunctionSpec ArrayBufferObject::jsfuncs[] = {
     JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2,0),
     JS_FS_END
 };
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -97,16 +97,18 @@ class ArrayBufferObjectMaybeShared : pub
  * ArrayBufferObject (or really the underlying memory) /is not racy/: the
  * memory is private to a single worker.
  */
 class ArrayBufferObject : public ArrayBufferObjectMaybeShared
 {
     static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
     static bool fun_slice_impl(JSContext* cx, const CallArgs& args);
 
+    static const ClassOps classOps_;
+
   public:
     static const uint8_t DATA_SLOT = 0;
     static const uint8_t BYTE_LENGTH_SLOT = 1;
     static const uint8_t FIRST_VIEW_SLOT = 2;
     static const uint8_t FLAGS_SLOT = 3;
 
     static const uint8_t RESERVED_SLOTS = 4;
 
--- a/js/src/vm/ArrayObject-inl.h
+++ b/js/src/vm/ArrayObject-inl.h
@@ -36,17 +36,17 @@ ArrayObject::setLength(ExclusiveContext*
 ArrayObject::createArrayInternal(ExclusiveContext* cx, gc::AllocKind kind, gc::InitialHeap heap,
                                  HandleShape shape, HandleObjectGroup group,
                                  AutoSetNewObjectMetadata&)
 {
     // Create a new array and initialize everything except for its elements.
     MOZ_ASSERT(shape && group);
     MOZ_ASSERT(group->clasp() == shape->getObjectClass());
     MOZ_ASSERT(group->clasp() == &ArrayObject::class_);
-    MOZ_ASSERT_IF(group->clasp()->finalize, heap == gc::TenuredHeap);
+    MOZ_ASSERT_IF(group->clasp()->hasFinalize(), heap == gc::TenuredHeap);
     MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(),
                   heap == js::gc::TenuredHeap);
     MOZ_ASSERT(group->clasp()->shouldDelayMetadataBuilder());
 
     // Arrays can use their fixed slots to store elements, so can't have shapes
     // which allow named properties to be stored in the fixed slots.
     MOZ_ASSERT(shape->numFixedSlots() == 0);
 
--- a/js/src/vm/Debugger-inl.h
+++ b/js/src/vm/Debugger-inl.h
@@ -24,17 +24,17 @@ js::Debugger::onLeaveFrame(JSContext* cx
         ok = slowPathOnLeaveFrame(cx, frame, pc, ok);
     MOZ_ASSERT(!inFrameMaps(frame));
     return ok;
 }
 
 /* static */ inline js::Debugger*
 js::Debugger::fromJSObject(const JSObject* obj)
 {
-    MOZ_ASSERT(js::GetObjectClass(obj) == &jsclass);
+    MOZ_ASSERT(js::GetObjectClass(obj) == &class_);
     return (Debugger*) obj->as<NativeObject>().getPrivate();
 }
 
 /* static */ inline bool
 js::Debugger::checkNoExecute(JSContext* cx, HandleScript script)
 {
     if (!cx->compartment()->isDebuggee() || !cx->runtime()->noExecuteDebuggerTop)
         return true;
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -54,68 +54,171 @@ using mozilla::DebugOnly;
 using mozilla::MakeScopeExit;
 using mozilla::Maybe;
 using mozilla::Some;
 using mozilla::Nothing;
 using mozilla::Variant;
 using mozilla::AsVariant;
 
 
-/*** Forward declarations ************************************************************************/
-
-extern const Class DebuggerFrame_class;
+/*** Forward declarations, ClassOps and Classes **************************************************/
+
+static void DebuggerFrame_finalize(FreeOp* fop, JSObject* obj);
+static void DebuggerEnv_trace(JSTracer* trc, JSObject* obj);
+static void DebuggerObject_trace(JSTracer* trc, JSObject* obj);
+static void DebuggerScript_trace(JSTracer* trc, JSObject* obj);
+static void DebuggerSource_trace(JSTracer* trc, JSObject* obj);
 
 enum {
     JSSLOT_DEBUGFRAME_OWNER,
     JSSLOT_DEBUGFRAME_ARGUMENTS,
     JSSLOT_DEBUGFRAME_ONSTEP_HANDLER,
     JSSLOT_DEBUGFRAME_ONPOP_HANDLER,
     JSSLOT_DEBUGFRAME_COUNT
 };
 
-extern const Class DebuggerArguments_class;
+static const ClassOps DebuggerFrame_classOps = {
+    nullptr,    /* addProperty */
+    nullptr,    /* delProperty */
+    nullptr,    /* getProperty */
+    nullptr,    /* setProperty */
+    nullptr,    /* enumerate   */
+    nullptr,    /* resolve     */
+    nullptr,    /* mayResolve  */
+    DebuggerFrame_finalize,
+    nullptr,    /* call        */
+    nullptr,    /* hasInstance */
+    nullptr,    /* construct   */
+    nullptr,    /* trace   */
+};
+
+static const Class DebuggerFrame_class = {
+    "Frame",
+    JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT),
+    &DebuggerFrame_classOps
+};
 
 enum {
     JSSLOT_DEBUGARGUMENTS_FRAME,
     JSSLOT_DEBUGARGUMENTS_COUNT
 };
 
-extern const Class DebuggerEnv_class;
+static const Class DebuggerArguments_class = {
+    "Arguments",
+    JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT)
+};
 
 enum {
     JSSLOT_DEBUGENV_OWNER,
     JSSLOT_DEBUGENV_COUNT
 };
 
-extern const Class DebuggerObject_class;
+static const ClassOps DebuggerEnv_classOps = {
+    nullptr,    /* addProperty */
+    nullptr,    /* delProperty */
+    nullptr,    /* getProperty */
+    nullptr,    /* setProperty */
+    nullptr,    /* enumerate   */
+    nullptr,    /* resolve     */
+    nullptr,    /* mayResolve  */
+    nullptr,    /* finalize    */
+    nullptr,    /* call        */
+    nullptr,    /* hasInstance */
+    nullptr,    /* construct   */
+    DebuggerEnv_trace
+};
+
+static const Class DebuggerEnv_class = {
+    "Environment",
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGENV_COUNT),
+    &DebuggerEnv_classOps
+};
 
 enum {
     JSSLOT_DEBUGOBJECT_OWNER,
     JSSLOT_DEBUGOBJECT_COUNT
 };
 
-extern const Class DebuggerScript_class;
+static const ClassOps DebuggerObject_classOps = {
+    nullptr,    /* addProperty */
+    nullptr,    /* delProperty */
+    nullptr,    /* getProperty */
+    nullptr,    /* setProperty */
+    nullptr,    /* enumerate   */
+    nullptr,    /* resolve     */
+    nullptr,    /* mayResolve  */
+    nullptr,    /* finalize    */
+    nullptr,    /* call        */
+    nullptr,    /* hasInstance */
+    nullptr,    /* construct   */
+    DebuggerObject_trace
+};
+
+static const Class DebuggerObject_class = {
+    "Object",
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT),
+    &DebuggerObject_classOps
+};
 
 enum {
     JSSLOT_DEBUGSCRIPT_OWNER,
     JSSLOT_DEBUGSCRIPT_COUNT
 };
 
-extern const Class DebuggerSource_class;
+static const ClassOps DebuggerScript_classOps = {
+    nullptr,    /* addProperty */
+    nullptr,    /* delProperty */
+    nullptr,    /* getProperty */
+    nullptr,    /* setProperty */
+    nullptr,    /* enumerate   */
+    nullptr,    /* resolve     */
+    nullptr,    /* mayResolve  */
+    nullptr,    /* finalize    */
+    nullptr,    /* call        */
+    nullptr,    /* hasInstance */
+    nullptr,    /* construct   */
+    DebuggerScript_trace
+};
+
+static const Class DebuggerScript_class = {
+    "Script",
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT),
+    &DebuggerScript_classOps
+};
 
 enum {
     JSSLOT_DEBUGSOURCE_OWNER,
     JSSLOT_DEBUGSOURCE_TEXT,
     JSSLOT_DEBUGSOURCE_COUNT
 };
 
-void DebuggerObject_trace(JSTracer* trc, JSObject* obj);
-void DebuggerEnv_trace(JSTracer* trc, JSObject* obj);
-void DebuggerScript_trace(JSTracer* trc, JSObject* obj);
-void DebuggerSource_trace(JSTracer* trc, JSObject* obj);
+static const ClassOps DebuggerSource_classOps = {
+    nullptr,    /* addProperty */
+    nullptr,    /* delProperty */
+    nullptr,    /* getProperty */
+    nullptr,    /* setProperty */
+    nullptr,    /* enumerate   */
+    nullptr,    /* resolve     */
+    nullptr,    /* mayResolve  */
+    nullptr,    /* finalize    */
+    nullptr,    /* call        */
+    nullptr,    /* hasInstance */
+    nullptr,    /* construct   */
+    DebuggerSource_trace
+};
+
+static const Class DebuggerSource_class = {
+    "Source",
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSOURCE_COUNT),
+    &DebuggerSource_classOps
+};
 
 
 /*** Utils ***************************************************************************************/
 
 static inline bool
 EnsureFunctionHasScript(JSContext* cx, HandleFunction fun)
 {
     if (fun->isInterpretedLazy()) {
@@ -2785,35 +2888,45 @@ Debugger::findZoneEdges(Zone* zone, js::
 Debugger::finalize(FreeOp* fop, JSObject* obj)
 {
     Debugger* dbg = fromJSObject(obj);
     if (!dbg)
         return;
     fop->delete_(dbg);
 }
 
-const Class Debugger::jsclass = {
+const ClassOps Debugger::classOps_ = {
+    nullptr,    /* addProperty */
+    nullptr,    /* delProperty */
+    nullptr,    /* getProperty */
+    nullptr,    /* setProperty */
+    nullptr,    /* enumerate   */
+    nullptr,    /* resolve     */
+    nullptr,    /* mayResolve  */
+    Debugger::finalize,
+    nullptr,    /* call        */
+    nullptr,    /* hasInstance */
+    nullptr,    /* construct   */
+    Debugger::traceObject
+};
+
+const Class Debugger::class_ = {
     "Debugger",
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT),
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, Debugger::finalize,
-    nullptr,              /* call        */
-    nullptr,              /* hasInstance */
-    nullptr,              /* construct   */
-    Debugger::traceObject
+    &Debugger::classOps_
 };
 
 /* static */ Debugger*
 Debugger::fromThisValue(JSContext* cx, const CallArgs& args, const char* fnname)
 {
     JSObject* thisobj = NonNullObject(cx, args.thisv());
     if (!thisobj)
         return nullptr;
-    if (thisobj->getClass() != &Debugger::jsclass) {
+    if (thisobj->getClass() != &Debugger::class_) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                              "Debugger", fnname, thisobj->getClass()->name);
         return nullptr;
     }
 
     /*
      * Forbid Debugger.prototype, which is of the Debugger JSClass but isn't
      * really a Debugger object. The prototype object is distinguished by
@@ -3390,23 +3503,23 @@ Debugger::construct(JSContext* cx, unsig
     }
 
     /* Get Debugger.prototype. */
     RootedValue v(cx);
     RootedObject callee(cx, &args.callee());
     if (!GetProperty(cx, callee, callee, cx->names().prototype, &v))
         return false;
     RootedNativeObject proto(cx, &v.toObject().as<NativeObject>());
-    MOZ_ASSERT(proto->getClass() == &Debugger::jsclass);
+    MOZ_ASSERT(proto->getClass() == &Debugger::class_);
     /*
      * Make the new Debugger object. Each one has a reference to
      * Debugger.{Frame,Object,Script,Memory}.prototype in reserved slots. The
      * rest of the reserved slots are for hooks; they default to undefined.
      */
-    RootedNativeObject obj(cx, NewNativeObjectWithGivenProto(cx, &Debugger::jsclass, proto));
+    RootedNativeObject obj(cx, NewNativeObjectWithGivenProto(cx, &Debugger::class_, proto));
     if (!obj)
         return false;
     for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP; slot++)
         obj->setReservedSlot(slot, proto->getReservedSlot(slot));
     obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, NullValue());
 
     Debugger* debugger;
     {
@@ -4879,28 +4992,16 @@ DebuggerScript_trace(JSTracer* trc, JSOb
             TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &wasm,
                                                        "Debugger.Script wasm referent");
             MOZ_ASSERT(wasm->is<WasmModuleObject>());
             obj->as<NativeObject>().setPrivateUnbarriered(wasm);
         }
     }
 }
 
-const Class DebuggerScript_class = {
-    "Script",
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT),
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr,              /* call        */
-    nullptr,              /* hasInstance */
-    nullptr,              /* construct   */
-    DebuggerScript_trace
-};
-
 class DebuggerScriptSetPrivateMatcher
 {
     NativeObject* obj_;
   public:
     explicit DebuggerScriptSetPrivateMatcher(NativeObject* obj) : obj_(obj) { }
     using ReturnType = void;
     ReturnType match(HandleScript script) { obj_->setPrivateGCThing(script); }
     ReturnType match(Handle<WasmModuleObject*> module) { obj_->setPrivateGCThing(module); }
@@ -6152,28 +6253,16 @@ DebuggerSource_trace(JSTracer* trc, JSOb
      */
     if (JSObject *referent = GetSourceReferentRawObject(obj)) {
         TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
                                                    "Debugger.Source referent");
         obj->as<NativeObject>().setPrivateUnbarriered(referent);
     }
 }
 
-const Class DebuggerSource_class = {
-    "Source",
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSOURCE_COUNT),
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr,              /* call        */
-    nullptr,              /* hasInstance */
-    nullptr,              /* construct   */
-    DebuggerSource_trace
-};
-
 class SetDebuggerSourcePrivateMatcher
 {
     NativeObject* obj_;
   public:
     explicit SetDebuggerSourcePrivateMatcher(NativeObject* obj) : obj_(obj) { }
     using ReturnType = void;
     ReturnType match(HandleScriptSource source) { obj_->setPrivateGCThing(source); }
     ReturnType match(Handle<WasmModuleObject*> module) { obj_->setPrivateGCThing(module); }
@@ -6715,22 +6804,16 @@ DebuggerFrame_maybeDecrementFrameScriptS
 }
 
 static void
 DebuggerFrame_finalize(FreeOp* fop, JSObject* obj)
 {
     DebuggerFrame_freeScriptFrameIterData(fop, obj);
 }
 
-const Class DebuggerFrame_class = {
-    "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT),
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, DebuggerFrame_finalize
-};
-
 static NativeObject*
 CheckThisFrame(JSContext* cx, const CallArgs& args, const char* fnname, bool checkLive)
 {
     JSObject* thisobj = NonNullObject(cx, args.thisv());
     if (!thisobj)
         return nullptr;
     if (thisobj->getClass() != &DebuggerFrame_class) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
@@ -6944,20 +7027,16 @@ DebuggerFrame_getOlder(JSContext* cx, un
                 return false;
             return dbg->getScriptFrame(cx, iter, args.rval());
         }
     }
     args.rval().setNull();
     return true;
 }
 
-const Class DebuggerArguments_class = {
-    "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT)
-};
-
 /* The getter used for each element of frame.arguments. See DebuggerFrame_getArguments. */
 static bool
 DebuggerArguments_getArg(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     int32_t i = args.callee().as<JSFunction>().getExtendedSlot(0).toInt32();
 
     /* Check that the this value is an Arguments object. */
@@ -7484,28 +7563,16 @@ DebuggerObject_trace(JSTracer* trc, JSOb
      */
     if (JSObject* referent = (JSObject*) obj->as<NativeObject>().getPrivate()) {
         TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
                                                    "Debugger.Object referent");
         obj->as<NativeObject>().setPrivateUnbarriered(referent);
     }
 }
 
-const Class DebuggerObject_class = {
-    "Object",
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT),
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr,              /* call        */
-    nullptr,              /* hasInstance */
-    nullptr,              /* construct   */
-    DebuggerObject_trace
-};
-
 static NativeObject*
 DebuggerObject_checkThis(JSContext* cx, const CallArgs& args, const char* fnname)
 {
     JSObject* thisobj = NonNullObject(cx, args.thisv());
     if (!thisobj)
         return nullptr;
     if (thisobj->getClass() != &DebuggerObject_class) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
@@ -8705,28 +8772,16 @@ DebuggerEnv_trace(JSTracer* trc, JSObjec
      */
     if (Env* referent = (JSObject*) obj->as<NativeObject>().getPrivate()) {
         TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
                                                    "Debugger.Environment referent");
         obj->as<NativeObject>().setPrivateUnbarriered(referent);
     }
 }
 
-const Class DebuggerEnv_class = {
-    "Environment",
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGENV_COUNT),
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr,              /* call        */
-    nullptr,              /* hasInstance */
-    nullptr,              /* construct   */
-    DebuggerEnv_trace
-};
-
 static NativeObject*
 DebuggerEnv_checkThis(JSContext* cx, const CallArgs& args, const char* fnname,
                       bool requireDebuggee = true)
 {
     JSObject* thisobj = NonNullObject(cx, args.thisv());
     if (!thisobj)
         return nullptr;
     if (thisobj->getClass() != &DebuggerEnv_class) {
@@ -9201,17 +9256,17 @@ JS_DefineDebuggerObject(JSContext* cx, H
     RootedObject debuggeeWouldRunProto(cx);
     RootedValue debuggeeWouldRunCtor(cx);
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
 
     objProto = global->getOrCreateObjectPrototype(cx);
     if (!objProto)
         return false;
     debugProto = InitClass(cx, obj,
-                           objProto, &Debugger::jsclass, Debugger::construct,
+                           objProto, &Debugger::class_, Debugger::construct,
                            1, Debugger::properties, Debugger::methods, nullptr,
                            Debugger::static_methods, debugCtor.address());
     if (!debugProto)
         return false;
 
     frameProto = InitClass(cx, debugCtor, objProto, &DebuggerFrame_class,
                            DebuggerFrame_construct, 0,
                            DebuggerFrame_properties, DebuggerFrame_methods,
@@ -9305,17 +9360,17 @@ JS::dbg::onPromiseSettled(JSContext* cx,
     Debugger::slowPathPromiseHook(cx, Debugger::OnPromiseSettled, promise);
 }
 
 JS_PUBLIC_API(bool)
 JS::dbg::IsDebugger(JSObject& obj)
 {
     JSObject* unwrapped = CheckedUnwrap(&obj);
     return unwrapped &&
-           js::GetObjectClass(unwrapped) == &Debugger::jsclass &&
+           js::GetObjectClass(unwrapped) == &Debugger::class_ &&
            js::Debugger::fromJSObject(unwrapped) != nullptr;
 }
 
 JS_PUBLIC_API(bool)
 JS::dbg::GetDebuggeeGlobals(JSContext* cx, JSObject& dbgObj, AutoObjectVector& vector)
 {
     MOZ_ASSERT(IsDebugger(dbgObj));
     js::Debugger* dbg = js::Debugger::fromJSObject(CheckedUnwrap(&dbgObj));
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -554,17 +554,18 @@ class Debugger : private mozilla::Linked
 
     GlobalObject* unwrapDebuggeeArgument(JSContext* cx, const Value& v);
 
     static void traceObject(JSTracer* trc, JSObject* obj);
     void trace(JSTracer* trc);
     static void finalize(FreeOp* fop, JSObject* obj);
     void markCrossCompartmentEdges(JSTracer* tracer);
 
-    static const Class jsclass;
+    static const ClassOps classOps_;
+    static const Class class_;
 
     static bool getHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which);
     static bool setHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which);
 
     static Debugger* fromThisValue(JSContext* cx, const CallArgs& ca, const char* fnname);
     static bool getEnabled(JSContext* cx, unsigned argc, Value* vp);
     static bool setEnabled(JSContext* cx, unsigned argc, Value* vp);
     static bool getOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -288,17 +288,17 @@ GlobalObject::initBuiltinConstructor(JSC
     AddTypePropertyId(cx, global, id, ObjectValue(*ctor));
     return true;
 }
 
 GlobalObject*
 GlobalObject::createInternal(JSContext* cx, const Class* clasp)
 {
     MOZ_ASSERT(clasp->flags & JSCLASS_IS_GLOBAL);
-    MOZ_ASSERT(clasp->trace == JS_GlobalObjectTraceHook);
+    MOZ_ASSERT(clasp->isTrace(JS_GlobalObjectTraceHook));
 
     JSObject* obj = NewObjectWithGivenProto(cx, clasp, nullptr, SingletonObject);
     if (!obj)
         return nullptr;
 
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
     MOZ_ASSERT(global->isUnqualifiedVarObj());
 
@@ -615,21 +615,32 @@ js::DefinePropertiesAndFunctions(JSConte
 }
 
 static void
 GlobalDebuggees_finalize(FreeOp* fop, JSObject* obj)
 {
     fop->delete_((GlobalObject::DebuggerVector*) obj->as<NativeObject>().getPrivate());
 }
 
+static const ClassOps
+GlobalDebuggees_classOps = {
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    GlobalDebuggees_finalize
+};
+
 static const Class
 GlobalDebuggees_class = {
     "GlobalDebuggee", JSCLASS_HAS_PRIVATE,
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, GlobalDebuggees_finalize
+    &GlobalDebuggees_classOps
 };
 
 GlobalObject::DebuggerVector*
 GlobalObject::getDebuggers() const
 {
     Value debuggers = getReservedSlot(DEBUGGERS);
     if (debuggers.isUndefined())
         return nullptr;
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -182,24 +182,28 @@ js::CancelOffThreadIonCompile(JSCompartm
         if (CompiledScriptMatches(compartment, script, builder->script())) {
             builder->script()->baselineScript()->removePendingIonBuilder(builder->script());
             jit::FinishOffThreadBuilder(nullptr, builder);
         }
         builder = next;
     }
 }
 
-static const JSClass parseTaskGlobalClass = {
-    "internal-parse-task-global", JSCLASS_GLOBAL_FLAGS,
+static const JSClassOps parseTaskGlobalClassOps = {
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr,
     JS_GlobalObjectTraceHook
 };
 
+static const JSClass parseTaskGlobalClass = {
+    "internal-parse-task-global", JSCLASS_GLOBAL_FLAGS,
+    &parseTaskGlobalClassOps
+};
+
 ParseTask::ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
                      JSContext* initCx, const char16_t* chars, size_t length,
                      JS::OffThreadCompileCallback callback, void* callbackData)
   : kind(kind), cx(cx), options(initCx), chars(chars), length(length),
     alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     exclusiveContextGlobal(initCx->runtime(), exclusiveContextGlobal),
     callback(callback), callbackData(callbackData),
     script(initCx->runtime()), sourceObject(initCx->runtime()),
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -571,18 +571,18 @@ TypeOfObjectOperation(JSObject* obj, JSR
     JSType type = js::TypeOfObject(obj);
     return TypeName(type, *rt->commonNames);
 }
 
 static MOZ_ALWAYS_INLINE bool
 InitElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleValue idval, HandleValue val)
 {
     MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE));
-    MOZ_ASSERT(!obj->getClass()->getProperty);
-    MOZ_ASSERT(!obj->getClass()->setProperty);
+    MOZ_ASSERT(!obj->getClass()->getGetProperty());
+    MOZ_ASSERT(!obj->getClass()->getSetProperty());
 
     RootedId id(cx);
     if (!ToPropertyKey(cx, idval, &id))
         return false;
 
     unsigned flags = JSOp(*pc) == JSOP_INITHIDDENELEM ? 0 : JSPROP_ENUMERATE;
     return DefineProperty(cx, obj, id, val, nullptr, nullptr, flags);
 }
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -532,17 +532,17 @@ js::Invoke(JSContext* cx, const Value& t
     return true;
 }
 
 static bool
 InternalConstruct(JSContext* cx, const AnyConstructArgs& args)
 {
     MOZ_ASSERT(args.array() + args.length() + 1 == args.end(),
                "must pass constructing arguments to a construction attempt");
-    MOZ_ASSERT(!JSFunction::class_.construct);
+    MOZ_ASSERT(!JSFunction::class_.getConstruct());
 
     // Callers are responsible for enforcing these preconditions.
     MOZ_ASSERT(IsConstructor(args.calleev()),
                "trying to construct a value that isn't a constructor");
     MOZ_ASSERT(IsConstructor(args.CallArgs::newTarget()),
                "provided new.target value must be a constructor");
 
     JSObject& callee = args.callee();
@@ -715,18 +715,18 @@ js::Execute(JSContext* cx, HandleScript 
                          NullFramePtr() /* evalInFrame */, rval);
 }
 
 bool
 js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp)
 {
     const Class* clasp = obj->getClass();
     RootedValue local(cx, v);
-    if (clasp->hasInstance)
-        return clasp->hasInstance(cx, obj, &local, bp);
+    if (JSHasInstanceOp hasInstance = clasp->getHasInstance())
+        return hasInstance(cx, obj, &local, bp);
 
     RootedValue val(cx, ObjectValue(*obj));
     ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
                         JSDVG_SEARCH_STACK, val, nullptr);
     return false;
 }
 
 static inline bool
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -389,26 +389,26 @@ CallResolveOp(JSContext* cx, HandleNativ
     if (resolving.alreadyStarted()) {
         // Already resolving id in obj, suppress recursion.
         *recursedp = true;
         return true;
     }
     *recursedp = false;
 
     bool resolved = false;
-    if (!obj->getClass()->resolve(cx, obj, id, &resolved))
+    if (!obj->getClass()->getResolve()(cx, obj, id, &resolved))
         return false;
 
     if (!resolved)
         return true;
 
     // Assert the mayResolve hook, if there is one, returns true for this
     // property.
-    MOZ_ASSERT_IF(obj->getClass()->mayResolve,
-                  obj->getClass()->mayResolve(cx->names(), id, obj));
+    MOZ_ASSERT_IF(obj->getClass()->getMayResolve(),
+                  obj->getClass()->getMayResolve()(cx->names(), id, obj));
 
     if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) {
         MarkDenseOrTypedArrayElementFound<CanGC>(propp);
         return true;
     }
 
     MOZ_ASSERT(!obj->is<TypedArrayObject>());
 
@@ -416,27 +416,27 @@ CallResolveOp(JSContext* cx, HandleNativ
     return true;
 }
 
 static MOZ_ALWAYS_INLINE bool
 ClassMayResolveId(const JSAtomState& names, const Class* clasp, jsid id, JSObject* maybeObj)
 {
     MOZ_ASSERT_IF(maybeObj, maybeObj->getClass() == clasp);
 
-    if (!clasp->resolve) {
+    if (!clasp->getResolve()) {
         // Sanity check: we should only have a mayResolve hook if we have a
         // resolve hook.
-        MOZ_ASSERT(!clasp->mayResolve, "Class with mayResolve hook but no resolve hook");
+        MOZ_ASSERT(!clasp->getMayResolve(), "Class with mayResolve hook but no resolve hook");
         return false;
     }
 
-    if (clasp->mayResolve) {
+    if (JSMayResolveOp mayResolve = clasp->getMayResolve()) {
         // Tell the analysis our mayResolve hooks won't trigger GC.
         JS::AutoSuppressGCAnalysis nogc;
-        if (!clasp->mayResolve(names, id, maybeObj))
+        if (!mayResolve(names, id, maybeObj))
             return false;
     }
 
     return true;
 }
 
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE bool
@@ -472,18 +472,17 @@ LookupOwnPropertyInline(ExclusiveContext
     // Check for a native property.
     if (Shape* shape = obj->lookup(cx, id)) {
         propp.set(shape);
         *donep = true;
         return true;
     }
 
     // id was not found in obj. Try obj's resolve hook, if any.
-    if (obj->getClass()->resolve)
-    {
+    if (obj->getClass()->getResolve()) {
         if (!cx->shouldBeJSContext() || !allowGC)
             return false;
 
         bool recursed;
         if (!CallResolveOp(cx->asJSContext(),
                            MaybeRooted<NativeObject*, allowGC>::toHandle(obj),
                            MaybeRooted<jsid, allowGC>::toHandle(id),
                            MaybeRooted<Shape*, allowGC>::toMutableHandle(propp),
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -995,17 +995,17 @@ js::NativeLookupOwnProperty<NoGC>(Exclus
                                   FakeMutableHandle<Shape*> propp);
 
 /*** [[DefineOwnProperty]] ***********************************************************************/
 
 static inline bool
 CallAddPropertyHook(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape,
                     HandleValue value)
 {
-    if (JSAddPropertyOp addProperty = obj->getClass()->addProperty) {
+    if (JSAddPropertyOp addProperty = obj->getClass()->getAddProperty()) {
         if (!cx->shouldBeJSContext())
             return false;
 
         RootedId id(cx, shape->propid());
         if (!CallJSAddPropertyOp(cx->asJSContext(), addProperty, obj, id, value)) {
             obj->removeProperty(cx, shape->propid());
             return false;
         }
@@ -1021,17 +1021,17 @@ CallAddPropertyHookDense(ExclusiveContex
     if (obj->is<ArrayObject>()) {
         ArrayObject* arr = &obj->as<ArrayObject>();
         uint32_t length = arr->length();
         if (index >= length)
             arr->setLength(cx, index + 1);
         return true;
     }
 
-    if (JSAddPropertyOp addProperty = obj->getClass()->addProperty) {
+    if (JSAddPropertyOp addProperty = obj->getClass()->getAddProperty()) {
         if (!cx->shouldBeJSContext())
             return false;
 
         if (!obj->maybeCopyElementsForWrite(cx))
             return false;
 
         RootedId id(cx, INT_TO_JSID(index));
         if (!CallJSAddPropertyOp(cx->asJSContext(), addProperty, obj, id, value)) {
@@ -1868,17 +1868,17 @@ static bool
 GetNonexistentProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
                        HandleValue receiver, IsNameLookup nameLookup, MutableHandleValue vp)
 {
     vp.setUndefined();
 
     // Non-standard extension: Call the getProperty hook. If it sets vp to a
     // value other than undefined, we're done. If not, fall through to the
     // warning/error checks below.
-    if (JSGetterOp getProperty = obj->getClass()->getProperty) {
+    if (JSGetterOp getProperty = obj->getClass()->getGetProperty()) {
         if (!CallJSGetterOp(cx, getProperty, obj, id, vp))
             return false;
 
         if (!vp.isUndefined())
             return true;
     }
 
     // If we are doing a name lookup, this is a ReferenceError.
@@ -2175,18 +2175,18 @@ js::SetPropertyByDefining(JSContext* cx,
     if (!PurgeScopeChain(cx, receiver, id))
         return false;
 
     // Steps 5.e.iii-iv. and 5.f.i. Define the new data property.
     unsigned attrs =
         existing
         ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
         : JSPROP_ENUMERATE;
-    JSGetterOp getter = clasp->getProperty;
-    JSSetterOp setter = clasp->setProperty;
+    JSGetterOp getter = clasp->getGetProperty();
+    JSSetterOp setter = clasp->getSetProperty();
     MOZ_ASSERT(getter != JS_PropertyStub);
     MOZ_ASSERT(setter != JS_StrictPropertyStub);
     if (!DefineProperty(cx, receiver, id, v, getter, setter, attrs, result))
         return false;
 
     // If the receiver is native, there is one more legacy wrinkle: the class
     // JSSetterOp is called after defining the new property.
     if (setter && receiver->is<NativeObject>()) {
@@ -2446,26 +2446,26 @@ js::NativeDeleteProperty(JSContext* cx, 
     RootedShape shape(cx);
     if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
         return false;
 
     // Step 4.
     if (!shape) {
         // If no property call the class's delProperty hook, passing succeeded
         // as the result parameter. This always succeeds when there is no hook.
-        return CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, result);
+        return CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id, result);
     }
 
     cx->runtime()->gc.poke();
 
     // Step 6. Non-configurable property.
     if (GetShapeAttributes(obj, shape) & JSPROP_PERMANENT)
         return result.failCantDelete();
 
-    if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, result))
+    if (!CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id, result))
         return false;
     if (!result)
         return true;
 
     // Step 5.
     if (IsImplicitDenseOrTypedArrayElement(shape)) {
         // Typed array elements are non-configurable.
         MOZ_ASSERT(!obj->is<TypedArrayObject>());
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -1275,20 +1275,18 @@ class PlainObject : public NativeObject
   public:
     static const js::Class class_;
 };
 
 inline void
 NativeObject::privateWriteBarrierPre(void** oldval)
 {
     JS::shadow::Zone* shadowZone = this->shadowZoneFromAnyThread();
-    if (shadowZone->needsIncrementalBarrier()) {
-        if (*oldval && getClass()->trace)
-            getClass()->trace(shadowZone->barrierTracer(), this);
-    }
+    if (shadowZone->needsIncrementalBarrier() && *oldval && getClass()->hasTrace())
+        getClass()->doTrace(shadowZone->barrierTracer(), this);
 }
 
 #ifdef DEBUG
 static inline bool
 IsObjectValueInCompartment(Value v, JSCompartment* comp)
 {
     if (!v.isObject())
         return true;
--- a/js/src/vm/PIC.cpp
+++ b/js/src/vm/PIC.cpp
@@ -289,31 +289,35 @@ ForOfPIC_finalize(FreeOp* fop, JSObject*
 
 static void
 ForOfPIC_traceObject(JSTracer* trc, JSObject* obj)
 {
     if (ForOfPIC::Chain* chain = ForOfPIC::fromJSObject(&obj->as<NativeObject>()))
         chain->mark(trc);
 }
 
-const Class ForOfPIC::jsclass = {
-    "ForOfPIC", JSCLASS_HAS_PRIVATE,
+static const ClassOps ForOfPICClassOps = {
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr, ForOfPIC_finalize,
     nullptr,              /* call        */
     nullptr,              /* hasInstance */
     nullptr,              /* construct   */
     ForOfPIC_traceObject
 };
 
+const Class ForOfPIC::class_ = {
+    "ForOfPIC", JSCLASS_HAS_PRIVATE,
+    &ForOfPICClassOps
+};
+
 /* static */ NativeObject*
 js::ForOfPIC::createForOfPICObject(JSContext* cx, Handle<GlobalObject*> global)
 {
     assertSameCompartment(cx, global);
-    NativeObject* obj = NewNativeObjectWithGivenProto(cx, &ForOfPIC::jsclass, nullptr);
+    NativeObject* obj = NewNativeObjectWithGivenProto(cx, &ForOfPIC::class_, nullptr);
     if (!obj)
         return nullptr;
     ForOfPIC::Chain* chain = cx->new_<ForOfPIC::Chain>();
     if (!chain)
         return nullptr;
     obj->setPrivate(chain);
     return obj;
 }
--- a/js/src/vm/PIC.h
+++ b/js/src/vm/PIC.h
@@ -248,22 +248,22 @@ struct ForOfPIC
         // Reset the PIC and all info associated with it.
         void reset(JSContext* cx);
 
         // Erase the stub chain.
         void eraseChain();
     };
 
     // Class for object that holds ForOfPIC chain.
-    static const Class jsclass;
+    static const Class class_;
 
     static NativeObject* createForOfPICObject(JSContext* cx, Handle<GlobalObject*> global);
 
     static inline Chain* fromJSObject(NativeObject* obj) {
-        MOZ_ASSERT(js::GetObjectClass(obj) == &ForOfPIC::jsclass);
+        MOZ_ASSERT(js::GetObjectClass(obj) == &ForOfPIC::class_);
         return (ForOfPIC::Chain*) obj->getPrivate();
     }
     static inline Chain* getOrCreate(JSContext* cx) {
         NativeObject* obj = cx->global()->getForOfPICObject();
         if (obj)
             return fromJSObject(obj);
         return create(cx);
     }
--- a/js/src/vm/ProxyObject.h
+++ b/js/src/vm/ProxyObject.h
@@ -86,18 +86,18 @@ class ProxyObject : public JSObject
         // they should be treated as such.
 
         // proxy_Trace is just a trivial wrapper around ProxyObject::trace for
         // friend api exposure.
 
         // Proxy classes are not allowed to have call or construct hooks directly. Their
         // callability is instead decided by handler()->isCallable().
         return clasp->isProxy() &&
-               clasp->trace == proxy_Trace &&
-               !clasp->call && !clasp->construct;
+               clasp->isTrace(proxy_Trace) &&
+               !clasp->getCall() && !clasp->getConstruct();
     }
 
   public:
     static unsigned grayLinkExtraSlot(JSObject* obj);
 
     void renew(JSContext* cx, const BaseProxyHandler* handler, Value priv);
 
     static void trace(JSTracer* trc, JSObject* obj);
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -180,42 +180,46 @@ RegExpObject::trace(JSTracer* trc, JSObj
         !obj->asTenured().zone()->isPreservingCode())
     {
         obj->as<RegExpObject>().NativeObject::setPrivate(nullptr);
     } else {
         shared->trace(trc);
     }
 }
 
+static const ClassOps RegExpObjectClassOps = {
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* getProperty */
+    nullptr, /* setProperty */
+    nullptr, /* enumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    nullptr, /* finalize */
+    nullptr, /* call */
+    nullptr, /* hasInstance */
+    nullptr, /* construct */
+    RegExpObject::trace,
+};
+
 static const ClassSpec RegExpObjectClassSpec = {
     GenericCreateConstructor<js::regexp_construct, 2, gc::AllocKind::FUNCTION>,
     CreateRegExpPrototype,
     nullptr,
     js::regexp_static_props,
     js::regexp_methods,
     js::regexp_properties
 };
 
 const Class RegExpObject::class_ = {
     js_RegExp_str,
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
-    nullptr, /* addProperty */
-    nullptr, /* delProperty */
-    nullptr, /* getProperty */
-    nullptr, /* setProperty */
-    nullptr, /* enumerate */
-    nullptr, /* resolve */
-    nullptr, /* mayResolve */
-    nullptr, /* finalize */
-    nullptr, /* call */
-    nullptr, /* hasInstance */
-    nullptr, /* construct */
-    RegExpObject::trace,
+    &RegExpObjectClassOps,
     &RegExpObjectClassSpec
 };
 
 RegExpObject*
 RegExpObject::create(ExclusiveContext* cx, const char16_t* chars, size_t length, RegExpFlag flags,
                      TokenStream* tokenStream, LifoAlloc& alloc)
 {
     RootedAtom source(cx, AtomizeChars(cx, chars, length));
--- a/js/src/vm/RegExpStatics.cpp
+++ b/js/src/vm/RegExpStatics.cpp
@@ -29,33 +29,37 @@ resc_finalize(FreeOp* fop, JSObject* obj
 static void
 resc_trace(JSTracer* trc, JSObject* obj)
 {
     void* pdata = obj->as<RegExpStaticsObject>().getPrivate();
     if (pdata)
         static_cast<RegExpStatics*>(pdata)->mark(trc);
 }
 
-const Class RegExpStaticsObject::class_ = {
-    "RegExpStatics",
-    JSCLASS_HAS_PRIVATE,
+static const ClassOps RegExpStaticsObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     resc_finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     resc_trace
 };
 
+const Class RegExpStaticsObject::class_ = {
+    "RegExpStatics",
+    JSCLASS_HAS_PRIVATE,
+    &RegExpStaticsObjectClassOps
+};
+
 RegExpStaticsObject*
 RegExpStatics::create(ExclusiveContext* cx, Handle<GlobalObject*> parent)
 {
     RegExpStaticsObject* obj = NewObjectWithGivenProto<RegExpStaticsObject>(cx, nullptr);
     if (!obj)
         return nullptr;
     RegExpStatics* res = cx->new_<RegExpStatics>();
     if (!res)
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -284,16 +284,31 @@ SavedFrame::finishSavedFrameInit(JSConte
 {
     // The only object with the SavedFrame::class_ that doesn't have a source
     // should be the prototype.
     proto->as<NativeObject>().setReservedSlot(SavedFrame::JSSLOT_SOURCE, NullValue());
 
     return FreezeObject(cx, proto);
 }
 
+static const ClassOps SavedFrameClassOps = {
+    nullptr,                    // addProperty
+    nullptr,                    // delProperty
+    nullptr,                    // getProperty
+    nullptr,                    // setProperty
+    nullptr,                    // enumerate
+    nullptr,                    // resolve
+    nullptr,                    // mayResolve
+    SavedFrame::finalize,       // finalize
+    nullptr,                    // call
+    nullptr,                    // hasInstance
+    nullptr,                    // construct
+    nullptr,                    // trace
+};
+
 const ClassSpec SavedFrame::classSpec_ = {
     GenericCreateConstructor<SavedFrame::construct, 0, gc::AllocKind::FUNCTION>,
     GenericCreatePrototype,
     SavedFrame::staticFunctions,
     nullptr,
     SavedFrame::protoFunctions,
     SavedFrame::protoAccessors,
     SavedFrame::finishSavedFrameInit,
@@ -301,28 +316,17 @@ const ClassSpec SavedFrame::classSpec_ =
 };
 
 /* static */ const Class SavedFrame::class_ = {
     "SavedFrame",
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame) |
     JSCLASS_IS_ANONYMOUS,
-    nullptr,                    // addProperty
-    nullptr,                    // delProperty
-    nullptr,                    // getProperty
-    nullptr,                    // setProperty
-    nullptr,                    // enumerate
-    nullptr,                    // resolve
-    nullptr,                    // mayResolve
-    SavedFrame::finalize,       // finalize
-    nullptr,                    // call
-    nullptr,                    // hasInstance
-    nullptr,                    // construct
-    nullptr,                    // trace
+    &SavedFrameClassOps,
     &SavedFrame::classSpec_
 };
 
 /* static */ const JSFunctionSpec
 SavedFrame::staticFunctions[] = {
     JS_FS_END
 };
 
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -453,28 +453,17 @@ const ObjectOps ModuleEnvironmentObject:
     ModuleEnvironmentObject::enumerate,
     nullptr
 };
 
 const Class ModuleEnvironmentObject::class_ = {
     "ModuleEnvironmentObject",
     JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS,
-    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_OPS,
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     &ModuleEnvironmentObject::objectOps_
 };
 
 /* static */ ModuleEnvironmentObject*
 ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module)
 {
@@ -693,18 +682,18 @@ DeclEnvObject::createTemplateObject(JSCo
     if (!obj)
         return nullptr;
 
     // Assign a fixed slot to a property with the same name as the lambda.
     Rooted<jsid> id(cx, AtomToId(fun->atom()));
     const Class* clasp = obj->getClass();
     unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY;
 
-    JSGetterOp getter = clasp->getProperty;
-    JSSetterOp setter = clasp->setProperty;
+    JSGetterOp getter = clasp->getGetProperty();
+    JSSetterOp setter = clasp->getSetProperty();
     MOZ_ASSERT(getter != JS_PropertyStub);
     MOZ_ASSERT(setter != JS_StrictPropertyStub);
 
     if (!NativeObject::putProperty(cx, obj, id, getter, setter, lambdaSlot(), attrs, 0))
         return nullptr;
 
     MOZ_ASSERT(!obj->hasDynamicSlots());
     return obj;
@@ -881,28 +870,17 @@ static const ObjectOps DynamicWithObject
     nullptr,             /* enumerate (native enumeration of target doesn't work) */
     nullptr,
 };
 
 const Class DynamicWithObject::class_ = {
     "With",
     JSCLASS_HAS_RESERVED_SLOTS(DynamicWithObject::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS,
-    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_OPS,
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     &DynamicWithObjectObjectOps
 };
 
 /* static */ StaticEvalScope*
 StaticEvalScope::create(JSContext* cx, HandleObject enclosing)
 {
@@ -1123,28 +1101,17 @@ ClonedBlockObject::thisValue() const
 
 static_assert(StaticBlockScope::RESERVED_SLOTS == ClonedBlockObject::RESERVED_SLOTS,
               "static block scopes and dynamic block environments share a Class");
 
 const Class ClonedBlockObject::class_ = {
     "Block",
     JSCLASS_HAS_RESERVED_SLOTS(ClonedBlockObject::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS,
-    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_OPS,
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     JS_NULL_OBJECT_OPS
 };
 
 template<XDRMode mode>
 bool
 js::XDRStaticBlockScope(XDRState<mode>* xdr, HandleObject enclosingScope,
@@ -1396,28 +1363,17 @@ static const ObjectOps RuntimeLexicalErr
     nullptr,             /* enumerate (native enumeration of target doesn't work) */
     nullptr,             /* this */
 };
 
 const Class RuntimeLexicalErrorObject::class_ = {
     "RuntimeLexicalError",
     JSCLASS_HAS_RESERVED_SLOTS(RuntimeLexicalErrorObject::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS,
-    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_OPS,
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     &RuntimeLexicalErrorObjectObjectOps
 };
 
 /*****************************************************************************/
 
 // Any name atom for a function which will be added as a DeclEnv object to the
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2638,24 +2638,28 @@ JSRuntime::createSelfHostingGlobal(JSCon
     JS::CompartmentOptions options;
     options.creationOptions().setZone(JS::FreshZone);
     options.behaviors().setDiscardSource(true);
 
     JSCompartment* compartment = NewCompartment(cx, nullptr, nullptr, options);
     if (!compartment)
         return nullptr;
 
-    static const Class shgClass = {
-        "self-hosting-global", JSCLASS_GLOBAL_FLAGS,
+    static const ClassOps shgClassOps = {
         nullptr, nullptr, nullptr, nullptr,
         nullptr, nullptr, nullptr, nullptr,
         nullptr, nullptr, nullptr,
         JS_GlobalObjectTraceHook
     };
 
+    static const Class shgClass = {
+        "self-hosting-global", JSCLASS_GLOBAL_FLAGS,
+        &shgClassOps
+    };
+
     AutoCompartment ac(cx, compartment);
     Rooted<GlobalObject*> shg(cx, GlobalObject::createInternal(cx, &shgClass));
     if (!shg)
         return nullptr;
 
     cx->runtime()->selfHostingGlobal_ = shg;
     compartment->isSelfHosting = true;
     compartment->setIsSystem(true);
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -341,33 +341,37 @@ SharedArrayBufferObject::addSizeOfExclud
         buf.byteLength() / buf.rawBufferObject()->refcount();
 }
 
 const Class SharedArrayBufferObject::protoClass = {
     "SharedArrayBufferPrototype",
     JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer)
 };
 
-const Class SharedArrayBufferObject::class_ = {
-    "SharedArrayBuffer",
-    JSCLASS_DELAY_METADATA_BUILDER |
-    JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer),
+static const ClassOps SharedArrayBufferObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     SharedArrayBufferObject::Finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr, /* trace */
+};
+
+const Class SharedArrayBufferObject::class_ = {
+    "SharedArrayBuffer",
+    JSCLASS_DELAY_METADATA_BUILDER |
+    JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer),
+    &SharedArrayBufferObjectClassOps,
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT
 };
 
 JSObject*
 js::InitSharedArrayBufferClass(JSContext* cx, HandleObject obj)
 {
     MOZ_ASSERT(obj->isNative());
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2461,17 +2461,17 @@ TemporaryTypeSet::propertyNeedsBarrier(C
     return false;
 }
 
 bool
 js::ClassCanHaveExtraProperties(const Class* clasp)
 {
     if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_)
         return false;
-    return clasp->resolve
+    return clasp->getResolve()
         || clasp->getOpsLookupProperty()
         || clasp->getOpsGetProperty()
         || IsTypedArrayClass(clasp);
 }
 
 void
 TypeZone::processPendingRecompiles(FreeOp* fop, RecompileInfoVector& recompiles)
 {
@@ -3404,17 +3404,17 @@ PreliminaryObjectArray::sweep()
             // group's Class is not going to change in that case.
             JSObject* obj = *ptr;
             GlobalObject* global = obj->compartment()->unsafeUnbarrieredMaybeGlobal();
             if (global && !obj->isSingleton()) {
                 JSObject* objectProto = GetBuiltinPrototypePure(global, JSProto_Object);
                 obj->setGroup(objectProto->groupRaw());
                 MOZ_ASSERT(obj->is<NativeObject>());
                 MOZ_ASSERT(obj->getClass() == objectProto->getClass());
-                MOZ_ASSERT(!obj->getClass()->finalize);
+                MOZ_ASSERT(!obj->getClass()->hasFinalize());
             }
 
             *ptr = nullptr;
         }
     }
 }
 
 void
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1139,28 +1139,17 @@ TypedArrayObject::sharedTypedArrayProtot
     // Actually ({}).toString.call(%TypedArray%.prototype) should throw,
     // because %TypedArray%.prototype lacks the the typed array internal
     // slots.  (It's not clear this is desirable -- particularly applied to
     // the actual typed array prototypes, see below -- but it's what ES6
     // draft 20140824 requires.)  But this is about as much as we can do
     // until we implement @@toStringTag.
     "???",
     JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray),
-    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_OPS,
     &TypedArrayObjectSharedTypedArrayPrototypeClassSpec
 };
 
 template<typename T>
 bool
 ArrayBufferObject::createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args)
 {
     typedef TypedArrayObjectTemplate<T> ArrayType;
@@ -2164,16 +2153,31 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uin
 IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint8Clamped, uint8_t, uint8_clamped)
 IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Int16, int16_t, int16_t)
 IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint16, uint16_t, uint16_t)
 IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Int32, int32_t, int32_t)
 IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint32, uint32_t, uint32_t)
 IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float32, float, float)
 IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double)
 
+static const ClassOps TypedArrayClassOps = {
+    nullptr,                 /* addProperty */
+    nullptr,                 /* delProperty */
+    nullptr,                 /* getProperty */
+    nullptr,                 /* setProperty */
+    nullptr,                 /* enumerate   */
+    nullptr,                 /* resolve     */
+    nullptr,                 /* mayResolve  */
+    nullptr,                 /* finalize    */
+    nullptr,                 /* call        */
+    nullptr,                 /* hasInstance */
+    nullptr,                 /* construct   */
+    TypedArrayObject::trace, /* trace  */
+};
+
 #define IMPL_TYPED_ARRAY_CLASS_SPEC(_type)                                     \
 {                                                                              \
     _type##Array::createConstructor,                                           \
     _type##Array::createPrototype,                                             \
     nullptr,                                                                   \
     nullptr,                                                                   \
     nullptr,                                                                   \
     nullptr,                                                                   \
@@ -2195,28 +2199,17 @@ static const ClassSpec TypedArrayObjectC
 
 #define IMPL_TYPED_ARRAY_CLASS(_type)                                          \
 {                                                                              \
     #_type "Array",                                                            \
     JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) |             \
     JSCLASS_HAS_PRIVATE |                                                      \
     JSCLASS_HAS_CACHED_PROTO(JSProto_##_type##Array) |                         \
     JSCLASS_DELAY_METADATA_BUILDER,                                            \
-    nullptr,                 /* addProperty */                                 \
-    nullptr,                 /* delProperty */                                 \
-    nullptr,                 /* getProperty */                                 \
-    nullptr,                 /* setProperty */                                 \
-    nullptr,                 /* enumerate   */                                 \
-    nullptr,                 /* resolve     */                                 \
-    nullptr,                 /* mayResolve  */                                 \
-    nullptr,                 /* finalize    */                                 \
-    nullptr,                 /* call        */                                 \
-    nullptr,                 /* hasInstance */                                 \
-    nullptr,                 /* construct   */                                 \
-    TypedArrayObject::trace, /* trace  */                                      \
+    &TypedArrayClassOps,                                                       \
     &TypedArrayObjectClassSpecs[Scalar::Type::_type]                           \
 }
 
 const Class TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = {
     IMPL_TYPED_ARRAY_CLASS(Int8),
     IMPL_TYPED_ARRAY_CLASS(Uint8),
     IMPL_TYPED_ARRAY_CLASS(Int16),
     IMPL_TYPED_ARRAY_CLASS(Uint16),
@@ -2264,28 +2257,17 @@ static const ClassSpec TypedArrayObjectP
      * Actually ({}).toString.call(Uint8Array.prototype) should throw, because
      * Uint8Array.prototype lacks the the typed array internal slots.  (Same as
      * with %TypedArray%.prototype.)  It's not clear this is desirable (see
      * above), but it's what we've always done, so keep doing it till we
      * implement @@toStringTag or ES6 changes.
      */ \
     #_type "ArrayPrototype", \
     JSCLASS_HAS_CACHED_PROTO(JSProto_##_type##Array), \
-    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_OPS, \
     &TypedArrayObjectProtoClassSpecs[Scalar::Type::_type] \
 }
 
 const Class TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = {
     IMPL_TYPED_ARRAY_PROTO_CLASS(Int8),
     IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8),
     IMPL_TYPED_ARRAY_PROTO_CLASS(Int16),
     IMPL_TYPED_ARRAY_PROTO_CLASS(Uint16),
@@ -2304,35 +2286,39 @@ TypedArrayObject::isOriginalLengthGetter
 
 const Class DataViewObject::protoClass = {
     "DataViewPrototype",
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_DataView)
 };
 
-const Class DataViewObject::class_ = {
-    "DataView",
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_DataView),
+static const ClassOps DataViewObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     nullptr, /* finalize */
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     ArrayBufferViewObject::trace
 };
 
+const Class DataViewObject::class_ = {
+    "DataView",
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_DataView),
+    &DataViewObjectClassOps
+};
+
 const JSFunctionSpec DataViewObject::jsfuncs[] = {
     JS_FN("getInt8",    DataViewObject::fun_getInt8,      1,0),
     JS_FN("getUint8",   DataViewObject::fun_getUint8,     1,0),
     JS_FN("getInt16",   DataViewObject::fun_getInt16,     2,0),
     JS_FN("getUint16",  DataViewObject::fun_getUint16,    2,0),
     JS_FN("getInt32",   DataViewObject::fun_getInt32,     2,0),
     JS_FN("getUint32",  DataViewObject::fun_getUint32,    2,0),
     JS_FN("getFloat32", DataViewObject::fun_getFloat32,   2,0),
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -900,16 +900,31 @@ UnboxedPlainObject::obj_enumerate(JSCont
     return true;
 }
 
 const Class UnboxedExpandoObject::class_ = {
     "UnboxedExpandoObject",
     0
 };
 
+static const ClassOps UnboxedPlainObjectClassOps = {
+    nullptr,        /* addProperty */
+    nullptr,        /* delProperty */
+    nullptr,        /* getProperty */
+    nullptr,        /* setProperty */
+    nullptr,        /* enumerate   */
+    nullptr,        /* resolve     */
+    nullptr,        /* mayResolve  */
+    nullptr,        /* finalize    */
+    nullptr,        /* call        */
+    nullptr,        /* hasInstance */
+    nullptr,        /* construct   */
+    UnboxedPlainObject::trace,
+};
+
 static const ObjectOps UnboxedPlainObjectObjectOps = {
     UnboxedPlainObject::obj_lookupProperty,
     UnboxedPlainObject::obj_defineProperty,
     UnboxedPlainObject::obj_hasProperty,
     UnboxedPlainObject::obj_getProperty,
     UnboxedPlainObject::obj_setProperty,
     UnboxedPlainObject::obj_getOwnPropertyDescriptor,
     UnboxedPlainObject::obj_deleteProperty,
@@ -920,28 +935,17 @@ static const ObjectOps UnboxedPlainObjec
     nullptr    /* funToString */
 };
 
 const Class UnboxedPlainObject::class_ = {
     js_Object_str,
     Class::NON_NATIVE |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
     JSCLASS_DELAY_METADATA_BUILDER,
-    nullptr,        /* addProperty */
-    nullptr,        /* delProperty */
-    nullptr,        /* getProperty */
-    nullptr,        /* setProperty */
-    nullptr,        /* enumerate   */
-    nullptr,        /* resolve     */
-    nullptr,        /* mayResolve  */
-    nullptr,        /* finalize    */
-    nullptr,        /* call        */
-    nullptr,        /* hasInstance */
-    nullptr,        /* construct   */
-    UnboxedPlainObject::trace,
+    &UnboxedPlainObjectClassOps,
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     &UnboxedPlainObjectObjectOps
 };
 
 /////////////////////////////////////////////////////////////////////
 // UnboxedArrayObject
 /////////////////////////////////////////////////////////////////////
@@ -1586,16 +1590,31 @@ UnboxedArrayObject::obj_enumerate(JSCont
     }
 
     if (!enumerableOnly && !properties.append(NameToId(cx->names().length)))
         return false;
 
     return true;
 }
 
+static const ClassOps UnboxedArrayObjectClassOps = {
+    nullptr,        /* addProperty */
+    nullptr,        /* delProperty */
+    nullptr,        /* getProperty */
+    nullptr,        /* setProperty */
+    nullptr,        /* enumerate   */
+    nullptr,        /* resolve     */
+    nullptr,        /* mayResolve  */
+    UnboxedArrayObject::finalize,
+    nullptr,        /* call        */
+    nullptr,        /* hasInstance */
+    nullptr,        /* construct   */
+    UnboxedArrayObject::trace,
+};
+
 static const ClassExtension UnboxedArrayObjectClassExtension = {
     nullptr,    /* weakmapKeyDelegateOp */
     UnboxedArrayObject::objectMoved
 };
 
 static const ObjectOps UnboxedArrayObjectObjectOps = {
     UnboxedArrayObject::obj_lookupProperty,
     UnboxedArrayObject::obj_defineProperty,
@@ -1611,28 +1630,17 @@ static const ObjectOps UnboxedArrayObjec
     nullptr    /* funToString */
 };
 
 const Class UnboxedArrayObject::class_ = {
     "Array",
     Class::NON_NATIVE |
     JSCLASS_SKIP_NURSERY_FINALIZE |
     JSCLASS_BACKGROUND_FINALIZE,
-    nullptr,        /* addProperty */
-    nullptr,        /* delProperty */
-    nullptr,        /* getProperty */
-    nullptr,        /* setProperty */
-    nullptr,        /* enumerate   */
-    nullptr,        /* resolve     */
-    nullptr,        /* mayResolve  */
-    UnboxedArrayObject::finalize,
-    nullptr,        /* call        */
-    nullptr,        /* hasInstance */
-    nullptr,        /* construct   */
-    UnboxedArrayObject::trace,
+    &UnboxedArrayObjectClassOps,
     JS_NULL_CLASS_SPEC,
     &UnboxedArrayObjectClassExtension,
     &UnboxedArrayObjectObjectOps
 };
 
 /////////////////////////////////////////////////////////////////////
 // API
 /////////////////////////////////////////////////////////////////////
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -545,44 +545,52 @@ 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::ClassOps SandboxClassOps = {
+    nullptr, nullptr, nullptr, nullptr,
+    sandbox_enumerate, sandbox_resolve,
+    nullptr,        /* mayResolve */
+    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),
-    nullptr, nullptr, nullptr, nullptr,
-    sandbox_enumerate, sandbox_resolve,
-    nullptr,        /* mayResolve */
-    sandbox_finalize,
-    nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
+    &SandboxClassOps,
     JS_NULL_CLASS_SPEC,
     &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),
+static const js::ClassOps SandboxWriteToProtoClassOps = {
     sandbox_addProperty, nullptr, nullptr, nullptr,
     sandbox_enumerate, sandbox_resolve,
     nullptr,        /* mayResolve */
     sandbox_finalize,
     nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
+};
+
+static const js::Class SandboxWriteToProtoClass = {
+    "Sandbox",
+    XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
+    &SandboxWriteToProtoClassOps,
     JS_NULL_CLASS_SPEC,
     &SandboxClassExtension,
     JS_NULL_OBJECT_OPS
 };
 
 static const JSFunctionSpec SandboxFunctions[] = {
     JS_FS("dump",    SandboxDump,    1,0),
     JS_FS("debug",   SandboxDebug,   1,0),
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -789,20 +789,24 @@ env_resolve(JSContext* cx, HandleObject 
         if (!JS_DefinePropertyById(cx, obj, id, valstr, JSPROP_ENUMERATE)) {
             return false;
         }
         *resolvedp = true;
     }
     return true;
 }
 
+static const JSClassOps env_classOps = {
+    nullptr, nullptr, nullptr, env_setProperty,
+    env_enumerate, env_resolve
+};
+
 static const JSClass env_class = {
     "environment", JSCLASS_HAS_PRIVATE,
-    nullptr, nullptr, nullptr, env_setProperty,
-    env_enumerate, env_resolve
+    &env_classOps
 };
 
 /***************************************************************************/
 
 typedef enum JSShellErrNum {
 #define MSG_DEF(name, number, count, exception, format) \
     name = number,
 #include "jsshell.msg"
--- a/js/xpconnect/src/XPCWrappedJSClass.cpp
+++ b/js/xpconnect/src/XPCWrappedJSClass.cpp
@@ -1423,33 +1423,37 @@ nsXPCWrappedJSClass::GetInterfaceName()
     return mName;
 }
 
 static void
 FinalizeStub(JSFreeOp* fop, JSObject* obj)
 {
 }
 
-static const JSClass XPCOutParamClass = {
-    "XPCOutParam",
-    0,
+static const JSClassOps XPCOutParamClassOps = {
     nullptr,   /* addProperty */
     nullptr,   /* delProperty */
     nullptr,   /* getProperty */
     nullptr,   /* setProperty */
     nullptr,   /* enumerate */
     nullptr,   /* resolve */
     nullptr,   /* mayResolve */
     FinalizeStub,
     nullptr,   /* call */
     nullptr,   /* hasInstance */
     nullptr,   /* construct */
     nullptr    /* trace */
 };
 
+static const JSClass XPCOutParamClass = {
+    "XPCOutParam",
+    0,
+    &XPCOutParamClassOps
+};
+
 bool
 xpc::IsOutObject(JSContext* cx, JSObject* obj)
 {
     return js::GetObjectJSClass(obj) == &XPCOutParamClass;
 }
 
 JSObject*
 xpc::NewOutObject(JSContext* cx)
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -770,18 +770,18 @@ XPCWrappedNative::Init(const XPCNativeSc
     const JSClass* jsclazz = si ? si->GetJSClass() : Jsvalify(&XPC_WN_NoHelper_JSClass);
 
     // We should have the global jsclass flag if and only if we're a global.
     MOZ_ASSERT_IF(si, !!si->GetFlags().IsGlobalObject() == !!(jsclazz->flags & JSCLASS_IS_GLOBAL));
 
     MOZ_ASSERT(jsclazz &&
                jsclazz->name &&
                jsclazz->flags &&
-               jsclazz->resolve &&
-               jsclazz->finalize, "bad class");
+               jsclazz->getResolve() &&
+               jsclazz->hasFinalize(), "bad class");
 
     // XXXbz JS_GetObjectPrototype wants an object, even though it then asserts
     // that this object is same-compartment with cx, which means it could just
     // use the cx global...
     RootedObject global(cx, CurrentGlobalOrNull(cx));
     RootedObject protoJSObject(cx, HasProto() ?
                                    GetProto()->GetJSProtoObject() :
                                    JS_GetObjectPrototype(cx, global));
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -615,49 +615,44 @@ 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::ClassOps XPC_WN_NoHelper_JSClassOps = {
+    XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty
+    XPC_WN_CantDeletePropertyStub,     // delProperty
+    nullptr,                           // getProperty
+    nullptr,                           // setProperty
+    XPC_WN_Shared_Enumerate,           // enumerate
+    XPC_WN_NoHelper_Resolve,           // resolve
+    nullptr,                           // mayResolve
+    XPC_WN_NoHelper_Finalize,          // finalize
+    nullptr,                           // call
+    nullptr,                           // construct
+    nullptr,                           // hasInstance
+    XPCWrappedNative::Trace,           // trace
+};
+
 static const js::ClassExtension XPC_WN_JSClassExtension = {
     nullptr, // weakmapKeyDelegateOp
     WrappedNativeObjectMoved
 };
 
 const js::Class XPC_WN_NoHelper_JSClass = {
-    "XPCWrappedNative_NoHelper",    // name;
+    "XPCWrappedNative_NoHelper",
     WRAPPER_FLAGS |
     JSCLASS_IS_WRAPPED_NATIVE |
-    JSCLASS_PRIVATE_IS_NSISUPPORTS, // flags
-
-    /* Mandatory non-null function pointer members. */
-    XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty
-    XPC_WN_CantDeletePropertyStub,     // delProperty
-    nullptr,                           // getProperty
-    nullptr,                           // setProperty
-
-    XPC_WN_Shared_Enumerate,           // enumerate
-    XPC_WN_NoHelper_Resolve,           // resolve
-    nullptr,                           // mayResolve
-    XPC_WN_NoHelper_Finalize,          // finalize
-
-    /* Optionally non-null members start here. */
-    nullptr,                         // call
-    nullptr,                         // construct
-    nullptr,                         // hasInstance
-    XPCWrappedNative::Trace,         // trace
+    JSCLASS_PRIVATE_IS_NSISUPPORTS,
+    &XPC_WN_NoHelper_JSClassOps,
     JS_NULL_CLASS_SPEC,
-
-    // ClassExtension
     &XPC_WN_JSClassExtension,
-
-    // ObjectOps
     JS_NULL_OBJECT_OPS
 };
 
 
 /***************************************************************************/
 
 static bool
 XPC_WN_MaybeResolvingPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v)
@@ -979,83 +974,85 @@ XPCNativeScriptableShared::XPCNativeScri
 
     mJSClass.flags = WRAPPER_FLAGS |
                      JSCLASS_PRIVATE_IS_NSISUPPORTS |
                      JSCLASS_IS_WRAPPED_NATIVE;
 
     if (mFlags.IsGlobalObject())
         mJSClass.flags |= XPCONNECT_GLOBAL_FLAGS;
 
-    JSAddPropertyOp addProperty;
+    // Initialize the js::ClassExtension.
+
+    // This is an unusual js::ClassOps: it is heap-allocated and belongs to
+    // |this|.
+    js::ClassOps* cOps = new js::ClassOps;
+    memset(cOps, 0, sizeof(js::ClassOps));
+    mJSClass.cOps = cOps;
+
     if (mFlags.WantAddProperty())
-        addProperty = XPC_WN_Helper_AddProperty;
+        cOps->addProperty = XPC_WN_Helper_AddProperty;
     else if (mFlags.UseJSStubForAddProperty())
-        addProperty = nullptr;
+        cOps->addProperty = nullptr;
     else if (mFlags.AllowPropModsDuringResolve())
-        addProperty = XPC_WN_MaybeResolvingPropertyStub;
+        cOps->addProperty = XPC_WN_MaybeResolvingPropertyStub;
     else
-        addProperty = XPC_WN_CannotModifyPropertyStub;
-    mJSClass.addProperty = addProperty;
+        cOps->addProperty = XPC_WN_CannotModifyPropertyStub;
 
-    JSDeletePropertyOp delProperty;
     if (mFlags.UseJSStubForDelProperty())
-        delProperty = nullptr;
+        cOps->delProperty = nullptr;
     else if (mFlags.AllowPropModsDuringResolve())
-        delProperty = XPC_WN_MaybeResolvingDeletePropertyStub;
+        cOps->delProperty = XPC_WN_MaybeResolvingDeletePropertyStub;
     else
-        delProperty = XPC_WN_CantDeletePropertyStub;
-    mJSClass.delProperty = delProperty;
+        cOps->delProperty = XPC_WN_CantDeletePropertyStub;
 
     if (mFlags.WantGetProperty())
-        mJSClass.getProperty = XPC_WN_Helper_GetProperty;
+        cOps->getProperty = XPC_WN_Helper_GetProperty;
     else
-        mJSClass.getProperty = nullptr;
+        cOps->getProperty = nullptr;
 
-    JSSetterOp setProperty;
     if (mFlags.WantSetProperty())
-        setProperty = XPC_WN_Helper_SetProperty;
+        cOps->setProperty = XPC_WN_Helper_SetProperty;
     else if (mFlags.UseJSStubForSetProperty())
-        setProperty = nullptr;
+        cOps->setProperty = nullptr;
     else if (mFlags.AllowPropModsDuringResolve())
-        setProperty = XPC_WN_MaybeResolvingSetPropertyStub;
+        cOps->setProperty = XPC_WN_MaybeResolvingSetPropertyStub;
     else
-        setProperty = XPC_WN_CannotModifySetPropertyStub;
-    mJSClass.setProperty = setProperty;
+        cOps->setProperty = XPC_WN_CannotModifySetPropertyStub;
 
     MOZ_ASSERT_IF(mFlags.WantEnumerate(), !mFlags.WantNewEnumerate());
     MOZ_ASSERT_IF(mFlags.WantNewEnumerate(), !mFlags.WantEnumerate());
 
     // We will use ops->enumerate set below for NewEnumerate
     if (mFlags.WantNewEnumerate())
-        mJSClass.enumerate = nullptr;
+        cOps->enumerate = nullptr;
     else if (mFlags.WantEnumerate())
-        mJSClass.enumerate = XPC_WN_Helper_Enumerate;
+        cOps->enumerate = XPC_WN_Helper_Enumerate;
     else
-        mJSClass.enumerate = XPC_WN_Shared_Enumerate;
+        cOps->enumerate = XPC_WN_Shared_Enumerate;
 
     // We have to figure out resolve strategy at call time
-    mJSClass.resolve = XPC_WN_Helper_Resolve;
+    cOps->resolve = XPC_WN_Helper_Resolve;
 
     if (mFlags.WantFinalize())
-        mJSClass.finalize = XPC_WN_Helper_Finalize;
+        cOps->finalize = XPC_WN_Helper_Finalize;
     else
-        mJSClass.finalize = XPC_WN_NoHelper_Finalize;
+        cOps->finalize = XPC_WN_NoHelper_Finalize;
 
     if (mFlags.WantCall())
-        mJSClass.call = XPC_WN_Helper_Call;
+        cOps->call = XPC_WN_Helper_Call;
     if (mFlags.WantConstruct())
-        mJSClass.construct = XPC_WN_Helper_Construct;
+        cOps->construct = XPC_WN_Helper_Construct;
 
     if (mFlags.WantHasInstance())
-        mJSClass.hasInstance = XPC_WN_Helper_HasInstance;
+        cOps->hasInstance = XPC_WN_Helper_HasInstance;
 
     if (mFlags.IsGlobalObject())
-        mJSClass.trace = JS_GlobalObjectTraceHook;
+        cOps->trace = JS_GlobalObjectTraceHook;
     else
-        mJSClass.trace = XPCWrappedNative::Trace;
+        cOps->trace = XPCWrappedNative::Trace;
 
     // Initialize the js::ClassExtension.
 
     mJSClass.ext = &XPC_WN_JSClassExtension;
 
     // Initialize the js::ObjectOps.
 
     if (mFlags.WantNewEnumerate())
@@ -1254,66 +1251,49 @@ 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);
 }
 
+static const js::ClassOps XPC_WN_ModsAllowed_Proto_JSClassOps = {
+    nullptr,                            // addProperty
+    nullptr,                            // delProperty
+    nullptr,                            // getProperty
+    nullptr,                            // setProperty
+    XPC_WN_Shared_Proto_Enumerate,      // enumerate
+    XPC_WN_ModsAllowed_Proto_Resolve,   // resolve
+    nullptr,                            // mayResolve
+    XPC_WN_Shared_Proto_Finalize,       // finalize
+    nullptr,                            // call
+    nullptr,                            // construct
+    nullptr,                            // hasInstance
+    XPC_WN_Shared_Proto_Trace,          // trace
+};
+
 static const js::ClassExtension XPC_WN_Shared_Proto_ClassExtension = {
     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;
-    nullptr,                        // getProperty;
-    nullptr,                        // setProperty;
-    XPC_WN_Shared_Proto_Enumerate,  // enumerate;
-    XPC_WN_ModsAllowed_Proto_Resolve, // resolve;
-    nullptr,                        // mayResolve;
-    XPC_WN_Shared_Proto_Finalize,   // finalize;
-
-    /* Optionally non-null members start here. */
-    nullptr,                        // call;
-    nullptr,                        // construct;
-    nullptr,                        // hasInstance;
-    XPC_WN_Shared_Proto_Trace,      // trace;
-
+    "XPC_WN_ModsAllowed_WithCall_Proto_JSClass",
+    WRAPPER_FLAGS,
+    &XPC_WN_ModsAllowed_Proto_JSClassOps,
     JS_NULL_CLASS_SPEC,
     &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. */
-    nullptr,                        // addProperty;
-    nullptr,                        // delProperty;
-    nullptr,                        // getProperty;
-    nullptr,                        // setProperty;
-    XPC_WN_Shared_Proto_Enumerate,  // enumerate;
-    XPC_WN_ModsAllowed_Proto_Resolve, // resolve;
-    nullptr,                        // mayResolve;
-    XPC_WN_Shared_Proto_Finalize,   // finalize;
-
-    /* Optionally non-null members start here. */
-    nullptr,                         // call;
-    nullptr,                         // construct;
-    nullptr,                         // hasInstance;
-    XPC_WN_Shared_Proto_Trace,      // trace;
-
+    "XPC_WN_ModsAllowed_NoCall_Proto_JSClass",
+    WRAPPER_FLAGS,
+    &XPC_WN_ModsAllowed_Proto_JSClassOps,
     JS_NULL_CLASS_SPEC,
     &XPC_WN_Shared_Proto_ClassExtension,
     XPC_WN_NoCall_ObjectOps
 };
 
 /***************************************************************************/
 
 static bool
@@ -1362,61 +1342,44 @@ XPC_WN_NoMods_Proto_Resolve(JSContext* c
                                  self->GetSet(), nullptr, nullptr,
                                  self->GetScope(),
                                  true, nullptr, nullptr, si,
                                  JSPROP_READONLY |
                                  JSPROP_PERMANENT |
                                  JSPROP_ENUMERATE, resolvedp);
 }
 
-const js::Class XPC_WN_NoMods_WithCall_Proto_JSClass = {
-    "XPC_WN_NoMods_WithCall_Proto_JSClass",    // name;
-    WRAPPER_FLAGS,                             // flags;
+static const js::ClassOps XPC_WN_NoMods_Proto_JSClassOps = {
+    XPC_WN_OnlyIWrite_Proto_AddPropertyStub,   // addProperty
+    XPC_WN_CantDeletePropertyStub,             // delProperty
+    nullptr,                                   // getProperty
+    nullptr,                                   // setProperty
+    XPC_WN_Shared_Proto_Enumerate,             // enumerate
+    XPC_WN_NoMods_Proto_Resolve,               // resolve
+    nullptr,                                   // mayResolve
+    XPC_WN_Shared_Proto_Finalize,              // finalize
+    nullptr,                                   // call
+    nullptr,                                   // construct
+    nullptr,                                   // hasInstance
+    XPC_WN_Shared_Proto_Trace,                 // trace
+};
 
-    /* Mandatory non-null function pointer members. */
-    XPC_WN_OnlyIWrite_Proto_AddPropertyStub,   // addProperty;
-    XPC_WN_CantDeletePropertyStub,             // delProperty;
-    nullptr,                                   // getProperty;
-    nullptr,                                   // setProperty;
-    XPC_WN_Shared_Proto_Enumerate,             // enumerate;
-    XPC_WN_NoMods_Proto_Resolve,               // resolve;
-    nullptr,                                   // mayResolve;
-    XPC_WN_Shared_Proto_Finalize,              // finalize;
-
-    /* Optionally non-null members start here. */
-    nullptr,                         // call;
-    nullptr,                         // construct;
-    nullptr,                         // hasInstance;
-    XPC_WN_Shared_Proto_Trace,      // trace;
-
+const js::Class XPC_WN_NoMods_WithCall_Proto_JSClass = {
+    "XPC_WN_NoMods_WithCall_Proto_JSClass",
+    WRAPPER_FLAGS,
+    &XPC_WN_NoMods_Proto_JSClassOps,
     JS_NULL_CLASS_SPEC,
     &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. */
-    XPC_WN_OnlyIWrite_Proto_AddPropertyStub,   // addProperty;
-    XPC_WN_CantDeletePropertyStub,             // delProperty;
-    nullptr,                                   // getProperty;
-    nullptr,                                   // setProperty;
-    XPC_WN_Shared_Proto_Enumerate,             // enumerate;
-    XPC_WN_NoMods_Proto_Resolve,               // resolve;
-    nullptr,                                   // mayResolve;
-    XPC_WN_Shared_Proto_Finalize,              // finalize;
-
-    /* Optionally non-null members start here. */
-    nullptr,                         // call;
-    nullptr,                         // construct;
-    nullptr,                         // hasInstance;
-    XPC_WN_Shared_Proto_Trace,      // trace;
-
+    "XPC_WN_NoMods_NoCall_Proto_JSClass",
+    WRAPPER_FLAGS,
+    &XPC_WN_NoMods_Proto_JSClassOps,
     JS_NULL_CLASS_SPEC,
     &XPC_WN_Shared_Proto_ClassExtension,
     XPC_WN_NoCall_ObjectOps
 };
 
 /***************************************************************************/
 
 static bool
@@ -1483,36 +1446,36 @@ 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::ClassOps XPC_WN_Tearoff_JSClassOps = {
+    XPC_WN_OnlyIWrite_AddPropertyStub,  // addProperty
+    XPC_WN_CantDeletePropertyStub,      // delProperty
+    nullptr,                            // getProperty
+    nullptr,                            // setProperty
+    XPC_WN_TearOff_Enumerate,           // enumerate
+    XPC_WN_TearOff_Resolve,             // resolve
+    nullptr,                            // mayResolve
+    XPC_WN_TearOff_Finalize,            // finalize
+    nullptr,                            // call
+    nullptr,                            // construct
+    nullptr,                            // hasInstance
+    nullptr,                            // trace
+};
+
 static const js::ClassExtension XPC_WN_Tearoff_JSClassExtension = {
-    nullptr,                               // weakmapKeyDelegateOp
+    nullptr,                            // weakmapKeyDelegateOp
     XPC_WN_TearOff_ObjectMoved
 };
 
 const js::Class XPC_WN_Tearoff_JSClass = {
-    "WrappedNative_TearOff",                   // name;
+    "WrappedNative_TearOff",
     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;
-    XPC_WN_TearOff_Enumerate,                  // enumerate;
-    XPC_WN_TearOff_Resolve,                    // resolve;
-    nullptr,                                   // mayResolve;
-    XPC_WN_TearOff_Finalize,                   // finalize;
-
-    /* Optionally non-null members start here. */
-    nullptr,                                   // call
-    nullptr,                                   // construct
-    nullptr,                                   // hasInstance
-    nullptr,                                   // trace
+    JSCLASS_HAS_RESERVED_SLOTS(XPC_WN_TEAROFF_RESERVED_SLOTS),
+    &XPC_WN_Tearoff_JSClassOps,
     JS_NULL_CLASS_SPEC,
-
-    // ClassExtension
     &XPC_WN_Tearoff_JSClassExtension
 };
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1645,33 +1645,37 @@ class XPCNativeScriptableShared
 public:
     const XPCNativeScriptableFlags& GetFlags() const { return mFlags; }
 
     const JSClass* GetJSClass() { return Jsvalify(&mJSClass); }
 
     XPCNativeScriptableShared(uint32_t aFlags, char* aName, bool aPopulate);
 
     ~XPCNativeScriptableShared() {
-        if (mJSClass.name)
-            free((void*)mJSClass.name);
+        free((void*)mJSClass.name);
+        free((void*)mJSClass.cOps);
         MOZ_COUNT_DTOR(XPCNativeScriptableShared);
     }
 
     char* TransferNameOwnership() {
         char* name = (char*)mJSClass.name;
         mJSClass.name = nullptr;
         return name;
     }
 
     void Mark()   { mFlags.Mark(); }
     void Unmark() { mFlags.Unmark(); }
     bool IsMarked() const { return mFlags.IsMarked(); }
 
 private:
     XPCNativeScriptableFlags mFlags;
+
+    // This is an unusual js::Class instance: its name and cOps members are
+    // heap-allocated, unlike all other instances for which they are statically
+    // allocated. So we must free them in the destructor.
     js::Class mJSClass;
 };
 
 /***************************************************************************/
 // XPCNativeScriptableInfo is used to hold the nsIXPCScriptable state for a
 // given class or instance.
 
 class XPCNativeScriptableInfo
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -1050,21 +1050,25 @@ GetExpandoObjectPrincipal(JSObject* expa
 static void
 ExpandoObjectFinalize(JSFreeOp* fop, JSObject* obj)
 {
     // Release the principal.
     nsIPrincipal* principal = GetExpandoObjectPrincipal(obj);
     NS_RELEASE(principal);
 }
 
+static const JSClassOps ExpandoObjectClassOps = {
+    nullptr, nullptr, nullptr, nullptr,
+    nullptr, nullptr, nullptr, ExpandoObjectFinalize
+};
+
 const JSClass ExpandoObjectClass = {
     "XrayExpandoObject",
     JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_EXPANDO_COUNT),
-    nullptr, nullptr, nullptr, nullptr,
-    nullptr, nullptr, nullptr, ExpandoObjectFinalize
+    &ExpandoObjectClassOps
 };
 
 bool
 XrayTraits::expandoObjectMatchesConsumer(JSContext* cx,
                                          HandleObject expandoObject,
                                          nsIPrincipal* consumerOrigin,
                                          HandleObject exclusiveGlobal)
 {
@@ -1701,24 +1705,25 @@ DOMXrayTraits::call(JSContext* cx, Handl
     const js::Class* clasp = js::GetObjectClass(obj);
     // What we have is either a WebIDL interface object, a WebIDL prototype
     // object, or a WebIDL instance object.  WebIDL prototype objects never have
     // a clasp->call.  WebIDL interface objects we want to invoke on the xray
     // compartment.  WebIDL instance objects either don't have a clasp->call or
     // are using "legacycaller", which basically means plug-ins.  We want to
     // call those on the content compartment.
     if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) {
-        if (!clasp->call) {
+        if (JSNative call = clasp->getCall()) {
+            // call it on the Xray compartment
+            if (!call(cx, args.length(), args.base()))
+                return false;
+        } else {
             RootedValue v(cx, ObjectValue(*wrapper));
             js::ReportIsNotFunction(cx, v);
             return false;
         }
-        // call it on the Xray compartment
-        if (!clasp->call(cx, args.length(), args.base()))
-            return false;
     } else {
         // This is only reached for WebIDL instance objects, and in practice
         // only for plugins.  Just call them on the content compartment.
         if (!baseInstance.call(cx, wrapper, args))
             return false;
     }
     return JS_WrapValue(cx, args.rval());
 }
@@ -1727,23 +1732,24 @@ bool
 DOMXrayTraits::construct(JSContext* cx, HandleObject wrapper,
                          const JS::CallArgs& args, const js::Wrapper& baseInstance)
 {
     RootedObject obj(cx, getTargetObject(wrapper));
     MOZ_ASSERT(mozilla::dom::HasConstructor(obj));
     const js::Class* clasp = js::GetObjectClass(obj);
     // See comments in DOMXrayTraits::call() explaining what's going on here.
     if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) {
-        if (!clasp->construct) {
+        if (JSNative construct = clasp->getConstruct()) {
+            if (!construct(cx, args.length(), args.base()))
+                return false;
+        } else {
             RootedValue v(cx, ObjectValue(*wrapper));
             js::ReportIsNotFunction(cx, v);
             return false;
         }
-        if (!clasp->construct(cx, args.length(), args.base()))
-            return false;
     } else {
         if (!baseInstance.construct(cx, wrapper, args))
             return false;
     }
     if (!args.rval().isObject() || !JS_WrapValue(cx, args.rval()))
         return false;
     return true;
 }
--- a/netwerk/base/ProxyAutoConfig.cpp
+++ b/netwerk/base/ProxyAutoConfig.cpp
@@ -661,25 +661,29 @@ private:
       return NS_ERROR_FAILURE;
 
     JS_FireOnNewGlobalObject(mContext, global);
 
     return NS_OK;
   }
 };
 
-const JSClass JSRuntimeWrapper::sGlobalClass = {
-  "PACResolutionThreadGlobal",
-  JSCLASS_GLOBAL_FLAGS,
+static const JSClassOps sJSRuntimeWrapperGlobalClassOps = {
   nullptr, nullptr, nullptr, nullptr,
   nullptr, nullptr, nullptr, nullptr,
   nullptr, nullptr, nullptr,
   JS_GlobalObjectTraceHook
 };
 
+const JSClass JSRuntimeWrapper::sGlobalClass = {
+  "PACResolutionThreadGlobal",
+  JSCLASS_GLOBAL_FLAGS,
+  &sJSRuntimeWrapperGlobalClassOps
+};
+
 void
 ProxyAutoConfig::SetThreadLocalIndex(uint32_t index)
 {
   sRunningIndex = index;
 }
 
 nsresult
 ProxyAutoConfig::Init(const nsCString &aPACURI,
--- a/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp
+++ b/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp
@@ -111,29 +111,33 @@ void Finalize(JSFreeOp *fop, JSObject *o
   nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   if (mainThread) {
     mainThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
   }
   // We may fail at dispatching to the main thread if we arrive too late
   // during shutdown. In that case, there is not much we can do.
 }
 
-static const JSClass sWitnessClass = {
-  "FinalizationWitness",
-  JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS),
+static const JSClassOps sWitnessClassOps = {
   nullptr /* addProperty */,
   nullptr /* delProperty */,
   nullptr /* getProperty */,
   nullptr /* setProperty */,
   nullptr /* enumerate */,
   nullptr /* resolve */,
   nullptr /* mayResolve */,
   Finalize /* finalize */
 };
 
+static const JSClass sWitnessClass = {
+  "FinalizationWitness",
+  JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS),
+  &sWitnessClassOps
+};
+
 bool IsWitness(JS::Handle<JS::Value> v)
 {
   return v.isObject() && JS_GetClass(&v.toObject()) == &sWitnessClass;
 }
 
 
 /**
  * JS method |forget()|
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -1244,17 +1244,17 @@ CycleCollectedJSRuntime::JSObjectsTenure
 
   for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
     nsWrapperCache* cache = iter.Get();
     JSObject* wrapper = cache->GetWrapperPreserveColor();
     MOZ_ASSERT(wrapper);
     if (!JS::ObjectIsTenured(wrapper)) {
       MOZ_ASSERT(!cache->PreservingWrapper());
       const JSClass* jsClass = js::GetObjectJSClass(wrapper);
-      jsClass->finalize(nullptr, wrapper);
+      jsClass->doFinalize(nullptr, wrapper);
     }
   }
 
 #ifdef DEBUG
 for (auto iter = mPreservedNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
   MOZ_ASSERT(JS::ObjectIsTenured(iter.Get().get()));
 }
 #endif
--- a/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp
+++ b/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp
@@ -79,24 +79,28 @@ RunTest(JSRuntime* rt, JSContext* cx, Ar
   }
 
   JS_RemoveExtraGCRootsTracer(rt, TraceArray<ArrayT>, array);
 }
 
 static void
 CreateGlobalAndRunTest(JSRuntime* rt, JSContext* cx)
 {
-  static const JSClass GlobalClass = {
-    "global", JSCLASS_GLOBAL_FLAGS,
+  static const JSClassOps GlobalClassOps = {
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr,
     JS_GlobalObjectTraceHook
   };
 
+  static const JSClass GlobalClass = {
+    "global", JSCLASS_GLOBAL_FLAGS,
+    &GlobalClassOps
+  };
+
   JS::CompartmentOptions options;
   options.behaviors().setVersion(JSVERSION_LATEST);
   JS::PersistentRootedObject global(cx);
   global = JS_NewGlobalObject(cx, &GlobalClass, nullptr, JS::FireOnNewGlobalHook, options);
   ASSERT_TRUE(global != nullptr);
 
   JSCompartment *oldCompartment = JS_EnterCompartment(cx, global);