Bug 1179003 - Convert the infallible objectClassIs proxy hook into a fallible getBuiltinClass hook that indicates class type via outparam. r=efaust, r=bz on DOM bits, r=billm on IPC bits
authorJeff Walden <jwalden@mit.edu>
Fri, 28 Aug 2015 21:55:40 -0700
changeset 263873 b30b0dcc562c4fce5802c1ff222d0cd6c7309f53
parent 263872 058927e0b31bd9453c4ac89e4ba260ee77065bb6
child 263874 731200b5ab4eb8de42f8fc803738849ba341a8ad
push id65451
push userjwalden@mit.edu
push dateWed, 23 Sep 2015 00:31:33 +0000
treeherdermozilla-inbound@b30b0dcc562c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust, bz, billm
bugs1179003
milestone44.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 1179003 - Convert the infallible objectClassIs proxy hook into a fallible getBuiltinClass hook that indicates class type via outparam. r=efaust, r=bz on DOM bits, r=billm on IPC bits
dom/base/Console.cpp
dom/base/Element.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/Date.cpp
dom/cache/Cache.cpp
dom/geolocation/nsGeolocationSettings.cpp
dom/indexedDB/IDBKeyRange.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/Key.cpp
dom/media/webspeech/synth/nsSpeechTask.cpp
dom/mobilemessage/MmsMessage.cpp
dom/mobilemessage/MobileMessageThread.cpp
dom/mobilemessage/SmsMessage.cpp
js/ipc/JavaScriptBase.h
js/ipc/PJavaScript.ipdl
js/ipc/WrapperAnswer.cpp
js/ipc/WrapperAnswer.h
js/ipc/WrapperOwner.cpp
js/ipc/WrapperOwner.h
js/public/Class.h
js/public/Proxy.h
js/src/builtin/RegExp.cpp
js/src/ctypes/CTypes.cpp
js/src/jsapi-tests/testNewObject.cpp
js/src/jsapi-tests/testParseJSON.cpp
js/src/jsapi-tests/testRegExp.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsdate.cpp
js/src/jsexn.cpp
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/json.cpp
js/src/jsstr.cpp
js/src/jswrapper.h
js/src/proxy/BaseProxyHandler.cpp
js/src/proxy/DeadObjectProxy.cpp
js/src/proxy/DeadObjectProxy.h
js/src/proxy/DirectProxyHandler.cpp
js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
js/src/proxy/Proxy.cpp
js/src/proxy/Proxy.h
js/src/proxy/ScriptedDirectProxyHandler.cpp
js/src/proxy/ScriptedDirectProxyHandler.h
js/src/proxy/SecurityWrapper.cpp
js/src/vm/ArrayBufferObject.cpp
js/src/vm/RegExpObject.h
js/src/vm/SharedArrayObject.cpp
js/src/vm/SharedTypedArrayObject.cpp
js/src/vm/StructuredClone.cpp
js/src/vm/TypedArrayCommon.h
js/src/vm/TypedArrayObject.cpp
js/xpconnect/loader/mozJSComponentLoader.cpp
js/xpconnect/src/Sandbox.cpp
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCConvert.cpp
js/xpconnect/src/XPCVariant.cpp
js/xpconnect/src/XPCWrappedNative.cpp
js/xpconnect/src/XPCWrappedNativeScope.cpp
js/xpconnect/wrappers/AccessCheck.cpp
storage/mozStoragePrivateHelpers.cpp
toolkit/components/places/History.cpp
widget/android/NativeJSContainer.cpp
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -531,17 +531,16 @@ private:
 
     JS::Rooted<JS::Value> argumentsValue(aCx);
     if (!Read(aCx, &argumentsValue)) {
       return;
     }
 
     MOZ_ASSERT(argumentsValue.isObject());
     JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
