Bug 1259194 (part 4) - Separate js::ObjectOps from js::Class. r=efaust,mrbkap,bz.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 24 Mar 2016 07:00:29 +1100
changeset 290968 a43d8b2d94db52eb484bfda5e0ee00819b48a4ef
parent 290967 3ff2b12ffedc9e74d01013d685ab5a07d7c9f87b
child 290969 2a9629aed11ca8226fb3f409556509a91c531eec
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust, mrbkap, bz
bugs1259194
milestone48.0a1
Bug 1259194 (part 4) - Separate js::ObjectOps from js::Class. r=efaust,mrbkap,bz. js::ObjectOps is often all null. When it's not all null, it's often duplicated many times among classes. By pulling it out into its own struct, and using a (possibly null) pointer in js::Class, we can save 208 KiB per process on 64-bit, and half that on 32-bit.
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/plugins/base/nsJSNPRuntime.cpp
js/public/Class.h
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/jit/IonBuilder.cpp
js/src/jit/VMFunctions.cpp
js/src/jsarray.cpp
js/src/jsfriendapi.h
js/src/jsfun.cpp
js/src/jsiter.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsstr.cpp
js/src/proxy/Proxy.cpp
js/src/vm/Interpreter-inl.h
js/src/vm/NativeObject.cpp
js/src/vm/NativeObject.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack-inl.h
js/src/vm/TypeInference.cpp
js/src/vm/UnboxedObject.cpp
js/src/vm/UnboxedObject.h
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
js/xpconnect/src/xpcprivate.h
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1792,16 +1792,31 @@ NativePropertyHooks sEmptyNativeProperty
     nullptr,
     nullptr
   },
   prototypes::id::_ID_Count,
   constructors::id::_ID_Count,
   nullptr
 };
 
+const js::ObjectOps sInterfaceObjectClassObjectOps = {
+  nullptr, /* lookupProperty */
+  nullptr, /* defineProperty */
+  nullptr, /* hasProperty */
+  nullptr, /* getProperty */
+  nullptr, /* setProperty */
+  nullptr, /* getOwnPropertyDescriptor */
+  nullptr, /* deleteProperty */
+  nullptr, /* watch */
+  nullptr, /* unwatch */
+  nullptr, /* getElements */
+  nullptr, /* enumerate */
+  InterfaceObjectToString, /* funToString */
+};
+
 bool
 GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
                        JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
                        bool* found, JS::MutableHandle<JS::Value> vp)
 {
   JS::Rooted<JSObject*> proto(cx);
   if (!js::GetObjectProto(cx, proxy, &proto)) {
     return false;
--- 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::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.
 // Note that some interface objects are not yet a JSFunction but a normal
 // JSObject with a DOMJSClass, those do not use these slots.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -695,30 +695,17 @@ class CGInterfaceObjectJSClass(CGThing):
                 nullptr,               /* mayResolve */
                 nullptr,               /* finalize */
                 ${ctorname}, /* call */
                 ${hasInstance}, /* hasInstance */
                 ${ctorname}, /* construct */
                 nullptr,               /* trace */
                 JS_NULL_CLASS_SPEC,
                 JS_NULL_CLASS_EXT,
-                {
-                  nullptr, /* lookupProperty */
-                  nullptr, /* defineProperty */
-                  nullptr, /* hasProperty */
-                  nullptr, /* getProperty */
-                  nullptr, /* setProperty */
-                  nullptr, /* getOwnPropertyDescriptor */
-                  nullptr, /* deleteProperty */
-                  nullptr, /* watch */
-                  nullptr, /* unwatch */
-                  nullptr, /* getElements */
-                  nullptr, /* enumerate */
-                  InterfaceObjectToString, /* funToString */
-                }
+                &sInterfaceObjectClassObjectOps
               },
               eInterface,
               ${prototypeID},
               ${depth},
               ${hooks},
               "function ${name}() {\\n    [native code]\\n}",
               ${protoGetter}
             };
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -213,16 +213,30 @@ 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::ObjectOps sNPObjectJSWrapperObjectOps = {
+    nullptr, // lookupProperty
+    nullptr, // defineProperty
+    nullptr, // hasProperty
+    nullptr, // getProperty
+    nullptr, // setProperty
+    nullptr, // getOwnPropertyDescriptor
+    nullptr, // deleteProperty
+    nullptr, nullptr, // watch/unwatch
+    nullptr, // getElements
+    NPObjWrapper_Enumerate,
+    nullptr,
+};
+
 const static js::Class sNPObjectJSWrapperClass =
   {
     NPRUNTIME_JSCLASS_NAME,
     JSCLASS_HAS_PRIVATE,
     NPObjWrapper_AddProperty,
     NPObjWrapper_DelProperty,
     NPObjWrapper_GetProperty,
     NPObjWrapper_SetProperty,
@@ -235,29 +249,17 @@ const static js::Class sNPObjectJSWrappe
     NPObjWrapper_Construct,
     nullptr,                                                /* trace */
     JS_NULL_CLASS_SPEC,
     {
       false,                                                /* isWrappedNative */
       nullptr,                                              /* weakmapKeyDelegateOp */
       NPObjWrapper_ObjectMoved
     },
-    {
-        nullptr, // lookupProperty
-        nullptr, // defineProperty
-        nullptr, // hasProperty
-        nullptr, // getProperty
-        nullptr, // setProperty
-        nullptr, // getOwnPropertyDescriptor
-        nullptr, // deleteProperty
-        nullptr, nullptr, // watch/unwatch
-        nullptr, // getElements
-        NPObjWrapper_Enumerate,
-        nullptr,
-    }
+    &sNPObjectJSWrapperObjectOps
   };
 
 typedef struct NPObjectMemberPrivate {
     JS::Heap<JSObject *> npobjWrapper;
     JS::Heap<JS::Value> fieldValue;
     JS::Heap<jsid> methodName;
     NPP   npp;
 } NPObjectMemberPrivate;
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -624,44 +624,42 @@ inline ClassObjectCreationOp DELEGATED_C
     return reinterpret_cast<ClassObjectCreationOp>(const_cast<ClassSpec*>(spec));
 }
 
 #define JS_NULL_CLASS_SPEC  {nullptr,nullptr,nullptr,nullptr,nullptr,nullptr}
 #define JS_NULL_CLASS_EXT   {false,nullptr}
 
 struct ObjectOps
 {
-    LookupPropertyOp    lookupProperty;
-    DefinePropertyOp    defineProperty;
-    HasPropertyOp       hasProperty;
-    GetPropertyOp       getProperty;
-    SetPropertyOp       setProperty;
-    GetOwnPropertyOp    getOwnPropertyDescriptor;
-    DeletePropertyOp    deleteProperty;
-    WatchOp             watch;
-    UnwatchOp           unwatch;
-    GetElementsOp       getElements;
-    JSNewEnumerateOp    enumerate;
-    JSFunToStringOp     funToString;
+    LookupPropertyOp lookupProperty;
+    DefinePropertyOp defineProperty;
+    HasPropertyOp    hasProperty;
+    GetPropertyOp    getProperty;
+    SetPropertyOp    setProperty;
+    GetOwnPropertyOp getOwnPropertyDescriptor;
+    DeletePropertyOp deleteProperty;
+    WatchOp          watch;
+    UnwatchOp        unwatch;
+    GetElementsOp    getElements;
+    JSNewEnumerateOp enumerate;
+    JSFunToStringOp  funToString;
 };
 
-#define JS_NULL_OBJECT_OPS                                                    \
-    {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,  \
-     nullptr, nullptr, nullptr, nullptr}
+#define JS_NULL_OBJECT_OPS nullptr
 
 } // namespace js
 
 // Classes, objects, and properties.
 
 typedef void (*JSClassInternal)();
 
 struct JSClass {
     JS_CLASS_MEMBERS(JSFinalizeOp);
 
-    void*               reserved[23];
+    void*               reserved[12];
 };
 
 #define JSCLASS_HAS_PRIVATE             (1<<0)  // objects have private slot
 #define JSCLASS_DELAY_METADATA_CALLBACK (1<<1)  // class's initialization code
                                                 // will call
                                                 // SetNewObjectMetadata itself
 #define JSCLASS_PRIVATE_IS_NSISUPPORTS  (1<<3)  // private is (nsISupports*)
 #define JSCLASS_IS_DOMJSCLASS           (1<<4)  // objects are DOM
