Bug 887558 (part 1) - Introduce ProxyObject and some sub-classes. r=jorendorff.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 20 Jun 2013 21:27:28 -0700
changeset 138270 9b78a71801209dfea5e12554b3c46cb280aabde4
parent 138269 58318862693968bd1b284c8131e34db8c975f81b
child 138271 96b8f28d35e8aa2ad1ab41fad4a17662a056ba9c
push id30910
push usernnethercote@mozilla.com
push dateFri, 12 Jul 2013 06:44:41 +0000
treeherdermozilla-inbound@08cb6548110d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs887558
milestone25.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 887558 (part 1) - Introduce ProxyObject and some sub-classes. r=jorendorff.
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
js/src/builtin/Object.cpp
js/src/builtin/TestingFunctions.cpp
js/src/ion/BaselineIC.cpp
js/src/ion/IonCaches.cpp
js/src/ion/IonMacroAssembler.h
js/src/jsapi.cpp
js/src/jsbool.cpp
js/src/jscntxtinlines.h
js/src/jscompartment.cpp
js/src/jsfriendapi.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/json.cpp
js/src/jsproxy.cpp
js/src/jsproxy.h
js/src/jsweakmap.cpp
js/src/jswrapper.cpp
js/src/shell/js.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/ObjectImpl-inl.h
js/src/vm/ObjectImpl.cpp
js/src/vm/ObjectImpl.h
js/src/vm/ProxyObject.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Shape.cpp
js/src/vm/TypedArrayObject.cpp
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1345,34 +1345,34 @@ HasPropertyOnPrototype(JSContext* cx, JS
 
 JSObject*
 GetXrayExpandoChain(JSObject* obj)
 {
   js::Class* clasp = js::GetObjectClass(obj);
   JS::Value v;
   if (IsDOMClass(clasp) || IsDOMIfaceAndProtoClass(clasp)) {
     v = js::GetReservedSlot(obj, DOM_XRAY_EXPANDO_SLOT);
-  } else if (js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) {
+  } else if (js::IsProxyClass(clasp)) {
     MOZ_ASSERT(js::GetProxyHandler(obj)->family() == ProxyFamily());
     v = js::GetProxyExtra(obj, JSPROXYSLOT_XRAY_EXPANDO);
   } else {
     MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
     v = js::GetFunctionNativeReserved(obj, CONSTRUCTOR_XRAY_EXPANDO_SLOT);
   }
   return v.isUndefined() ? nullptr : &v.toObject();
 }
 
 void
 SetXrayExpandoChain(JSObject* obj, JSObject* chain)
 {
   JS::Value v = chain ? JS::ObjectValue(*chain) : JSVAL_VOID;
   js::Class* clasp = js::GetObjectClass(obj);
   if (IsDOMClass(clasp) || IsDOMIfaceAndProtoClass(clasp)) {
     js::SetReservedSlot(obj, DOM_XRAY_EXPANDO_SLOT, v);
-  } else if (js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) {
+  } else if (js::IsProxyClass(clasp)) {
     MOZ_ASSERT(js::GetProxyHandler(obj)->family() == ProxyFamily());
     js::SetProxyExtra(obj, JSPROXYSLOT_XRAY_EXPANDO, v);
   } else {
     MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
     js::SetFunctionNativeReserved(obj, CONSTRUCTOR_XRAY_EXPANDO_SLOT, v);
   }
 }
 
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -145,17 +145,17 @@ UnwrapDOMObject(JSObject* obj)
 inline const DOMClass*
 GetDOMClass(JSObject* obj)
 {
   js::Class* clasp = js::GetObjectClass(obj);
   if (IsDOMClass(clasp)) {
     return &DOMJSClass::FromJSClass(clasp)->mClass;
   }
 
-  if (js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) {
+  if (js::IsProxyClass(clasp)) {
     js::BaseProxyHandler* handler = js::GetProxyHandler(obj);
     if (handler->family() == ProxyFamily()) {
       return &static_cast<DOMProxyHandler*>(handler)->mClass;
     }
   }
 
   return nullptr;
 }
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -415,17 +415,17 @@ obj_lookupGetter(JSContext *cx, unsigned
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedId id(cx);
     if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(0), &id))
         return JS_FALSE;
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return JS_FALSE;
-    if (obj->isProxy()) {
+    if (obj->is<ProxyObject>()) {
         // The vanilla getter lookup code below requires that the object is
         // native. Handle proxies separately.
         args.rval().setUndefined();
         AutoPropertyDescriptorRooter desc(cx);
         if (!Proxy::getPropertyDescriptor(cx, obj, id, &desc, 0))
             return JS_FALSE;
         if (desc.obj && (desc.attrs & JSPROP_GETTER) && desc.getter)
             args.rval().set(CastAsObjectJsval(desc.getter));
@@ -451,17 +451,17 @@ obj_lookupSetter(JSContext *cx, unsigned
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedId id(cx);
     if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(0), &id))
         return JS_FALSE;
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return JS_FALSE;
-    if (obj->isProxy()) {
+    if (obj->is<ProxyObject>()) {
         // The vanilla setter lookup code below requires that the object is
         // native. Handle proxies separately.
         args.rval().setUndefined();
         AutoPropertyDescriptorRooter desc(cx);
         if (!Proxy::getPropertyDescriptor(cx, obj, id, &desc, 0))
             return JS_FALSE;
         if (desc.obj && (desc.attrs & JSPROP_SETTER) && desc.setter)
             args.rval().set(CastAsObjectJsval(desc.setter));
@@ -607,17 +607,17 @@ obj_hasOwnProperty(JSContext *cx, unsign
 
     HandleValue idValue = args.handleOrUndefinedAt(0);
 
     /* Step 1, 2. */
     jsid id;
     if (args.thisv().isObject() && ValueToId<NoGC>(cx, idValue, &id)) {
         JSObject *obj = &args.thisv().toObject(), *obj2;
         Shape *prop;
-        if (!obj->isProxy() &&
+        if (!obj->is<ProxyObject>() &&
             HasOwnProperty<NoGC>(cx, obj->getOps()->lookupGeneric, obj, id, &obj2, &prop))
         {
             args.rval().setBoolean(!!prop);
             return true;
         }
     }
 
     /* Step 1. */
@@ -628,17 +628,17 @@ obj_hasOwnProperty(JSContext *cx, unsign
     /* Step 2. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Non-standard code for proxies. */
     RootedObject obj2(cx);
     RootedShape prop(cx);
-    if (obj->isProxy()) {
+    if (obj->is<ProxyObject>()) {
         bool has;
         if (!Proxy::hasOwn(cx, obj, idRoot, &has))
             return false;
         args.rval().setBoolean(has);
         return true;
     }
 
     /* Step 3. */
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -326,17 +326,17 @@ IsProxy(JSContext *cx, unsigned argc, js
     if (argc != 1) {
         JS_ReportError(cx, "the function takes exactly one argument");
         return false;
     }
     if (!args[0].isObject()) {
         args.rval().setBoolean(false);
         return true;
     }
-    args.rval().setBoolean(args[0].toObject().isProxy());
+    args.rval().setBoolean(args[0].toObject().is<ProxyObject>());
     return true;
 }
 
 static JSBool
 InternalConst(JSContext *cx, unsigned argc, jsval *vp)
 {
     if (argc != 1) {
         JS_ReportError(cx, "the function takes exactly one argument");
--- a/js/src/ion/BaselineIC.cpp
+++ b/js/src/ion/BaselineIC.cpp
@@ -3061,17 +3061,17 @@ static void GetFixedOrDynamicSlotOffset(
     *isFixed = obj->isFixedSlot(slot);
     *offset = *isFixed ? JSObject::getFixedSlotOffset(slot)
                        : obj->dynamicSlotIndex(slot) * sizeof(Value);
 }
 
 static bool
 IsCacheableDOMProxy(JSObject *obj)
 {
-    if (!obj->isProxy())
+    if (!obj->is<ProxyObject>())
         return false;
 
     BaseProxyHandler *handler = GetProxyHandler(obj);
 
     if (handler->family() != GetDOMProxyHandlerFamily())
         return false;
 
     if (obj->numFixedSlots() <= GetDOMProxyExpandoSlot())
@@ -8565,17 +8565,17 @@ ICGetPropCallDOMProxyNativeCompiler::ICG
     firstMonitorStub_(firstMonitorStub),
     obj_(cx, obj),
     holder_(cx, holder),
     getter_(cx, getter),
     pcOffset_(pcOffset)
 {
     JS_ASSERT(kind == ICStub::GetProp_CallDOMProxyNative ||
               kind == ICStub::GetProp_CallDOMProxyWithGenerationNative);
-    JS_ASSERT(obj_->isProxy());
+    JS_ASSERT(obj_->is<ProxyObject>());
     JS_ASSERT(GetProxyHandler(obj_)->family() == GetDOMProxyHandlerFamily());
 }
 
 ICGetProp_DOMProxyShadowed::ICGetProp_DOMProxyShadowed(IonCode *stubCode,
                                                        ICStub *firstMonitorStub,
                                                        HandleShape shape,
                                                        BaseProxyHandler *proxyHandler,
                                                        HandlePropertyName name,
--- a/js/src/ion/IonCaches.cpp
+++ b/js/src/ion/IonCaches.cpp
@@ -419,17 +419,17 @@ IonCache::updateBaseAddress(IonCode *cod
 void
 IonCache::initializeAddCacheState(LInstruction *ins, AddCacheState *addState)
 {
 }
 
 static bool
 IsCacheableDOMProxy(JSObject *obj)
 {
-    if (!obj->isProxy())
+    if (!obj->is<ProxyObject>())
         return false;
 
     BaseProxyHandler *handler = GetProxyHandler(obj);
 
     if (handler->family() != GetDOMProxyHandlerFamily())
         return false;
 
     if (obj->numFixedSlots() <= GetDOMProxyExpandoSlot())
@@ -1343,17 +1343,17 @@ DetermineGetPropKind(JSContext *cx, IonC
     jsbytecode *pc;
     cache.getScriptedLocation(&script, &pc);
 
     if (IsCacheableGetPropReadSlot(checkObj, holder, shape) ||
         IsCacheableNoProperty(receiver, holder, shape, pc, output))
     {
         // With Proxies, we cannot garantee any property access as the proxy can
         // mask any property from the prototype chain.
-        JS_ASSERT(!checkObj->isProxy());
+        JS_ASSERT(!checkObj->is<ProxyObject>());
         *readSlot = true;
     } else if (IsCacheableGetPropCallNative(checkObj, holder, shape) ||
                IsCacheableGetPropCallPropertyOp(checkObj, holder, shape))
     {
         // Don't enable getter call if cache is idempotent, since
         // they can be effectful.
         if (!cache.idempotent() && allowGetters)
             *callGetter = true;
--- a/js/src/ion/IonMacroAssembler.h
+++ b/js/src/ion/IonMacroAssembler.h
@@ -19,16 +19,17 @@
 # include "ion/arm/MacroAssembler-arm.h"
 #endif
 #include "ion/AsmJS.h"
 #include "ion/IonCompartment.h"
 #include "ion/IonInstrumentation.h"
 #include "ion/ParallelFunctions.h"
 #include "ion/VMFunctions.h"
 #include "vm/ForkJoin.h"
+#include "vm/ProxyObject.h"
 #include "vm/Shape.h"
 #include "vm/TypedArrayObject.h"
 
 namespace js {
 namespace ion {
 
 // The public entrypoint for emitting assembly. Note that a MacroAssembler can
 // use cx->lifoAlloc, so take care not to interleave masm use with other
@@ -748,19 +749,19 @@ class MacroAssembler : public MacroAssem
 
     Condition branchTestObjectTruthy(bool truthy, Register objReg, Register scratch,
                                      Label *slowCheck)
     {
         // The branches to out-of-line code here implement a conservative version
         // of the JSObject::isWrapper test performed in EmulatesUndefined.  If none
         // of the branches are taken, we can check class flags directly.
         loadObjClass(objReg, scratch);
-        branchPtr(Assembler::Equal, scratch, ImmWord(&ObjectProxyClass), slowCheck);
-        branchPtr(Assembler::Equal, scratch, ImmWord(&OuterWindowProxyClass), slowCheck);
-        branchPtr(Assembler::Equal, scratch, ImmWord(&FunctionProxyClass), slowCheck);
+        branchPtr(Assembler::Equal, scratch, ImmWord(&ObjectProxyObject::class_), slowCheck);
+        branchPtr(Assembler::Equal, scratch, ImmWord(&OuterWindowProxyObject::class_), slowCheck);
+        branchPtr(Assembler::Equal, scratch, ImmWord(&FunctionProxyObject::class_), slowCheck);
 
         test32(Address(scratch, Class::offsetOfFlags()), Imm32(JSCLASS_EMULATES_UNDEFINED));
         return truthy ? Assembler::Zero : Assembler::NonZero;
     }
 
     void pushCalleeToken(Register callee, ExecutionMode mode);
     void PushCalleeToken(Register callee, ExecutionMode mode);
     void tagCallee(Register callee, ExecutionMode mode);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -109,17 +109,17 @@ bool
 JS::detail::CallMethodIfWrapped(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                                CallArgs args)
 {
     const Value &thisv = args.thisv();
     JS_ASSERT(!test(thisv));
 
     if (thisv.isObject()) {
         JSObject &thisObj = args.thisv().toObject();
-        if (thisObj.isProxy())
+        if (thisObj.is<ProxyObject>())
             return Proxy::nativeCall(cx, test, impl, args);
     }
 
     ReportIncompatible(cx, args);
     return false;
 }
 
 #ifdef HAVE_VA_LIST_AS_ARRAY
@@ -1774,17 +1774,17 @@ static const JSStdName standard_class_at
     {js_InitJSONClass,                  EAGER_ATOM_AND_CLASP(JSON)},
     {js_InitTypedArrayClasses,          EAGER_CLASS_ATOM(ArrayBuffer), &js::ArrayBufferObject::protoClass},
     {js_InitWeakMapClass,               EAGER_ATOM_AND_OCLASP(WeakMap)},
     {js_InitMapClass,                   EAGER_ATOM_AND_OCLASP(Map)},
     {js_InitSetClass,                   EAGER_ATOM_AND_OCLASP(Set)},
 #ifdef ENABLE_PARALLEL_JS
     {js_InitParallelArrayClass,         EAGER_ATOM_AND_OCLASP(ParallelArray)},
 #endif
-    {js_InitProxyClass,                 EAGER_CLASS_ATOM(Proxy), &js::ObjectProxyClass},
+    {js_InitProxyClass,                 EAGER_CLASS_ATOM(Proxy), OCLASP(ObjectProxy)},
 #if ENABLE_INTL_API
     {js_InitIntlClass,                  EAGER_ATOM_AND_CLASP(Intl)},
 #endif
     {NULL,                              0, NULL}
 };
 
 /*
  * Table of top-level function and constant names and their init functions.
@@ -3478,17 +3478,17 @@ LookupResult(JSContext *cx, HandleObject
 {
     if (!shape) {
         /* XXX bad API: no way to tell "not defined" from "void value" */
         vp->setUndefined();
         return JS_TRUE;
     }
 
     if (!obj2->isNative()) {
-        if (obj2->isProxy()) {
+        if (obj2->is<ProxyObject>()) {
             AutoPropertyDescriptorRooter desc(cx);
             if (!Proxy::getPropertyDescriptor(cx, obj2, id, &desc, 0))
                 return false;
             if (!(desc.attrs & JSPROP_SHARED)) {
                 *vp = desc.value;
                 return true;
             }
         }
@@ -3989,17 +3989,17 @@ GetPropertyDescriptorById(JSContext *cx,
             desc->getter = shape->getter();
             desc->setter = shape->setter();
             if (shape->hasSlot())
                 desc->value = obj2->nativeGetSlot(shape->slot());
             else
                 desc->value.setUndefined();
         }
     } else {
-        if (obj2->isProxy()) {
+        if (obj2->is<ProxyObject>()) {
             JSAutoResolveFlags rf(cx, flags);
             return own
                    ? Proxy::getOwnPropertyDescriptor(cx, obj2, id, desc, 0)
                    : Proxy::getPropertyDescriptor(cx, obj2, id, desc, 0);
         }
         if (!JSObject::getGenericAttributes(cx, obj2, id, &desc->attrs))
             return false;
         desc->getter = NULL;
--- a/js/src/jsbool.cpp
+++ b/js/src/jsbool.cpp
@@ -12,16 +12,17 @@
 
 #include "jstypes.h"
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsobj.h"
 
 #include "vm/GlobalObject.h"
+#include "vm/ProxyObject.h"
 #include "vm/StringBuffer.h"
 
 #include "jsboolinlines.h"
 
 #include "vm/BooleanObject-inl.h"
 #include "vm/GlobalObject-inl.h"
 
 using namespace js;
@@ -195,13 +196,13 @@ js::ToBooleanSlow(const Value &v)
 
 /*
  * This slow path is only ever taken for proxies wrapping Boolean objects
  * The only caller of the fast path, JSON's PreprocessValue, ensures that.
  */
 bool
 js::BooleanGetPrimitiveValueSlow(HandleObject wrappedBool, JSContext *cx)
 {
-    JS_ASSERT(wrappedBool->isProxy());
+    JS_ASSERT(wrappedBool->is<ProxyObject>());
     JSObject *obj = Wrapper::wrappedObject(wrappedBool);
     JS_ASSERT(obj);
     return obj->as<BooleanObject>().unbox();
 }
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -273,17 +273,17 @@ CallJSNativeConstructor(JSContext *cx, N
      * - CallOrConstructBoundFunction is an exception as well because we might
      *   have used bind on a proxy function.
      *
      * - new Iterator(x) is user-hookable; it returns x.__iterator__() which
      *   could be any object.
      *
      * - (new Object(Object)) returns the callee.
      */
-    JS_ASSERT_IF(native != FunctionProxyClass.construct &&
+    JS_ASSERT_IF(native != FunctionProxyObject::class_.construct &&
                  native != js::CallOrConstructBoundFunction &&
                  native != js::IteratorConstructor &&
                  (!callee->is<JSFunction>() || callee->as<JSFunction>().native() != obj_construct),
                  !args.rval().isPrimitive() && callee != &args.rval().toObject());
 
     return true;
 }
 
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -307,17 +307,18 @@ JSCompartment::wrap(JSContext *cx, Mutab
     }
 
     RootedObject proto(cx, Proxy::LazyProto);
     RootedObject obj(cx, &vp.toObject());
     RootedObject existing(cx, existingArg);
     if (existing) {
         /* Is it possible to reuse |existing|? */
         if (!existing->getTaggedProto().isLazy() ||
-            existing->getClass() != &ObjectProxyClass ||
+            // Note: don't use is<ObjectProxyObject>() here -- it also matches subclasses!
+            existing->getClass() != &ObjectProxyObject::class_ ||
             existing->getParent() != global ||
             obj->isCallable())
         {
             existing = NULL;
         }
     }
 
     /*
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -569,17 +569,17 @@ JS_FRIEND_API(size_t)
 JS_GetCustomIteratorCount(JSContext *cx)
 {
     return sCustomIteratorCount;
 }
 
 JS_FRIEND_API(JSBool)
 JS_IsDeadWrapper(JSObject *obj)
 {
-    if (!IsProxy(obj)) {
+    if (!obj->is<ProxyObject>()) {
         return false;
     }
 
     BaseProxyHandler *handler = GetProxyHandler(obj);
     return handler->family() == &DeadObjectProxy::sDeadObjectFamily;
 }
 
 void
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -382,19 +382,22 @@ struct Function {
 struct Atom {
     static const size_t LENGTH_SHIFT = 4;
     size_t lengthAndFlags;
     const jschar *chars;
 };
 
 } /* namespace shadow */
 
-extern JS_FRIEND_DATA(js::Class) FunctionProxyClass;
-extern JS_FRIEND_DATA(js::Class) OuterWindowProxyClass;
-extern JS_FRIEND_DATA(js::Class) ObjectProxyClass;
+// These are equal to |&{Function,Object,OuterWindow}ProxyObject::class_|.  Use
+// them in places where you don't want to #include vm/ProxyObject.h.
+extern JS_FRIEND_DATA(js::Class*) FunctionProxyClassPtr;
+extern JS_FRIEND_DATA(js::Class*) ObjectProxyClassPtr;
+extern JS_FRIEND_DATA(js::Class*) OuterWindowProxyClassPtr;
+
 extern JS_FRIEND_DATA(js::Class) ObjectClass;
 
 inline js::Class *
 GetObjectClass(JSObject *obj)
 {
     return reinterpret_cast<const shadow::Object*>(obj)->type->clasp;
 }
 
@@ -487,19 +490,19 @@ GetFunctionNativeReserved(JSObject *fun,
 
 JS_FRIEND_API(void)
 SetFunctionNativeReserved(JSObject *fun, size_t which, const Value &val);
 
 inline bool
 GetObjectProto(JSContext *cx, JS::Handle<JSObject*> obj, JS::MutableHandle<JSObject*> proto)
 {
     js::Class *clasp = GetObjectClass(obj);
-    if (clasp == &js::ObjectProxyClass ||
-        clasp == &js::OuterWindowProxyClass ||
-        clasp == &js::FunctionProxyClass)
+    if (clasp == js::ObjectProxyClassPtr ||
+        clasp == js::OuterWindowProxyClassPtr ||
+        clasp == js::FunctionProxyClassPtr)
     {
         return JS_GetPrototype(cx, obj, proto.address());
     }
 
     proto.set(reinterpret_cast<const shadow::Object*>(obj.get())->type->proto);
     return true;
 }
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -741,17 +741,17 @@ js::FunctionToString(JSContext *cx, Hand
     }
     return out.finishString();
 }
 
 JSString *
 fun_toStringHelper(JSContext *cx, HandleObject obj, unsigned indent)
 {
     if (!obj->is<JSFunction>()) {
-        if (IsFunctionProxy(obj))
+        if (obj->is<FunctionProxyObject>())
             return Proxy::fun_toString(cx, obj, indent);
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_INCOMPATIBLE_PROTO,
                              js_Function_str, js_toString_str,
                              "object");
         return NULL;
     }
 
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -101,28 +101,28 @@ Enumerate(JSContext *cx, HandleObject po
      * show up in property enumeration, even if only for |Object.prototype|
      * (think introspection by Prototype-like frameworks that add methods to
      * the built-in prototypes).  So exclude __proto__ if the object where the
      * property was found has no [[Prototype]] and might be |Object.prototype|.
      */
     if (JS_UNLIKELY(!pobj->getTaggedProto().isObject() && JSID_IS_ATOM(id, cx->names().proto)))
         return true;
 
-    if (!(flags & JSITER_OWNONLY) || pobj->isProxy() || pobj->getOps()->enumerate) {
+    if (!(flags & JSITER_OWNONLY) || pobj->is<ProxyObject>() || pobj->getOps()->enumerate) {
         /* If we've already seen this, we definitely won't add it. */
         IdSet::AddPtr p = ht.lookupForAdd(id);
         if (JS_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->isProxy() || pobj->getProto() || pobj->getOps()->enumerate) && !ht.add(p, id))
+        if ((pobj->is<ProxyObject>() || pobj->getProto() || pobj->getOps()->enumerate) && !ht.add(p, id))
             return false;
     }
 
     if (enumerable || (flags & JSITER_HIDDEN))
         return props->append(id);
 
     return true;
 }
@@ -201,17 +201,17 @@ Snapshot(JSContext *cx, JSObject *pobj_,
         if (pobj->isNative() &&
             !pobj->getOps()->enumerate &&
             !(clasp->flags & JSCLASS_NEW_ENUMERATE)) {
             if (!clasp->enumerate(cx, pobj))
                 return false;
             if (!EnumerateNativeProperties(cx, pobj, flags, ht, props))
                 return false;
         } else {
-            if (pobj->isProxy()) {
+            if (pobj->is<ProxyObject>()) {
                 AutoIdVector proxyProps(cx);
                 if (flags & JSITER_OWNONLY) {
                     if (flags & JSITER_HIDDEN) {
                         if (!Proxy::getOwnPropertyNames(cx, pobj, proxyProps))
                             return false;
                     } else {
                         if (!Proxy::keys(cx, pobj, proxyProps))
                             return false;
@@ -677,17 +677,17 @@ js::GetIterator(JSContext *cx, HandleObj
                     if (shapes.length() == 2)
                         cx->runtime()->nativeIterCache.last = iterobj;
                     return true;
                 }
             }
         }
 
       miss:
-        if (obj->isProxy()) {
+        if (obj->is<ProxyObject>()) {
             types::MarkIteratorUnknown(cx);
             return Proxy::iterate(cx, obj, flags, vp);
         }
         if (!GetCustomIterator(cx, obj, flags, vp))
             return false;
         if (!vp.isUndefined()) {
             types::MarkIteratorUnknown(cx);
             return true;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -262,17 +262,17 @@ PropDesc::makeObject(JSContext *cx)
     return true;
 }
 
 bool
 js::GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
                              PropertyDescriptor *desc)
 {
     // FIXME: Call TrapGetOwnProperty directly once ScriptedIndirectProxies is removed
-    if (obj->isProxy())
+    if (obj->is<ProxyObject>())
         return Proxy::getOwnPropertyDescriptor(cx, obj, id, desc, 0);
 
     RootedObject pobj(cx);
     RootedShape shape(cx);
     if (!HasOwnProperty<CanGC>(cx, obj->getOps()->lookupGeneric, obj, id, &pobj, &shape))
         return false;
     if (!shape) {
         desc->obj = NULL;
@@ -985,17 +985,17 @@ js::DefineProperty(JSContext *cx, Handle
         return DefinePropertyOnArray(cx, arr, id, desc, throwError, rval);
     }
 
     if (obj->getOps()->lookupGeneric) {
         /*
          * FIXME: Once ScriptedIndirectProxies are removed, this code should call
          * TrapDefineOwnProperty directly
          */
-        if (obj->isProxy()) {
+        if (obj->is<ProxyObject>()) {
             RootedValue pd(cx, desc.pd());
             return Proxy::defineProperty(cx, obj, id, pd);
         }
         return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval);
     }
 
     return DefinePropertyOnObject(cx, obj, id, desc, throwError, rval);
 }
@@ -1075,17 +1075,17 @@ js::DefineProperties(JSContext *cx, Hand
         return true;
     }
 
     if (obj->getOps()->lookupGeneric) {
         /*
          * FIXME: Once ScriptedIndirectProxies are removed, this code should call
          * TrapDefineOwnProperty directly
          */
-        if (obj->isProxy()) {
+        if (obj->is<ProxyObject>()) {
             for (size_t i = 0, len = ids.length(); i < len; i++) {
                 RootedValue pd(cx, descs[i].pd());
                 if (!Proxy::defineProperty(cx, obj, ids.handleAt(i), pd))
                     return false;
             }
             return true;
         }
         bool dummy;
@@ -1265,17 +1265,17 @@ JSObject::isSealedOrFrozen(JSContext *cx
 }
 
 /* static */
 const char *
 JSObject::className(JSContext *cx, HandleObject obj)
 {
     assertSameCompartment(cx, obj);
 
-    if (obj->isProxy())
+    if (obj->is<ProxyObject>())
         return Proxy::className(cx, obj);
 
     return obj->getClass()->name;
 }
 
 /*
  * Get the GC kind to use for scripted 'new' on the given class.
  * FIXME bug 547327: estimate the size from the allocation site.
@@ -1837,17 +1837,17 @@ CopySlots(JSContext *cx, HandleObject fr
         to->setSlot(n, v);
     }
     return true;
 }
 
 JSObject *
 js::CloneObject(JSContext *cx, HandleObject obj, Handle<js::TaggedProto> proto, HandleObject parent)
 {
-    if (!obj->isNative() && !obj->isProxy()) {
+    if (!obj->isNative() && !obj->is<ProxyObject>()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_CANT_CLONE_OBJECT);
         return NULL;
     }
     RootedObject clone(cx, NewObjectWithGivenProto(cx, obj->getClass(), proto, parent));
     if (!clone)
         return NULL;
     if (obj->isNative()) {
@@ -1855,17 +1855,17 @@ js::CloneObject(JSContext *cx, HandleObj
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                                  JSMSG_CANT_CLONE_OBJECT);
             return NULL;
         }
 
         if (obj->hasPrivate())
             clone->setPrivate(obj->getPrivate());
     } else {
-        JS_ASSERT(obj->isProxy());
+        JS_ASSERT(obj->is<ProxyObject>());
         if (!CopySlots(cx, obj, clone))
             return NULL;
     }
 
     return clone;
 }
 
 JSObject *
@@ -4149,17 +4149,17 @@ GetPropertyHelperInline(JSContext *cx,
 
     if (!obj2->isNative()) {
         if (!allowGC)
             return false;
         HandleObject obj2Handle = MaybeRooted<JSObject*, allowGC>::toHandle(obj2);
         HandleObject receiverHandle = MaybeRooted<JSObject*, allowGC>::toHandle(receiver);
         HandleId idHandle = MaybeRooted<jsid, allowGC>::toHandle(id);
         MutableHandleValue vpHandle = MaybeRooted<Value, allowGC>::toMutableHandle(vp);
-        return obj2->isProxy()
+        return obj2->template is<ProxyObject>()
                ? Proxy::get(cx, obj2Handle, receiverHandle, idHandle, vpHandle)
                : JSObject::getGeneric(cx, obj2Handle, obj2Handle, idHandle, vpHandle);
     }
 
     if (IsImplicitDenseElement(shape)) {
         vp.set(obj2->getDenseElement(JSID_TO_INT(id)));
         return true;
     }
@@ -4510,17 +4510,17 @@ baseops::SetPropertyHelper(JSContext *cx
     }
 
     RootedObject pobj(cx);
     RootedShape shape(cx);
     if (!LookupPropertyWithFlags(cx, obj, id, cx->resolveFlags, &pobj, &shape))
         return false;
     if (shape) {
         if (!pobj->isNative()) {
-            if (pobj->isProxy()) {
+            if (pobj->is<ProxyObject>()) {
                 AutoPropertyDescriptorRooter pd(cx);
                 if (!Proxy::getPropertyDescriptor(cx, pobj, id, &pd, JSRESOLVE_ASSIGNING))
                     return false;
 
                 if ((pd.attrs & (JSPROP_SHARED | JSPROP_SHADOWABLE)) == JSPROP_SHARED) {
                     return !pd.setter ||
                            CallSetter(cx, receiver, id, pd.setter, pd.attrs, pd.shortid, strict,
                                       vp);
@@ -5370,30 +5370,30 @@ DumpProperty(JSObject *obj, Shape &shape
         fprintf(stderr, " (INVALID!)");
     }
     fprintf(stderr, "\n");
 }
 
 bool
 JSObject::uninlinedIsProxy() const
 {
-    return isProxy();
+    return is<ProxyObject>();
 }
 
 void
 JSObject::dump()
 {
     JSObject *obj = this;
     fprintf(stderr, "object %p\n", (void *) obj);
     Class *clasp = obj->getClass();
     fprintf(stderr, "class %p %s\n", (void *)clasp, clasp->name);
 
     fprintf(stderr, "flags:");
     if (obj->isDelegate()) fprintf(stderr, " delegate");
-    if (!obj->isProxy() && !obj->nonProxyIsExtensible()) fprintf(stderr, " not_extensible");
+    if (!obj->is<ProxyObject>() && !obj->nonProxyIsExtensible()) fprintf(stderr, " not_extensible");
     if (obj->isIndexed()) fprintf(stderr, " indexed");
 
     if (obj->isNative()) {
         if (obj->inDictionaryMode())
             fprintf(stderr, " inDictionaryMode");
         if (obj->hasShapeTable())
             fprintf(stderr, " hasShapeTable");
     }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -988,21 +988,16 @@ class JSObject : public js::ObjectImpl
      * these has a respective class that derives and adds operations.
      *
      * A class XObject is defined in a vm/XObject{.h, .cpp, -inl.h} file
      * triplet (along with any class YObject that derives XObject).
      *
      * Note that X represents a low-level representation and does not query the
      * [[Class]] property of object defined by the spec (for this, see
      * js::ObjectClassIs).
-     *
-     * SpiderMonkey has not been completely switched to the is/as/XObject
-     * pattern so in some cases there is no XObject class and the engine
-     * instead pokes directly at reserved slots and getPrivate. In such cases,
-     * consider adding the missing XObject class.
      */
 
     template <class T>
     inline bool is() const { return getClass() == &T::class_; }
 
     template <class T>
     T &as() {
         JS_ASSERT(is<T>());
@@ -1012,21 +1007,19 @@ class JSObject : public js::ObjectImpl
     template <class T>
     const T &as() const {
         JS_ASSERT(is<T>());
         return *static_cast<const T *>(this);
     }
 
     /* Direct subtypes of JSObject: */
     inline bool isObject()           const { return hasClass(&js::ObjectClass); }
-    using js::ObjectImpl::isProxy;
 
     /* Subtypes of Proxy. */
     inline bool isWrapper()                 const;
-    inline bool isFunctionProxy()           const { return hasClass(&js::FunctionProxyClass); }
     inline bool isCrossCompartmentWrapper() const;
 
     static inline js::ThingRootKind rootKind() { return js::THING_ROOT_OBJECT; }
 
 #ifdef DEBUG
     void dump();
 #endif
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -533,17 +533,17 @@ JSObject::setType(js::types::TypeObject 
     JS_ASSERT(!hasSingletonType());
     type_ = newType;
 }
 
 /* static */ inline bool
 JSObject::getProto(JSContext *cx, js::HandleObject obj, js::MutableHandleObject protop)
 {
     if (obj->getTaggedProto().isLazy()) {
-        JS_ASSERT(obj->isProxy());
+        JS_ASSERT(obj->is<js::ProxyObject>());
         return js::Proxy::getPrototypeOf(cx, obj, protop);
     } else {
         protop.set(obj->js::ObjectImpl::getProto());
         return true;
     }
 }
 
 inline bool JSObject::isVarObj()
@@ -845,17 +845,17 @@ IsNativeFunction(const js::Value &v, JSN
  * or shadowed. However, we can still do better than the general case by
  * hard-coding the necessary properties for us to find the native we expect.
  *
  * TODO: a per-thread shape-based cache would be faster and simpler.
  */
 static JS_ALWAYS_INLINE bool
 ClassMethodIsNative(JSContext *cx, JSObject *obj, Class *clasp, jsid methodid, JSNative native)
 {
-    JS_ASSERT(!obj->isProxy());
+    JS_ASSERT(!obj->is<ProxyObject>());
     JS_ASSERT(obj->getClass() == clasp);
 
     Value v;
     if (!HasDataProperty(cx, obj, methodid, &v)) {
         JSObject *proto = obj->getProto();
         if (!proto || proto->getClass() != clasp || !HasDataProperty(cx, proto, methodid, &v))
             return false;
     }
@@ -1152,17 +1152,17 @@ DefineConstructorAndPrototype(JSContext 
     }
 
     return true;
 }
 
 inline bool
 ObjectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx)
 {
-    if (JS_UNLIKELY(obj->isProxy()))
+    if (JS_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::objectClassIs(obj, classValue, cx);
 
     switch (classValue) {
       case ESClass_Array: return obj->is<ArrayObject>();
       case ESClass_Number: return obj->is<NumberObject>();
       case ESClass_String: return obj->is<StringObject>();
       case ESClass_Boolean: return obj->is<BooleanObject>();
       case ESClass_RegExp: return obj->is<RegExpObject>();
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -667,17 +667,17 @@ Walk(JSContext *cx, HandleObject holder,
     if (!JSObject::getGeneric(cx, holder, holder, name, &val))
         return false;
 
     /* Step 2. */
     if (val.isObject()) {
         RootedObject obj(cx, &val.toObject());
 
         /* 'val' must have been produced by the JSON parser, so not a proxy. */
-        JS_ASSERT(!obj->isProxy());
+        JS_ASSERT(!obj->is<ProxyObject>());
         if (obj->is<ArrayObject>()) {
             /* Step 2a(ii). */
             uint32_t length = obj->as<ArrayObject>().length();
 
             /* Step 2a(i), 2a(iii-iv). */
             RootedId id(cx);
             RootedValue newElement(cx);
             for (uint32_t i = 0; i < length; i++) {
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -24,32 +24,32 @@
 
 using namespace js;
 using namespace js::gc;
 using mozilla::ArrayLength;
 
 static inline HeapSlot &
 GetCall(JSObject *proxy)
 {
-    JS_ASSERT(IsFunctionProxy(proxy));
+    JS_ASSERT(proxy->is<FunctionProxyObject>());
     return proxy->getSlotRef(JSSLOT_PROXY_CALL);
 }
 
 static inline Value
 GetConstruct(JSObject *proxy)
 {
     if (proxy->slotSpan() <= JSSLOT_PROXY_CONSTRUCT)
         return UndefinedValue();
     return proxy->getSlot(JSSLOT_PROXY_CONSTRUCT);
 }
 
 static inline HeapSlot &
 GetFunctionProxyConstruct(JSObject *proxy)
 {
-    JS_ASSERT(IsFunctionProxy(proxy));
+    JS_ASSERT(proxy->is<FunctionProxyObject>());
     JS_ASSERT(proxy->slotSpan() > JSSLOT_PROXY_CONSTRUCT);
     return proxy->getSlotRef(JSSLOT_PROXY_CONSTRUCT);
 }
 
 void
 js::AutoEnterPolicy::reportError(JSContext *cx, jsid id)
 {
     if (JSID_IS_VOID(id)) {
@@ -83,17 +83,17 @@ js::AutoEnterPolicy::recordLeave()
         JS_ASSERT(context->runtime()->enteredPolicy == this);
         context->runtime()->enteredPolicy = prev;
     }
 }
 
 JS_FRIEND_API(void)
 js::assertEnteredPolicy(JSContext *cx, JSObject *proxy, jsid id)
 {
-    MOZ_ASSERT(proxy->isProxy());
+    MOZ_ASSERT(proxy->is<ProxyObject>());
     MOZ_ASSERT(cx->runtime()->enteredPolicy);
     MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredProxy.ref().get() == proxy);
     MOZ_ASSERT(cx->runtime()->enteredPolicy->enteredId.ref().get() == id);
 }
 #endif
 
 BaseProxyHandler::BaseProxyHandler(void *family)
   : mFamily(family),
@@ -206,17 +206,17 @@ BaseProxyHandler::set(JSContext *cx, Han
         if (!desc.setter) {
             // Be wary of the odd explicit undefined setter case possible through
             // Object.defineProperty.
             if (!(desc.attrs & JSPROP_SETTER))
                 desc.setter = JS_StrictPropertyStub;
         } else if ((desc.attrs & JSPROP_SETTER) || desc.setter != JS_StrictPropertyStub) {
             if (!CallSetter(cx, receiver, id, desc.setter, desc.attrs, desc.shortid, strict, vp))
                 return false;
-            if (!proxy->isProxy() || GetProxyHandler(proxy) != this)
+            if (!proxy->is<ProxyObject>() || GetProxyHandler(proxy) != this)
                 return true;
             if (desc.attrs & JSPROP_SHARED)
                 return true;
         }
         if (!desc.getter) {
             // Same as above for the null setter case.
             if (!(desc.attrs & JSPROP_GETTER))
                 desc.getter = JS_PropertyStub;
@@ -233,17 +233,17 @@ BaseProxyHandler::set(JSContext *cx, Han
         if (!desc.setter) {
             // Be wary of the odd explicit undefined setter case possible through
             // Object.defineProperty.
             if (!(desc.attrs & JSPROP_SETTER))
                 desc.setter = JS_StrictPropertyStub;
         } else if ((desc.attrs & JSPROP_SETTER) || desc.setter != JS_StrictPropertyStub) {
             if (!CallSetter(cx, receiver, id, desc.setter, desc.attrs, desc.shortid, strict, vp))
                 return false;
-            if (!proxy->isProxy() || GetProxyHandler(proxy) != this)
+            if (!proxy->is<ProxyObject>() || GetProxyHandler(proxy) != this)
                 return true;
             if (desc.attrs & JSPROP_SHARED)
                 return true;
         }
         if (!desc.getter) {
             // Same as above for the null setter case.
             if (!(desc.attrs & JSPROP_GETTER))
                 desc.getter = JS_PropertyStub;
@@ -315,17 +315,17 @@ bool
 BaseProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args)
 {
     MOZ_ASSUME_UNREACHABLE("callable proxies should implement construct trap");
 }
 
 const char *
 BaseProxyHandler::className(JSContext *cx, HandleObject proxy)
 {
-    return IsFunctionProxy(proxy) ? "Function" : "Object";
+    return proxy->is<FunctionProxyObject>() ? "Function" : "Object";
 }
 
 JSString *
 BaseProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent)
 {
     MOZ_ASSUME_UNREACHABLE("callable proxies should implement fun_toString trap");
 }
 
@@ -397,17 +397,17 @@ DirectProxyHandler::getPropertyDescripto
 }
 
 static bool
 GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, unsigned flags,
                          JSPropertyDescriptor *desc)
 {
     // If obj is a proxy, we can do better than just guessing. This is
     // important for certain types of wrappers that wrap other wrappers.
-    if (obj->isProxy())
+    if (obj->is<ProxyObject>())
         return Proxy::getOwnPropertyDescriptor(cx, obj, id, desc, flags);
 
     if (!JS_GetPropertyDescriptorById(cx, obj, id, flags, desc))
         return false;
     if (desc->obj != obj)
         desc->obj = NULL;
     return true;
 }
@@ -1034,17 +1034,17 @@ ScriptedIndirectProxyHandler::nativeCall
     return BaseProxyHandler::nativeCall(cx, test, impl, args);
 }
 
 JSString *
 ScriptedIndirectProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent)
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID);
     Value fval = GetCall(proxy);
-    if (IsFunctionProxy(proxy) &&
+    if (proxy->is<FunctionProxyObject>() &&
         (fval.isPrimitive() || !fval.toObject().is<JSFunction>())) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_INCOMPATIBLE_PROTO,
                              js_Function_str, js_toString_str,
                              "object");
         return NULL;
     }
     RootedObject obj(cx, &fval.toObject());
@@ -3047,31 +3047,31 @@ proxy_TraceFunction(JSTracer *trc, JSObj
     MarkCrossCompartmentSlot(trc, obj, &GetCall(obj), "call");
     MarkSlot(trc, &GetFunctionProxyConstruct(obj), "construct");
     proxy_TraceObject(trc, obj);
 }
 
 static JSObject *
 proxy_WeakmapKeyDelegate(JSObject *obj)
 {
-    JS_ASSERT(obj->isProxy());
+    JS_ASSERT(obj->is<ProxyObject>());
     return GetProxyHandler(obj)->weakmapKeyDelegate(obj);
 }
 
 static JSBool
 proxy_Convert(JSContext *cx, HandleObject proxy, JSType hint, MutableHandleValue vp)
 {
-    JS_ASSERT(proxy->isProxy());
+    JS_ASSERT(proxy->is<ProxyObject>());
     return Proxy::defaultValue(cx, proxy, hint, vp);
 }
 
 static void
 proxy_Finalize(FreeOp *fop, JSObject *obj)
 {
-    JS_ASSERT(obj->isProxy());
+    JS_ASSERT(obj->is<ProxyObject>());
     GetProxyHandler(obj)->finalize(fop, obj);
 }
 
 static JSBool
 proxy_HasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, JSBool *bp)
 {
     bool b;
     if (!Proxy::hasInstance(cx, proxy, v, &b))
@@ -3084,17 +3084,17 @@ proxy_HasInstance(JSContext *cx, HandleO
     {                                               \
         NULL,                /* outerObject */      \
         NULL,                /* innerObject */      \
         NULL,                /* iteratorObject */   \
         false,               /* isWrappedNative */  \
         proxy_WeakmapKeyDelegate                    \
     }
 
-JS_FRIEND_DATA(Class) js::ObjectProxyClass = {
+Class js::ObjectProxyObject::class_ = {
     "Proxy",
     Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_HAS_RESERVED_SLOTS(4) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy),
     JS_PropertyStub,         /* addProperty */
     JS_DeletePropertyStub,   /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
@@ -3136,17 +3136,19 @@ JS_FRIEND_DATA(Class) js::ObjectProxyCla
         proxy_DeleteProperty,
         proxy_DeleteElement,
         proxy_DeleteSpecial,
         NULL,                /* enumerate       */
         NULL,                /* thisObject      */
     }
 };
 
-JS_FRIEND_DATA(Class) js::OuterWindowProxyClass = {
+JS_FRIEND_DATA(Class*) js::ObjectProxyClassPtr = &ObjectProxyObject::class_;
+
+Class js::OuterWindowProxyObject::class_ = {
     "Proxy",
     Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_HAS_RESERVED_SLOTS(4),
     JS_PropertyStub,         /* addProperty */
     JS_DeletePropertyStub,   /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
@@ -3193,35 +3195,37 @@ JS_FRIEND_DATA(Class) js::OuterWindowPro
         proxy_DeleteProperty,
         proxy_DeleteElement,
         proxy_DeleteSpecial,
         NULL,                /* enumerate       */
         NULL,                /* thisObject      */
     }
 };
 
+JS_FRIEND_DATA(Class*) js::OuterWindowProxyClassPtr = &OuterWindowProxyObject::class_;
+
 static JSBool
 proxy_Call(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject proxy(cx, &args.callee());
-    JS_ASSERT(proxy->isProxy());
+    JS_ASSERT(proxy->is<ProxyObject>());
     return Proxy::call(cx, proxy, args);
 }
 
 static JSBool
 proxy_Construct(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject proxy(cx, &args.callee());
-    JS_ASSERT(proxy->isProxy());
+    JS_ASSERT(proxy->is<ProxyObject>());
     return Proxy::construct(cx, proxy, args);
 }
 
-JS_FRIEND_DATA(Class) js::FunctionProxyClass = {
+Class js::FunctionProxyObject::class_ = {
     "Proxy",
     Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_HAS_RESERVED_SLOTS(6),
     JS_PropertyStub,         /* addProperty */
     JS_DeletePropertyStub,   /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
@@ -3262,43 +3266,47 @@ JS_FRIEND_DATA(Class) js::FunctionProxyC
         proxy_DeleteProperty,
         proxy_DeleteElement,
         proxy_DeleteSpecial,
         NULL,                /* enumerate       */
         NULL,                /* thisObject      */
     }
 };
 
+JS_FRIEND_DATA(Class*) js::FunctionProxyClassPtr = &FunctionProxyObject::class_;
+
 static JSObject *
 NewProxyObject(JSContext *cx, BaseProxyHandler *handler, HandleValue priv, TaggedProto proto_,
                JSObject *parent_, ProxyCallable callable)
 {
     Rooted<TaggedProto> proto(cx, proto_);
     RootedObject parent(cx, parent_);
 
     JS_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment());
     JS_ASSERT_IF(parent, cx->compartment() == parent->compartment());
     Class *clasp;
     if (callable)
-        clasp = &FunctionProxyClass;
+        clasp = &FunctionProxyObject::class_;
     else
-        clasp = handler->isOuterWindow() ? &OuterWindowProxyClass : &ObjectProxyClass;
+        clasp = handler->isOuterWindow() ? &OuterWindowProxyObject::class_
+                                         : &ObjectProxyObject::class_;
 
     /*
      * Eagerly mark properties unknown for proxies, so we don't try to track
      * their properties and so that we don't need to walk the compartment if
      * their prototype changes later.
      */
     if (proto.isObject()) {
         RootedObject protoObj(cx, proto.toObject());
         if (!JSObject::setNewTypeUnknown(cx, clasp, protoObj))
             return NULL;
     }
 
-    NewObjectKind newKind = clasp == &OuterWindowProxyClass ? SingletonObject : GenericObject;
+    NewObjectKind newKind =
+        clasp == &OuterWindowProxyObject::class_ ? SingletonObject : GenericObject;
     gc::AllocKind allocKind = gc::GetGCObjectKind(clasp);
     if (handler->finalizeInBackground(priv))
         allocKind = GetBackgroundAllocKind(allocKind);
     RootedObject obj(cx, NewObjectWithGivenProto(cx, clasp, proto, parent, allocKind, newKind));
     if (!obj)
         return NULL;
     obj->initSlot(JSSLOT_PROXY_HANDLER, PrivateValue(handler));
     obj->initCrossCompartmentSlot(JSSLOT_PROXY_PRIVATE, priv);
@@ -3341,17 +3349,17 @@ NewProxyObject(JSContext *cx, BaseProxyH
 }
 
 JSObject *
 js::RenewProxyObject(JSContext *cx, JSObject *obj,
                      BaseProxyHandler *handler, Value priv)
 {
     JS_ASSERT_IF(IsCrossCompartmentWrapper(obj), IsDeadProxyObject(obj));
     JS_ASSERT(obj->getParent() == cx->global());
-    JS_ASSERT(obj->getClass() == &ObjectProxyClass);
+    JS_ASSERT(obj->getClass() == &ObjectProxyObject::class_);
     JS_ASSERT(obj->getTaggedProto().isLazy());
 #ifdef DEBUG
     AutoSuppressGC suppressGC(cx);
     JS_ASSERT(!handler->isOuterWindow());
 #endif
 
     obj->setSlot(JSSLOT_PROXY_HANDLER, PrivateValue(handler));
     obj->setCrossCompartmentSlot(JSSLOT_PROXY_PRIVATE, priv);
@@ -3477,11 +3485,11 @@ js_InitProxyClass(JSContext *cx, HandleO
 
     if (!JS_DefineFunctions(cx, ctor, static_methods))
         return NULL;
     if (!JS_DefineProperty(cx, obj, "Proxy", OBJECT_TO_JSVAL(ctor),
                            JS_PropertyStub, JS_StrictPropertyStub, 0)) {
         return NULL;
     }
 
-    MarkStandardClassInitializedNoProto(obj, &ObjectProxyClass);
+    MarkStandardClassInitializedNoProto(obj, &ObjectProxyObject::class_);
     return ctor;
 }
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -254,38 +254,42 @@ class Proxy
     static bool defaultValue(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp);
     static bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
 
     static JSObject * const LazyProto;
 };
 
 inline bool IsObjectProxyClass(const Class *clasp)
 {
-    return clasp == &js::ObjectProxyClass || clasp == &js::OuterWindowProxyClass;
+    return clasp == js::ObjectProxyClassPtr || clasp == js::OuterWindowProxyClassPtr;
 }
 
 inline bool IsFunctionProxyClass(const Class *clasp)
 {
-    return clasp == &js::FunctionProxyClass;
+    return clasp == js::FunctionProxyClassPtr;
+}
+
+inline bool IsProxyClass(const Class *clasp)
+{
+    return IsObjectProxyClass(clasp) || IsFunctionProxyClass(clasp);
 }
 
 inline bool IsObjectProxy(JSObject *obj)
 {
     return IsObjectProxyClass(GetObjectClass(obj));
 }
 
 inline bool IsFunctionProxy(JSObject *obj)
 {
     return IsFunctionProxyClass(GetObjectClass(obj));
 }
 
 inline bool IsProxy(JSObject *obj)
 {
-    Class *clasp = GetObjectClass(obj);
-    return IsObjectProxyClass(clasp) || IsFunctionProxyClass(clasp);
+    return IsProxyClass(GetObjectClass(obj));
 }
 
 /* Shared between object and function proxies. */
 /*
  * NOTE: JSSLOT_PROXY_PRIVATE is 0, because that way slot 0 is usable by API
  * clients for both proxy and non-proxy objects.  So an API client that only
  * needs to store one slot's worth of data doesn't need to branch on what sort
  * of object it has.
--- a/js/src/jsweakmap.cpp
+++ b/js/src/jsweakmap.cpp
@@ -253,17 +253,17 @@ WeakMap_delete(JSContext *cx, unsigned a
     return CallNonGenericMethod<IsWeakMap, WeakMap_delete_impl>(cx, args);
 }
 
 static bool
 TryPreserveReflector(JSContext *cx, HandleObject obj)
 {
     if (obj->getClass()->ext.isWrappedNative ||
         (obj->getClass()->flags & JSCLASS_IS_DOMJSCLASS) ||
-        (obj->isProxy() && GetProxyHandler(obj)->family() == GetDOMProxyHandlerFamily()))
+        (obj->is<ProxyObject>() && GetProxyHandler(obj)->family() == GetDOMProxyHandlerFamily()))
     {
         JS_ASSERT(cx->runtime()->preserveWrapperCallback);
         if (!cx->runtime()->preserveWrapperCallback(cx, obj)) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_WEAKMAP_KEY);
             return false;
         }
     }
     return true;
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -831,17 +831,17 @@ js::NewDeadProxyObject(JSContext *cx, JS
 {
     return NewProxyObject(cx, &DeadObjectProxy::singleton, JS::NullHandleValue,
                           NULL, parent, ProxyNotCallable);
 }
 
 bool
 js::IsDeadProxyObject(JSObject *obj)
 {
-    return IsProxy(obj) && GetProxyHandler(obj) == &DeadObjectProxy::singleton;
+    return obj->is<ProxyObject>() && GetProxyHandler(obj) == &DeadObjectProxy::singleton;
 }
 
 static void
 NukeSlot(JSObject *wrapper, uint32_t slot, Value v)
 {
     Value old = wrapper->getSlot(slot);
     if (old.isMarkable()) {
         Zone *zone = ZoneOfValue(old);
@@ -857,17 +857,17 @@ js::NukeCrossCompartmentWrapper(JSContex
 {
     JS_ASSERT(IsCrossCompartmentWrapper(wrapper));
 
     NotifyGCNukeWrapper(wrapper);
 
     NukeSlot(wrapper, JSSLOT_PROXY_PRIVATE, NullValue());
     SetProxyHandler(wrapper, &DeadObjectProxy::singleton);
 
-    if (IsFunctionProxy(wrapper)) {
+    if (wrapper->is<FunctionProxyObject>()) {
         NukeSlot(wrapper, JSSLOT_PROXY_CALL, NullValue());
         NukeSlot(wrapper, JSSLOT_PROXY_CONSTRUCT, NullValue());
     }
 
     NukeSlot(wrapper, JSSLOT_PROXY_EXTRA + 0, NullValue());
     NukeSlot(wrapper, JSSLOT_PROXY_EXTRA + 1, NullValue());
 
     JS_ASSERT(IsDeadProxyObject(wrapper));
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2680,17 +2680,17 @@ CopyProperty(JSContext *cx, HandleObject
         desc.getter = shape->getter();
         if (!desc.getter && !(desc.attrs & JSPROP_GETTER))
             desc.getter = JS_PropertyStub;
         desc.setter = shape->setter();
         if (!desc.setter && !(desc.attrs & JSPROP_SETTER))
             desc.setter = JS_StrictPropertyStub;
         desc.shortid = shape->shortid();
         propFlags = shape->getFlags();
-    } else if (IsProxy(referent)) {
+    } else if (referent->is<ProxyObject>()) {
         if (!Proxy::getOwnPropertyDescriptor(cx, referent, id, &desc, 0))
             return false;
         if (!desc.obj)
             return true;
     } else {
         if (!JSObject::lookupGeneric(cx, referent, id, objp, &shape))
             return false;
         if (objp != referent)
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -96,17 +96,17 @@ TestProtoSetterThis(const Value &v)
     if (v.isNullOrUndefined())
         return false;
 
     /* These will work as if on a boxed primitive; dumb, but whatever. */
     if (!v.isObject())
         return true;
 
     /* Otherwise, only accept non-proxies. */
-    return !v.toObject().isProxy();
+    return !v.toObject().is<ProxyObject>();
 }
 
 static bool
 ProtoSetterImpl(JSContext *cx, CallArgs args)
 {
     JS_ASSERT(TestProtoSetterThis(args.thisv()));
 
     const Value &thisv = args.thisv();
@@ -133,20 +133,20 @@ ProtoSetterImpl(JSContext *cx, CallArgs 
     }
 
     /*
      * Disallow mutating the [[Prototype]] of a proxy that wasn't simply
      * wrapping some other object.  Also disallow it on ArrayBuffer objects,
      * which due to their complicated delegate-object shenanigans can't easily
      * have a mutable [[Prototype]].
      */
-    if (obj->isProxy() || obj->is<ArrayBufferObject>()) {
+    if (obj->is<ProxyObject>() || obj->is<ArrayBufferObject>()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
                              "Object", "__proto__ setter",
-                             obj->isProxy() ? "Proxy" : "ArrayBuffer");
+                             obj->is<ProxyObject>() ? "Proxy" : "ArrayBuffer");
         return false;
     }
 
     /* Do nothing if __proto__ isn't being set to an object or null. */
     if (args.length() == 0 || !args[0].isObjectOrNull()) {
         args.rval().setUndefined();
         return true;
     }
--- a/js/src/vm/ObjectImpl-inl.h
+++ b/js/src/vm/ObjectImpl-inl.h
@@ -14,16 +14,19 @@
 #include "jsproxy.h"
 
 #include "gc/Heap.h"
 #include "gc/Marking.h"
 #include "js/TemplateLib.h"
 #include "vm/ObjectImpl.h"
 
 #include "gc/Barrier-inl.h"
+#include "vm/Interpreter.h"
+#include "vm/ObjectImpl.h"
+#include "vm/ProxyObject.h"
 
 inline JSCompartment *
 js::ObjectImpl::compartment() const
 {
     return lastProperty()->base()->compartment();
 }
 
 inline bool
@@ -36,47 +39,41 @@ inline bool
 js::ObjectImpl::nativeContainsPure(Shape *shape)
 {
     return nativeLookupPure(shape->propid()) == shape;
 }
 
 inline bool
 js::ObjectImpl::nonProxyIsExtensible() const
 {
-    MOZ_ASSERT(!isProxy());
+    MOZ_ASSERT(!asObjectPtr()->is<ProxyObject>());
 
     // [[Extensible]] for ordinary non-proxy objects is an object flag.
     return !lastProperty()->hasObjectFlag(BaseShape::NOT_EXTENSIBLE);
 }
 
 /* static */ inline bool
 js::ObjectImpl::isExtensible(ExclusiveContext *cx, js::Handle<ObjectImpl*> obj, bool *extensible)
 {
-    if (obj->isProxy()) {
+    if (obj->asObjectPtr()->is<ProxyObject>()) {
         HandleObject h =
             HandleObject::fromMarkedLocation(reinterpret_cast<JSObject* const*>(obj.address()));
         return Proxy::isExtensible(cx->asJSContext(), h, extensible);
     }
 
     *extensible = obj->nonProxyIsExtensible();
     return true;
 }
 
 inline bool
 js::ObjectImpl::isNative() const
 {
     return lastProperty()->isNative();
 }
 
-inline bool
-js::ObjectImpl::isProxy() const
-{
-    return js::IsProxy(const_cast<JSObject*>(this->asObjectPtr()));
-}
-
 #ifdef DEBUG
 inline bool
 IsObjectValueInCompartment(js::Value v, JSCompartment *comp)
 {
     if (!v.isObject())
         return true;
     return v.toObject().compartment() == comp;
 }
--- a/js/src/vm/ObjectImpl.cpp
+++ b/js/src/vm/ObjectImpl.cpp
@@ -151,17 +151,17 @@ PropDesc::wrapInto(JSContext *cx, Handle
     RootedValue set(cx, desc->set_);
 
     if (!comp->wrap(cx, &value) || !comp->wrap(cx, &get) || !comp->wrap(cx, &set))
         return false;
 
     desc->value_ = value;
     desc->get_ = get;
     desc->set_ = set;
-    return !obj->isProxy() || desc->makeObject(cx);
+    return !obj->is<ProxyObject>() || desc->makeObject(cx);
 }
 
 static ObjectElements emptyElementsHeader(0, 0);
 
 /* Objects with no elements share one empty set of elements. */
 HeapSlot *js::emptyObjectElements =
     reinterpret_cast<HeapSlot *>(uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements));
 
@@ -563,19 +563,18 @@ js::GetOwnProperty(JSContext *cx, Handle
                    PropDesc *desc)
 {
     NEW_OBJECT_REPRESENTATION_ONLY();
 
     JS_CHECK_RECURSION(cx, return false);
 
     Rooted<PropertyId> pid(cx, pid_);
 
-    if (static_cast<JSObject *>(obj.get())->isProxy()) {
+    if (Downcast(obj)->is<ProxyObject>())
         MOZ_ASSUME_UNREACHABLE("NYI: proxy [[GetOwnProperty]]");
-    }
 
     RootedShape shape(cx, obj->nativeLookup(cx, pid));
     if (!shape) {
         /* Not found: attempt to resolve it. */
         Class *clasp = obj->getClass();
         JSResolveOp resolve = clasp->resolve;
         if (resolve != JS_ResolveStub) {
             Rooted<jsid> id(cx, pid.get().asId());
@@ -657,19 +656,18 @@ js::GetProperty(JSContext *cx, Handle<Ob
 
     MOZ_ASSERT(receiver);
 
     Rooted<ObjectImpl*> current(cx, obj);
 
     do {
         MOZ_ASSERT(obj);
 
-        if (Downcast(current)->isProxy()) {
+        if (Downcast(current)->is<ProxyObject>())
             MOZ_ASSUME_UNREACHABLE("NYI: proxy [[GetP]]");
-        }
 
         AutoPropDescRooter desc(cx);
         if (!GetOwnProperty(cx, current, pid, resolveFlags, &desc.getPropDesc()))
             return false;
 
         /* No property?  Recur or bottom out. */
         if (desc.isUndefined()) {
             current = current->getProto();
@@ -720,19 +718,18 @@ js::GetElement(JSContext *cx, Handle<Obj
     NEW_OBJECT_REPRESENTATION_ONLY();
 
     Rooted<ObjectImpl*> current(cx, obj);
 
     RootedValue getter(cx);
     do {
         MOZ_ASSERT(current);
 
-        if (Downcast(current)->isProxy()) {
+        if (Downcast(current)->is<ProxyObject>())
             MOZ_ASSUME_UNREACHABLE("NYI: proxy [[GetP]]");
-        }
 
         PropDesc desc;
         if (!GetOwnElement(cx, current, index, resolveFlags, &desc))
             return false;
 
         /* No property?  Recur or bottom out. */
         if (desc.isUndefined()) {
             current = current->getProto();
@@ -783,19 +780,18 @@ js::HasElement(JSContext *cx, Handle<Obj
 {
     NEW_OBJECT_REPRESENTATION_ONLY();
 
     Rooted<ObjectImpl*> current(cx, obj);
 
     do {
         MOZ_ASSERT(current);
 
-        if (Downcast(current)->isProxy()) {
+        if (Downcast(current)->is<ProxyObject>())
             MOZ_ASSUME_UNREACHABLE("NYI: proxy [[HasProperty]]");
-        }
 
         PropDesc prop;
         if (!GetOwnElement(cx, current, index, resolveFlags, &prop))
             return false;
 
         if (!prop.isUndefined()) {
             *found = true;
             return true;
@@ -947,19 +943,18 @@ js::SetElement(JSContext *cx, Handle<Obj
     Rooted<ObjectImpl*> current(cx, obj);
     RootedValue setter(cx);
 
     MOZ_ASSERT(receiver);
 
     do {
         MOZ_ASSERT(current);
 
-        if (Downcast(current)->isProxy()) {
+        if (Downcast(current)->is<ProxyObject>())
             MOZ_ASSUME_UNREACHABLE("NYI: proxy [[SetP]]");
-        }
 
         PropDesc ownDesc;
         if (!GetOwnElement(cx, current, index, resolveFlags, &ownDesc))
             return false;
 
         if (!ownDesc.isUndefined()) {
             if (ownDesc.isDataDescriptor()) {
                 if (!ownDesc.writable()) {
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -1277,18 +1277,16 @@ class ObjectImpl : public gc::Cell
         return getElementsHeader()->capacity;
     }
 
     bool makeElementsSparse(JSContext *cx) {
         NEW_OBJECT_REPRESENTATION_ONLY();
         MOZ_ASSUME_UNREACHABLE("NYI");
     }
 
-    inline bool isProxy() const;
-
   protected:
 #ifdef DEBUG
     void checkShapeConsistency();
 #else
     void checkShapeConsistency() { }
 #endif
 
     Shape *
new file mode 100644
--- /dev/null
+++ b/js/src/vm/ProxyObject.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#ifndef vm_ProxyObject_h
+#define vm_ProxyObject_h
+
+#include "jsobj.h"
+#include "jsproxy.h"
+
+namespace js {
+
+// This is the base class for the various kinds of proxy objects.  It's never
+// instantiated.
+class ProxyObject : public JSObject
+{
+};
+
+class FunctionProxyObject : public ProxyObject
+{
+  public:
+    static Class class_;
+};
+
+class ObjectProxyObject : public ProxyObject
+{
+  public:
+    static Class class_;
+};
+
+class OuterWindowProxyObject : public ObjectProxyObject
+{
+  public:
+    static Class class_;
+};
+
+} // namespace js
+
+// Note: the following |JSObject::is<T>| methods are implemented in terms of
+// the Is*Proxy() friend API functions to ensure the implementations are tied
+// together.  The exception is |JSObject::is<js::OuterWindowProxyObject>()
+// const|, which uses the standard template definition, because there is no
+// IsOuterWindowProxy() function in the friend API.
+
+template<>
+inline bool
+JSObject::is<js::ProxyObject>() const
+{
+    return js::IsProxy(const_cast<JSObject*>(this));
+}
+
+template<>
+inline bool
+JSObject::is<js::FunctionProxyObject>() const
+{
+    return js::IsFunctionProxy(const_cast<JSObject*>(this));
+}
+
+// WARNING: This function succeeds for ObjectProxyObject *and*
+// OuterWindowProxyObject (which is a sub-class).  If you want a test that only
+// succeeds for ObjectProxyObject, use |hasClass(&ObjectProxyObject::class_)|.
+template<>
+inline bool
+JSObject::is<js::ObjectProxyObject>() const
+{
+    return js::IsObjectProxy(const_cast<JSObject*>(this));
+}
+
+#endif /* vm_ProxyObject_h */
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/PodOperations.h"
 
 #include "jscompartment.h"
 #include "jsiter.h"
 
 #include "vm/GlobalObject.h"
+#include "vm/ProxyObject.h"
 #include "vm/ScopeObject.h"
 #include "vm/Shape.h"
 #include "vm/Xdr.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 
 #include "gc/Barrier-inl.h"
@@ -1583,20 +1584,20 @@ DebugScopeObject::initSnapshot(JSObject 
 bool
 DebugScopeObject::isForDeclarative() const
 {
     ScopeObject &s = scope();
     return s.is<CallObject>() || s.is<BlockObject>() || s.is<DeclEnvObject>();
 }
 
 bool
-js_IsDebugScopeSlow(JSObject *obj)
+js_IsDebugScopeSlow(ObjectProxyObject *proxy)
 {
-    return obj->getClass() == &ObjectProxyClass &&
-           GetProxyHandler(obj) == &DebugScopeProxy::singleton;
+    JS_ASSERT(proxy->hasClass(&ObjectProxyObject::class_));
+    return GetProxyHandler(proxy) == &DebugScopeProxy::singleton;
 }
 
 /*****************************************************************************/
 
 DebugScopes::DebugScopes(JSContext *cx)
  : proxiedScopes(cx),
    missingScopes(cx->runtime()),
    liveScopes(cx->runtime())
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -7,16 +7,17 @@
 #ifndef vm_ScopeObject_h
 #define vm_ScopeObject_h
 
 #include "jscntxt.h"
 #include "jsobj.h"
 #include "jsweakmap.h"
 
 #include "gc/Barrier.h"
+#include "vm/ProxyObject.h"
 
 namespace js {
 
 /*****************************************************************************/
 
 /*
  * All function scripts have an "enclosing static scope" that refers to the
  * innermost enclosing let or function in the program text. This allows full
@@ -582,17 +583,17 @@ class ScopeIterKey
 
 extern JSObject *
 GetDebugScopeForFunction(JSContext *cx, HandleFunction fun);
 
 extern JSObject *
 GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame);
 
 /* Provides debugger access to a scope. */
-class DebugScopeObject : public JSObject
+class DebugScopeObject : public ObjectProxyObject
 {
     /*
      * The enclosing scope on the dynamic scope chain. This slot is analogous
      * to the SCOPE_CHAIN_SLOT of a ScopeObject.
      */
     static const unsigned ENCLOSING_EXTRA = 0;
 
     /*
@@ -694,18 +695,21 @@ JSObject::is<js::ScopeObject>() const
 {
     return is<js::CallObject>() || is<js::DeclEnvObject>() || is<js::NestedScopeObject>();
 }
 
 template<>
 inline bool
 JSObject::is<js::DebugScopeObject>() const
 {
-    extern bool js_IsDebugScopeSlow(JSObject *obj);
-    return getClass() == &js::ObjectProxyClass && js_IsDebugScopeSlow(const_cast<JSObject*>(this));
+    extern bool js_IsDebugScopeSlow(js::ObjectProxyObject *proxy);
+
+    // Note: don't use is<ObjectProxyObject>() here -- it also matches subclasses!
+    return hasClass(&js::ObjectProxyObject::class_) &&
+           js_IsDebugScopeSlow(&const_cast<JSObject*>(this)->as<js::ObjectProxyObject>());
 }
 
 template<>
 inline bool
 JSObject::is<js::ClonedBlockObject>() const
 {
     return is<js::BlockObject>() && !!getProto();
 }
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1088,17 +1088,17 @@ js::ObjectImpl::preventExtensions(JSCont
     bool extensible;
     if (!JSObject::isExtensible(cx, obj, &extensible))
         return false;
     MOZ_ASSERT(extensible,
                "Callers must ensure |obj| is extensible before calling "
                "preventExtensions");
 #endif
 
-    if (obj->isProxy()) {
+    if (Downcast(obj)->is<ProxyObject>()) {
         RootedObject object(cx, obj->asObjectPtr());
         return js::Proxy::preventExtensions(cx, object);
     }
 
     RootedObject self(cx, obj->asObjectPtr());
 
     /*
      * Force lazy properties to be resolved by iterating over the objects' own
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -2135,18 +2135,18 @@ class TypedArrayObjectTemplate : public 
     fromBuffer(JSContext *cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt,
                HandleObject proto)
     {
         if (!ObjectClassIs(bufobj, ESClass_ArrayBuffer, cx)) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS);
             return NULL; // must be arrayBuffer
         }
 
-        JS_ASSERT(bufobj->is<ArrayBufferObject>() || bufobj->isProxy());
-        if (bufobj->isProxy()) {
+        JS_ASSERT(bufobj->is<ArrayBufferObject>() || bufobj->is<ProxyObject>());
+        if (bufobj->is<ProxyObject>()) {
             /*
              * Normally, NonGenericMethodGuard handles the case of transparent
              * wrappers. However, we have a peculiar situation: we want to
              * construct the new typed array in the compartment of the buffer,
              * so that the typed array can point directly at their buffer's
              * data without crossing compartment boundaries. So we use the
              * machinery underlying NonGenericMethodGuard directly to proxy the
              * native call. We will end up with a wrapper in the origin