-    MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
 
     uint32_t length;
     if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
       return;
     }
 
     for (uint32_t i = 0; i < length; ++i) {
       JS::Rooted<JS::Value> value(aCx);
@@ -626,17 +625,16 @@ private:
     mClonedData.mParent = nullptr;
 
     if (!ok) {
       return;
     }
 
     MOZ_ASSERT(argumentsValue.isObject());
     JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
-    MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
 
     uint32_t length;
     if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
       return;
     }
 
     Sequence<JS::Value> arguments;
 
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3253,24 +3253,32 @@ Element::MozRequestFullScreen(JSContext*
 
   auto request = MakeUnique<FullscreenRequest>(this);
   request->mIsCallerChrome = nsContentUtils::IsCallerChrome();
 
   RequestFullscreenOptions fsOptions;
   // We need to check if options is convertible to a dict first before
   // trying to init fsOptions; otherwise Init() would throw, and we want to
   // silently ignore non-dictionary values
-  if (aCx && IsConvertibleToDictionary(aCx, aOptions)) {
-    if (!fsOptions.Init(aCx, aOptions)) {
+  if (aCx) {
+    bool convertible;
+    if (!IsConvertibleToDictionary(aCx, aOptions, &convertible)) {
       aError.Throw(NS_ERROR_FAILURE);
       return;
     }
 
-    if (fsOptions.mVrDisplay) {
-      request->mVRHMDDevice = fsOptions.mVrDisplay->GetHMD();
+    if (convertible) {
+      if (!fsOptions.Init(aCx, aOptions)) {
+        aError.Throw(NS_ERROR_FAILURE);
+        return;
+      }
+
+      if (fsOptions.mVrDisplay) {
+        request->mVRHMDDevice = fsOptions.mVrDisplay->GetHMD();
+      }
     }
   }
 
   OwnerDoc()->AsyncRequestFullScreen(Move(request));
 }
 
 void
 Element::MozRequestPointerLock()
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -249,41 +249,59 @@ template <prototypes::ID PrototypeID, cl
 MOZ_ALWAYS_INLINE nsresult
 UnwrapObject(JSObject* obj, U& value)
 {
   return UnwrapObject<T>(obj, value, PrototypeID,
                          PrototypeTraits<PrototypeID>::Depth);
 }
 
 inline bool
-IsNotDateOrRegExp(JSContext* cx, JS::Handle<JSObject*> obj)
+IsNotDateOrRegExp(JSContext* cx, JS::Handle<JSObject*> obj,
+                  bool* notDateOrRegExp)
 {
   MOZ_ASSERT(obj);
-  return !JS_ObjectIsDate(cx, obj) && !JS_ObjectIsRegExp(cx, obj);
+
+  js::ESClassValue cls;
+  if (!js::GetBuiltinClass(cx, obj, &cls)) {
+    return false;
+  }
+
+  *notDateOrRegExp = cls != js::ESClass_Date && cls != js::ESClass_RegExp;
+  return true;
 }
 
 MOZ_ALWAYS_INLINE bool
 IsObjectValueConvertibleToDictionary(JSContext* cx,
-                                     JS::Handle<JS::Value> objVal)
+                                     JS::Handle<JS::Value> objVal,
+                                     bool* convertible)
 {
   JS::Rooted<JSObject*> obj(cx, &objVal.toObject());
-  return IsNotDateOrRegExp(cx, obj);
+  return IsNotDateOrRegExp(cx, obj, convertible);
 }
 
 MOZ_ALWAYS_INLINE bool
-IsConvertibleToDictionary(JSContext* cx, JS::Handle<JS::Value> val)
+IsConvertibleToDictionary(JSContext* cx, JS::Handle<JS::Value> val,
+                          bool* convertible)
 {
-  return val.isNullOrUndefined() ||
-    (val.isObject() && IsObjectValueConvertibleToDictionary(cx, val));
+  if (val.isNullOrUndefined()) {
+    *convertible = true;
+    return true;
+  }
+  if (!val.isObject()) {
+    *convertible = false;
+    return true;
+  }
+  return IsObjectValueConvertibleToDictionary(cx, val, convertible);
 }
 
 MOZ_ALWAYS_INLINE bool
-IsConvertibleToCallbackInterface(JSContext* cx, JS::Handle<JSObject*> obj)
+IsConvertibleToCallbackInterface(JSContext* cx, JS::Handle<JSObject*> obj,
+                                 bool* convertible)
 {
-  return IsNotDateOrRegExp(cx, obj);
+  return IsNotDateOrRegExp(cx, obj, convertible);
 }
 
 // The items in the protoAndIfaceCache are indexed by the prototypes::id::ID,
 // constructors::id::ID and namedpropertiesobjects::id::ID enums, in that order.
 // The end of the prototype objects should be the start of the interface
 // objects, and the end of the interface objects should be the start of the
 // named properties objects.
 static_assert((size_t)constructors::id::_ID_Start ==
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -5433,43 +5433,58 @@ def getJSToNativeConversionInfo(type, de
         # default value.
         if (not isNullOrUndefined and not isDefinitelyObject and
             defaultValue is not None):
             assert(isinstance(defaultValue, IDLNullValue))
             val = "(${haveValue}) ? ${val} : JS::NullHandleValue"
         else:
             val = "${val}"
 
-        if failureCode is not None:
-            if isDefinitelyObject:
-                dictionaryTest = "IsObjectValueConvertibleToDictionary"
-            else:
-                dictionaryTest = "IsConvertibleToDictionary"
-            # Check that the value we have can in fact be converted to
-            # a dictionary, and return failureCode if not.
-            template = CGIfWrapper(
-                CGGeneric(failureCode),
-                "!%s(cx, ${val})" % dictionaryTest).define() + "\n"
-        else:
-            template = ""
-
         dictLoc = "${declName}"
         if type.nullable():
             dictLoc += ".SetValue()"
 
-        template += fill("""
-            if (!${dictLoc}.Init(cx, ${val}, "${desc}", $${passedToJSImpl})) {
+        conversionCode = fill("""
+            if (!${dictLoc}.Init(cx, ${val},  "${desc}", $${passedToJSImpl})) {
               $*{exceptionCode}
             }
             """,
             dictLoc=dictLoc,
             val=val,
             desc=firstCap(sourceDescription),
             exceptionCode=exceptionCode)
 
+        if failureCode is not None:
+            if isDefinitelyObject:
+                dictionaryTest = "IsObjectValueConvertibleToDictionary"
+            else:
+                dictionaryTest = "IsConvertibleToDictionary"
+
+            template = fill("""
+                { // scope for isConvertible
+                  bool isConvertible;
+                  if (!${testConvertible}(cx, ${val}, &isConvertible)) {
+                    $*{exceptionCode}
+                  }
+                  if (!isConvertible) {
+                    $*{failureCode}
+                  }
+
+                  $*{conversionCode}
+                }
+
+                """,
+                testConvertible=dictionaryTest,
+                val=val,
+                exceptionCode=exceptionCode,
+                failureCode=failureCode,
+                conversionCode=conversionCode)
+        else:
+            template = conversionCode
+
         if type.nullable():
             declType = CGTemplatedType("Nullable", declType)
             template = CGIfElseWrapper("${val}.isNullOrUndefined()",
                                        CGGeneric("${declName}.SetNull();\n"),
                                        CGGeneric(template)).define()
 
         # Dictionary arguments that might contain traceable things need to get
         # traced
@@ -5506,21 +5521,30 @@ def getJSToNativeConversionInfo(type, de
             notDate = ('ThrowErrorMessage(cx, MSG_NOT_DATE, "%s");\n'
                        "%s" % (firstCap(sourceDescription), exceptionCode))
         else:
             notDate = failureCode
 
         conversion = fill(
             """
             JS::Rooted<JSObject*> possibleDateObject(cx, &$${val}.toObject());
-            if (!JS_ObjectIsDate(cx, possibleDateObject) ||
-                !${dateVal}.SetTimeStamp(cx, possibleDateObject)) {
-              $*{notDate}
-            }
-            """,
+            { // scope for isDate
+              bool isDate;
+              if (!JS_ObjectIsDate(cx, possibleDateObject, &isDate)) {
+                $*{exceptionCode}
+              }
+              if (!isDate) {
+                $*{notDate}
+              }
+              if (!${dateVal}.SetTimeStamp(cx, possibleDateObject)) {
+                $*{exceptionCode}
+              }
+            }
+            """,
+            exceptionCode=exceptionCode,
             dateVal=dateVal,
             notDate=notDate)
 
         conversion = wrapObjectTemplate(conversion, type,
                                         "${declName}.SetNull();\n", notDate)
         return JSToNativeConversionInfo(conversion,
                                         declType=declType,
                                         dealWithOptional=isOptional)
@@ -11911,25 +11935,30 @@ class CGDictionary(CGThing):
 
         if self.dictionary.parent:
             body += fill(
                 """
                 // Per spec, we init the parent's members first
                 if (!${dictName}::Init(cx, val)) {
                   return false;
                 }
-                MOZ_ASSERT(IsConvertibleToDictionary(cx, val));
 
                 """,
                 dictName=self.makeClassName(self.dictionary.parent))
         else:
             body += dedent(
                 """
-                if (!IsConvertibleToDictionary(cx, val)) {
-                  return ThrowErrorMessage(cx, MSG_NOT_DICTIONARY, sourceDescription);
+                { // scope for isConvertible
+                  bool isConvertible;
+                  if (!IsConvertibleToDictionary(cx, val, &isConvertible)) {
+                    return false;
+                  }
+                  if (!isConvertible) {
+                    return ThrowErrorMessage(cx, MSG_NOT_DICTIONARY, sourceDescription);
+                  }
                 }
 
                 """)
 
         memberInits = [self.getMemberConversion(m).define()
                        for m in self.memberInfo]
         if memberInits:
             body += fill(
--- a/dom/bindings/Date.cpp
+++ b/dom/bindings/Date.cpp
@@ -15,20 +15,25 @@
 
 namespace mozilla {
 namespace dom {
 
 bool
 Date::SetTimeStamp(JSContext* aCx, JSObject* aObject)
 {
   JS::Rooted<JSObject*> obj(aCx, aObject);
-  MOZ_ASSERT(JS_ObjectIsDate(aCx, obj));
-  double msecs = js::DateGetMsecSinceEpoch(aCx, obj);
+
+  double msecs;
+  if (!js::DateGetMsecSinceEpoch(aCx, obj, &msecs)) {
+    return false;
+  }
+
   JS::ClippedTime time = JS::TimeClip(msecs);
   MOZ_ASSERT(NumbersAreIdentical(msecs, time.toDouble()));
+
   mMsecSinceEpoch = time;
   return true;
 }
 
 bool
 Date::ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const
 {
   JSObject* obj = JS::NewDateObject(aCx, mMsecSinceEpoch);
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -112,17 +112,18 @@ public:
 
     // Promise::All() passed an array of fetch() Promises should give us
     // an Array of Response objects.  The following code unwraps these
     // JS values back to an nsTArray<nsRefPtr<Response>>.
 
     nsAutoTArray<nsRefPtr<Response>, 256> responseList;
     responseList.SetCapacity(mRequestList.Length());
 
-    if (NS_WARN_IF(!JS_IsArrayObject(aCx, aValue))) {
+    bool isArray;
+    if (NS_WARN_IF(!JS_IsArrayObject(aCx, aValue, &isArray) || !isArray)) {
       Fail();
       return;
     }
 
     JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
 
     uint32_t length;
     if (NS_WARN_IF(!JS_GetArrayLength(aCx, obj, &length))) {
--- a/dom/geolocation/nsGeolocationSettings.cpp
+++ b/dom/geolocation/nsGeolocationSettings.cpp
@@ -317,17 +317,18 @@ nsGeolocationSettings::HandleGeolocation
   nsIGlobalObject* global = xpc::NativeGlobal(obj);
   NS_ENSURE_TRUE_VOID(global && global->GetGlobalJSObject());
 
   // the spec requires calling getters when accessing array by index
   AutoEntryScript aes(global, "geolocation.always_precise indexing");
   aes.TakeOwnershipOfErrorReporting();
   JSContext *cx = aes.cx();
 
-  if (!JS_IsArrayObject(cx, obj)) {
+  bool isArray;
+  if (!JS_IsArrayObject(cx, obj, &isArray) || !isArray) {
     return;
   }
 
   uint32_t length;
   if (!JS_GetArrayLength(cx, obj, &length)) {
     return;
   }
 
--- a/dom/indexedDB/IDBKeyRange.cpp
+++ b/dom/indexedDB/IDBKeyRange.cpp
@@ -102,18 +102,25 @@ IDBKeyRange::FromJSVal(JSContext* aCx,
 
   if (aVal.isNullOrUndefined()) {
     // undefined and null returns no IDBKeyRange.
     keyRange.forget(aKeyRange);
     return NS_OK;
   }
 
   JS::Rooted<JSObject*> obj(aCx, aVal.isObject() ? &aVal.toObject() : nullptr);
-  if (aVal.isPrimitive() || JS_IsArrayObject(aCx, obj) ||
-      JS_ObjectIsDate(aCx, obj)) {
+  bool isValidKey = aVal.isPrimitive();
+  if (!isValidKey) {
+    js::ESClassValue cls;
+    if (!js::GetBuiltinClass(aCx, obj, &cls)) {
+      return NS_ERROR_UNEXPECTED;
+    }
+    isValidKey = cls == js::ESClass_Array || cls == js::ESClass_Date;
+  }
+  if (isValidKey) {
     // A valid key returns an 'only' IDBKeyRange.
     keyRange = new IDBKeyRange(nullptr, false, false, true);
 
     nsresult rv = GetKeyFromJSVal(aCx, aVal, keyRange->Lower());
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -996,17 +996,22 @@ IDBObjectStore::AppendIndexUpdateInfo(
     return NS_OK;
   }
 
   JS::Rooted<JS::Value> val(aCx);
   if (NS_FAILED(aKeyPath.ExtractKeyAsJSVal(aCx, aVal, val.address()))) {
     return NS_OK;
   }
 
-  if (JS_IsArrayObject(aCx, val)) {
+  bool isArray;
+  if (!JS_IsArrayObject(aCx, val, &isArray)) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+  if (isArray) {
     JS::Rooted<JSObject*> array(aCx, &val.toObject());
     uint32_t arrayLength;
     if (NS_WARN_IF(!JS_GetArrayLength(aCx, array, &arrayLength))) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
     for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
--- a/dom/indexedDB/Key.cpp
+++ b/dom/indexedDB/Key.cpp
@@ -226,17 +226,23 @@ Key::EncodeJSValInternal(JSContext* aCx,
       return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
     }
     EncodeNumber(d, eFloat + aTypeOffset);
     return NS_OK;
   }
 
   if (aVal.isObject()) {
     JS::Rooted<JSObject*> obj(aCx, &aVal.toObject());
-    if (JS_IsArrayObject(aCx, obj)) {
+
+    js::ESClassValue cls;
+    if (!js::GetBuiltinClass(aCx, obj, &cls)) {
+      IDB_REPORT_INTERNAL_ERR();
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+    if (cls == js::ESClass_Array) {
       aTypeOffset += eMaxType;
 
       if (aTypeOffset == eMaxType * kMaxArrayCollapse) {
         mBuffer.Append(aTypeOffset);
         aTypeOffset = 0;
       }
       NS_ASSERTION((aTypeOffset % eMaxType) == 0 &&
                    aTypeOffset < (eMaxType * kMaxArrayCollapse),
@@ -264,21 +270,31 @@ Key::EncodeJSValInternal(JSContext* aCx,
         aTypeOffset = 0;
       }
 
       mBuffer.Append(eTerminator + aTypeOffset);
 
       return NS_OK;
     }
 
-    if (JS_ObjectIsDate(aCx, obj)) {
-      if (!js::DateIsValid(aCx, obj))  {
+    if (cls == js::ESClass_Date) {
+      bool valid;
+      if (!js::DateIsValid(aCx, obj, &valid)) {
+        IDB_REPORT_INTERNAL_ERR();
+        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      }
+      if (!valid)  {
         return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
       }
-      EncodeNumber(js::DateGetMsecSinceEpoch(aCx, obj), eDate + aTypeOffset);
+      double t;
+      if (!js::DateGetMsecSinceEpoch(aCx, obj, &t)) {
+        IDB_REPORT_INTERNAL_ERR();
+        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      }
+      EncodeNumber(t, eDate + aTypeOffset);
       return NS_OK;
     }
   }
 
   return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
 }
 
 // static
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -241,18 +241,24 @@ nsSpeechTask::SendAudio(JS::Handle<JS::V
   JS::Rooted<JSObject*> darray(aCx, &aData.toObject());
   JSAutoCompartment ac(aCx, darray);
 
   JS::Rooted<JSObject*> tsrc(aCx, nullptr);
 
   // Allow either Int16Array or plain JS Array
   if (JS_IsInt16Array(darray)) {
     tsrc = darray;
-  } else if (JS_IsArrayObject(aCx, darray)) {
-    tsrc = JS_NewInt16ArrayFromArray(aCx, darray);
+  } else {
+    bool isArray;
+    if (!JS_IsArrayObject(aCx, darray, &isArray)) {
+      return NS_ERROR_UNEXPECTED;
+    }
+    if (isArray) {
+      tsrc = JS_NewInt16ArrayFromArray(aCx, darray);
+    }
   }
 
   if (!tsrc) {
     return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
   }
 
   uint32_t dataLen = JS_GetTypedArrayLength(tsrc);
   nsRefPtr<mozilla::SharedBuffer> samples;
--- a/dom/mobilemessage/MmsMessage.cpp
+++ b/dom/mobilemessage/MmsMessage.cpp
@@ -1,18 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MmsMessage.h"
 #include "nsIDOMClassInfo.h"
-#include "jsapi.h" // For OBJECT_TO_JSVAL and JS_NewDateObjectMsec
-#include "jsfriendapi.h" // For js_DateGetMsecSinceEpoch
+#include "jsapi.h" // For JS_IsArrayObject, JS_GetElement, etc.
 #include "nsJSUtils.h"
 #include "nsContentUtils.h"
 #include "nsTArrayHelpers.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/mobilemessage/Constants.h" // For MessageType
 #include "mozilla/dom/mobilemessage/SmsTypes.h"
 #include "mozilla/dom/ScriptSettings.h"
@@ -208,17 +207,21 @@ MmsMessage::Create(int32_t aId,
     return NS_ERROR_INVALID_ARG;
   }
 
   // Set |deliveryInfo|.
   if (!aDeliveryInfo.isObject()) {
     return NS_ERROR_INVALID_ARG;
   }
   JS::Rooted<JSObject*> deliveryInfoObj(aCx, &aDeliveryInfo.toObject());
-  if (!JS_IsArrayObject(aCx, deliveryInfoObj)) {
+  bool isArray;
+  if (!JS_IsArrayObject(aCx, deliveryInfoObj, &isArray)) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!isArray) {
     return NS_ERROR_INVALID_ARG;
   }
 
   uint32_t length;
   MOZ_ALWAYS_TRUE(JS_GetArrayLength(aCx, deliveryInfoObj, &length));
 
   nsTArray<MmsDeliveryInfo> deliveryInfo;
   JS::Rooted<JS::Value> infoJsVal(aCx);
@@ -236,17 +239,20 @@ MmsMessage::Create(int32_t aId,
     deliveryInfo.AppendElement(info);
   }
 
   // Set |receivers|.
   if (!aReceivers.isObject()) {
     return NS_ERROR_INVALID_ARG;
   }
   JS::Rooted<JSObject*> receiversObj(aCx, &aReceivers.toObject());
-  if (!JS_IsArrayObject(aCx, receiversObj)) {
+  if (!JS_IsArrayObject(aCx, receiversObj, &isArray)) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!isArray) {
     return NS_ERROR_INVALID_ARG;
   }
 
   MOZ_ALWAYS_TRUE(JS_GetArrayLength(aCx, receiversObj, &length));
 
   nsTArray<nsString> receivers;
   JS::Rooted<JS::Value> receiverJsVal(aCx);
   for (uint32_t i = 0; i < length; ++i) {
@@ -263,17 +269,20 @@ MmsMessage::Create(int32_t aId,
     receivers.AppendElement(receiverStr);
   }
 
   // Set |attachments|.
   if (!aAttachments.isObject()) {
     return NS_ERROR_INVALID_ARG;
   }
   JS::Rooted<JSObject*> attachmentsObj(aCx, &aAttachments.toObject());
-  if (!JS_IsArrayObject(aCx, attachmentsObj)) {
+  if (!JS_IsArrayObject(aCx, attachmentsObj, &isArray)) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!isArray) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsTArray<Attachment> attachments;
   MOZ_ALWAYS_TRUE(JS_GetArrayLength(aCx, attachmentsObj, &length));
 
   JS::Rooted<JS::Value> attachmentJsVal(aCx);
   for (uint32_t i = 0; i < length; ++i) {
--- a/dom/mobilemessage/MobileMessageThread.cpp
+++ b/dom/mobilemessage/MobileMessageThread.cpp
@@ -2,17 +2,16 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MobileMessageThread.h"
 #include "nsIDOMClassInfo.h"
 #include "jsapi.h"           // For OBJECT_TO_JSVAL and JS_NewDateObjectMsec
-#include "jsfriendapi.h"     // For js_DateGetMsecSinceEpoch
 #include "nsJSUtils.h"       // For nsAutoJSString
 #include "nsTArrayHelpers.h" // For nsTArrayToJSArray
 #include "mozilla/dom/mobilemessage/Constants.h" // For MessageType
 
 using namespace mozilla::dom::mobilemessage;
 
 namespace mozilla {
 namespace dom {
@@ -49,17 +48,21 @@ MobileMessageThread::Create(uint64_t aId
 
   // Participants.
   {
     if (!aParticipants.isObject()) {
       return NS_ERROR_INVALID_ARG;
     }
 
     JS::Rooted<JSObject*> obj(aCx, &aParticipants.toObject());
-    if (!JS_IsArrayObject(aCx, obj)) {
+    bool isArray;
+    if (!JS_IsArrayObject(aCx, obj, &isArray)) {
+      return NS_ERROR_FAILURE;
+    }
+    if (!isArray) {
       return NS_ERROR_INVALID_ARG;
     }
 
     uint32_t length;
     MOZ_ALWAYS_TRUE(JS_GetArrayLength(aCx, obj, &length));
     NS_ENSURE_TRUE(length, NS_ERROR_INVALID_ARG);
 
     for (uint32_t i = 0; i < length; ++i) {
--- a/dom/mobilemessage/SmsMessage.cpp
+++ b/dom/mobilemessage/SmsMessage.cpp
@@ -1,18 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "SmsMessage.h"
 #include "nsIDOMClassInfo.h"
-#include "jsapi.h" // For OBJECT_TO_JSVAL and JS_NewDateObjectMsec
-#include "jsfriendapi.h" // For js_DateGetMsecSinceEpoch
 #include "mozilla/dom/mobilemessage/Constants.h" // For MessageType
 
 using namespace mozilla::dom::mobilemessage;
 
 namespace mozilla {
 namespace dom {
 
 NS_INTERFACE_MAP_BEGIN(SmsMessage)
--- a/js/ipc/JavaScriptBase.h
+++ b/js/ipc/JavaScriptBase.h
@@ -82,19 +82,18 @@ class JavaScriptBase : public WrapperOwn
     bool RecvCallOrConstruct(const uint64_t& objId, InfallibleTArray<JSParam>&& argv,
                              const bool& construct, ReturnStatus* rs, JSVariant* result,
                              nsTArray<JSParam>* outparams) {
         return Answer::RecvCallOrConstruct(ObjectId::deserialize(objId), Move(argv), construct, rs, result, outparams);
     }
     bool RecvHasInstance(const uint64_t& objId, const JSVariant& v, ReturnStatus* rs, bool* bp) {
         return Answer::RecvHasInstance(ObjectId::deserialize(objId), v, rs, bp);
     }
-    bool RecvObjectClassIs(const uint64_t& objId, const uint32_t& classValue,
-                             bool* result) {
-        return Answer::RecvObjectClassIs(ObjectId::deserialize(objId), classValue, result);
+    bool RecvGetBuiltinClass(const uint64_t& objId, ReturnStatus* rs, uint32_t* classValue) {
+        return Answer::RecvGetBuiltinClass(ObjectId::deserialize(objId), rs, classValue);
     }
     bool RecvIsArray(const uint64_t& objId, ReturnStatus* rs, uint32_t* answer) {
         return Answer::RecvIsArray(ObjectId::deserialize(objId), rs, answer);
     }
     bool RecvClassName(const uint64_t& objId, nsCString* result) {
         return Answer::RecvClassName(ObjectId::deserialize(objId), result);
     }
     bool RecvGetPrototype(const uint64_t& objId, ReturnStatus* rs, ObjectOrNullVariant* result) {
@@ -173,19 +172,18 @@ class JavaScriptBase : public WrapperOwn
     bool SendCallOrConstruct(const ObjectId& objId, const nsTArray<JSParam>& argv,
                              const bool& construct, ReturnStatus* rs, JSVariant* result,
                              nsTArray<JSParam>* outparams) {
         return Base::SendCallOrConstruct(objId.serialize(), argv, construct, rs, result, outparams);
     }
     bool SendHasInstance(const ObjectId& objId, const JSVariant& v, ReturnStatus* rs, bool* bp) {
         return Base::SendHasInstance(objId.serialize(), v, rs, bp);
     }
-    bool SendObjectClassIs(const ObjectId& objId, const uint32_t& classValue,
-                           bool* result) {
-        return Base::SendObjectClassIs(objId.serialize(), classValue, result);
+    bool SendGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs, uint32_t* classValue) {
+        return Base::SendGetBuiltinClass(objId.serialize(), rs, classValue);
     }
     bool SendIsArray(const ObjectId& objId, ReturnStatus* rs, uint32_t* answer)
     {
         return Base::SendIsArray(objId.serialize(), rs, answer);
     }
     bool SendClassName(const ObjectId& objId, nsCString* result) {
         return Base::SendClassName(objId.serialize(), result);
     }
--- a/js/ipc/PJavaScript.ipdl
+++ b/js/ipc/PJavaScript.ipdl
@@ -33,17 +33,17 @@ both:
     prio(high) sync Has(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, bool has);
     prio(high) sync HasOwn(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, bool has);
     prio(high) sync Get(uint64_t objId, JSVariant receiver, JSIDVariant id) returns (ReturnStatus rs, JSVariant result);
     prio(high) sync Set(uint64_t objId, JSIDVariant id, JSVariant value, JSVariant receiver) returns (ReturnStatus rs);
 
     prio(high) sync IsExtensible(uint64_t objId) returns (ReturnStatus rs, bool result);
     prio(high) sync CallOrConstruct(uint64_t objId, JSParam[] argv, bool construct) returns (ReturnStatus rs, JSVariant result, JSParam[] outparams);
     prio(high) sync HasInstance(uint64_t objId, JSVariant v) returns (ReturnStatus rs, bool has);
-    prio(high) sync ObjectClassIs(uint64_t objId, uint32_t classValue) returns (bool result);
+    prio(high) sync GetBuiltinClass(uint64_t objId) returns (ReturnStatus rs, uint32_t classValue);
     prio(high) sync IsArray(uint64_t objId) returns (ReturnStatus rs, uint32_t ans);
     prio(high) sync ClassName(uint64_t objId) returns (nsCString name);
     prio(high) sync GetPrototype(uint64_t objId) returns (ReturnStatus rs, ObjectOrNullVariant result);
     prio(high) sync RegExpToShared(uint64_t objId) returns (ReturnStatus rs, nsString source, uint32_t flags);
 
     prio(high) sync GetPropertyKeys(uint64_t objId, uint32_t flags) returns (ReturnStatus rs, JSIDVariant[] ids);
     prio(high) sync InstanceOf(uint64_t objId, JSIID iid) returns (ReturnStatus rs, bool instanceof);
     prio(high) sync DOMInstanceOf(uint64_t objId, int prototypeID, int depth) returns (ReturnStatus rs, bool instanceof);
--- a/js/ipc/WrapperAnswer.cpp
+++ b/js/ipc/WrapperAnswer.cpp
@@ -495,36 +495,39 @@ WrapperAnswer::RecvHasInstance(const Obj
 
     if (!JS_HasInstance(cx, obj, val, bp))
         return fail(jsapi, rs);
 
     return ok(rs);
 }
 
 bool
-WrapperAnswer::RecvObjectClassIs(const ObjectId& objId, const uint32_t& classValue,
-                                 bool* result)
+WrapperAnswer::RecvGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs,
+                                   uint32_t* classValue)
 {
+    *classValue = js::ESClass_Other;
+
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     jsapi.TakeOwnershipOfErrorReporting();
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
-    if (!obj) {
-        // This is very unfortunate, but we have no choice.
-        *result = false;
-        return true;
-    }
+    if (!obj)
+        return fail(jsapi, rs);
+
+    LOG("%s.getBuiltinClass()", ReceiverObj(objId));
 
-    LOG("%s.objectClassIs()", ReceiverObj(objId));
+    js::ESClassValue cls;
+    if (!js::GetBuiltinClass(cx, obj, &cls))
+        return fail(jsapi, rs);
 
-    *result = js::ObjectClassIs(cx, obj, (js::ESClassValue)classValue);
-    return true;
+    *classValue = cls;
+    return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvIsArray(const ObjectId& objId, ReturnStatus* rs,
                            uint32_t* ans)
 {
     *ans = uint32_t(IsArrayAnswer::NotArray);
 
@@ -605,17 +608,16 @@ WrapperAnswer::RecvRegExpToShared(const 
         return false;
     jsapi.TakeOwnershipOfErrorReporting();
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return fail(jsapi, rs);
 
-    MOZ_RELEASE_ASSERT(JS_ObjectIsRegExp(cx, obj));
     RootedString sourceJSStr(cx, JS_GetRegExpSource(cx, obj));
     if (!sourceJSStr)
         return fail(jsapi, rs);
     nsAutoJSString sourceStr;
     if (!sourceStr.init(cx, sourceJSStr))
         return fail(jsapi, rs);
     source->Assign(sourceStr);
 
--- a/js/ipc/WrapperAnswer.h
+++ b/js/ipc/WrapperAnswer.h
@@ -46,18 +46,18 @@ class WrapperAnswer : public virtual Jav
                  const JSVariant& receiverVar, ReturnStatus* rs);
 
     bool RecvIsExtensible(const ObjectId& objId, ReturnStatus* rs,
                           bool* result);
     bool RecvCallOrConstruct(const ObjectId& objId, InfallibleTArray<JSParam>&& argv,
                              const bool& construct, ReturnStatus* rs, JSVariant* result,
                              nsTArray<JSParam>* outparams);
     bool RecvHasInstance(const ObjectId& objId, const JSVariant& v, ReturnStatus* rs, bool* bp);
-    bool RecvObjectClassIs(const ObjectId& objId, const uint32_t& classValue,
-                           bool* result);
+    bool RecvGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs,
+                             uint32_t* classValue);
     bool RecvIsArray(const ObjectId& objId, ReturnStatus* rs, uint32_t* ans);
     bool RecvClassName(const ObjectId& objId, nsCString* result);
     bool RecvGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result);
     bool RecvRegExpToShared(const ObjectId& objId, ReturnStatus* rs, nsString* source, uint32_t* flags);
 
     bool RecvGetPropertyKeys(const ObjectId& objId, const uint32_t& flags,
                              ReturnStatus* rs, nsTArray<JSIDVariant>* ids);
     bool RecvInstanceOf(const ObjectId& objId, const JSIID& iid,
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -120,18 +120,18 @@ class CPOWProxyHandler : public BaseProx
 
     virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
                                               AutoIdVector& props) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy,
                              MutableHandleValue v, bool* bp) const override;
-    virtual bool objectClassIs(HandleObject obj, js::ESClassValue classValue,
-                               JSContext* cx) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject obj,
+                                 js::ESClassValue* classValue) const override;
     virtual bool isArray(JSContext* cx, HandleObject obj,
                          IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
     virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override;
     virtual void objectMoved(JSObject* proxy, const JSObject* old) const override;
     virtual bool isCallable(JSObject* obj) const override;
     virtual bool isConstructor(JSObject* obj) const override;
@@ -717,35 +717,37 @@ WrapperOwner::hasInstance(JSContext* cx,
         return ipcfail(cx);
 
     LOG_STACK();
 
     return ok(cx, status);
 }
 
 bool
-CPOWProxyHandler::objectClassIs(HandleObject proxy, js::ESClassValue classValue, JSContext* cx) const
+CPOWProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
+                                  js::ESClassValue* classValue) const
 {
-    FORWARD(objectClassIs, (cx, proxy, classValue));
+    FORWARD(getBuiltinClass, (cx, proxy, classValue));
 }
 
 bool
-WrapperOwner::objectClassIs(JSContext* cx, HandleObject proxy, js::ESClassValue classValue)
+WrapperOwner::getBuiltinClass(JSContext* cx, HandleObject proxy,
+                              js::ESClassValue* classValue)
 {
     ObjectId objId = idOf(proxy);
 
-    // This function is assumed infallible, so we just return false if the IPC
-    // channel fails.
-    bool result;
-    if (!SendObjectClassIs(objId, classValue, &result))
-        return false;
+    uint32_t cls = ESClass_Other;
+    ReturnStatus status;
+    if (!SendGetBuiltinClass(objId, &status, &cls))
+        return ipcfail(cx);
+    *classValue = ESClassValue(cls);
 
     LOG_STACK();
 
-    return result;
+    return ok(cx, status);
 }
 
 bool
 CPOWProxyHandler::isArray(JSContext* cx, HandleObject proxy,
                           IsArrayAnswer* answer) const
 {
     FORWARD(isArray, (cx, proxy, answer));
 }
--- a/js/ipc/WrapperOwner.h
+++ b/js/ipc/WrapperOwner.h
@@ -49,17 +49,17 @@ class WrapperOwner : public virtual Java
 
     // SpiderMonkey extensions.
     bool getPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
                                JS::MutableHandle<JSPropertyDescriptor> desc);
     bool hasOwn(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, bool* bp);
     bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::HandleObject proxy,
                                       JS::AutoIdVector& props);
     bool hasInstance(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleValue v, bool* bp);
-    bool objectClassIs(JSContext* cx, JS::HandleObject obj, js::ESClassValue classValue);
+    bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy, js::ESClassValue* classValue);
     bool isArray(JSContext* cx, JS::HandleObject proxy, JS::IsArrayAnswer* answer);
     const char* className(JSContext* cx, JS::HandleObject proxy);
     bool getPrototype(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleObject protop);
 
     bool regexp_toShared(JSContext* cx, JS::HandleObject proxy, js::RegExpGuard* g);
 
     nsresult instanceOf(JSObject* obj, const nsID* id, bool* bp);
 
@@ -134,18 +134,18 @@ class WrapperOwner : public virtual Java
 
     virtual bool SendIsExtensible(const ObjectId& objId, ReturnStatus* rs,
                                   bool* result) = 0;
     virtual bool SendCallOrConstruct(const ObjectId& objId, const nsTArray<JSParam>& argv,
                                      const bool& construct, ReturnStatus* rs, JSVariant* result,
                                      nsTArray<JSParam>* outparams) = 0;
     virtual bool SendHasInstance(const ObjectId& objId, const JSVariant& v,
                                  ReturnStatus* rs, bool* bp) = 0;
-    virtual bool SendObjectClassIs(const ObjectId& objId, const uint32_t& classValue,
-                                   bool* result) = 0;
+    virtual bool SendGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs,
+                                     uint32_t* classValue) = 0;
     virtual bool SendIsArray(const ObjectId& objId, ReturnStatus* rs,
                              uint32_t* answer) = 0;
     virtual bool SendClassName(const ObjectId& objId, nsCString* result) = 0;
     virtual bool SendGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result) = 0;
     virtual bool SendRegExpToShared(const ObjectId& objId, ReturnStatus* rs, nsString* source,
                                     uint32_t* flags) = 0;
 
     virtual bool SendGetPropertyKeys(const ObjectId& objId, const uint32_t& flags,
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -826,31 +826,21 @@ Valueify(const JSClass* c)
 
 /*
  * Enumeration describing possible values of the [[Class]] internal property
  * value of objects.
  */
 enum ESClassValue {
     ESClass_Object, ESClass_Array, ESClass_Number, ESClass_String,
     ESClass_Boolean, ESClass_RegExp, ESClass_ArrayBuffer, ESClass_SharedArrayBuffer,
-    ESClass_Date, ESClass_Set, ESClass_Map
-};
+    ESClass_Date, ESClass_Set, ESClass_Map,
 
-/*
- * Return whether the given object has the given [[Class]] internal property
- * value. Beware, this query says nothing about the js::Class of the JSObject
- * so the caller must not assume anything about obj's representation (e.g., obj
- * may be a proxy).
- */
-inline bool
-ObjectClassIs(JSObject& obj, ESClassValue classValue, JSContext* cx);
-
-/* Just a helper that checks v.isObject before calling ObjectClassIs. */
-inline bool
-IsObjectWithClass(const JS::Value& v, ESClassValue classValue, JSContext* cx);
+    // None of the above.
+    ESClass_Other
+};
 
 /* Fills |vp| with the unboxed value for boxed types, or undefined otherwise. */
 inline bool
 Unbox(JSContext* cx, JS::HandleObject obj, JS::MutableHandleValue vp);
 
 #ifdef DEBUG
 JS_FRIEND_API(bool)
 HasObjectMovedOp(JSObject* obj);
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -317,17 +317,18 @@ class JS_FRIEND_API(BaseProxyHandler)
     virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) const;
     virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const;
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
                                               AutoIdVector& props) const;
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const;
-    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx) const;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
+                                 ESClassValue* classValue) const;
     virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const;
     virtual const char* className(JSContext* cx, HandleObject proxy) const;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const;
     virtual bool defaultValue(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp) const;
     virtual void trace(JSTracer* trc, JSObject* proxy) const;
     virtual void finalize(JSFreeOp* fop, JSObject* proxy) const;
@@ -408,18 +409,18 @@ class JS_FRIEND_API(DirectProxyHandler) 
     virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id,
                         bool* bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
                                               AutoIdVector& props) const override;
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const override;
-    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
-                               JSContext* cx) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
+                                 ESClassValue* classValue) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy,
                          JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
                                    unsigned indent) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy,
                                  RegExpGuard* g) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override;
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -220,17 +220,20 @@ CompileRegExpObject(JSContext* cx, RegEx
     }
 
     RootedValue patternValue(cx, args.get(0));
 
     /*
      * 21.2.3.1 step 5
      * B.2.5.1 step 3.
      */
-    if (IsObjectWithClass(patternValue, ESClass_RegExp, cx)) {
+    ESClassValue cls;
+    if (!GetClassOfValue(cx, patternValue, &cls))
+        return false;
+    if (cls == ESClass_RegExp) {
         /*
          * B.2.5.1 step 3.a.
          */
         if (args.hasDefined(1) && creationMode == CreateForCompile) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED);
             return false;
         }
 
@@ -350,17 +353,21 @@ js::IsRegExp(JSContext* cx, HandleValue 
 
     /* Step 4. */
     if (!isRegExp.isUndefined()) {
         *result = ToBoolean(isRegExp);
         return true;
     }
 
     /* Steps 5-6. */
-    *result = IsObjectWithClass(value, ESClass_RegExp, cx);
+    ESClassValue cls;
+    if (!GetClassOfValue(cx, value, &cls))
+        return false;
+
+    *result = cls == ESClass_RegExp;
     return true;
 }
 
 /* ES6 draft rc4 B.2.5.1. */
 MOZ_ALWAYS_INLINE bool
 regexp_compile_impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(IsRegExpObject(args.thisv()));
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -37,16 +37,17 @@
 #include "jsnum.h"
 #include "jsprf.h"
 
 #include "builtin/TypedObject.h"
 #include "ctypes/Library.h"
 #include "gc/Zone.h"
 
 #include "jsatominlines.h"
+#include "jsobjinlines.h"
 
 using namespace std;
 using mozilla::NumericLimits;
 
 using JS::AutoCheckCannotGC;
 
 namespace js {
 namespace ctypes {
@@ -3178,87 +3179,91 @@ ImplicitConvert(JSContext* cx,
           dest[sourceLength] = 0;
 
         break;
       }
       default:
         return ConvError(cx, targetType, val, convType, funObj, argIndex,
                          arrObj, arrIndex);
       }
-
-    } else if (val.isObject() && JS_IsArrayObject(cx, valObj)) {
-      // Convert each element of the array by calling ImplicitConvert.
-      uint32_t sourceLength;
-      if (!JS_GetArrayLength(cx, valObj, &sourceLength) ||
-          targetLength != size_t(sourceLength)) {
-        MOZ_ASSERT(!funObj);
-        return ArrayLengthMismatch(cx, targetLength, targetType,
-                                   size_t(sourceLength), val, convType);
-      }
-
-      // Convert into an intermediate, in case of failure.
-      size_t elementSize = CType::GetSize(baseType);
-      size_t arraySize = elementSize * targetLength;
-      auto intermediate = cx->make_pod_array<char>(arraySize);
-      if (!intermediate) {
-        JS_ReportAllocationOverflow(cx);
+    } else {
+      ESClassValue cls;
+      if (!GetClassOfValue(cx, val, &cls))
         return false;
-      }
-
-      for (uint32_t i = 0; i < sourceLength; ++i) {
-        RootedValue item(cx);
-        if (!JS_GetElement(cx, valObj, i, &item))
+
+      if (cls == ESClass_Array) {
+        // Convert each element of the array by calling ImplicitConvert.
+        uint32_t sourceLength;
+        if (!JS_GetArrayLength(cx, valObj, &sourceLength) ||
+            targetLength != size_t(sourceLength)) {
+          MOZ_ASSERT(!funObj);
+          return ArrayLengthMismatch(cx, targetLength, targetType,
+                                     size_t(sourceLength), val, convType);
+        }
+
+        // Convert into an intermediate, in case of failure.
+        size_t elementSize = CType::GetSize(baseType);
+        size_t arraySize = elementSize * targetLength;
+        auto intermediate = cx->make_pod_array<char>(arraySize);
+        if (!intermediate) {
+          JS_ReportAllocationOverflow(cx);
           return false;
-
-        char* data = intermediate.get() + elementSize * i;
-        if (!ImplicitConvert(cx, item, baseType, data, convType, nullptr,
-                             funObj, argIndex, targetType, i))
-          return false;
-      }
-
-      memcpy(buffer, intermediate.get(), arraySize);
-
-    } else if (val.isObject() && JS_IsArrayBufferObject(valObj)) {
-      // Check that array is consistent with type, then
-      // copy the array.
-      uint32_t sourceLength = JS_GetArrayBufferByteLength(valObj);
-      size_t elementSize = CType::GetSize(baseType);
-      size_t arraySize = elementSize * targetLength;
-      if (arraySize != size_t(sourceLength)) {
-        MOZ_ASSERT(!funObj);
-        return ArrayLengthMismatch(cx, arraySize, targetType,
-                                   size_t(sourceLength), val, convType);
-      }
-      JS::AutoCheckCannotGC nogc;
-      memcpy(buffer, JS_GetArrayBufferData(valObj, nogc), sourceLength);
-      break;
-    } else if (val.isObject() && JS_IsTypedArrayObject(valObj)) {
-      // Check that array is consistent with type, then
-      // copy the array.
-      if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
+        }
+
+        RootedValue item(cx);
+        for (uint32_t i = 0; i < sourceLength; ++i) {
+          if (!JS_GetElement(cx, valObj, i, &item))
+            return false;
+
+          char* data = intermediate.get() + elementSize * i;
+          if (!ImplicitConvert(cx, item, baseType, data, convType, nullptr,
+                               funObj, argIndex, targetType, i))
+            return false;
+        }
+
+        memcpy(buffer, intermediate.get(), arraySize);
+      } else if (cls == ESClass_ArrayBuffer) {
+        // Check that array is consistent with type, then
+        // copy the array.
+        uint32_t sourceLength = JS_GetArrayBufferByteLength(valObj);
+        size_t elementSize = CType::GetSize(baseType);
+        size_t arraySize = elementSize * targetLength;
+        if (arraySize != size_t(sourceLength)) {
+          MOZ_ASSERT(!funObj);
+          return ArrayLengthMismatch(cx, arraySize, targetType,
+                                     size_t(sourceLength), val, convType);
+        }
+        JS::AutoCheckCannotGC nogc;
+        memcpy(buffer, JS_GetArrayBufferData(valObj, nogc), sourceLength);
+        break;
+      } else if (JS_IsTypedArrayObject(valObj)) {
+        // Check that array is consistent with type, then
+        // copy the array.
+        if (!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
+          return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                           arrObj, arrIndex);
+        }
+
+        uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj);
+        size_t elementSize = CType::GetSize(baseType);
+        size_t arraySize = elementSize * targetLength;
+        if (arraySize != size_t(sourceLength)) {
+          MOZ_ASSERT(!funObj);
+          return ArrayLengthMismatch(cx, arraySize, targetType,
+                                     size_t(sourceLength), val, convType);
+        }
+        JS::AutoCheckCannotGC nogc;
+        memcpy(buffer, JS_GetArrayBufferViewData(valObj, nogc), sourceLength);
+        break;
+      } else {
+        // Don't implicitly convert to string. Users can implicitly convert
+        // with `String(x)` or `""+x`.
         return ConvError(cx, targetType, val, convType, funObj, argIndex,
                          arrObj, arrIndex);
       }
-
-      uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj);
-      size_t elementSize = CType::GetSize(baseType);
-      size_t arraySize = elementSize * targetLength;
-      if (arraySize != size_t(sourceLength)) {
-        MOZ_ASSERT(!funObj);
-        return ArrayLengthMismatch(cx, arraySize, targetType,
-                                   size_t(sourceLength), val, convType);
-      }
-      JS::AutoCheckCannotGC nogc;
-      memcpy(buffer, JS_GetArrayBufferViewData(valObj, nogc), sourceLength);
-      break;
-    } else {
-      // Don't implicitly convert to string. Users can implicitly convert
-      // with `String(x)` or `""+x`.
-      return ConvError(cx, targetType, val, convType, funObj, argIndex,
-                       arrObj, arrIndex);
     }
     break;
   }
   case TYPE_struct: {
     if (val.isObject() && !sourceData) {
       // Enumerate the properties of the object; if they match the struct
       // specification, convert the fields.
       Rooted<IdVector> props(cx, IdVector(cx));
@@ -5433,20 +5438,26 @@ StructType::Create(JSContext* cx, unsign
   // have undefined size and alignment and no ffi_type.
   RootedObject result(cx, CType::Create(cx, typeProto, nullptr, TYPE_struct,
                                         name.toString(), JS::UndefinedValue(),
                                         JS::UndefinedValue(), nullptr));
   if (!result)
     return false;
 
   if (args.length() == 2) {
-    RootedObject arr(cx, args[1].isPrimitive() ? nullptr : &args[1].toObject());
-    if (!arr || !JS_IsArrayObject(cx, arr)) {
+    RootedObject arr(cx, args[1].isObject() ? &args[1].toObject() : nullptr);
+    bool isArray;
+    if (!arr) {
+        isArray = false;
+    } else {
+        if (!JS_IsArrayObject(cx, arr, &isArray))
+           return false;
+    }
+    if (!isArray)
       return ArgumentTypeMismatch(cx, "second ", "StructType", "an array");
-    }
 
     // Define the struct fields.
     if (!DefineInternal(cx, result, arr))
       return false;
   }
 
   args.rval().setObject(*result);
   return true;
@@ -5700,27 +5711,36 @@ StructType::Define(JSContext* cx, unsign
     JS_ReportError(cx, "StructType has already been defined");
     return false;
   }
 
   if (args.length() != 1) {
     return ArgumentLengthError(cx, "StructType.prototype.define", "one", "");
   }
 
-  Value arg = args[0];
+  HandleValue arg = args[0];
   if (arg.isPrimitive()) {
     return ArgumentTypeMismatch(cx, "", "StructType.prototype.define",
                                 "an array");
   }
-  RootedObject arr(cx, arg.toObjectOrNull());
-  if (!JS_IsArrayObject(cx, arr)) {
+
+  bool isArray;
+  if (!arg.isObject()) {
+    isArray = false;
+  } else {
+    if (!JS_IsArrayObject(cx, arg, &isArray))
+      return false;
+  }
+
+  if (!isArray) {
     return ArgumentTypeMismatch(cx, "", "StructType.prototype.define",
                                 "an array");
   }
 
+  RootedObject arr(cx, &arg.toObject());
   return DefineInternal(cx, obj, arr);
 }
 
 bool
 StructType::ConstructData(JSContext* cx,
                           HandleObject obj,
                           const CallArgs& args)
 {
@@ -5890,17 +5910,16 @@ StructType::FieldsArrayGetter(JSContext*
     if (!fields)
       return false;
     JS_SetReservedSlot(obj, SLOT_FIELDS, ObjectValue(*fields));
 
     args.rval().setObject(*fields);
   }
 
   MOZ_ASSERT(args.rval().isObject());
-  MOZ_ASSERT(JS_IsArrayObject(cx, args.rval()));
   return true;
 }
 
 bool
 StructType::FieldGetter(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -6340,21 +6359,28 @@ FunctionType::Create(JSContext* cx, unsi
     return ArgumentLengthError(cx, "FunctionType", "two or three", "s");
   }
 
   AutoValueVector argTypes(cx);
   RootedObject arrayObj(cx, nullptr);
 
   if (args.length() == 3) {
     // Prepare an array of Values for the arguments.
-    if (args[2].isObject())
-      arrayObj = &args[2].toObject();
-    if (!arrayObj || !JS_IsArrayObject(cx, arrayObj)) {
+    bool isArray;
+    if (!args[2].isObject()) {
+      isArray = false;
+    } else {
+      if (!JS_IsArrayObject(cx, args[2], &isArray))
+        return false;
+    }
+
+    if (!isArray)
       return ArgumentTypeMismatch(cx, "third ", "FunctionType", "an array");
-    }
+
+    arrayObj = &args[2].toObject();
 
     uint32_t len;
     ASSERT_OK(JS_GetArrayLength(cx, arrayObj, &len));
 
     if (!argTypes.resize(len)) {
       JS_ReportOutOfMemory(cx);
       return false;
     }
--- a/js/src/jsapi-tests/testNewObject.cpp
+++ b/js/src/jsapi-tests/testNewObject.cpp
@@ -57,41 +57,46 @@ BEGIN_TEST(testNewObject_1)
     static const size_t N = 1000;
     JS::AutoValueVector argv(cx);
     CHECK(argv.resize(N));
 
     JS::RootedValue v(cx);
     EVAL("Array", &v);
     JS::RootedObject Array(cx, v.toObjectOrNull());
 
+    bool isArray;
+
     // With no arguments.
     JS::RootedObject obj(cx, JS_New(cx, Array, JS::HandleValueArray::empty()));
     CHECK(obj);
     JS::RootedValue rt(cx, JS::ObjectValue(*obj));
-    CHECK(JS_IsArrayObject(cx, obj));
+    CHECK(JS_IsArrayObject(cx, obj, &isArray));
+    CHECK(isArray);
     uint32_t len;
     CHECK(JS_GetArrayLength(cx, obj, &len));
     CHECK_EQUAL(len, 0u);
 
     // With one argument.
     argv[0].setInt32(4);
     obj = JS_New(cx, Array, JS::HandleValueArray::subarray(argv, 0, 1));
     CHECK(obj);
     rt = JS::ObjectValue(*obj);
-    CHECK(JS_IsArrayObject(cx, obj));
+    CHECK(JS_IsArrayObject(cx, obj, &isArray));
+    CHECK(isArray);
     CHECK(JS_GetArrayLength(cx, obj, &len));
     CHECK_EQUAL(len, 4u);
 
     // With N arguments.
     for (size_t i = 0; i < N; i++)
         argv[i].setInt32(i);
     obj = JS_New(cx, Array, JS::HandleValueArray::subarray(argv, 0, N));
     CHECK(obj);
     rt = JS::ObjectValue(*obj);
-    CHECK(JS_IsArrayObject(cx, obj));
+    CHECK(JS_IsArrayObject(cx, obj, &isArray));
+    CHECK(isArray);
     CHECK(JS_GetArrayLength(cx, obj, &len));
     CHECK_EQUAL(len, N);
     CHECK(JS_GetElement(cx, obj, N - 1, &v));
     CHECK(v.isInt32(N - 1));
 
     // With JSClass.construct.
     static const JSClass cls = {
         "testNewObject_1",
--- a/js/src/jsapi-tests/testParseJSON.cpp
+++ b/js/src/jsapi-tests/testParseJSON.cpp
@@ -97,43 +97,49 @@ BEGIN_TEST(testParseJSON_success)
     CHECK(TryParse(cx, "\"\\n\"", expected));
     CHECK(TryParse(cx, "\"\\u000A\"", expected));
 
 
     // Arrays
     JS::RootedValue v(cx), v2(cx);
     JS::RootedObject obj(cx);
 
+    bool isArray;
+
     CHECK(Parse(cx, "[]", &v));
     CHECK(v.isObject());
     obj = &v.toObject();
-    CHECK(JS_IsArrayObject(cx, obj));
+    CHECK(JS_IsArrayObject(cx, obj, &isArray));
+    CHECK(isArray);
     CHECK(JS_GetProperty(cx, obj, "length", &v2));
     CHECK(v2.isInt32(0));
 
     CHECK(Parse(cx, "[1]", &v));
     CHECK(v.isObject());
     obj = &v.toObject();
-    CHECK(JS_IsArrayObject(cx, obj));
+    CHECK(JS_IsArrayObject(cx, obj, &isArray));
+    CHECK(isArray);
     CHECK(JS_GetProperty(cx, obj, "0", &v2));
     CHECK(v2.isInt32(1));
     CHECK(JS_GetProperty(cx, obj, "length", &v2));
     CHECK(v2.isInt32(1));
 
 
     // Objects
     CHECK(Parse(cx, "{}", &v));
     CHECK(v.isObject());
     obj = &v.toObject();
-    CHECK(!JS_IsArrayObject(cx, obj));
+    CHECK(JS_IsArrayObject(cx, obj, &isArray));
+    CHECK(!isArray);
 
     CHECK(Parse(cx, "{ \"f\": 17 }", &v));
     CHECK(v.isObject());
     obj = &v.toObject();
-    CHECK(!JS_IsArrayObject(cx, obj));
+    CHECK(JS_IsArrayObject(cx, obj, &isArray));
+    CHECK(!isArray);
     CHECK(JS_GetProperty(cx, obj, "f", &v2));
     CHECK(v2.isInt32(17));
 
     return true;
 }
 
 template<size_t N> static JSFlatString*
 NewString(JSContext* cx, const char16_t (&chars)[N])
--- a/js/src/jsapi-tests/testRegExp.cpp
+++ b/js/src/jsapi-tests/testRegExp.cpp
@@ -3,23 +3,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jsapi-tests/tests.h"
 
 BEGIN_TEST(testObjectIsRegExp)
 {
     JS::RootedValue val(cx);
 
+    bool isRegExp;
+
     EVAL("new Object", &val);
     JS::RootedObject obj(cx, val.toObjectOrNull());
-    CHECK(!JS_ObjectIsRegExp(cx, obj));
+    CHECK(JS_ObjectIsRegExp(cx, obj, &isRegExp));
+    CHECK(!isRegExp);
 
     EVAL("/foopy/", &val);
     obj = val.toObjectOrNull();
-    CHECK(JS_ObjectIsRegExp(cx, obj));
+    CHECK(JS_ObjectIsRegExp(cx, obj, &isRegExp));
+    CHECK(isRegExp);
 
     return true;
 }
 END_TEST(testObjectIsRegExp)
 
 BEGIN_TEST(testGetRegExpFlags)
 {
     JS::RootedValue val(cx);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3207,29 +3207,38 @@ JS_NewArrayObject(JSContext* cx, size_t 
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     return NewDenseFullyAllocatedArray(cx, length);
 }
 
 JS_PUBLIC_API(bool)
-JS_IsArrayObject(JSContext* cx, JS::HandleObject obj)
+JS_IsArrayObject(JSContext* cx, JS::HandleObject obj, bool* isArray)
 {
     assertSameCompartment(cx, obj);
-    return ObjectClassIs(obj, ESClass_Array, cx);
-}
-
-JS_PUBLIC_API(bool)
-JS_IsArrayObject(JSContext* cx, JS::HandleValue value)
-{
-    if (!value.isObject())
+
+    ESClassValue cls;
+    if (!GetBuiltinClass(cx, obj, &cls))
         return false;
+
+    *isArray = cls == ESClass_Array;
+    return true;
+}
+
+JS_PUBLIC_API(bool)
+JS_IsArrayObject(JSContext* cx, JS::HandleValue value, bool* isArray)
+{
+    if (!value.isObject()) {
+        *isArray = false;
+        return true;
+    }
+
     RootedObject obj(cx, &value.toObject());
-    return JS_IsArrayObject(cx, obj);
+    return JS_IsArrayObject(cx, obj, isArray);
 }
 
 JS_PUBLIC_API(bool)
 JS_GetArrayLength(JSContext* cx, HandleObject obj, uint32_t* lengthp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
@@ -5493,20 +5502,26 @@ JS_PUBLIC_API(JSObject*)
 JS::NewDateObject(JSContext* cx, JS::ClippedTime time)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     return NewDateObjectMsec(cx, time);
 }
 
 JS_PUBLIC_API(bool)
-JS_ObjectIsDate(JSContext* cx, HandleObject obj)
+JS_ObjectIsDate(JSContext* cx, HandleObject obj, bool* isDate)
 {
     assertSameCompartment(cx, obj);
-    return ObjectClassIs(obj, ESClass_Date, cx);
+
+    ESClassValue cls;
+    if (!GetBuiltinClass(cx, obj, &cls))
+        return false;
+
+    *isDate = cls == ESClass_Date;
+    return true;
 }
 
 JS_PUBLIC_API(void)
 JS_ClearDateCaches(JSContext* cx)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     cx->runtime()->dateTimeInfo.updateTimeZoneAdjustment();
@@ -5631,20 +5646,26 @@ JS_ExecuteRegExpNoStatics(JSContext* cx,
     if (!input)
         return false;
 
     return ExecuteRegExpLegacy(cx, nullptr, obj->as<RegExpObject>(), input, indexp, test,
                                rval);
 }
 
 JS_PUBLIC_API(bool)
-JS_ObjectIsRegExp(JSContext* cx, HandleObject obj)
+JS_ObjectIsRegExp(JSContext* cx, HandleObject obj, bool* isRegExp)
 {
     assertSameCompartment(cx, obj);
-    return ObjectClassIs(obj, ESClass_RegExp, cx);
+
+    ESClassValue cls;
+    if (!GetBuiltinClass(cx, obj, &cls))
+        return false;
+
+    *isRegExp = cls == ESClass_RegExp;
+    return true;
 }
 
 JS_PUBLIC_API(unsigned)
 JS_GetRegExpFlags(JSContext* cx, HandleObject obj)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2951,21 +2951,31 @@ JS_DeleteUCProperty(JSContext* cx, JS::H
                     JS::ObjectOpResult& result);
 
 extern JS_PUBLIC_API(JSObject*)
 JS_NewArrayObject(JSContext* cx, const JS::HandleValueArray& contents);
 
 extern JS_PUBLIC_API(JSObject*)
 JS_NewArrayObject(JSContext* cx, size_t length);
 
-extern JS_PUBLIC_API(bool)
-JS_IsArrayObject(JSContext* cx, JS::HandleValue value);
-
-extern JS_PUBLIC_API(bool)
-JS_IsArrayObject(JSContext* cx, JS::HandleObject obj);
+// Returns true and sets |*isArray| indicating whether |value| is an Array
+// object or a wrapper around one, otherwise returns false on failure.
+//
+// This method returns true with |*isArray == false| when passed a proxy whose
+// target is an Array, or when passed a revoked proxy.
+extern JS_PUBLIC_API(bool)
+JS_IsArrayObject(JSContext* cx, JS::HandleValue value, bool* isArray);
+
+// Returns true and sets |*isArray| indicating whether |obj| is an Array object
+// or a wrapper around one, otherwise returns false on failure.
+//
+// This method returns true with |*isArray == false| when passed a proxy whose
+// target is an Array, or when passed a revoked proxy.
+extern JS_PUBLIC_API(bool)
+JS_IsArrayObject(JSContext* cx, JS::HandleObject obj, bool* isArray);
 
 extern JS_PUBLIC_API(bool)
 JS_GetArrayLength(JSContext* cx, JS::Handle<JSObject*> obj, uint32_t* lengthp);
 
 extern JS_PUBLIC_API(bool)
 JS_SetArrayLength(JSContext* cx, JS::Handle<JSObject*> obj, uint32_t length);
 
 extern JS_PUBLIC_API(bool)
@@ -4717,21 +4727,23 @@ SetForEach(JSContext *cx, HandleObject o
 
 /*
  * Dates.
  */
 
 extern JS_PUBLIC_API(JSObject*)
 JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min, int sec);
 
-/*
- * Infallible predicate to test whether obj is a date object.
- */
-extern JS_PUBLIC_API(bool)
-JS_ObjectIsDate(JSContext* cx, JS::HandleObject obj);
+// Returns true and sets |*isDate| indicating whether |obj| is a Date object or
+// a wrapper around one, otherwise returns false on failure.
+//
+// This method returns true with |*isDate == false| when passed a proxy whose
+// target is a Date, or when passed a revoked proxy.
+extern JS_PUBLIC_API(bool)
+JS_ObjectIsDate(JSContext* cx, JS::HandleObject obj, bool* isDate);
 
 /*
  * Clears the cache of calculated local time from each Date object.
  * Call to propagate a system timezone change.
  */
 extern JS_PUBLIC_API(void)
 JS_ClearDateCaches(JSContext* cx);
 
@@ -4772,18 +4784,23 @@ JS_NewRegExpObjectNoStatics(JSContext* c
 
 extern JS_PUBLIC_API(JSObject*)
 JS_NewUCRegExpObjectNoStatics(JSContext* cx, char16_t* chars, size_t length, unsigned flags);
 
 extern JS_PUBLIC_API(bool)
 JS_ExecuteRegExpNoStatics(JSContext* cx, JS::HandleObject reobj, char16_t* chars, size_t length,
                           size_t* indexp, bool test, JS::MutableHandleValue rval);
 
-extern JS_PUBLIC_API(bool)
-JS_ObjectIsRegExp(JSContext* cx, JS::HandleObject obj);
+// Returns true and sets |*isRegExp| indicating whether |obj| is a RegExp
+// object or a wrapper around one, otherwise returns false on failure.
+//
+// This method returns true with |*isRegExp == false| when passed a proxy whose
+// target is a RegExp, or when passed a revoked proxy.
+extern JS_PUBLIC_API(bool)
+JS_ObjectIsRegExp(JSContext* cx, JS::HandleObject obj, bool* isRegExp);
 
 extern JS_PUBLIC_API(unsigned)
 JS_GetRegExpFlags(JSContext* cx, JS::HandleObject obj);
 
 extern JS_PUBLIC_API(JSString*)
 JS_GetRegExpSource(JSContext* cx, JS::HandleObject obj);
 
 /************************************************************************/
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -2874,27 +2874,29 @@ static bool
 date_toString(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     // Step 2.a. (reordered)
     double tv = GenericNaN();
     if (args.thisv().isObject()) {
         // Step 1.
         RootedObject obj(cx, &args.thisv().toObject());
+
         // Step 2.
-        if (ObjectClassIs(obj, ESClass_Date, cx)) {
+        ESClassValue cls;
+        if (!GetBuiltinClass(cx, obj, &cls))
+            return false;
+
+        if (cls == ESClass_Date) {
             // Step 3.a.
             RootedValue unboxed(cx);
             if (!Unbox(cx, obj, &unboxed))
                 return false;
             tv = unboxed.toNumber();
         }
-        // ObjectClassIs can throw for objects from other compartments.
-        if (cx->isExceptionPending())
-            return false;
     }
     // Step 4.
     return date_format(cx, tv, FORMATSPEC_FULL, args.rval());
 }
 
 MOZ_ALWAYS_INLINE bool
 date_valueOf_impl(JSContext* cx, const CallArgs& args)
 {
@@ -3224,42 +3226,46 @@ js::NewDateObject(JSContext* cx, int yea
                   int hour, int min, int sec)
 {
     MOZ_ASSERT(mon < 12);
     double msec_time = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
     return NewDateObjectMsec(cx, TimeClip(UTC(msec_time, &cx->runtime()->dateTimeInfo)));
 }
 
 JS_FRIEND_API(bool)
-js::DateIsValid(JSContext* cx, JSObject* objArg)
+js::DateIsValid(JSContext* cx, HandleObject obj, bool* isValid)
 {
-    RootedObject obj(cx, objArg);
-    if (!ObjectClassIs(obj, ESClass_Date, cx))
+    ESClassValue cls;
+    if (!GetBuiltinClass(cx, obj, &cls))
         return false;
 
+    if (cls != ESClass_Date) {
+        *isValid = false;
+        return true;
+    }
+
     RootedValue unboxed(cx);
-    if (!Unbox(cx, obj, &unboxed)) {
-        // This can't actually happen, so we don't force consumers to deal with
-        // a clunky out-param API. Do something sane-ish if it does happen.
-        cx->clearPendingException();
+    if (!Unbox(cx, obj, &unboxed))
         return false;
+
+    *isValid = !IsNaN(unboxed.toNumber());
+    return true;
+}
+
+JS_FRIEND_API(bool)
+js::DateGetMsecSinceEpoch(JSContext* cx, HandleObject obj, double* msecsSinceEpoch)
+{
+    ESClassValue cls;
+    if (!GetBuiltinClass(cx, obj, &cls))
+        return false;
+
+    if (cls != ESClass_Date) {
+        *msecsSinceEpoch = 0;
+        return true;
     }
 
-    return !IsNaN(unboxed.toNumber());
-}
-
-JS_FRIEND_API(double)
-js::DateGetMsecSinceEpoch(JSContext* cx, JSObject* objArg)
-{
-    RootedObject obj(cx, objArg);
-    if (!ObjectClassIs(obj, ESClass_Date, cx))
-        return 0;
-
     RootedValue unboxed(cx);
-    if (!Unbox(cx, obj, &unboxed)) {
-        // This can't actually happen, so we don't force consumers to deal with
-        // a clunky out-param API. Do something sane-ish if it does happen.
-        cx->clearPendingException();
-        return 0;
-    }
-
-    return unboxed.toNumber();
+    if (!Unbox(cx, obj, &unboxed))
+        return false;
+
+    *msecsSinceEpoch = unboxed.toNumber();
+    return true;
 }
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -1011,19 +1011,24 @@ js::ValueToSourceForError(JSContext* cx,
     if (!str) {
         JS_ClearPendingException(cx);
         return "<<error converting value to string>>";
     }
 
     StringBuffer sb(cx);
     if (val.isObject()) {
         RootedObject valObj(cx, val.toObjectOrNull());
-        if (JS_IsArrayObject(cx, valObj)) {
+        ESClassValue cls;
+        if (!GetBuiltinClass(cx, valObj, &cls)) {
+            JS_ClearPendingException(cx);
+            return "<<error determining class of value>>";
+        }
+        if (cls == ESClass_Array) {
             sb.append("the array ");
-        } else if (JS_IsArrayBufferObject(valObj)) {
+        } else if (cls == ESClass_ArrayBuffer) {
             sb.append("the array buffer ");
         } else if (JS_IsArrayBufferViewObject(valObj)) {
             sb.append("the typed array ");
         } else {
             sb.append("the object ");
         }
     } else if (val.isNumber()) {
         sb.append("the number ");
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -257,19 +257,47 @@ JS_DefineFunctionsWithHelp(JSContext* cx
                 return false;
         }
     }
 
     return true;
 }
 
 JS_FRIEND_API(bool)
-js::ObjectClassIs(JSContext* cx, HandleObject obj, ESClassValue classValue)
+js::GetBuiltinClass(JSContext* cx, HandleObject obj, ESClassValue* classValue)
 {
-    return ObjectClassIs(obj, classValue, cx);
+    if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
+        return Proxy::getBuiltinClass(cx, obj, classValue);
+
+    if (obj->is<PlainObject>() || obj->is<UnboxedPlainObject>())
+        *classValue = ESClass_Object;
+    else if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>())
+        *classValue = ESClass_Array;
+    else if (obj->is<NumberObject>())
+        *classValue = ESClass_Number;
+    else if (obj->is<StringObject>())
+        *classValue = ESClass_String;
+    else if (obj->is<BooleanObject>())
+        *classValue = ESClass_Boolean;
+    else if (obj->is<RegExpObject>())
+        *classValue = ESClass_RegExp;
+    else if (obj->is<ArrayBufferObject>())
+        *classValue = ESClass_ArrayBuffer;
+    else if (obj->is<SharedArrayBufferObject>())
+        *classValue = ESClass_SharedArrayBuffer;
+    else if (obj->is<DateObject>())
+        *classValue = ESClass_Date;
+    else if (obj->is<SetObject>())
+        *classValue = ESClass_Set;
+    else if (obj->is<MapObject>())
+        *classValue = ESClass_Map;
+    else
+        *classValue = ESClass_Other;
+
+    return true;
 }
 
 JS_FRIEND_API(const char*)
 js::ObjectClassName(JSContext* cx, HandleObject obj)
 {
     return GetObjectClassName(cx, obj);
 }
 
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -175,17 +175,17 @@ JS_InitializePropertiesFromCompatibleNat
                                                   JS::HandleObject src);
 
 extern JS_FRIEND_API(JSString*)
 JS_BasicObjectToString(JSContext* cx, JS::HandleObject obj);
 
 namespace js {
 
 JS_FRIEND_API(bool)
-ObjectClassIs(JSContext* cx, JS::HandleObject obj, ESClassValue classValue);
+GetBuiltinClass(JSContext* cx, JS::HandleObject obj, ESClassValue* classValue);
 
 JS_FRIEND_API(const char*)
 ObjectClassName(JSContext* cx, JS::HandleObject obj);
 
 JS_FRIEND_API(void)
 ReportOverRecursed(JSContext* maybecx);
 
 JS_FRIEND_API(bool)
@@ -1253,25 +1253,22 @@ DOMProxyShadowsCheck GetDOMProxyShadowsC
 inline bool DOMProxyIsShadowing(DOMProxyShadowsResult result) {
     return result == Shadows ||
            result == ShadowsViaDirectExpando ||
            result == ShadowsViaIndirectExpando;
 }
 
 /* Implemented in jsdate.cpp. */
 
-/*
- * Detect whether the internal date value is NaN.  (Because failure is
- * out-of-band for js_DateGet*)
- */
+/* Detect whether the internal date value is NaN. */
 extern JS_FRIEND_API(bool)
-DateIsValid(JSContext* cx, JSObject* obj);
-
-extern JS_FRIEND_API(double)
-DateGetMsecSinceEpoch(JSContext* cx, JSObject* obj);
+DateIsValid(JSContext* cx, JS::HandleObject obj, bool* isValid);
+
+extern JS_FRIEND_API(bool)
+DateGetMsecSinceEpoch(JSContext* cx, JS::HandleObject obj, double* msecSinceEpoch);
 
 } /* namespace js */
 
 /* Implemented in jscntxt.cpp. */
 
 /*
  * Report an exception, which is currently realized as a printf-style format
  * string and its arguments.
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -531,17 +531,17 @@ class JSObject : public js::gc::Cell
      * is<BlockObject>, is<NestedScopeObject> and is<ScopeObject>. Each of
      * 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).
+     * js::GetBuiltinClass).
      */
 
     template <class T>
     inline bool is() const { return getClass() == &T::class_; }
 
     template <class T>
     T& as() {
         MOZ_ASSERT(this->is<T>());
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -6,16 +6,17 @@
 
 #ifndef jsobjinlines_h
 #define jsobjinlines_h
 
 #include "jsobj.h"
 
 #include "mozilla/DebugOnly.h"
 
+#include "jsfriendapi.h"
 #include "jsfun.h"
 
 #include "builtin/MapObject.h"
 #include "builtin/TypedObject.h"
 #include "gc/Allocator.h"
 #include "vm/ArrayObject.h"
 #include "vm/DateObject.h"
 #include "vm/NumberObject.h"
@@ -805,46 +806,29 @@ GuessObjectGCKind(size_t numSlots)
 static inline gc::AllocKind
 GuessArrayGCKind(size_t numSlots)
 {
     if (numSlots)
         return gc::GetGCArrayKind(numSlots);
     return gc::AllocKind::OBJECT8;
 }
 
+// Returns ESClass_Other if the value isn't an object, or if the object
+// isn't of one of the enumerated classes.  Otherwise returns the appropriate
+// class.
 inline bool
-ObjectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx)
+GetClassOfValue(JSContext* cx, HandleValue v, ESClassValue* classValue)
 {
-    if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
-        return Proxy::objectClassIs(obj, classValue, cx);
+    if (!v.isObject()) {
+        *classValue = ESClass_Other;
+        return true;
+    }
 
-    switch (classValue) {
-      case ESClass_Object: return obj->is<PlainObject>() || obj->is<UnboxedPlainObject>();
-      case ESClass_Array:
-        return obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>();
-      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>();
-      case ESClass_ArrayBuffer: return obj->is<ArrayBufferObject>();
-      case ESClass_SharedArrayBuffer: return obj->is<SharedArrayBufferObject>();
-      case ESClass_Date: return obj->is<DateObject>();
-      case ESClass_Set: return obj->is<SetObject>();
-      case ESClass_Map: return obj->is<MapObject>();
-    }
-    MOZ_CRASH("bad classValue");
-}
-
-inline bool
-IsObjectWithClass(const Value& v, ESClassValue classValue, JSContext* cx)
-{
-    if (!v.isObject())
-        return false;
     RootedObject obj(cx, &v.toObject());
-    return ObjectClassIs(obj, classValue, cx);
+    return GetBuiltinClass(cx, obj, classValue);
 }
 
 inline bool
 Unbox(JSContext* cx, HandleObject obj, MutableHandleValue vp)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::boxedValue_unbox(cx, obj, vp);
 
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -260,27 +260,32 @@ PreprocessValue(JSContext* cx, HandleObj
         if (!Invoke(cx, args))
             return false;
         vp.set(args.rval());
     }
 
     /* Step 4. */
     if (vp.get().isObject()) {
         RootedObject obj(cx, &vp.get().toObject());
-        if (ObjectClassIs(obj, ESClass_Number, cx)) {
+
+        ESClassValue cls;
+        if (!GetBuiltinClass(cx, obj, &cls))
+            return false;
+
+        if (cls == ESClass_Number) {
             double d;
             if (!ToNumber(cx, vp, &d))
                 return false;
             vp.setNumber(d);
-        } else if (ObjectClassIs(obj, ESClass_String, cx)) {
+        } else if (cls == ESClass_String) {
             JSString* str = ToStringSlow<CanGC>(cx, vp);
             if (!str)
                 return false;
             vp.setString(str);
-        } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) {
+        } else if (cls == ESClass_Boolean) {
             if (!Unbox(cx, obj, vp))
                 return false;
         }
     }
 
     return true;
 }
 
@@ -620,25 +625,32 @@ js::Stringify(JSContext* cx, MutableHand
                     /* Step 4b(iv)(4). */
                     int32_t n;
                     if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) {
                         id = INT_TO_JSID(n);
                     } else {
                         if (!ValueToId<CanGC>(cx, v, &id))
                             return false;
                     }
-                } else if (v.isString() ||
-                           IsObjectWithClass(v, ESClass_String, cx) ||
-                           IsObjectWithClass(v, ESClass_Number, cx))
-                {
-                    /* Step 4b(iv)(3), 4b(iv)(5). */
-                    if (!ValueToId<CanGC>(cx, v, &id))
-                        return false;
                 } else {
-                    continue;
+                    bool shouldAdd = v.isString();
+                    if (!shouldAdd) {
+                        ESClassValue cls;
+                        if (!GetClassOfValue(cx, v, &cls))
+                            return false;
+                        shouldAdd = cls == ESClass_String || cls == ESClass_Number;
+                    }
+
+                    if (shouldAdd) {
+                        /* Step 4b(iv)(3), 4b(iv)(5). */
+                        if (!ValueToId<CanGC>(cx, v, &id))
+                            return false;
+                    } else {
+                        continue;
+                    }
                 }
 
                 /* Step 4b(iv)(6). */
                 HashSet<jsid, JsidHasher>::AddPtr p = idSet.lookupForAdd(id);
                 if (!p) {
                     /* Step 4b(iv)(6)(a). */
                     if (!idSet.add(p, id) || !propertyList.append(id))
                         return false;
@@ -647,22 +659,27 @@ js::Stringify(JSContext* cx, MutableHand
         } else {
             replacer = nullptr;
         }
     }
 
     /* Step 5. */
     if (space.isObject()) {
         RootedObject spaceObj(cx, &space.toObject());
-        if (ObjectClassIs(spaceObj, ESClass_Number, cx)) {
+
+        ESClassValue cls;
+        if (!GetBuiltinClass(cx, spaceObj, &cls))
+            return false;
+
+        if (cls == ESClass_Number) {
             double d;
             if (!ToNumber(cx, space, &d))
                 return false;
             space = NumberValue(d);
-        } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) {
+        } else if (cls == ESClass_String) {
             JSString* str = ToStringSlow<CanGC>(cx, space);
             if (!str)
                 return false;
             space = StringValue(str);
         }
     }
 
     StringBuffer gap(cx);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2095,18 +2095,24 @@ class MOZ_STACK_CLASS StringRegExpGuard
   public:
     explicit StringRegExpGuard(JSContext* cx)
       : re_(cx), fm(cx), obj_(cx)
     { }
 
     /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */
     bool init(JSContext* cx, const CallArgs& args, bool convertVoid = false)
     {
-        if (args.length() != 0 && IsObjectWithClass(args[0], ESClass_RegExp, cx))
-            return initRegExp(cx, &args[0].toObject());
+        if (args.length() != 0) {
+            ESClassValue cls;
+            if (!GetClassOfValue(cx, args[0], &cls))
+                return false;
+
+            if (cls == ESClass_RegExp)
+                return initRegExp(cx, &args[0].toObject());
+        }
 
         if (convertVoid && !args.hasDefined(0)) {
             fm.pat_ = cx->runtime()->emptyString;
             return true;
         }
 
         JSString* arg = ArgToRootedString(cx, args, 0);
         if (!arg)
@@ -2116,19 +2122,16 @@ class MOZ_STACK_CLASS StringRegExpGuard
         if (!fm.pat_)
             return false;
 
         return true;
     }
 
     bool initRegExp(JSContext* cx, JSObject* regexp) {
         obj_ = regexp;
-
-        MOZ_ASSERT(ObjectClassIs(obj_, ESClass_RegExp, cx));
-
         return RegExpToShared(cx, obj_, &re_);
     }
 
     bool init(JSContext* cx, HandleString pattern) {
         fm.pat_ = AtomizeString(cx, pattern);
         if (!fm.pat_)
             return false;
         return true;
@@ -3856,17 +3859,21 @@ js::str_split(JSContext* cx, unsigned ar
         limit = UINT32_MAX;
     }
 
     /* Step 8. */
     RegExpGuard re(cx);
     RootedLinearString sepstr(cx);
     bool sepDefined = args.hasDefined(0);
     if (sepDefined) {
-        if (IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
+        ESClassValue cls;
+        if (!GetClassOfValue(cx, args[0], &cls))
+            return false;
+
+        if (cls == ESClass_RegExp) {
             RootedObject obj(cx, &args[0].toObject());
             if (!RegExpToShared(cx, obj, &re))
                 return false;
         } else {
             sepstr = ArgToRootedString(cx, args, 0);
             if (!sepstr)
                 return false;
         }
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -200,17 +200,18 @@ class JS_FRIEND_API(OpaqueCrossCompartme
 
     /* SpiderMonkey extensions. */
     virtual bool getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext* cx, HandleObject wrapper, HandleId id,
                         bool* bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject wrapper,
                                               AutoIdVector& props) const override;
-    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject wrapper,
+                                 ESClassValue* classValue) const override;
     virtual bool isArray(JSContext* cx, HandleObject obj,
                          JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject wrapper) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override;
     virtual bool defaultValue(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp) const override;
 
     static const OpaqueCrossCompartmentWrapper singleton;
 };
@@ -242,18 +243,18 @@ class JS_FRIEND_API(SecurityWrapper) : p
     virtual bool preventExtensions(JSContext* cx, HandleObject wrapper,
                                    ObjectOpResult& result) const override;
     virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
                               ObjectOpResult& result) const override;
     virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const override;
 
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
-    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
-                               JSContext* cx) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject wrapper,
+                                 ESClassValue* classValue) const override;
     virtual bool isArray(JSContext* cx, HandleObject wrapper, JS::IsArrayAnswer* answer) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override;
     virtual bool defaultValue(JSContext* cx, HandleObject wrapper, JSType hint,
                               MutableHandleValue vp) const override;
 
     // Allow isCallable and isConstructor. They used to be class-level, and so could not be guarded
     // against.
--- a/js/src/proxy/BaseProxyHandler.cpp
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -314,19 +314,21 @@ BaseProxyHandler::hasInstance(JSContext*
     assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
     RootedValue val(cx, ObjectValue(*proxy.get()));
     ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
                      JSDVG_SEARCH_STACK, val, nullptr);
     return false;
 }
 
 bool
-BaseProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext* cx) const
+BaseProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
+                                  ESClassValue* classValue) const
 {
-    return false;
+    *classValue = ESClass_Other;
+    return true;
 }
 
 bool
 BaseProxyHandler::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer) const
 {
     *answer = IsArrayAnswer::NotArray;
     return true;
 }
--- a/js/src/proxy/DeadObjectProxy.cpp
+++ b/js/src/proxy/DeadObjectProxy.cpp
@@ -109,17 +109,18 @@ bool
 DeadObjectProxy::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const
 {
     ReportDead(cx);
     return false;
 }
 
 bool
-DeadObjectProxy::objectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx) const
+DeadObjectProxy::getBuiltinClass(JSContext* cx, HandleObject proxy,
+                                 ESClassValue* classValue) const
 {
     ReportDead(cx);
     return false;
 }
 
 bool
 DeadObjectProxy::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswer* answer) const
 {
--- a/js/src/proxy/DeadObjectProxy.h
+++ b/js/src/proxy/DeadObjectProxy.h
@@ -38,18 +38,18 @@ class DeadObjectProxy : public BaseProxy
     virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
 
     /* SpiderMonkey extensions. */
     // BaseProxyHandler::getPropertyDescriptor will throw by calling getOwnPropertyDescriptor.
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const override;
-    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
-                               JSContext* cx) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
+                                 ESClassValue* classValue) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
     virtual bool defaultValue(JSContext* cx, HandleObject obj, JSType hint,
                               MutableHandleValue vp) const override;
 
     static const char family;
--- a/js/src/proxy/DirectProxyHandler.cpp
+++ b/js/src/proxy/DirectProxyHandler.cpp
@@ -149,21 +149,21 @@ DirectProxyHandler::preventExtensions(JS
 bool
 DirectProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return IsExtensible(cx, target, extensible);
 }
 
 bool
-DirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
-                                  JSContext* cx) const
+DirectProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
+                                    ESClassValue* classValue) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
-    return ObjectClassIs(target, classValue, cx);
+    return GetBuiltinClass(cx, target, classValue);
 }
 
 bool
 DirectProxyHandler::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return IsArray(cx, target, answer);
 }
--- a/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
+++ b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
@@ -146,20 +146,21 @@ OpaqueCrossCompartmentWrapper::hasOwn(JS
 bool
 OpaqueCrossCompartmentWrapper::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject wrapper,
                                                             AutoIdVector& props) const
 {
     return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, wrapper, props);
 }
 
 bool
-OpaqueCrossCompartmentWrapper::objectClassIs(HandleObject obj, ESClassValue classValue,
-                                             JSContext* cx) const
+OpaqueCrossCompartmentWrapper::getBuiltinClass(JSContext* cx, HandleObject wrapper,
+                                               ESClassValue* classValue) const
 {
-  return false;
+    *classValue = ESClass_Other;
+    return true;
 }
 
 bool
 OpaqueCrossCompartmentWrapper::isArray(JSContext* cx, HandleObject obj,
                                        JS::IsArrayAnswer* answer) const
 {
     *answer = JS::IsArrayAnswer::NotArray;
     return true;
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -450,20 +450,20 @@ Proxy::hasInstance(JSContext* cx, Handle
     *bp = false; // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
     return proxy->as<ProxyObject>().handler()->hasInstance(cx, proxy, v, bp);
 }
 
 bool
-Proxy::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext* cx)
+Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClassValue* classValue)
 {
     JS_CHECK_RECURSION(cx, return false);
-    return proxy->as<ProxyObject>().handler()->objectClassIs(proxy, classValue, cx);
+    return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, classValue);
 }
 
 bool
 Proxy::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer)
 {
     return proxy->as<ProxyObject>().handler()->isArray(cx, proxy, answer);
 }
 
--- a/js/src/proxy/Proxy.h
+++ b/js/src/proxy/Proxy.h
@@ -51,17 +51,17 @@ class Proxy
     static bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                                       MutableHandle<JSPropertyDescriptor> desc);
     static bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp);
     static bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
                                              AutoIdVector& props);
     static bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                            const CallArgs& args);
     static bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp);