@@ -755,17 +753,17 @@ struct JSClass {
 
 namespace js {
 
 struct Class
 {
     JS_CLASS_MEMBERS(FinalizeOp);
     ClassSpec          spec;
     ClassExtension      ext;
-    ObjectOps           ops;
+    const ObjectOps*    ops;
 
     /*
      * Objects of this class aren't native objects. They don't have Shapes that
      * describe their properties and layout. Classes using this flag must
      * provide their own property behavior, either by being proxy classes (do
      * this) or by overriding all the ObjectOps except getElements, watch and
      * unwatch (don't do this).
      */
@@ -800,16 +798,31 @@ struct Class
         return flags & JSCLASS_IS_DOMJSCLASS;
     }
 
     bool shouldDelayMetadataCallback() const {
         return flags & JSCLASS_DELAY_METADATA_CALLBACK;
     }
 
     static size_t offsetOfFlags() { return offsetof(Class, flags); }
+
+    LookupPropertyOp getOpsLookupProperty() const { return ops ? ops->lookupProperty : nullptr; }
+    DefinePropertyOp getOpsDefineProperty() const { return ops ? ops->defineProperty : nullptr; }
+    HasPropertyOp    getOpsHasProperty()    const { return ops ? ops->hasProperty    : nullptr; }
+    GetPropertyOp    getOpsGetProperty()    const { return ops ? ops->getProperty    : nullptr; }
+    SetPropertyOp    getOpsSetProperty()    const { return ops ? ops->setProperty    : nullptr; }
+    GetOwnPropertyOp getOpsGetOwnPropertyDescriptor()
+                                            const { return ops ? ops->getOwnPropertyDescriptor
+                                                                                     : nullptr; }
+    DeletePropertyOp getOpsDeleteProperty() const { return ops ? ops->deleteProperty : nullptr; }
+    WatchOp          getOpsWatch()          const { return ops ? ops->watch          : nullptr; }
+    UnwatchOp        getOpsUnwatch()        const { return ops ? ops->unwatch        : nullptr; }
+    GetElementsOp    getOpsGetElements()    const { return ops ? ops->getElements    : nullptr; }
+    JSNewEnumerateOp getOpsEnumerate()      const { return ops ? ops->enumerate      : nullptr; }
+    JSFunToStringOp  getOpsFunToString()    const { return ops ? ops->funToString    : nullptr; }
 };
 
 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");
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -2241,16 +2241,30 @@ OutlineTransparentTypedObject::getOrCrea
         return &owner().as<ArrayBufferObject>();
     return owner().as<InlineTransparentTypedObject>().getOrCreateBuffer(cx);
 }
 
 /******************************************************************************
  * Typed object classes
  */
 
+const ObjectOps TypedObject::objectOps_ = {
+    TypedObject::obj_lookupProperty,
+    TypedObject::obj_defineProperty,
+    TypedObject::obj_hasProperty,
+    TypedObject::obj_getProperty,
+    TypedObject::obj_setProperty,
+    TypedObject::obj_getOwnPropertyDescriptor,
+    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,                        \
         nullptr,        /* addProperty */                \
         nullptr,        /* delProperty */                \
         nullptr,        /* getProperty */                \
         nullptr,        /* setProperty */                \
@@ -2259,29 +2273,17 @@ OutlineTransparentTypedObject::getOrCrea
         nullptr,        /* mayResolve  */                \
         nullptr,        /* finalize    */                \
         nullptr,        /* call        */                \
         nullptr,        /* hasInstance */                \
         nullptr,        /* construct   */                \
         Trace,                                           \
         JS_NULL_CLASS_SPEC,                              \
         JS_NULL_CLASS_EXT,                               \
-        {                                                \
-            TypedObject::obj_lookupProperty,             \
-            TypedObject::obj_defineProperty,             \
-            TypedObject::obj_hasProperty,                \
-            TypedObject::obj_getProperty,                \
-            TypedObject::obj_setProperty,                \
-            TypedObject::obj_getOwnPropertyDescriptor,   \
-            TypedObject::obj_deleteProperty,             \
-            nullptr, nullptr, /* watch/unwatch */        \
-            nullptr,   /* getElements */                 \
-            TypedObject::obj_enumerate,                  \
-            nullptr, /* thisValue */                     \
-        }                                                \
+        &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,
                       JSCLASS_DELAY_METADATA_CALLBACK);
 DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject,       InlineTypedObject::obj_trace,
                       JSCLASS_DELAY_METADATA_CALLBACK);
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -494,16 +494,18 @@ class TypedObject : public JSObject
 
     static bool obj_getArrayElement(JSContext* cx,
                                     Handle<TypedObject*> typedObj,
                                     Handle<TypeDescr*> typeDescr,
                                     uint32_t index,
                                     MutableHandleValue vp);
 
   protected:
+    static const ObjectOps objectOps_;
+
     HeapPtrShape shape_;
 
     static bool obj_lookupProperty(JSContext* cx, HandleObject obj,
                                    HandleId id, MutableHandleObject objp,
                                    MutableHandleShape propp);
 
     static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
                                    Handle<PropertyDescriptor> desc,
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -7802,17 +7802,17 @@ IonBuilder::maybeMarkEmpty(MDefinition* 
 
 // Return whether property lookups can be performed effectlessly on clasp.
 static bool
 ClassHasEffectlessLookup(const Class* clasp)
 {
     return (clasp == &UnboxedPlainObject::class_) ||
            (clasp == &UnboxedArrayObject::class_) ||
            IsTypedObjectClass(clasp) ||
-           (clasp->isNative() && !clasp->ops.lookupProperty);
+           (clasp->isNative() && !clasp->getOpsLookupProperty());
 }
 
 // Return whether an object might have a property for name which is not
 // accounted for by type information.
 static bool
 ObjectHasExtraOwnProperty(CompileCompartment* comp, TypeSet::ObjectKey* object, jsid id)
 {
     // Some typed object properties are not reflected in type information.
@@ -10629,19 +10629,19 @@ IonBuilder::objectsHaveCommonPrototype(T
             if (ObjectHasExtraOwnProperty(compartment, key, NameToId(name))) {
                 if (!singleton || !singleton->is<GlobalObject>())
                     return false;
                 *guardGlobal = true;
             }
 
             // Look for a getter/setter on the class itself which may need
             // to be called.
-            if (isGetter && clasp->ops.getProperty)
+            if (isGetter && clasp->getOpsGetProperty())
                 return false;
-            if (!isGetter && clasp->ops.setProperty)
+            if (!isGetter && clasp->getOpsSetProperty())
                 return false;
 
             // Test for isOwnProperty() without freezing. If we end up
             // optimizing, freezePropertiesForCommonPropFunc will freeze the
             // property type sets later on.
             HeapTypeSetKey property = key->property(NameToId(name));
             if (TypeSet* types = property.maybeTypes()) {
                 if (!types->empty() || types->nonDataProperty())
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -437,17 +437,17 @@ SetProperty(JSContext* cx, HandleObject 
         Shape* shape = obj->as<NativeObject>().lookup(cx, name);
         MOZ_ASSERT(shape && shape->hasSlot());
         obj->as<NativeObject>().setSlotWithType(cx, shape, value);
         return true;
     }
 
     RootedValue receiver(cx, ObjectValue(*obj));
     ObjectOpResult result;
-    if (MOZ_LIKELY(!obj->getOps()->setProperty)) {
+    if (MOZ_LIKELY(!obj->getOpsSetProperty())) {
         if (!NativeSetProperty(
                 cx, obj.as<NativeObject>(), id, value, receiver,
                 (op == JSOP_SETNAME || op == JSOP_STRICTSETNAME ||
                  op == JSOP_SETGNAME || op == JSOP_STRICTSETGNAME)
                 ? Unqualified
                 : Qualified,
                 result))
         {
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -370,17 +370,17 @@ js::GetElements(JSContext* cx, HandleObj
     if (aobj->is<ArgumentsObject>()) {
         ArgumentsObject& argsobj = aobj->as<ArgumentsObject>();
         if (!argsobj.hasOverriddenLength()) {
             if (argsobj.maybeGetElements(0, length, vp))
                 return true;
         }
     }
 
-    if (js::GetElementsOp op = aobj->getOps()->getElements) {
+    if (js::GetElementsOp op = aobj->getOpsGetElements()) {
         ElementAdder adder(cx, vp, length, ElementAdder::GetElement);
         return op(cx, aobj, 0, length, &adder);
     }
 
     for (uint32_t i = 0; i < length; i++) {
         if (!GetElement(cx, aobj, aobj, i, MutableHandleValue::fromMarkedLocation(&vp[i])))
             return false;
     }
@@ -2733,17 +2733,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->getOps()->lookupProperty)
+        if (!pobj->isNative() || pobj->getClass()->resolve || pobj->getOpsLookupProperty())
             return true;
     } while ((pobj = pobj->getProto()));
 
     // Collect indexed property names.
     pobj = obj;
     do {
         // Append dense elements.
         NativeObject* nativeObj = &pobj->as<NativeObject>();
@@ -2925,17 +2925,17 @@ js::array_slice(JSContext* cx, unsigned 
         args.rval().setObject(*narr);
         return true;
     }
 
     RootedObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, end - begin));
     if (!narr)
         return false;
 
-    if (js::GetElementsOp op = obj->getOps()->getElements) {
+    if (js::GetElementsOp op = obj->getOpsGetElements()) {
         ElementAdder adder(cx, narr, end - begin, ElementAdder::CheckHasElemPreserveHoles);
         if (!op(cx, obj, begin, end, &adder))
             return false;
 
         args.rval().setObject(*narr);
         return true;
     }
 
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -308,16 +308,18 @@ 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::ObjectOps) ProxyObjectOps;
+
 /*
  * Helper Macros for creating JSClasses that function as proxies.
  *
  * NB: The macro invocation must be surrounded by braces, so as to
  *     allow for potential JSClass extensions.
  */
 #define PROXY_MAKE_EXT(isWrappedNative, objectMoved)                    \
     {                                                                   \
@@ -342,29 +344,17 @@ namespace js {
         nullptr,                 /* mayResolve */                                       \
         js::proxy_Finalize,      /* finalize    */                                      \
         nullptr,                 /* call        */                                      \
         js::proxy_HasInstance,   /* hasInstance */                                      \
         nullptr,                 /* construct   */                                      \
         js::proxy_Trace,         /* trace       */                                      \
         JS_NULL_CLASS_SPEC,                                                             \
         ext,                                                                            \
-        {                                                                               \
-            js::proxy_LookupProperty,                                                   \
-            js::proxy_DefineProperty,                                                   \
-            js::proxy_HasProperty,                                                      \
-            js::proxy_GetProperty,                                                      \
-            js::proxy_SetProperty,                                                      \
-            js::proxy_GetOwnPropertyDescriptor,                                         \
-            js::proxy_DeleteProperty,                                                   \
-            js::proxy_Watch, js::proxy_Unwatch,                                         \
-            js::proxy_GetElements,                                                      \
-            nullptr,             /* enumerate       */                                  \
-            js::proxy_FunToString,                                                      \
-        }                                                                               \
+        &js::ProxyObjectOps                                                             \
     }
 
 #define PROXY_CLASS_DEF(name, flags)                                    \
   PROXY_CLASS_WITH_EXT(name, flags,                                     \
                        PROXY_MAKE_EXT(                                  \
                          false,   /* isWrappedNative */                 \
                          js::proxy_ObjectMoved                          \
                        ))
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1071,17 +1071,17 @@ js::FunctionToString(JSContext* cx, Hand
     }
     return out.finishString();
 }
 
 JSString*
 fun_toStringHelper(JSContext* cx, HandleObject obj, unsigned indent)
 {
     if (!obj->is<JSFunction>()) {
-        if (JSFunToStringOp op = obj->getOps()->funToString)
+        if (JSFunToStringOp op = obj->getOpsFunToString())
             return op(cx, obj, indent);
 
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
                              JSMSG_INCOMPATIBLE_PROTO,
                              js_Function_str, js_toString_str,
                              "object");
         return nullptr;
     }
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -89,34 +89,34 @@ NewKeyValuePair(JSContext* cx, jsid id, 
 static inline bool
 Enumerate(JSContext* cx, HandleObject pobj, jsid id,
           bool enumerable, unsigned flags, Maybe<IdSet>& ht, AutoIdVector* props)
 {
     // Allow duplicate properties from Proxy's [[OwnPropertyKeys]].
     bool proxyOwnProperty = pobj->is<ProxyObject>() && (flags & JSITER_OWNONLY);
 
     if (!proxyOwnProperty && (!(flags & JSITER_OWNONLY) || pobj->is<ProxyObject>() ||
-        pobj->getOps()->enumerate))
+        pobj->getOpsEnumerate()))
     {
         if (!ht) {
             ht.emplace(cx);
             // Most of the time there are only a handful of entries.
             if (!ht->init(5))
                 return false;
         }
 
         // If we've already seen this, we definitely won't add it.
         IdSet::AddPtr p = ht->lookupForAdd(id);
         if (MOZ_UNLIKELY(!!p))
             return true;
 
         // It's not necessary to add properties to the hash table at the end of
         // the prototype chain, but custom enumeration behaviors might return
         // duplicated properties, so always add in such cases.
-        if ((pobj->is<ProxyObject>() || pobj->getProto() || pobj->getOps()->enumerate) && !ht->add(p, id))
+        if ((pobj->is<ProxyObject>() || pobj->getProto() || pobj->getOpsEnumerate()) && !ht->add(p, id))
             return false;
     }
 
     // Symbol-keyed properties and nonenumerable properties are skipped unless
     // the caller specifically asks for them. A caller can also filter out
     // non-symbols by asking for JSITER_SYMBOLSONLY.
     if (JSID_IS_SYMBOL(id) ? !(flags & JSITER_SYMBOLS) : (flags & JSITER_SYMBOLSONLY))
         return true;
@@ -125,21 +125,21 @@ Enumerate(JSContext* cx, HandleObject po
 
     return props->append(id);
 }
 
 static bool
 EnumerateExtraProperties(JSContext* cx, HandleObject obj, unsigned flags, Maybe<IdSet>& ht,
                          AutoIdVector* props)
 {
-    MOZ_ASSERT(obj->getOps()->enumerate);
+    MOZ_ASSERT(obj->getOpsEnumerate());
 
     AutoIdVector properties(cx);
     bool enumerableOnly = !(flags & JSITER_HIDDEN);
-    if (!obj->getOps()->enumerate(cx, obj, properties, enumerableOnly))
+    if (!obj->getOpsEnumerate()(cx, obj, properties, enumerableOnly))
         return false;
 
     RootedId id(cx);
     for (size_t n = 0; n < properties.length(); n++) {
         id = properties[n];
 
         // The enumerate hook does not indicate whether the properties
         // it returns are enumerable or not. Since we already passed
@@ -356,17 +356,17 @@ static bool
 Snapshot(JSContext* cx, HandleObject pobj_, unsigned flags, AutoIdVector* props)
 {
     // We initialize |ht| lazily (in Enumerate()) because it ends up unused
     // anywhere from 67--99.9% of the time.
     Maybe<IdSet> ht;
     RootedObject pobj(cx, pobj_);
 
     do {
-        if (pobj->getOps()->enumerate) {
+        if (pobj->getOpsEnumerate()) {
             if (pobj->is<UnboxedPlainObject>() && pobj->as<UnboxedPlainObject>().maybeExpando()) {
                 // Special case unboxed objects with an expando object.
                 RootedNativeObject expando(cx, pobj->as<UnboxedPlainObject>().maybeExpando());
                 if (!EnumerateNativeProperties(cx, expando, flags, ht, props,
                                                pobj.as<UnboxedPlainObject>()))
                 {
                     return false;
                 }
@@ -792,17 +792,17 @@ CanCompareIterableObjectToCache(JSObject
 static inline bool
 CanCacheIterableObject(JSContext* cx, JSObject* obj)
 {
     if (!CanCompareIterableObjectToCache(obj))
         return false;
     if (obj->isNative()) {
         if (obj->is<TypedArrayObject>() ||
             obj->hasUncacheableProto() ||
-            obj->getOps()->enumerate ||
+            obj->getOpsEnumerate() ||
             obj->getClass()->enumerate ||
             obj->as<NativeObject>().containsPure(cx->names().iteratorIntrinsic))
         {
             return false;
         }
     }
     return true;
 }
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1039,17 +1039,17 @@ JSObject::nonNativeSetProperty(JSContext
                                HandleValue receiver, ObjectOpResult& result)
 {
     RootedValue value(cx, v);
     if (MOZ_UNLIKELY(obj->watched())) {
         WatchpointMap* wpmap = cx->compartment()->watchpointMap;
         if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, &value))
             return false;
     }
-    return obj->getOps()->setProperty(cx, obj, id, value, receiver, result);
+    return obj->getOpsSetProperty()(cx, obj, id, value, receiver, result);
 }
 
 /* static */ bool
 JSObject::nonNativeSetElement(JSContext* cx, HandleObject obj, uint32_t index, HandleValue v,
                               HandleValue receiver, ObjectOpResult& result)
 {
     RootedId id(cx);
     if (!IndexToId(cx, index, &id))
@@ -2142,17 +2142,17 @@ JSObject::constructHook() const
 bool
 js::LookupProperty(JSContext* cx, HandleObject obj, js::HandleId id,
                    MutableHandleObject objp, MutableHandleShape propp)
 {
     /* NB: The logic of lookupProperty is implicitly reflected in
      *     BaselineIC.cpp's |EffectlesslyLookupProperty| logic.
      *     If this changes, please remember to update the logic there as well.
      */
-    if (LookupPropertyOp op = obj->getOps()->lookupProperty)
+    if (LookupPropertyOp op = obj->getOpsLookupProperty())
         return op(cx, obj, id, objp, propp);
     return LookupPropertyInline<CanGC>(cx, obj.as<NativeObject>(), id, objp, propp);
 }
 
 bool
 js::LookupName(JSContext* cx, HandlePropertyName name, HandleObject scopeChain,
                MutableHandleObject objp, MutableHandleObject pobjp, MutableHandleShape propp)
 {
@@ -2177,17 +2177,17 @@ bool
 js::LookupNameNoGC(JSContext* cx, PropertyName* name, JSObject* scopeChain,
                    JSObject** objp, JSObject** pobjp, Shape** propp)
 {
     AutoAssertNoException nogc(cx);
 
     MOZ_ASSERT(!*objp && !*pobjp && !*propp);
 
     for (JSObject* scope = scopeChain; scope; scope = scope->enclosingScope()) {
-        if (scope->getOps()->lookupProperty)
+        if (scope->getOpsLookupProperty())
             return false;
         if (!LookupPropertyInline<NoGC>(cx, &scope->as<NativeObject>(), NameToId(name), pobjp, propp))
             return false;
         if (*propp) {
             *objp = scope;
             return true;
         }
     }
@@ -2252,17 +2252,17 @@ js::LookupNameUnqualified(JSContext* cx,
 }
 
 bool
 js::HasOwnProperty(JSContext* cx, HandleObject obj, HandleId id, bool* result)
 {
     if (obj->is<ProxyObject>())
         return Proxy::hasOwn(cx, obj, id, result);
 
-    if (GetOwnPropertyOp op = obj->getOps()->getOwnPropertyDescriptor) {
+    if (GetOwnPropertyOp op = obj->getOpsGetOwnPropertyDescriptor()) {
         Rooted<PropertyDescriptor> desc(cx);
         if (!op(cx, obj, id, &desc))
             return false;
         *result = !!desc.object();
         return true;
     }
 
     RootedShape shape(cx);
@@ -2559,17 +2559,17 @@ js::PreventExtensions(JSContext* cx, Han
     ObjectOpResult result;
     return PreventExtensions(cx, obj, result) && result.checkStrict(cx, obj);
 }
 
 bool
 js::GetOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
                              MutableHandle<PropertyDescriptor> desc)
 {
-    if (GetOwnPropertyOp op = obj->getOps()->getOwnPropertyDescriptor) {
+    if (GetOwnPropertyOp op = obj->getOpsGetOwnPropertyDescriptor()) {
         bool ok = op(cx, obj, id, desc);
         if (ok)
             desc.assertCompleteIfFound();
         return ok;
     }
 
     return NativeGetOwnPropertyDescriptor(cx, obj.as<NativeObject>(), id, desc);
 }
@@ -2582,31 +2582,31 @@ js::DefineProperty(JSContext* cx, Handle
            result.checkStrict(cx, obj, id);
 }
 
 bool
 js::DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle<PropertyDescriptor> desc,
                    ObjectOpResult& result)
 {
     desc.assertValid();
-    if (DefinePropertyOp op = obj->getOps()->defineProperty)
+    if (DefinePropertyOp op = obj->getOpsDefineProperty())
         return op(cx, obj, id, desc, result);
     return NativeDefineProperty(cx, obj.as<NativeObject>(), id, desc, result);
 }
 
 bool
 js::DefineProperty(ExclusiveContext* cx, HandleObject obj, HandleId id, HandleValue value,
                    JSGetterOp getter, JSSetterOp setter, unsigned attrs,
                    ObjectOpResult& result)
 {
     MOZ_ASSERT(!(attrs & JSPROP_PROPOP_ACCESSORS));
 
     Rooted<PropertyDescriptor> desc(cx);
     desc.initFields(nullptr, value, attrs, getter, setter);
-    if (DefinePropertyOp op = obj->getOps()->defineProperty) {
+    if (DefinePropertyOp op = obj->getOpsDefineProperty()) {
         if (!cx->shouldBeJSContext())
             return false;
         return op(cx->asJSContext(), obj, id, desc, result);
     }
     return NativeDefineProperty(cx, obj.as<NativeObject>(), id, desc, result);
 }
 
 bool
@@ -2751,32 +2751,32 @@ js::UnwatchGuts(JSContext* cx, JS::Handl
     if (WatchpointMap* wpmap = cx->compartment()->watchpointMap)
         wpmap->unwatch(obj, id, nullptr, nullptr);
     return true;
 }
 
 bool
 js::WatchProperty(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable)
 {
-    if (WatchOp op = obj->getOps()->watch)
+    if (WatchOp op = obj->getOpsWatch())
         return op(cx, obj, id, callable);
 
     if (!obj->isNative() || obj->is<TypedArrayObject>()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_WATCH,
                              obj->getClass()->name);
         return false;
     }
 
     return WatchGuts(cx, obj, id, callable);
 }
 
 bool
 js::UnwatchProperty(JSContext* cx, HandleObject obj, HandleId id)
 {
-    if (UnwatchOp op = obj->getOps()->unwatch)
+    if (UnwatchOp op = obj->getOpsUnwatch())
         return op(cx, obj, id);
 
     return UnwatchGuts(cx, obj, id);
 }
 
 const char*
 js::GetObjectClassName(JSContext* cx, HandleObject obj)
 {
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -122,19 +122,30 @@ class JSObject : public js::gc::Cell
         return group_->clasp();
     }
     const JSClass* getJSClass() const {
         return Jsvalify(getClass());
     }
     bool hasClass(const js::Class* c) const {
         return getClass() == c;
     }
-    const js::ObjectOps* getOps() const {
-        return &getClass()->ops;
-    }
+
+    js::LookupPropertyOp getOpsLookupProperty() const { return getClass()->getOpsLookupProperty(); }
+    js::DefinePropertyOp getOpsDefineProperty() const { return getClass()->getOpsDefineProperty(); }
+    js::HasPropertyOp    getOpsHasProperty()    const { return getClass()->getOpsHasProperty(); }
+    js::GetPropertyOp    getOpsGetProperty()    const { return getClass()->getOpsGetProperty(); }
+    js::SetPropertyOp    getOpsSetProperty()    const { return getClass()->getOpsSetProperty(); }
+    js::GetOwnPropertyOp getOpsGetOwnPropertyDescriptor()
+                                                const { return getClass()->getOpsGetOwnPropertyDescriptor(); }
+    js::DeletePropertyOp getOpsDeleteProperty() const { return getClass()->getOpsDeleteProperty(); }
+    js::WatchOp          getOpsWatch()          const { return getClass()->getOpsWatch(); }
+    js::UnwatchOp        getOpsUnwatch()        const { return getClass()->getOpsUnwatch(); }
+    js::GetElementsOp    getOpsGetElements()    const { return getClass()->getOpsGetElements(); }
+    JSNewEnumerateOp     getOpsEnumerate()      const { return getClass()->getOpsEnumerate(); }
+    JSFunToStringOp      getOpsFunToString()    const { return getClass()->getOpsFunToString(); }
 
     js::ObjectGroup* group() const {
         MOZ_ASSERT(!hasLazyGroup());
         return groupRaw();
     }
 
     js::ObjectGroup* groupRaw() const {
         return group_;
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -197,17 +197,17 @@ js::GetElement(JSContext* cx, HandleObje
 {
     RootedValue receiverValue(cx, ObjectValue(*receiver));
     return GetElement(cx, obj, receiverValue, index, vp);
 }
 
 inline bool
 js::GetElementNoGC(JSContext* cx, JSObject* obj, const Value& receiver, uint32_t index, Value* vp)
 {
-    if (obj->getOps()->getProperty)
+    if (obj->getOpsGetProperty())
         return false;
 
     if (index > JSID_INT_MAX)
         return false;
     return GetPropertyNoGC(cx, obj, receiver, INT_TO_JSID(index), vp);
 }
 
 inline bool
@@ -215,17 +215,17 @@ js::GetElementNoGC(JSContext* cx, JSObje
 {
     return GetElementNoGC(cx, obj, ObjectValue(*receiver), index, vp);
 }
 
 inline bool
 js::DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
 {
     MarkTypePropertyNonData(cx, obj, id);
-    if (DeletePropertyOp op = obj->getOps()->deleteProperty)
+    if (DeletePropertyOp op = obj->getOpsDeleteProperty())
         return op(cx, obj, id, result);
     return NativeDeleteProperty(cx, obj.as<NativeObject>(), id, result);
 }
 
 inline bool
 js::DeleteElement(JSContext* cx, HandleObject obj, uint32_t index, ObjectOpResult& result)
 {
     RootedId id(cx);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2805,18 +2805,18 @@ FindReplaceLength(JSContext* cx, RegExpS
     if (rdata.elembase) {
         /*
          * The base object is used when replace was passed a lambda which looks like
          * 'function(a) { return b[a]; }' for the base object b.  b will not change
          * in the course of the replace unless we end up making a scripted call due
          * to accessing a scripted getter or a value with a scripted toString.
          */
         MOZ_ASSERT(rdata.lambda);
-        MOZ_ASSERT(!rdata.elembase->getOps()->lookupProperty);
-        MOZ_ASSERT(!rdata.elembase->getOps()->getProperty);
+        MOZ_ASSERT(!rdata.elembase->getOpsLookupProperty());
+        MOZ_ASSERT(!rdata.elembase->getOpsGetProperty());
 
         RootedValue match(cx);
         if (!res->createLastMatch(cx, &match))
             return false;
         JSAtom* atom = ToAtom<CanGC>(cx, match);
         if (!atom)
             return false;
 
@@ -3696,17 +3696,17 @@ LambdaIsGetElem(JSContext* cx, JSObject&
         return true;
 
     /* 'b' must behave like a normal object. */
     if (!b.isObject())
         return true;
 
     JSObject& bobj = b.toObject();
     const Class* clasp = bobj.getClass();
-    if (!clasp->isNative() || clasp->ops.lookupProperty || clasp->ops.getProperty)
+    if (!clasp->isNative() || clasp->getOpsLookupProperty() || clasp->getOpsGetProperty())
         return true;
 
     pobj.set(&bobj.as<NativeObject>());
     return true;
 }
 
 bool
 js::str_replace(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -711,16 +711,30 @@ js::proxy_GetElements(JSContext* cx, Han
 }
 
 JSString*
 js::proxy_FunToString(JSContext* cx, HandleObject proxy, unsigned indent)
 {
     return Proxy::fun_toString(cx, proxy, indent);
 }
 
+const ObjectOps js::ProxyObjectOps = {
+    js::proxy_LookupProperty,
+    js::proxy_DefineProperty,
+    js::proxy_HasProperty,
+    js::proxy_GetProperty,
+    js::proxy_SetProperty,
+    js::proxy_GetOwnPropertyDescriptor,
+    js::proxy_DeleteProperty,
+    js::proxy_Watch, js::proxy_Unwatch,
+    js::proxy_GetElements,
+    nullptr,  /* enumerate */
+    js::proxy_FunToString
+};
+
 const Class js::ProxyObject::proxyClass =
     PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy));
 
 const Class* const js::ProxyClassPtr = &js::ProxyObject::proxyClass;
 
 JS_FRIEND_API(JSObject*)
 js::NewProxyObject(JSContext* cx, const BaseProxyHandler* handler, HandleValue priv, JSObject* proto_,
                    const ProxyOptions& options)
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -278,17 +278,17 @@ SetNameOperation(JSContext* cx, JSScript
     RootedId id(cx, NameToId(name));
     RootedValue receiver(cx, ObjectValue(*scope));
     if (scope->isUnqualifiedVarObj()) {
         RootedNativeObject varobj(cx);
         if (scope->is<DebugScopeObject>())
             varobj = &scope->as<DebugScopeObject>().scope().as<NativeObject>();
         else
             varobj = &scope->as<NativeObject>();
-        MOZ_ASSERT(!varobj->getOps()->setProperty);
+        MOZ_ASSERT(!varobj->getOpsSetProperty());
         ok = NativeSetProperty(cx, varobj, id, val, receiver, Unqualified, result);
     } else {
         ok = SetProperty(cx, scope, id, val, receiver, result);
     }
     return ok && result.checkStrictErrorOrWarning(cx, scope, id, strict);
 }
 
 inline bool
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -2017,17 +2017,17 @@ NativeGetPropertyInline(JSContext* cx,
         if (!proto)
             return GetNonexistentProperty(cx, obj, id, receiver, nameLookup, vp);
 
         // Step 4.d. If the prototype is also native, this step is a
         // recursive tail call, and we don't need to go through all the
         // plumbing of JSObject::getGeneric; the top of the loop is where
         // we're going to end up anyway. But if pobj is non-native,
         // that optimization would be incorrect.
-        if (proto->getOps()->getProperty)
+        if (proto->getOpsGetProperty())
             return GeneralizedGetProperty(cx, proto, id, receiver, nameLookup, vp);
 
         pobj = &proto->as<NativeObject>();
     }
 }
 
 bool
 js::NativeGetProperty(JSContext* cx, HandleNativeObject obj, HandleValue receiver, HandleId id,
@@ -2042,17 +2042,17 @@ js::NativeGetPropertyNoGC(JSContext* cx,
     AutoAssertNoException noexc(cx);
     return NativeGetPropertyInline<NoGC>(cx, obj, receiver, id, NotNameLookup, vp);
 }
 
 bool
 js::GetPropertyForNameLookup(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
 {
     RootedValue receiver(cx, ObjectValue(*obj));
-    if (obj->getOps()->getProperty)
+    if (obj->getOpsGetProperty())
         return GeneralizedGetProperty(cx, obj, id, receiver, NameLookup, vp);
     return NativeGetPropertyInline<CanGC>(cx, obj.as<NativeObject>(), receiver, id, NameLookup, vp);
 }
 
 
 /*** [[Set]] *************************************************************************************/
 
 static bool
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -1458,49 +1458,49 @@ MaybeNativeObject(JSObject* obj)
 } // namespace js
 
 
 /*** Inline functions declared in jsobj.h that use the native declarations above *****************/
 
 inline bool
 js::HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
 {
-    if (HasPropertyOp op = obj->getOps()->hasProperty)
+    if (HasPropertyOp op = obj->getOpsHasProperty())
         return op(cx, obj, id, foundp);
     return NativeHasProperty(cx, obj.as<NativeObject>(), id, foundp);
 }
 
 inline bool
 js::GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
                 MutableHandleValue vp)
 {
-    if (GetPropertyOp op = obj->getOps()->getProperty)
+    if (GetPropertyOp op = obj->getOpsGetProperty())
         return op(cx, obj, receiver, id, vp);
     return NativeGetProperty(cx, obj.as<NativeObject>(), receiver, id, vp);
 }
 
 inline bool
 js::GetPropertyNoGC(JSContext* cx, JSObject* obj, const Value& receiver, jsid id, Value* vp)
 {
-    if (obj->getOps()->getProperty)
+    if (obj->getOpsGetProperty())
         return false;
     return NativeGetPropertyNoGC(cx, &obj->as<NativeObject>(), receiver, id, vp);
 }
 
 inline bool
 js::SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                 HandleValue receiver, ObjectOpResult& result)
 {
-    if (obj->getOps()->setProperty)
+    if (obj->getOpsSetProperty())
         return JSObject::nonNativeSetProperty(cx, obj, id, v, receiver, result);
     return NativeSetProperty(cx, obj.as<NativeObject>(), id, v, receiver, Qualified, result);
 }
 
 inline bool
 js::SetElement(JSContext* cx, HandleObject obj, uint32_t index, HandleValue v,
                HandleValue receiver, ObjectOpResult& result)
 {
-    if (obj->getOps()->setProperty)
+    if (obj->getOpsSetProperty())
         return JSObject::nonNativeSetElement(cx, obj, index, v, receiver, result);
     return NativeSetElement(cx, obj.as<NativeObject>(), index, v, receiver, result);
 }
 
 #endif /* vm_NativeObject_h */
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -435,16 +435,30 @@ CallObject::createHollowForDebug(JSConte
 
 const Class CallObject::class_ = {
     "Call",
     JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS)
 };
 
 /*****************************************************************************/
 
+const ObjectOps ModuleEnvironmentObject::objectOps_ = {
+    ModuleEnvironmentObject::lookupProperty,
+    nullptr,                                             /* defineProperty */
+    ModuleEnvironmentObject::hasProperty,
+    ModuleEnvironmentObject::getProperty,
+    ModuleEnvironmentObject::setProperty,
+    ModuleEnvironmentObject::getOwnPropertyDescriptor,
+    ModuleEnvironmentObject::deleteProperty,
+    nullptr, nullptr,                                    /* watch/unwatch */
+    nullptr,                                             /* getElements */
+    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 */
@@ -453,29 +467,17 @@ const Class ModuleEnvironmentObject::cla
     nullptr,        /* mayResolve  */
     nullptr,        /* finalize    */
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
     nullptr,        /* trace       */
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
-    {
-        ModuleEnvironmentObject::lookupProperty,
-        nullptr,                                             /* defineProperty */
-        ModuleEnvironmentObject::hasProperty,
-        ModuleEnvironmentObject::getProperty,
-        ModuleEnvironmentObject::setProperty,
-        ModuleEnvironmentObject::getOwnPropertyDescriptor,
-        ModuleEnvironmentObject::deleteProperty,
-        nullptr, nullptr,                                    /* watch/unwatch */
-        nullptr,                                             /* getElements */
-        ModuleEnvironmentObject::enumerate,
-        nullptr
-    }
+    &ModuleEnvironmentObject::objectOps_
 };
 
 /* static */ ModuleEnvironmentObject*
 ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module)
 {
     RootedScript script(cx, module->script());
     RootedShape shape(cx, script->bindings.callObjShape());
     MOZ_ASSERT(shape->getObjectClass() == &class_);
@@ -861,16 +863,30 @@ with_GetOwnPropertyDescriptor(JSContext*
 static bool
 with_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
 {
     MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
     RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
     return DeleteProperty(cx, actual, id, result);
 }
 
+const ObjectOps DynamicWithObject::objectOps_ = {
+    with_LookupProperty,
+    with_DefineProperty,
+    with_HasProperty,
+    with_GetProperty,
+    with_SetProperty,
+    with_GetOwnPropertyDescriptor,
+    with_DeleteProperty,
+    nullptr, nullptr,    /* watch/unwatch */
+    nullptr,             /* getElements */
+    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 */
@@ -879,29 +895,17 @@ const Class DynamicWithObject::class_ = 
     nullptr, /* mayResolve */
     nullptr, /* finalize */
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr, /* trace */
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
-    {
-        with_LookupProperty,
-        with_DefineProperty,
-        with_HasProperty,
-        with_GetProperty,
-        with_SetProperty,
-        with_GetOwnPropertyDescriptor,
-        with_DeleteProperty,
-        nullptr, nullptr,    /* watch/unwatch */
-        nullptr,             /* getElements */
-        nullptr,             /* enumerate (native enumeration of target doesn't work) */
-        nullptr,
-    }
+    &DynamicWithObject::objectOps_
 };
 
 /* static */ StaticEvalScope*
 StaticEvalScope::create(JSContext* cx, HandleObject enclosing)
 {
     StaticEvalScope* obj =
         NewObjectWithNullTaggedProto<StaticEvalScope>(cx, TenuredObject, BaseShape::DELEGATE);
     if (!obj)
@@ -1115,16 +1119,30 @@ ClonedBlockObject::thisValue() const
         return ObjectValue(*ToWindowProxyIfWindow(&v.toObject()));
     }
     return v;
 }
 
 static_assert(StaticBlockScope::RESERVED_SLOTS == ClonedBlockObject::RESERVED_SLOTS,
               "static block scopes and dynamic block environments share a Class");
 
+const ObjectOps ClonedBlockObject::objectOps_ = {
+    nullptr,          /* lookupProperty */
+    nullptr,          /* defineProperty */
+    nullptr,          /* hasProperty */
+    nullptr,          /* getProperty */
+    nullptr,          /* setProperty */
+    nullptr,          /* getOwnPropertyDescriptor */
+    nullptr,          /* deleteProperty */
+    nullptr, nullptr, /* watch/unwatch */
+    nullptr,          /* getElements */
+    nullptr,          /* enumerate (native enumeration of target doesn't work) */
+    nullptr,
+};
+
 const Class ClonedBlockObject::class_ = {
     "Block",
     JSCLASS_HAS_RESERVED_SLOTS(ClonedBlockObject::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS,
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
@@ -1133,29 +1151,17 @@ const Class ClonedBlockObject::class_ = 
     nullptr, /* mayResolve */
     nullptr, /* finalize */
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr, /* trace */
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
-    {
-        nullptr,          /* lookupProperty */
-        nullptr,          /* defineProperty */
-        nullptr,          /* hasProperty */
-        nullptr,          /* getProperty */
-        nullptr,          /* setProperty */
-        nullptr,          /* getOwnPropertyDescriptor */
-        nullptr,          /* deleteProperty */
-        nullptr, nullptr, /* watch/unwatch */
-        nullptr,          /* getElements */
-        nullptr,          /* enumerate (native enumeration of target doesn't work) */
-        nullptr,
-    }
+    &ClonedBlockObject::objectOps_
 };
 
 template<XDRMode mode>
 bool
 js::XDRStaticBlockScope(XDRState<mode>* xdr, HandleObject enclosingScope,
                         MutableHandle<StaticBlockScope*> objp)
 {
     /* NB: Keep this in sync with CloneStaticBlockScope. */
@@ -1386,16 +1392,30 @@ lexicalError_GetOwnPropertyDescriptor(JS
 
 static bool
 lexicalError_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
 {
     ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
     return false;
 }
 
+const ObjectOps RuntimeLexicalErrorObject::objectOps_ = {
+    lexicalError_LookupProperty,
+    nullptr,             /* defineProperty */
+    lexicalError_HasProperty,
+    lexicalError_GetProperty,
+    lexicalError_SetProperty,
+    lexicalError_GetOwnPropertyDescriptor,
+    lexicalError_DeleteProperty,
+    nullptr, nullptr,    /* watch/unwatch */
+    nullptr,             /* getElements */
+    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 */
@@ -1404,29 +1424,17 @@ const Class RuntimeLexicalErrorObject::c
     nullptr, /* mayResolve */
     nullptr, /* finalize */
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr, /* trace */
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
-    {
-        lexicalError_LookupProperty,
-        nullptr,             /* defineProperty */
-        lexicalError_HasProperty,
-        lexicalError_GetProperty,
-        lexicalError_SetProperty,
-        lexicalError_GetOwnPropertyDescriptor,
-        lexicalError_DeleteProperty,
-        nullptr, nullptr,    /* watch/unwatch */
-        nullptr,             /* getElements */
-        nullptr,             /* enumerate (native enumeration of target doesn't work) */
-        nullptr,             /* this */
-    }
+    &RuntimeLexicalErrorObject::objectOps_
 };
 
 /*****************************************************************************/
 
 // Any name atom for a function which will be added as a DeclEnv object to the
 // scope chain above call objects for fun.
 static inline JSAtom*
 CallObjectLambdaName(JSFunction& fun)
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -812,16 +812,17 @@ class CallObject : public LexicalScopeBa
     }
 };
 
 class ModuleEnvironmentObject : public LexicalScopeBase
 {
     static const uint32_t MODULE_SLOT = 1;
 
   public:
+    static const ObjectOps objectOps_;
     static const Class class_;
 
     static const uint32_t RESERVED_SLOTS = 2;
 
     static ModuleEnvironmentObject* create(ExclusiveContext* cx, HandleModuleObject module);
     ModuleObject& module();
     IndirectBindingMap& importBindings();
 
@@ -906,16 +907,17 @@ class NestedScopeObject : public ScopeOb
 class DynamicWithObject : public NestedScopeObject
 {
     static const unsigned OBJECT_SLOT = 1;
     static const unsigned THIS_SLOT = 2;
     static const unsigned KIND_SLOT = 3;
 
   public:
     static const unsigned RESERVED_SLOTS = 4;
+    static const ObjectOps objectOps_;
     static const Class class_;
 
     enum WithKind {
         SyntacticWith,
         NonSyntacticWith
     };
 
     static DynamicWithObject*
@@ -956,16 +958,17 @@ class DynamicWithObject : public NestedS
 };
 
 class ClonedBlockObject : public NestedScopeObject
 {
     static const unsigned THIS_VALUE_SLOT = 1;
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
+    static const ObjectOps objectOps_;
     static const Class class_;
 
   private:
     static ClonedBlockObject* create(JSContext* cx, Handle<StaticBlockScope*> block,
                                      HandleObject enclosing);
 
   public:
     static ClonedBlockObject* create(JSContext* cx, Handle<StaticBlockScope*> block,
@@ -1067,16 +1070,17 @@ class ClonedBlockObject : public NestedS
 // ES6 'const' bindings induce a runtime error when assigned to outside
 // of initialization, regardless of strictness.
 class RuntimeLexicalErrorObject : public ScopeObject
 {
     static const unsigned ERROR_SLOT = 1;
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
+    static const ObjectOps objectOps_;
     static const Class class_;
 
     static RuntimeLexicalErrorObject* create(JSContext* cx, HandleObject enclosing,
                                              unsigned errorNumber);
 
     unsigned errorNumber() {
         return getReservedSlot(ERROR_SLOT).toInt32();
     }
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -33,17 +33,17 @@ namespace js {
  * see bug 462734 and bug 487039.
  */
 static inline bool
 IsCacheableNonGlobalScope(JSObject* obj)
 {
     bool cacheable =
         obj->is<CallObject>() || obj->is<ClonedBlockObject>() || obj->is<DeclEnvObject>();
 
-    MOZ_ASSERT_IF(cacheable, !obj->getOps()->lookupProperty);
+    MOZ_ASSERT_IF(cacheable, !obj->getOpsLookupProperty());
     return cacheable;
 }
 
 inline HandleObject
 InterpreterFrame::scopeChain() const
 {
     return HandleObject::fromMarkedLocation(&scopeChain_);
 }
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2462,18 +2462,18 @@ TemporaryTypeSet::propertyNeedsBarrier(C
 }
 
 bool
 js::ClassCanHaveExtraProperties(const Class* clasp)
 {
     if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_)
         return false;
     return clasp->resolve
-        || clasp->ops.lookupProperty
-        || clasp->ops.getProperty
+        || clasp->getOpsLookupProperty()
+        || clasp->getOpsGetProperty()
         || IsTypedArrayClass(clasp);
 }
 
 void
 TypeZone::processPendingRecompiles(FreeOp* fop, RecompileInfoVector& recompiles)
 {
     MOZ_ASSERT(!recompiles.empty());
 
--- 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
 };
 
+const ObjectOps UnboxedPlainObject::objectOps_ = {
+    UnboxedPlainObject::obj_lookupProperty,
+    UnboxedPlainObject::obj_defineProperty,
+    UnboxedPlainObject::obj_hasProperty,
+    UnboxedPlainObject::obj_getProperty,
+    UnboxedPlainObject::obj_setProperty,
+    UnboxedPlainObject::obj_getOwnPropertyDescriptor,
+    UnboxedPlainObject::obj_deleteProperty,
+    UnboxedPlainObject::obj_watch,
+    nullptr,   /* No unwatch needed, as watch() converts the object to native */
+    nullptr,   /* getElements */
+    UnboxedPlainObject::obj_enumerate,
+    nullptr    /* funToString */
+};
+
 const Class UnboxedPlainObject::class_ = {
     js_Object_str,
     Class::NON_NATIVE |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
     JSCLASS_DELAY_METADATA_CALLBACK,
     nullptr,        /* addProperty */
     nullptr,        /* delProperty */
     nullptr,        /* getProperty */
@@ -919,29 +934,17 @@ const Class UnboxedPlainObject::class_ =
     nullptr,        /* mayResolve  */
     nullptr,        /* finalize    */
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
     UnboxedPlainObject::trace,
     JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
-    {
-        UnboxedPlainObject::obj_lookupProperty,
-        UnboxedPlainObject::obj_defineProperty,
-        UnboxedPlainObject::obj_hasProperty,
-        UnboxedPlainObject::obj_getProperty,
-        UnboxedPlainObject::obj_setProperty,
-        UnboxedPlainObject::obj_getOwnPropertyDescriptor,
-        UnboxedPlainObject::obj_deleteProperty,
-        UnboxedPlainObject::obj_watch,
-        nullptr,   /* No unwatch needed, as watch() converts the object to native */
-        nullptr,   /* getElements */
-        UnboxedPlainObject::obj_enumerate,
-    }
+    &UnboxedPlainObject::objectOps_
 };
 
 /////////////////////////////////////////////////////////////////////
 // UnboxedArrayObject
 /////////////////////////////////////////////////////////////////////
 
 template <JSValueType Type>
 DenseElementResult
@@ -1583,16 +1586,31 @@ UnboxedArrayObject::obj_enumerate(JSCont
     }
 
     if (!enumerableOnly && !properties.append(NameToId(cx->names().length)))
         return false;
 
     return true;
 }
 
+const ObjectOps UnboxedArrayObject::objectOps_ = {
+    UnboxedArrayObject::obj_lookupProperty,
+    UnboxedArrayObject::obj_defineProperty,
+    UnboxedArrayObject::obj_hasProperty,
+    UnboxedArrayObject::obj_getProperty,
+    UnboxedArrayObject::obj_setProperty,
+    UnboxedArrayObject::obj_getOwnPropertyDescriptor,
+    UnboxedArrayObject::obj_deleteProperty,
+    UnboxedArrayObject::obj_watch,
+    nullptr,   /* No unwatch needed, as watch() converts the object to native */
+    nullptr,   /* getElements */
+    UnboxedArrayObject::obj_enumerate,
+    nullptr    /* funToString */
+};
+
 const Class UnboxedArrayObject::class_ = {
     "Array",
     Class::NON_NATIVE |
     JSCLASS_SKIP_NURSERY_FINALIZE |
     JSCLASS_BACKGROUND_FINALIZE,
     nullptr,        /* addProperty */
     nullptr,        /* delProperty */
     nullptr,        /* getProperty */
@@ -1606,29 +1624,17 @@ const Class UnboxedArrayObject::class_ =
     nullptr,        /* construct   */
     UnboxedArrayObject::trace,
     JS_NULL_CLASS_SPEC,
     {
         false,      /* isWrappedNative */
         nullptr,    /* weakmapKeyDelegateOp */
         UnboxedArrayObject::objectMoved
     },
-    {
-        UnboxedArrayObject::obj_lookupProperty,
-        UnboxedArrayObject::obj_defineProperty,
-        UnboxedArrayObject::obj_hasProperty,
-        UnboxedArrayObject::obj_getProperty,
-        UnboxedArrayObject::obj_setProperty,
-        UnboxedArrayObject::obj_getOwnPropertyDescriptor,
-        UnboxedArrayObject::obj_deleteProperty,
-        UnboxedArrayObject::obj_watch,
-        nullptr,   /* No unwatch needed, as watch() converts the object to native */
-        nullptr,   /* getElements */
-        UnboxedArrayObject::obj_enumerate,
-    }
+    &UnboxedArrayObject::objectOps_
 };
 
 /////////////////////////////////////////////////////////////////////
 // API
 /////////////////////////////////////////////////////////////////////
 
 static bool
 UnboxedTypeIncludes(JSValueType supertype, JSValueType subtype)
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -233,16 +233,17 @@ class UnboxedPlainObject : public JSObje
     // not automatically barriered to avoid problems if the object is converted
     // to a native. See ensureExpando().
     UnboxedExpandoObject* expando_;
 
     // Start of the inline data, which immediately follows the group and extra properties.
     uint8_t data_[1];
 
   public:
+    static const ObjectOps objectOps_;
     static const Class class_;
 
     static bool obj_lookupProperty(JSContext* cx, HandleObject obj,
                                    HandleId id, MutableHandleObject objp,
                                    MutableHandleShape propp);
 
     static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
                                    Handle<PropertyDescriptor> desc,
@@ -369,16 +370,17 @@ class UnboxedArrayObject : public JSObje
             return length;
         return CapacityArray[index];
     }
 
     static uint32_t chooseCapacityIndex(uint32_t capacity, uint32_t length);
     static uint32_t exactCapacityIndex(uint32_t capacity);
 
   public:
+    static const ObjectOps objectOps_;
     static const Class class_;
 
     static bool obj_lookupProperty(JSContext* cx, HandleObject obj,
                                    HandleId id, MutableHandleObject objp,
                                    MutableHandleShape propp);
 
     static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
                                    Handle<PropertyDescriptor> desc,
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -646,29 +646,17 @@ const js::Class XPC_WN_NoHelper_JSClass 
     // ClassExtension
     {
         true,    // isWrappedNative
         nullptr, // weakmapKeyDelegateOp
         WrappedNativeObjectMoved
     },
 
     // ObjectOps
-    {
-        nullptr, // lookupProperty
-        nullptr, // defineProperty
-        nullptr, // hasProperty
-        nullptr, // getProperty
-        nullptr, // setProperty
-        nullptr, // getOwnPropertyDescriptor
-        nullptr, // deleteProperty
-        nullptr, nullptr, // watch/unwatch
-        nullptr, // getElements
-        nullptr, // enumerate
-        nullptr, // funToString
-    }
+    JS_NULL_OBJECT_OPS
 };
 
 
 /***************************************************************************/
 
 static bool
 XPC_WN_MaybeResolvingPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v)
 {
@@ -952,16 +940,31 @@ XPCNativeScriptableInfo::Construct(const
     if (!success) {
         delete newObj;
         return nullptr;
     }
 
     return newObj;
 }
 
+static const js::ObjectOps XPC_WN_ObjectOpsWithEnumerate = {
+    nullptr,  // lookupProperty
+    nullptr,  // defineProperty
+    nullptr,  // hasProperty
+    nullptr,  // getProperty
+    nullptr,  // setProperty
+    nullptr,  // getOwnPropertyDescriptor
+    nullptr,  // deleteProperty
+    nullptr,  // watch
+    nullptr,  // unwatch
+    nullptr,  // getElements
+    XPC_WN_JSOp_Enumerate,
+    nullptr,  // funToString
+};
+
 XPCNativeScriptableShared::XPCNativeScriptableShared(uint32_t aFlags,
                                                      char* aName,
                                                      bool aPopulate)
     : mFlags(aFlags)
 {
     MOZ_COUNT_CTOR(XPCNativeScriptableShared);
 
     memset(&mJSClass, 0, sizeof(mJSClass));
@@ -1025,19 +1028,18 @@ XPCNativeScriptableShared::XPCNativeScri
     // We have to figure out resolve strategy at call time
     mJSClass.resolve = XPC_WN_Helper_Resolve;
 
     if (mFlags.WantFinalize())
         mJSClass.finalize = XPC_WN_Helper_Finalize;
     else
         mJSClass.finalize = XPC_WN_NoHelper_Finalize;
 
-    js::ObjectOps* ops = &mJSClass.ops;
     if (mFlags.WantNewEnumerate())
-        ops->enumerate = XPC_WN_JSOp_Enumerate;
+        mJSClass.ops = &XPC_WN_ObjectOpsWithEnumerate;
 
     if (mFlags.WantCall())
         mJSClass.call = XPC_WN_Helper_Call;
     if (mFlags.WantConstruct())
         mJSClass.construct = XPC_WN_Helper_Construct;
 
     if (mFlags.WantHasInstance())
         mJSClass.hasInstance = XPC_WN_Helper_HasInstance;
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -937,43 +937,18 @@ extern const js::Class XPC_WN_NoHelper_P
 
 extern bool
 XPC_WN_CallMethod(JSContext* cx, unsigned argc, JS::Value* vp);
 
 extern bool
 XPC_WN_GetterSetter(JSContext* cx, unsigned argc, JS::Value* vp);
 
 // Macros to initialize Object or Function like XPC_WN classes
-#define XPC_WN_WithCall_ObjectOps                                             \
-    {                                                                         \
-        nullptr, /* lookupProperty */                                         \
-        nullptr, /* defineProperty */                                         \
-        nullptr, /* hasProperty */                                            \
-        nullptr, /* getProperty    */                                         \
-        nullptr, /* setProperty    */                                         \
-        nullptr, /* getOwnPropertyDescriptor */                               \
-        nullptr, /* deleteProperty */                                         \
-        nullptr, nullptr, /* watch/unwatch */                                 \
-        nullptr, /* getElements */                                            \
-        nullptr, /* enumerate */                                              \
-    }
-
-#define XPC_WN_NoCall_ObjectOps                                               \
-    {                                                                         \
-        nullptr, /* lookupProperty */                                         \
-        nullptr, /* defineProperty */                                         \
-        nullptr, /* hasProperty */                                            \
-        nullptr, /* getProperty    */                                         \
-        nullptr, /* setProperty    */                                         \
-        nullptr, /* getOwnPropertyDescriptor */                               \
-        nullptr, /* deleteProperty */                                         \
-        nullptr, nullptr, /* watch/unwatch */                                 \
-        nullptr, /* getElements */                                            \
-        nullptr, /* enumerate */                                              \
-    }
+#define XPC_WN_WithCall_ObjectOps JS_NULL_OBJECT_OPS
+#define XPC_WN_NoCall_ObjectOps   JS_NULL_OBJECT_OPS
 
 // Maybe this macro should check for class->enumerate ==
 // XPC_WN_Shared_Proto_Enumerate or something rather than checking for
 // 4 classes?
 static inline bool IS_PROTO_CLASS(const js::Class* clazz)
 {
     return clazz == &XPC_WN_NoMods_WithCall_Proto_JSClass ||
            clazz == &XPC_WN_NoMods_NoCall_Proto_JSClass ||