-    static bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx);
+    static bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClassValue* classValue);
     static bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer);
     static const char* className(JSContext* cx, HandleObject proxy);
     static JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent);
     static bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g);
     static bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp);
     static bool defaultValue(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp);
 
     static bool watch(JSContext* cx, HandleObject proxy, HandleId id, HandleObject callable);
--- a/js/src/proxy/ScriptedDirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp
@@ -1097,20 +1097,21 @@ ScriptedDirectProxyHandler::hasInstance(
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
         return false;
     }
 
     return HasInstance(cx, target, v, bp);
 }
 
 bool
-ScriptedDirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
-                                          JSContext* cx) const
+ScriptedDirectProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
+                                            ESClassValue* classValue) const
 {
-    return false;
+    *classValue = ESClass_Other;
+    return true;
 }
 
 bool
 ScriptedDirectProxyHandler::isArray(JSContext* cx, HandleObject proxy,
                                     IsArrayAnswer* answer) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     if (target)
--- a/js/src/proxy/ScriptedDirectProxyHandler.h
+++ b/js/src/proxy/ScriptedDirectProxyHandler.h
@@ -65,18 +65,18 @@ class ScriptedDirectProxyHandler : publi
         return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
     }
 
     // A scripted proxy should not be treated as generic in most contexts.
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const override;
-    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
-                               JSContext* cx) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
+                                 ESClassValue* classValue) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy,
                          JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
                                    unsigned indent) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy,
                                  RegExpGuard* g) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy,
--- a/js/src/proxy/SecurityWrapper.cpp
+++ b/js/src/proxy/SecurityWrapper.cpp
@@ -82,19 +82,21 @@ bool
 SecurityWrapper<Base>::defaultValue(JSContext* cx, HandleObject wrapper,
                                     JSType hint, MutableHandleValue vp) const
 {
     return OrdinaryToPrimitive(cx, wrapper, hint, vp);
 }
 
 template <class Base>
 bool
-SecurityWrapper<Base>::objectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx) const
+SecurityWrapper<Base>::getBuiltinClass(JSContext* cx, HandleObject wrapper,
+                                       ESClassValue* classValue) const
 {
-    return false;
+    *classValue = ESClass_Other;
+    return true;
 }
 
 template <class Base>
 bool
 SecurityWrapper<Base>::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswer* answer) const
 {
     // This should ReportUnwrapDenied(cx), but bug 849730 disagrees.  :-(
     *answer = JS::IsArrayAnswer::NotArray;
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -349,17 +349,20 @@ ArrayBufferObject::fun_transfer(JSContex
     HandleValue newByteLengthArg = args.get(1);
 
     if (!oldBufferArg.isObject()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
         return false;
     }
 
     RootedObject oldBufferObj(cx, &oldBufferArg.toObject());
-    if (!ObjectClassIs(oldBufferObj, ESClass_ArrayBuffer, cx)) {
+    ESClassValue cls;
+    if (!GetBuiltinClass(cx, oldBufferObj, &cls))
+        return false;
+    if (cls != ESClass_ArrayBuffer) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
         return false;
     }
 
     // Beware: oldBuffer can point across compartment boundaries. ArrayBuffer
     // contents are not compartment-specific so this is safe.
     Rooted<ArrayBufferObject*> oldBuffer(cx);
     if (oldBufferObj->is<ArrayBufferObject>()) {
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -482,23 +482,23 @@ str_replace_regexp_raw(JSContext* cx, Ha
  * Parse regexp flags. Report an error and return false if an invalid
  * sequence of flags is encountered (repeat/invalid flag).
  *
  * N.B. flagStr must be rooted.
  */
 bool
 ParseRegExpFlags(JSContext* cx, JSString* flagStr, RegExpFlag* flagsOut);
 
-/* Assuming ObjectClassIs(obj, ESClass_RegExp), return a RegExpShared for obj. */
+/* Assuming GetBuiltinClass(obj) is ESClass_RegExp, return a RegExpShared for obj. */
 inline bool
 RegExpToShared(JSContext* cx, HandleObject obj, RegExpGuard* g)
 {
     if (obj->is<RegExpObject>())
         return obj->as<RegExpObject>().getShared(cx, g);
-    MOZ_ASSERT(Proxy::objectClassIs(obj, ESClass_RegExp, cx));
+
     return Proxy::regexp_toShared(cx, obj, g);
 }
 
 template<XDRMode mode>
 bool
 XDRScriptRegExpObject(XDRState<mode>* xdr, MutableHandle<RegExpObject*> objp);
 
 extern JSObject*
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -1,35 +1,37 @@
 /* -*- 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/. */
 
 #include "vm/SharedArrayObject.h"
 
+#include "mozilla/Atomics.h"
+
+#include "jsfriendapi.h"
 #include "jsprf.h"
-#include "jsobjinlines.h"
 
 #ifdef XP_WIN
 # include "jswin.h"
 #endif
 #include "jswrapper.h"
 #ifndef XP_WIN
 # include <sys/mman.h>
 #endif
 #ifdef MOZ_VALGRIND
 # include <valgrind/memcheck.h>
 #endif
 
-#include "mozilla/Atomics.h"
-
 #include "asmjs/AsmJSValidate.h"
 #include "vm/TypedArrayCommon.h"
 
+#include "jsobjinlines.h"
+
 using namespace js;
 
 static inline void*
 MapMemory(size_t length, bool commit)
 {
 #ifdef XP_WIN
     int prot = (commit ? MEM_COMMIT : MEM_RESERVE);
     int flags = (commit ? PAGE_READWRITE : PAGE_NOACCESS);
@@ -203,19 +205,24 @@ SharedArrayBufferObject::fun_isView(JSCo
 }
 
 bool
 SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!args.isConstructing()) {
-        if (args.hasDefined(0) && IsObjectWithClass(args[0], ESClass_SharedArrayBuffer, cx)) {
-            args.rval().set(args[0]);
-            return true;
+        if (args.hasDefined(0)) {
+            ESClassValue cls;
+            if (!GetClassOfValue(cx, args[0], &cls))
+                return false;
+            if (cls == ESClass_SharedArrayBuffer) {
+                args.rval().set(args[0]);
+                return true;
+            }
         }
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_OBJECT);
         return false;
     }
 
     // Bugs 1068458, 1161298: Limit length to 2^31-1.
     uint32_t length;
     bool overflow_unused;
--- a/js/src/vm/SharedTypedArrayObject.cpp
+++ b/js/src/vm/SharedTypedArrayObject.cpp
@@ -406,17 +406,20 @@ class SharedTypedArrayObjectTemplate : p
     static bool fun_copyWithin(JSContext* cx, unsigned argc, Value* vp);
     static bool fun_set(JSContext* cx, unsigned argc, Value* vp);
 
   public:
     static JSObject*
     fromBufferWithProto(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, uint32_t length,
                         HandleObject proto)
     {
-        if (!ObjectClassIs(bufobj, ESClass_SharedArrayBuffer, cx)) {
+        ESClassValue cls;
+        if (!GetBuiltinClass(cx, bufobj, &cls))
+            return nullptr;
+        if (cls != ESClass_SharedArrayBuffer) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SHARED_TYPED_ARRAY_BAD_OBJECT);
             return nullptr; // must be SharedArrayBuffer
         }
 
         if (bufobj->is<ProxyObject>()) {
             // Complicated, see TypedArrayObject.cpp for code.  For now, punt.
             JS_ReportError(cx, "Permission denied to access object");
             return nullptr;
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -747,17 +747,20 @@ JSStructuredCloneWriter::parseTransferab
     if (transferable.isNull() || transferable.isUndefined())
         return true;
 
     if (!transferable.isObject())
         return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
 
     JSContext* cx = context();
     RootedObject array(cx, &transferable.toObject());
-    if (!JS_IsArrayObject(cx, array))
+    bool isArray;
+    if (!JS_IsArrayObject(cx, array, &isArray))
+        return false;
+    if (!isArray)
         return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
 
     uint32_t length;
     if (!JS_GetArrayLength(cx, array, &length)) {
         return false;
     }
 
     RootedValue v(context());
@@ -959,17 +962,20 @@ JSStructuredCloneWriter::traverseObject(
 
     /* Push obj and count to the stack. */
     if (!objs.append(ObjectValue(*obj)) || !counts.append(properties.length()))
         return false;
 
     checkStack();
 
     /* Write the header for obj. */
-    return out.writePair(ObjectClassIs(obj, ESClass_Array, context()) ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
+    ESClassValue cls;
+    if (!GetBuiltinClass(context(), obj, &cls))
+        return false;
+    return out.writePair(cls == ESClass_Array ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
 }
 
 bool
 JSStructuredCloneWriter::traverseMap(HandleObject obj)
 {
     AutoValueVector newEntries(context());
     {
         // If there is no wrapper, the compartment munging is a no-op.
@@ -1048,59 +1054,63 @@ JSStructuredCloneWriter::startWrite(Hand
         RootedObject obj(context(), &v.toObject());
 
         bool backref;
         if (!startObject(obj, &backref))
             return false;
         if (backref)
             return true;
 
-        if (ObjectClassIs(obj, ESClass_RegExp, context())) {
+        ESClassValue cls;
+        if (!GetBuiltinClass(context(), obj, &cls))
+            return false;
+
+        if (cls == ESClass_RegExp) {
             RegExpGuard re(context());
             if (!RegExpToShared(context(), obj, &re))
                 return false;
             return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) &&
                    writeString(SCTAG_STRING, re->getSource());
-        } else if (ObjectClassIs(obj, ESClass_Date, context())) {
+        } else if (cls == ESClass_Date) {
             RootedValue unboxed(context());
             if (!Unbox(context(), obj, &unboxed))
                 return false;
             return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(unboxed.toNumber());
         } else if (JS_IsTypedArrayObject(obj)) {
             return writeTypedArray(obj);
         } else if (JS_IsDataViewObject(obj)) {
             return writeDataView(obj);
         } else if (JS_IsArrayBufferObject(obj) && JS_ArrayBufferHasData(obj)) {
             return writeArrayBuffer(obj);
         } else if (JS_IsSharedTypedArrayObject(obj)) {
             return writeSharedTypedArray(obj);
         } else if (JS_IsSharedArrayBufferObject(obj)) {
             return writeSharedArrayBuffer(obj);
-        } else if (ObjectClassIs(obj, ESClass_Object, context())) {
+        } else if (cls == ESClass_Object) {
             return traverseObject(obj);
-        } else if (ObjectClassIs(obj, ESClass_Array, context())) {
+        } else if (cls == ESClass_Array) {
             return traverseObject(obj);
-        } else if (ObjectClassIs(obj, ESClass_Boolean, context())) {
+        } else if (cls == ESClass_Boolean) {
             RootedValue unboxed(context());
             if (!Unbox(context(), obj, &unboxed))
                 return false;
             return out.writePair(SCTAG_BOOLEAN_OBJECT, unboxed.toBoolean());
-        } else if (ObjectClassIs(obj, ESClass_Number, context())) {
+        } else if (cls == ESClass_Number) {
             RootedValue unboxed(context());
             if (!Unbox(context(), obj, &unboxed))
                 return false;
             return out.writePair(SCTAG_NUMBER_OBJECT, 0) && out.writeDouble(unboxed.toNumber());
-        } else if (ObjectClassIs(obj, ESClass_String, context())) {
+        } else if (cls == ESClass_String) {
             RootedValue unboxed(context());
             if (!Unbox(context(), obj, &unboxed))
                 return false;
             return writeString(SCTAG_STRING_OBJECT, unboxed.toString());
-        } else if (ObjectClassIs(obj, ESClass_Map, context())) {
+        } else if (cls == ESClass_Map) {
             return traverseMap(obj);
-        } else if (ObjectClassIs(obj, ESClass_Set, context())) {
+        } else if (cls == ESClass_Set) {
             return traverseSet(obj);
         }
 
         if (callbacks && callbacks->write)
             return callbacks->write(context(), this, obj, closure);
         /* else fall through */
     }
 
@@ -1163,17 +1173,21 @@ JSStructuredCloneWriter::transferOwnersh
         uint64_t extraData;
 
 #if DEBUG
         SCInput::getPair(point, &tag, (uint32_t*) &ownership);
         MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
         MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
 #endif
 
-        if (ObjectClassIs(obj, ESClass_ArrayBuffer, context())) {
+        ESClassValue cls;
+        if (!GetBuiltinClass(context(), obj, &cls))
+            return false;
+
+        if (cls == ESClass_ArrayBuffer) {
             // The current setup of the array buffer inheritance hierarchy doesn't
             // lend itself well to generic manipulation via proxies.
             Rooted<ArrayBufferObject*> arrayBuffer(context(), &CheckedUnwrap(obj)->as<ArrayBufferObject>());
             size_t nbytes = arrayBuffer->byteLength();
 
             // Structured cloning currently only has optimizations for mapped
             // and malloc'd buffers, not asm.js-ified buffers.
             bool hasStealableContents = arrayBuffer->hasStealableContents() &&
@@ -1186,17 +1200,17 @@ JSStructuredCloneWriter::transferOwnersh
 
             content = bufContents.data();
             tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
             if (bufContents.kind() == ArrayBufferObject::MAPPED)
                 ownership = JS::SCTAG_TMO_MAPPED_DATA;
             else
                 ownership = JS::SCTAG_TMO_ALLOC_DATA;
             extraData = nbytes;
-        } else if (ObjectClassIs(obj, ESClass_SharedArrayBuffer, context())) {
+        } else if (cls == ESClass_SharedArrayBuffer) {
             Rooted<SharedArrayBufferObject*> sharedArrayBuffer(context(), &CheckedUnwrap(obj)->as<SharedArrayBufferObject>());
             SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
 
             // Avoids a race condition where the parent thread frees the buffer
             // before the child has accepted the transferable.
             rawbuf->addReference();
 
             tag = SCTAG_TRANSFER_MAP_SHARED_BUFFER;
@@ -1233,25 +1247,29 @@ JSStructuredCloneWriter::write(HandleVal
         RootedObject obj(context(), &objs.back().toObject());
         AutoCompartment ac(context(), obj);
         if (counts.back()) {
             counts.back()--;
             RootedValue key(context(), entries.back());
             entries.popBack();
             checkStack();
 
-            if (ObjectClassIs(obj, ESClass_Map, context())) {
+            ESClassValue cls;
+            if (!GetBuiltinClass(context(), obj, &cls))
+                return false;
+
+            if (cls == ESClass_Map) {
                 counts.back()--;
                 RootedValue val(context(), entries.back());
                 entries.popBack();
                 checkStack();
 
                 if (!startWrite(key) || !startWrite(val))
                     return false;
-            } else if (ObjectClassIs(obj, ESClass_Set, context())) {
+            } else if (cls == ESClass_Set) {
                 if (!startWrite(key))
                     return false;
             } else {
                 RootedId id(context());
                 if (!ValueToId<CanGC>(context(), key, &id))
                   return false;
                 MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
 
--- a/js/src/vm/TypedArrayCommon.h
+++ b/js/src/vm/TypedArrayCommon.h
@@ -8,16 +8,20 @@
 #define vm_TypedArrayCommon_h
 
 /* Utilities and common inline code for TypedArray and SharedTypedArray */
 
 #include "mozilla/Assertions.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/PodOperations.h"
 
+#include "jsarray.h"
+#include "jscntxt.h"
+#include "jsnum.h"
+
 #include "js/Conversions.h"
 #include "js/Value.h"
 
 #include "vm/SharedTypedArrayObject.h"
 #include "vm/TypedArrayObject.h"
 
 namespace js {
 
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -483,19 +483,22 @@ class TypedArrayObjectTemplate : public 
     fromBuffer(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt) {
         return fromBufferWithProto(cx, bufobj, byteOffset, lengthInt, nullptr);
     }
 
     static JSObject*
     fromBufferWithProto(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt,
                         HandleObject proto)
     {
-        if (!ObjectClassIs(bufobj, ESClass_ArrayBuffer, cx)) {
+        ESClassValue cls;
+        if (!GetBuiltinClass(cx, bufobj, &cls))
+            return nullptr;
+        if (cls != ESClass_ArrayBuffer) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-            return nullptr; // must be arrayBuffer
+            return nullptr;
         }
 
         MOZ_ASSERT(IsArrayBuffer(bufobj) || 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,
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -1180,17 +1180,21 @@ mozJSComponentLoader::ImportInto(const n
         RootedValue symbols(cx);
         RootedObject modObj(cx, mod->obj);
         if (!JS_GetProperty(cx, modObj,
                             "EXPORTED_SYMBOLS", &symbols)) {
             return ReportOnCaller(cxhelper, ERROR_NOT_PRESENT,
                                   PromiseFlatCString(aLocation).get());
         }
 
-        if (!JS_IsArrayObject(cx, symbols)) {
+        bool isArray;
+        if (!JS_IsArrayObject(cx, symbols, &isArray)) {
+            return NS_ERROR_FAILURE;
+        }
+        if (!isArray) {
             return ReportOnCaller(cxhelper, ERROR_NOT_AN_ARRAY,
                                   PromiseFlatCString(aLocation).get());
         }
 
         RootedObject symbolsObj(cx, &symbols.toObject());
 
         // Iterate over symbols array, installing symbols on targetObj:
 
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -858,18 +858,16 @@ xpc::SandboxProxyHandler::enumerate(JSCo
                                     JS::MutableHandle<JSObject*> objp) const
 {
     return BaseProxyHandler::enumerate(cx, proxy, objp);
 }
 
 bool
 xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj)
 {
-    MOZ_ASSERT(JS_IsArrayObject(cx, obj));
-
     uint32_t length;
     bool ok = JS_GetArrayLength(cx, obj, &length);
     NS_ENSURE_TRUE(ok, false);
     for (uint32_t i = 0; i < length; i++) {
         RootedValue nameValue(cx);
         ok = JS_GetElement(cx, obj, i, &nameValue);
         NS_ENSURE_TRUE(ok, false);
         if (!nameValue.isString()) {
@@ -1243,20 +1241,19 @@ GetPrincipalOrSOP(JSContext* cx, HandleO
  * format or actual objects (see GetPrincipalOrSOP)
  */
 static bool
 GetExpandedPrincipal(JSContext* cx, HandleObject arrayObj, nsIExpandedPrincipal** out)
 {
     MOZ_ASSERT(out);
     uint32_t length;
 
-    if (!JS_IsArrayObject(cx, arrayObj) ||
-        !JS_GetArrayLength(cx, arrayObj, &length) ||
-        !length)
-    {
+    if (!JS_GetArrayLength(cx, arrayObj, &length))
+        return false;
+    if (!length) {
         // We need a whitelist of principals or uri strings to create an
         // expanded principal, if we got an empty array or something else
         // report error.
         JS_ReportError(cx, "Expected an array of URI strings");
         return false;
     }
 
     nsTArray< nsCOMPtr<nsIPrincipal> > allowedDomains(length);
@@ -1476,17 +1473,20 @@ SandboxOptions::ParseGlobalProperties()
         return true;
 
     if (!value.isObject()) {
         JS_ReportError(mCx, "Expected an array value for wantGlobalProperties");
         return false;
     }
 
     RootedObject ctors(mCx, &value.toObject());
-    if (!JS_IsArrayObject(mCx, ctors)) {
+    bool isArray;
+    if (!JS_IsArrayObject(mCx, ctors, &isArray))
+        return false;
+    if (!isArray) {
         JS_ReportError(mCx, "Expected an array value for wantGlobalProperties");
         return false;
     }
 
     return globalProperties.Parse(mCx, ctors);
 }
 
 /*
@@ -1572,17 +1572,20 @@ nsXPCComponents_utils_Sandbox::CallOrCon
     nsCOMPtr<nsISupports> prinOrSop;
 
     if (args[0].isString()) {
         RootedString str(cx, args[0].toString());
         ok = ParsePrincipal(cx, str, getter_AddRefs(principal));
         prinOrSop = principal;
     } else if (args[0].isObject()) {
         RootedObject obj(cx, &args[0].toObject());
-        if (JS_IsArrayObject(cx, obj)) {
+        bool isArray;
+        if (!JS_IsArrayObject(cx, obj, &isArray)) {
+            ok = false;
+        } else if (isArray) {
             ok = GetExpandedPrincipal(cx, obj, getter_AddRefs(expanded));
             prinOrSop = expanded;
         } else {
             ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop));
         }
     } else if (args[0].isNull()) {
         // Null means that we just pass prinOrSop = nullptr, and get an
         // nsNullPrincipal.
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2540,18 +2540,26 @@ nsXPCComponents_Utils::ImportGlobalPrope
     // Don't allow doing this if the global is a Window
     nsGlobalWindow* win;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, win))) {
         return NS_ERROR_NOT_AVAILABLE;
     }
 
     GlobalProperties options;
     NS_ENSURE_TRUE(aPropertyList.isObject(), NS_ERROR_INVALID_ARG);
+
     RootedObject propertyList(cx, &aPropertyList.toObject());
-    NS_ENSURE_TRUE(JS_IsArrayObject(cx, propertyList), NS_ERROR_INVALID_ARG);
+    bool isArray;
+    if (NS_WARN_IF(!JS_IsArrayObject(cx, propertyList, &isArray))) {
+        return NS_ERROR_FAILURE;
+    }
+    if (NS_WARN_IF(!isArray)) {
+        return NS_ERROR_INVALID_ARG;
+    }
+
     if (!options.Parse(cx, propertyList) ||
         !options.Define(cx, global))
     {
         return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
 }
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -1504,17 +1504,18 @@ XPCConvert::JSArray2Native(void** d, Han
 
     RootedObject jsarray(cx, &s.toObject());
 
     // If this is a typed array, then try a fast conversion with memcpy.
     if (JS_IsTypedArrayObject(jsarray)) {
         return JSTypedArray2Native(d, jsarray, count, type, pErr);
     }
 
-    if (!JS_IsArrayObject(cx, jsarray)) {
+    bool isArray;
+    if (!JS_IsArrayObject(cx, jsarray, &isArray) || !isArray) {
         if (pErr)
             *pErr = NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY;
         return false;
     }
 
     uint32_t len;
     if (!JS_GetArrayLength(cx, jsarray, &len) || len < count) {
         if (pErr)
--- a/js/xpconnect/src/XPCVariant.cpp
+++ b/js/xpconnect/src/XPCVariant.cpp
@@ -186,17 +186,22 @@ XPCArrayHomogenizer::GetTypeForArray(JSC
             break;
         } else if (val.isNull()) {
             type = tNull;
         } else if (val.isString()) {
             type = tStr;
         } else {
             MOZ_ASSERT(val.isObject(), "invalid type of jsval!");
             jsobj = &val.toObject();
-            if (JS_IsArrayObject(cx, jsobj))
+
+            bool isArray;
+            if (!JS_IsArrayObject(cx, jsobj, &isArray))
+                return false;
+
+            if (isArray)
                 type = tArr;
             else if (xpc_JSObjectIsID(cx, jsobj))
                 type = tID;
             else
                 type = tISup;
         }
 
         MOZ_ASSERT(state != tErr, "bad state table!");
@@ -299,17 +304,24 @@ bool XPCVariant::InitializeData(JSContex
     const nsID* id = xpc_JSObjectToID(cx, jsobj);
     if (id)
         return NS_SUCCEEDED(mData.SetFromID(*id));
 
     // Let's see if it is a js array object.
 
     uint32_t len;
 
-    if (JS_IsArrayObject(cx, jsobj) && JS_GetArrayLength(cx, jsobj, &len)) {
+    bool isArray;
+    if (!JS_IsArrayObject(cx, jsobj, &isArray) ||
+        (isArray && !JS_GetArrayLength(cx, jsobj, &len)))
+    {
+        return false;
+    }
+
+    if (isArray) {
         if (!len) {
             // Zero length array
             mData.SetToEmptyArray();
             return true;
         }
 
         nsXPTType type;
         nsID id;
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -1494,17 +1494,20 @@ CallMethodHelper::GetArraySizeFromParam(
     // When converting arguments from JS to C++, we pass the array as |maybeArray|,
     // and give ourselves the chance to infer the length. Once we have it, we stick
     // it in the right slot so that we can find it again when cleaning up the params.
     // from the array.
     if (paramIndex >= mArgc && maybeArray.isObject()) {
         MOZ_ASSERT(mMethodInfo->GetParam(paramIndex).IsOptional());
         RootedObject arrayOrNull(mCallContext, maybeArray.isObject() ? &maybeArray.toObject()
                                                                      : nullptr);
-        if (!JS_IsArrayObject(mCallContext, maybeArray) ||
+
+        bool isArray;
+        if (!JS_IsArrayObject(mCallContext, maybeArray, &isArray) ||
+            !isArray ||
             !JS_GetArrayLength(mCallContext, arrayOrNull, &GetDispatchParam(paramIndex)->val.u32))
         {
             return Throw(NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY, mCallContext);
         }
     }
 
     *result = GetDispatchParam(paramIndex)->val.u32;
 
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -778,23 +778,29 @@ XPCWrappedNativeScope::UpdateInterpositi
     if (!AccessCheck::isChrome(whitelistObj)) {
         JS_ReportError(cx, "Whitelist must be from system scope.");
         return false;
     }
 
     {
         JSAutoCompartment ac(cx, whitelistObj);
 
-        uint32_t length;
-        if (!JS_IsArrayObject(cx, whitelistObj) ||
-            !JS_GetArrayLength(cx, whitelistObj, &length)) {
+        bool isArray;
+        if (!JS_IsArrayObject(cx, whitelistObj, &isArray))
+            return false;
+
+        if (!isArray) {
             JS_ReportError(cx, "Whitelist must be an array.");
             return false;
         }
 
+        uint32_t length;
+        if (!JS_GetArrayLength(cx, whitelistObj, &length))
+            return false;
+
         for (uint32_t i = 0; i < length; i++) {
             RootedValue idval(cx);
             if (!JS_GetElement(cx, whitelistObj, i, &idval))
                 return false;
 
             if (!idval.isString()) {
                 JS_ReportError(cx, "Whitelist must contain strings only.");
                 return false;
--- a/js/xpconnect/wrappers/AccessCheck.cpp
+++ b/js/xpconnect/wrappers/AccessCheck.cpp
@@ -304,17 +304,21 @@ ExposedPropertiesOnly::check(JSContext* 
     if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
         return false;
 
     // If no __exposedProps__ existed, deny access.
     if (!found) {
         // Previously we automatically granted access to indexed properties and
         // .length for Array COWs. We're not doing that anymore, so make sure to
         // let people know what's going on.
-        bool isArray = JS_IsArrayObject(cx, wrappedObject) || JS_IsTypedArrayObject(wrappedObject);
+        bool isArray;
+        if (!JS_IsArrayObject(cx, wrappedObject, &isArray))
+            return false;
+        if (!isArray)
+            isArray = JS_IsTypedArrayObject(wrappedObject);
         bool isIndexedAccessOnArray = isArray && JSID_IS_INT(id) && JSID_TO_INT(id) >= 0;
         bool isLengthAccessOnArray = isArray && JSID_IS_STRING(id) &&
                                      JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length");
         if (isIndexedAccessOnArray || isLengthAccessOnArray) {
             JSAutoCompartment ac2(cx, wrapper);
             ReportWrapperDenial(cx, id, WrapperDenialForCOW,
                                 "Access to elements and length of privileged Array not permitted");
         }
--- a/storage/mozStoragePrivateHelpers.cpp
+++ b/storage/mozStoragePrivateHelpers.cpp
@@ -131,20 +131,24 @@ convertJSValToVariant(
     return new IntegerVariant(aValue.isTrue() ? 1 : 0);
 
   if (aValue.isNull())
     return new NullVariant();
 
   if (aValue.isObject()) {
     JS::Rooted<JSObject*> obj(aCtx, &aValue.toObject());
     // We only support Date instances, all others fail.
-    if (!js::DateIsValid(aCtx, obj))
+    bool valid;
+    if (!js::DateIsValid(aCtx, obj, &valid) || !valid)
       return nullptr;
 
-    double msecd = js::DateGetMsecSinceEpoch(aCtx, obj);
+    double msecd;
+    if (!js::DateGetMsecSinceEpoch(aCtx, obj, &msecd))
+      return nullptr;
+
     msecd *= 1000.0;
     int64_t msec = msecd;
 
     return new IntegerVariant(msec);
   }
 
   return nullptr;
 }
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -270,17 +270,21 @@ namespace {
  */
 nsresult
 GetJSArrayFromJSValue(JS::Handle<JS::Value> aValue,
                       JSContext* aCtx,
                       JS::MutableHandle<JSObject*> _array,
                       uint32_t* _arrayLength) {
   if (aValue.isObjectOrNull()) {
     JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull());
-    if (JS_IsArrayObject(aCtx, val)) {
+    bool isArray;
+    if (!JS_IsArrayObject(aCtx, val, &isArray)) {
+      return NS_ERROR_UNEXPECTED;
+    }
+    if (isArray) {
       _array.set(val);
       (void)JS_GetArrayLength(aCtx, _array, _arrayLength);
       NS_ENSURE_ARG(*_arrayLength > 0);
       return NS_OK;
     }
   }
 
   // Build a temporary array to store this one item so the code below can
@@ -451,19 +455,16 @@ GetIntFromJSObject(JSContext* aCtx,
  *        Set to the JSObject pointer on success.
  */
 nsresult
 GetJSObjectFromArray(JSContext* aCtx,
                      JS::Handle<JSObject*> aArray,
                      uint32_t aIndex,
                      JS::MutableHandle<JSObject*> objOut)
 {
-  NS_PRECONDITION(JS_IsArrayObject(aCtx, aArray),
-                  "Must provide an object that is an array!");
-
   JS::Rooted<JS::Value> value(aCtx);
   bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
   NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   NS_ENSURE_ARG(!value.isPrimitive());
   objOut.set(&value.toObject());
   return NS_OK;
 }
 
@@ -2875,17 +2876,23 @@ History::UpdatePlaces(JS::Handle<JS::Val
 
     JS::Rooted<JSObject*> visits(aCtx, nullptr);
     {
       JS::Rooted<JS::Value> visitsVal(aCtx);
       bool rc = JS_GetProperty(aCtx, info, "visits", &visitsVal);
       NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
       if (!visitsVal.isPrimitive()) {
         visits = visitsVal.toObjectOrNull();
-        NS_ENSURE_ARG(JS_IsArrayObject(aCtx, visits));
+        bool isArray;
+        if (!JS_IsArrayObject(aCtx, visits, &isArray)) {
+          return NS_ERROR_UNEXPECTED;
+        }
+        if (!isArray) {
+          return NS_ERROR_INVALID_ARG;
+        }
       }
     }
     NS_ENSURE_ARG(visits);
 
     uint32_t visitsLength = 0;
     if (visits) {
       (void)JS_GetArrayLength(aCtx, visits, &visitsLength);
     }
--- a/widget/android/NativeJSContainer.cpp
+++ b/widget/android/NativeJSContainer.cpp
@@ -426,18 +426,20 @@ class NativeJSContainerImpl final
 
     template<class Prop> bool
     ArrayInValue(JS::HandleValue val) const
     {
         if (!val.isObject()) {
             return false;
         }
         JS::RootedObject obj(mJSContext, &val.toObject());
+        bool isArray;
         uint32_t length = 0;
-        if (!JS_IsArrayObject(mJSContext, obj) ||
+        if (!JS_IsArrayObject(mJSContext, obj, &isArray) ||
+            !isArray ||
             !JS_GetArrayLength(mJSContext, obj, &length)) {
             return false;
         }
         if (!length) {
             // Empty arrays are always okay.
             return true;
         }
         // We only check to see the first element is the target type. If the