Bug 1187234 - Throw a TypeError when Array.isArray is passed a revoked proxy. r=efaust
authorJeff Walden <jwalden@mit.edu>
Sun, 23 Aug 2015 01:10:24 -0700
changeset 263655 6c90d3eab1f7f9cbc195a6c0dad7f4cf39368237
parent 263654 5e0c5de50004021b23aaf5a498fff6e30e205200
child 263656 bfb0223e309336ced37770e5ce235bc68caf4acd
push id29415
push usercbook@mozilla.com
push dateTue, 22 Sep 2015 10:41:24 +0000
treeherdermozilla-central@a1ccea59e254 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust
bugs1187234
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 1187234 - Throw a TypeError when Array.isArray is passed a revoked proxy. r=efaust
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/jsarray.cpp
js/src/jsobjinlines.h
js/src/json.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/tests/ecma_6/Array/isArray.js
--- a/js/ipc/JavaScriptBase.h
+++ b/js/ipc/JavaScriptBase.h
@@ -86,16 +86,19 @@ class JavaScriptBase : public WrapperOwn
     }
     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 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) {
         return Answer::RecvGetPrototype(ObjectId::deserialize(objId), rs, result);
     }
     bool RecvRegExpToShared(const uint64_t& objId, ReturnStatus* rs, nsString* source, uint32_t* flags) {
         return Answer::RecvRegExpToShared(ObjectId::deserialize(objId), rs, source, flags);
@@ -174,16 +177,20 @@ class JavaScriptBase : public WrapperOwn
     }
     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 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);
     }
     bool SendGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result) {
         return Base::SendGetPrototype(objId.serialize(), rs, result);
     }
 
     bool SendRegExpToShared(const ObjectId& objId, ReturnStatus* rs,
--- a/js/ipc/PJavaScript.ipdl
+++ b/js/ipc/PJavaScript.ipdl
@@ -34,16 +34,17 @@ both:
     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 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
@@ -6,16 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WrapperAnswer.h"
 #include "JavaScriptLogging.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "xpcprivate.h"
+#include "js/Class.h"
 #include "jsfriendapi.h"
 
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 
 // Note - Using AutoJSAPI (rather than AutoEntryScript) for a trap means
 // that we don't expect it to run script. For most of these traps that will only
@@ -517,16 +518,42 @@ WrapperAnswer::RecvObjectClassIs(const O
 
     LOG("%s.objectClassIs()", ReceiverObj(objId));
 
     *result = js::ObjectClassIs(cx, obj, (js::ESClassValue)classValue);
     return true;
 }
 
 bool
+WrapperAnswer::RecvIsArray(const ObjectId& objId, ReturnStatus* rs,
+                           uint32_t* ans)
+{
+    *ans = uint32_t(IsArrayAnswer::NotArray);
+
+    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)
+        return fail(jsapi, rs);
+
+    LOG("%s.isArray()", ReceiverObj(objId));
+
+    IsArrayAnswer answer;
+    if (!JS::IsArray(cx, obj, &answer))
+        return fail(jsapi, rs);
+
+    *ans = uint32_t(answer);
+    return ok(rs);
+}
+
+bool
 WrapperAnswer::RecvClassName(const ObjectId& objId, nsCString* name)
 {
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     jsapi.TakeOwnershipOfErrorReporting();
     JSContext* cx = jsapi.cx();
 
--- a/js/ipc/WrapperAnswer.h
+++ b/js/ipc/WrapperAnswer.h
@@ -48,16 +48,17 @@ class WrapperAnswer : public virtual Jav
     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 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,
                         ReturnStatus* rs, bool* instanceof);
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -122,16 +122,18 @@ class CPOWProxyHandler : public BaseProx
                                        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 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;
     virtual bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const override;
 
@@ -736,16 +738,43 @@ WrapperOwner::objectClassIs(JSContext* c
     if (!SendObjectClassIs(objId, classValue, &result))
         return false;
 
     LOG_STACK();
 
     return result;
 }
 
+bool
+CPOWProxyHandler::isArray(JSContext* cx, HandleObject proxy,
+                          IsArrayAnswer* answer) const
+{
+    FORWARD(isArray, (cx, proxy, answer));
+}
+
+bool
+WrapperOwner::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer)
+{
+    ObjectId objId = idOf(proxy);
+
+    uint32_t ans;
+    ReturnStatus status;
+    if (!SendIsArray(objId, &status, &ans))
+        return ipcfail(cx);
+
+    LOG_STACK();
+
+    *answer = IsArrayAnswer(ans);
+    MOZ_ASSERT(*answer == IsArrayAnswer::Array ||
+               *answer == IsArrayAnswer::NotArray ||
+               *answer == IsArrayAnswer::RevokedProxy);
+
+    return ok(cx, status);
+}
+
 const char*
 CPOWProxyHandler::className(JSContext* cx, HandleObject proxy) const
 {
     WrapperOwner* parent = OwnerOf(proxy);
     if (!parent->active())
         return "<dead CPOW>";
     return parent->className(cx, proxy);
 }
--- a/js/ipc/WrapperOwner.h
+++ b/js/ipc/WrapperOwner.h
@@ -50,16 +50,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 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);
 
     bool toString(JSContext* cx, JS::HandleObject callee, JS::CallArgs& args);
@@ -135,16 +136,18 @@ class WrapperOwner : public virtual Java
                                   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 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,
                                      ReturnStatus* rs, nsTArray<JSIDVariant>* ids) = 0;
     virtual bool SendInstanceOf(const ObjectId& objId, const JSIID& iid,
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -41,16 +41,47 @@ extern JS_FRIEND_DATA(const js::Class* c
 } // namespace js
 
 namespace JS {
 
 template <typename T>
 class AutoVectorRooter;
 typedef AutoVectorRooter<jsid> AutoIdVector;
 
+// The answer to a successful query as to whether an object is an Array per
+// ES6's internal |IsArray| operation (as exposed by |Array.isArray|).
+enum class IsArrayAnswer
+{
+    Array,
+    NotArray,
+    RevokedProxy
+};
+
+// ES6 7.2.2.
+//
+// Returns false on failure, otherwise returns true and sets |*isArray|
+// indicating whether the object passes ECMAScript's IsArray test.  This is the
+// same test performed by |Array.isArray|.
+//
+// This is NOT the same as asking whether |obj| is an Array or a wrapper around
+// one.  If |obj| is a proxy created by |Proxy.revocable()| and has been
+// revoked, or if |obj| is a proxy whose target (at any number of hops) is a
+// revoked proxy, this method throws a TypeError and returns false.
+extern JS_PUBLIC_API(bool)
+IsArray(JSContext* cx, HandleObject obj, bool* isArray);
+
+// Identical to IsArray above, but the nature of the object (if successfully
+// determined) is communicated via |*answer|.  In particular this method
+// returns true and sets |*answer = IsArrayAnswer::RevokedProxy| when called on
+// a revoked proxy.
+//
+// Most users will want the overload above, not this one.
+extern JS_PUBLIC_API(bool)
+IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer);
+
 /*
  * Per ES6, the [[DefineOwnProperty]] internal method has three different
  * possible outcomes:
  *
  * -   It can throw an exception (which we indicate by returning false).
  *
  * -   It can return true, indicating unvarnished success.
  *
@@ -795,21 +826,17 @@ 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,
-
-    // Special snowflake for the ES6 IsArray method.
-    // Please don't use it without calling that function.
-    ESClass_IsArray
+    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).
  */
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -318,16 +318,17 @@ class JS_FRIEND_API(BaseProxyHandler)
                                        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 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;
     virtual void objectMoved(JSObject* proxy, const JSObject* old) const;
@@ -409,16 +410,18 @@ class JS_FRIEND_API(DirectProxyHandler) 
     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 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;
     virtual bool isCallable(JSObject* obj) const override;
     virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const override;
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -52,19 +52,51 @@ using mozilla::Abs;
 using mozilla::ArrayLength;
 using mozilla::CeilingLog2;
 using mozilla::CheckedInt;
 using mozilla::DebugOnly;
 using mozilla::IsNaN;
 using mozilla::UniquePtr;
 
 using JS::AutoCheckCannotGC;
+using JS::IsArrayAnswer;
 using JS::ToUint32;
 
 bool
+JS::IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer)
+{
+    if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) {
+        *answer = IsArrayAnswer::Array;
+        return true;
+    }
+
+    if (obj->is<ProxyObject>())
+        return Proxy::isArray(cx, obj, answer);
+
+    *answer = IsArrayAnswer::NotArray;
+    return true;
+}
+
+bool
+JS::IsArray(JSContext* cx, HandleObject obj, bool* isArray)
+{
+    IsArrayAnswer answer;
+    if (!IsArray(cx, obj, &answer))
+        return false;
+
+    if (answer == IsArrayAnswer::RevokedProxy) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
+        return false;
+    }
+
+    *isArray = answer == IsArrayAnswer::Array;
+    return true;
+}
+
+bool
 js::GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp)
 {
     if (obj->is<ArrayObject>()) {
         *lengthp = obj->as<ArrayObject>().length();
         return true;
     }
 
     if (obj->is<UnboxedArrayObject>()) {
@@ -1991,18 +2023,23 @@ js::array_push(JSContext* cx, unsigned a
                 return false;
 
             uint32_t newlength = length + args.length();
             args.rval().setNumber(newlength);
 
             // SetOrExtendAnyBoxedOrUnboxedDenseElements takes care of updating the
             // length for boxed and unboxed arrays. Handle updates to the length of
             // non-arrays here.
-            if (!IsArray(obj, cx))
+            bool isArray;
+            if (!IsArray(cx, obj, &isArray))
+                return false;
+
+            if (!isArray)
                 return SetLengthProperty(cx, obj, newlength);
+
             return true;
         }
     }
 
     /* Steps 4-5. */
     if (!InitArrayElements(cx, obj, length, args.length(), args.array()))
         return false;
 
@@ -2585,17 +2622,20 @@ js::array_concat(JSContext* cx, unsigned
 
     /* Create a new Array object and root it using *vp. */
     RootedObject aobj(cx, ToObject(cx, args.thisv()));
     if (!aobj)
         return false;
 
     RootedObject narr(cx);
     uint32_t length;
-    if (IsArray(aobj, cx) && !ObjectMayHaveExtraIndexedProperties(aobj)) {
+    bool isArray;
+    if (!IsArray(cx, aobj, &isArray))
+        return false;
+    if (isArray && !ObjectMayHaveExtraIndexedProperties(aobj)) {
         if (!GetLengthProperty(cx, aobj, &length))
             return false;
 
         size_t initlen = GetAnyBoxedOrUnboxedInitializedLength(aobj);
         narr = NewFullyAllocatedArrayTryReuseGroup(cx, aobj, initlen);
         if (!narr)
             return false;
         CopyAnyBoxedOrUnboxedDenseElements(cx, narr, aobj, 0, 0, initlen);
@@ -2610,17 +2650,20 @@ js::array_concat(JSContext* cx, unsigned
         if (length == initlen) {
             while (argc) {
                 HandleValue v = HandleValue::fromMarkedLocation(p);
                 if (!v.isObject())
                     break;
                 RootedObject obj(cx, &v.toObject());
 
                 // This should be IsConcatSpreadable
-                if (!IsArray(obj, cx) || ObjectMayHaveExtraIndexedProperties(obj))
+                bool isArray;
+                if (!IsArray(cx, obj, &isArray))
+                    return false;
+                if (!isArray || ObjectMayHaveExtraIndexedProperties(obj))
                     break;
 
                 uint32_t argLength;
                 if (!GetLengthProperty(cx, obj, &argLength))
                     return false;
 
                 initlen = GetAnyBoxedOrUnboxedInitializedLength(obj);
                 if (argLength != initlen)
@@ -2666,17 +2709,20 @@ js::array_concat(JSContext* cx, unsigned
     /* Loop over [0, argc] to concat args into narr, expanding all Arrays. */
     for (unsigned i = 0; i <= argc; i++) {
         if (!CheckForInterrupt(cx))
             return false;
         HandleValue v = HandleValue::fromMarkedLocation(&p[i]);
         if (v.isObject()) {
             RootedObject obj(cx, &v.toObject());
             // This should be IsConcatSpreadable
-            if (IsArray(obj, cx)) {
+            bool isArray;
+            if (!IsArray(cx, obj, &isArray))
+                return false;
+            if (isArray) {
                 uint32_t alength;
                 if (!GetLengthProperty(cx, obj, &alength))
                     return false;
                 RootedValue tmp(cx);
                 for (uint32_t slot = 0; slot < alength; slot++) {
                     bool hole;
                     if (!CheckForInterrupt(cx) || !GetElement(cx, obj, slot, &hole, &tmp))
                         return false;
@@ -2989,17 +3035,18 @@ js::array_slice_dense(JSContext* cx, Han
 
 static bool
 array_isArray(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     bool isArray = false;
     if (args.get(0).isObject()) {
         RootedObject obj(cx, &args[0].toObject());
-        isArray = IsArray(obj, cx);
+        if (!IsArray(cx, obj, &isArray))
+            return false;
     }
     args.rval().setBoolean(isArray);
     return true;
 }
 
 static bool
 IsArrayConstructor(const Value& v)
 {
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -814,18 +814,16 @@ inline bool
 ObjectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::objectClassIs(obj, classValue, cx);
 
     switch (classValue) {
       case ESClass_Object: return obj->is<PlainObject>() || obj->is<UnboxedPlainObject>();
       case ESClass_Array:
-      case ESClass_IsArray:
-        // The difference between Array and IsArray is only relevant for proxies.
         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>();
@@ -839,26 +837,16 @@ 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);
 }
 
-// ES6 7.2.2
-inline bool
-IsArray(HandleObject obj, JSContext* cx)
-{
-    if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>())
-        return true;
-
-    return ObjectClassIs(obj, ESClass_IsArray, cx);
-}
-
 inline bool
 Unbox(JSContext* cx, HandleObject obj, MutableHandleValue vp)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::boxedValue_unbox(cx, obj, vp);
 
     if (obj->is<BooleanObject>())
         vp.setBoolean(obj->as<BooleanObject>().unbox());
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -3,16 +3,17 @@
  * 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 "json.h"
 
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Range.h"
+#include "mozilla/ScopeExit.h"
 
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsstr.h"
 #include "jstypes.h"
@@ -526,40 +527,42 @@ Str(JSContext* cx, const Value& v, Strin
         return NumberValueToStringBuffer(cx, v, scx->sb);
     }
 
     /* Step 10. */
     MOZ_ASSERT(v.isObject());
     RootedObject obj(cx, &v.toObject());
 
     scx->depth++;
-    bool ok;
-    if (IsArray(obj, cx))
-        ok = JA(cx, obj, scx);
-    else
-        ok = JO(cx, obj, scx);
-    scx->depth--;
+    auto dec = mozilla::MakeScopeExit([&] { scx->depth--; });
 
-    return ok;
+    bool isArray;
+    if (!IsArray(cx, obj, &isArray))
+        return false;
+
+    return isArray ? JA(cx, obj, scx) : JO(cx, obj, scx);
 }
 
 /* ES5 15.12.3. */
 bool
 js::Stringify(JSContext* cx, MutableHandleValue vp, JSObject* replacer_, Value space_,
               StringBuffer& sb)
 {
     RootedObject replacer(cx, replacer_);
     RootedValue space(cx, space_);
 
     /* Step 4. */
     AutoIdVector propertyList(cx);
     if (replacer) {
+        bool isArray;
         if (replacer->isCallable()) {
             /* Step 4a(i): use replacer to transform values.  */
-        } else if (IsArray(replacer, cx)) {
+        } else if (!IsArray(cx, replacer, &isArray)) {
+            return false;
+        } else if (isArray) {
             /*
              * Step 4b: The spec algorithm is unhelpfully vague about the exact
              * steps taken when the replacer is an array, regarding the exact
              * sequence of [[Get]] calls for the array's elements, when its
              * overall length is calculated, whether own or own plus inherited
              * properties are considered, and so on.  A rewrite was proposed in
              * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
              * whose steps are copied below, and which are implemented here.
@@ -716,17 +719,21 @@ Walk(JSContext* cx, HandleObject holder,
     RootedValue val(cx);
     if (!GetProperty(cx, holder, holder, name, &val))
         return false;
 
     /* Step 2. */
     if (val.isObject()) {
         RootedObject obj(cx, &val.toObject());
 
-        if (IsArray(obj, cx)) {
+        bool isArray;
+        if (!IsArray(cx, obj, &isArray))
+            return false;
+
+        if (isArray) {
             /* Step 2a(ii). */
             uint32_t length;
             if (!GetLengthProperty(cx, obj, &length))
                 return false;
 
             /* Step 2a(i), 2a(iii-iv). */
             RootedId id(cx);
             RootedValue newElement(cx);
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -201,16 +201,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 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,16 +244,17 @@ class JS_FRIEND_API(SecurityWrapper) : p
     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 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
@@ -7,16 +7,18 @@
 #include "js/Proxy.h"
 #include "vm/ProxyObject.h"
 
 #include "jscntxtinlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 
+using JS::IsArrayAnswer;
+
 bool
 BaseProxyHandler::enter(JSContext* cx, HandleObject wrapper, HandleId id, Action act,
                         bool* bp) const
 {
     *bp = true;
     return true;
 }
 
@@ -317,16 +319,23 @@ BaseProxyHandler::hasInstance(JSContext*
 }
 
 bool
 BaseProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext* cx) const
 {
     return false;
 }
 
+bool
+BaseProxyHandler::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer) const
+{
+    *answer = IsArrayAnswer::NotArray;
+    return true;
+}
+
 void
 BaseProxyHandler::trace(JSTracer* trc, JSObject* proxy) const
 {
 }
 
 void
 BaseProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const
 {
--- a/js/src/proxy/DeadObjectProxy.cpp
+++ b/js/src/proxy/DeadObjectProxy.cpp
@@ -115,16 +115,23 @@ DeadObjectProxy::hasInstance(JSContext* 
 
 bool
 DeadObjectProxy::objectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx) const
 {
     ReportDead(cx);
     return false;
 }
 
+bool
+DeadObjectProxy::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswer* answer) const
+{
+    ReportDead(cx);
+    return false;
+}
+
 const char*
 DeadObjectProxy::className(JSContext* cx, HandleObject wrapper) const
 {
     return "DeadObject";
 }
 
 JSString*
 DeadObjectProxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const
--- a/js/src/proxy/DeadObjectProxy.h
+++ b/js/src/proxy/DeadObjectProxy.h
@@ -40,16 +40,17 @@ class DeadObjectProxy : public BaseProxy
     /* 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 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;
     static const DeadObjectProxy singleton;
--- a/js/src/proxy/DirectProxyHandler.cpp
+++ b/js/src/proxy/DirectProxyHandler.cpp
@@ -156,16 +156,23 @@ DirectProxyHandler::isExtensible(JSConte
 bool
 DirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
                                   JSContext* cx) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return ObjectClassIs(target, classValue, cx);
 }
 
+bool
+DirectProxyHandler::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const
+{
+    RootedObject target(cx, proxy->as<ProxyObject>().target());
+    return IsArray(cx, target, answer);
+}
+
 const char*
 DirectProxyHandler::className(JSContext* cx, HandleObject proxy) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetObjectClassName(cx, target);
 }
 
--- a/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
+++ b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
@@ -152,16 +152,24 @@ OpaqueCrossCompartmentWrapper::getOwnEnu
 
 bool
 OpaqueCrossCompartmentWrapper::objectClassIs(HandleObject obj, ESClassValue classValue,
                                              JSContext* cx) const
 {
   return false;
 }
 
+bool
+OpaqueCrossCompartmentWrapper::isArray(JSContext* cx, HandleObject obj,
+                                       JS::IsArrayAnswer* answer) const
+{
+    *answer = JS::IsArrayAnswer::NotArray;
+    return true;
+}
+
 const char*
 OpaqueCrossCompartmentWrapper::className(JSContext* cx,
                                          HandleObject proxy) const
 {
     return "Opaque";
 }
 
 JSString*
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -456,16 +456,22 @@ Proxy::hasInstance(JSContext* cx, Handle
 
 bool
 Proxy::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext* cx)
 {
     JS_CHECK_RECURSION(cx, return false);
     return proxy->as<ProxyObject>().handler()->objectClassIs(proxy, classValue, cx);
 }
 
+bool
+Proxy::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer)
+{
+    return proxy->as<ProxyObject>().handler()->isArray(cx, proxy, answer);
+}
+
 const char*
 Proxy::className(JSContext* cx, HandleObject proxy)
 {
     // Check for unbounded recursion, but don't signal an error; className
     // needs to be infallible.
     int stackDummy;
     if (!JS_CHECK_STACK_SIZE(GetNativeStackLimit(cx), &stackDummy))
         return "too much recursion";
--- a/js/src/proxy/Proxy.h
+++ b/js/src/proxy/Proxy.h
@@ -52,16 +52,17 @@ class Proxy
                                       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 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);
     static bool unwatch(JSContext* cx, HandleObject proxy, HandleId id);
--- a/js/src/proxy/ScriptedDirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp
@@ -6,16 +6,18 @@
 
 #include "proxy/ScriptedDirectProxyHandler.h"
 
 #include "jsapi.h"
 
 #include "jsobjinlines.h"
 
 using namespace js;
+
+using JS::IsArrayAnswer;
 using mozilla::ArrayLength;
 
 static inline bool
 IsDataDescriptor(const PropertyDescriptor& desc)
 {
     return desc.obj && !(desc.attrs & (JSPROP_GETTER | JSPROP_SETTER));
 }
 
@@ -1098,30 +1100,29 @@ ScriptedDirectProxyHandler::hasInstance(
 
     return HasInstance(cx, target, v, bp);
 }
 
 bool
 ScriptedDirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
                                           JSContext* cx) const
 {
-    // Special case IsArray. In every other instance ES wants to have exactly
-    // one object type and not a proxy around it, so return false.
-    if (classValue != ESClass_IsArray)
-        return false;
+    return false;
+}
 
-    // In ES6 IsArray is supposed to poke at the Proxy target, instead we do this here.
-    // The reason for this is that we have proxies for which looking at the target might
-    // be impossible. So instead we use our little objectClassIs function that just works
-    // already across different wrappers.
+bool
+ScriptedDirectProxyHandler::isArray(JSContext* cx, HandleObject proxy,
+                                    IsArrayAnswer* answer) const
+{
     RootedObject target(cx, proxy->as<ProxyObject>().target());
-    if (!target)
-        return false;
+    if (target)
+        return JS::IsArray(cx, target, answer);
 
-    return IsArray(target, cx);
+    *answer = IsArrayAnswer::RevokedProxy;
+    return true;
 }
 
 const char*
 ScriptedDirectProxyHandler::className(JSContext* cx, HandleObject proxy) const
 {
     // Right now the caller is not prepared to handle failures.
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     if (!target)
--- a/js/src/proxy/ScriptedDirectProxyHandler.h
+++ b/js/src/proxy/ScriptedDirectProxyHandler.h
@@ -67,16 +67,18 @@ class ScriptedDirectProxyHandler : publi
 
     // 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 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/proxy/SecurityWrapper.cpp
+++ b/js/src/proxy/SecurityWrapper.cpp
@@ -89,16 +89,25 @@ template <class Base>
 bool
 SecurityWrapper<Base>::objectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx) const
 {
     return false;
 }
 
 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;
+    return true;
+}
+
+template <class Base>
+bool
 SecurityWrapper<Base>::regexp_toShared(JSContext* cx, HandleObject obj, RegExpGuard* g) const
 {
     return Base::regexp_toShared(cx, obj, g);
 }
 
 template <class Base>
 bool
 SecurityWrapper<Base>::boxedValue_unbox(JSContext* cx, HandleObject obj, MutableHandleValue vp) const
--- a/js/src/tests/ecma_6/Array/isArray.js
+++ b/js/src/tests/ecma_6/Array/isArray.js
@@ -1,30 +1,67 @@
-assertEq(Array.isArray([]), true);
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+var global = this;
+var otherGlobal = newGlobal();
+
+var thisGlobal = () => global;
+var alternateGlobals = (function(i) {
+    return () => (i++ % 2) === 0 ? global : otherGlobal;
+})(0);
+
+function performTests(pickGlobal)
+{
+    // Base case.
+    assertEq(Array.isArray([]), true);
+
+    // Simple case: proxy to an array.
+    var proxy = new (pickGlobal()).Proxy([], {});
+    assertEq(Array.isArray(proxy), true);
 
-var proxy = new Proxy([], {});
-assertEq(Array.isArray(proxy), true);
+    // Recursive proxy ultimately terminating in an array.
+    for (var i = 0; i < 10; i++) {
+        proxy = new (pickGlobal()).Proxy(proxy, {});
+        assertEq(Array.isArray(proxy), true);
+    }
+
+    // Revocable proxy to an array.
+    var revocable = (pickGlobal()).Proxy.revocable([], {});
+    proxy = revocable.proxy;
+    assertEq(Array.isArray(proxy), true);
 
-for (var i = 0; i < 10; i++) {
-    proxy = new Proxy(proxy, {});
-    assertEq(Array.isArray(proxy), true);
+    // Recursive proxy ultimately terminating in a revocable proxy to an array.
+    for (var i = 0; i < 10; i++) {
+        proxy = new (pickGlobal()).Proxy(proxy, {});
+        assertEq(Array.isArray(proxy), true);
+    }
+
+    // Revoked proxy to (formerly) an array.
+    revocable.revoke();
+    assertThrowsInstanceOf(() => Array.isArray(revocable.proxy), TypeError);
+
+    // Recursive proxy ultimately terminating in a revoked proxy to an array.
+    assertThrowsInstanceOf(() => Array.isArray(proxy), TypeError);
+
 }
 
-var revocable = Proxy.revocable([], {});
-proxy = revocable.proxy;
-assertEq(Array.isArray(proxy), true);
+performTests(thisGlobal);
+performTests(alternateGlobals);
+
+function crossGlobalTest()
+{
+    var array = new otherGlobal.Array();
 
-for (var i = 0; i < 10; i++) {
-    proxy = new Proxy(proxy, {});
-    assertEq(Array.isArray(proxy), true);
+    // Array from another global.
+    assertEq(Array.isArray(array), true);
+
+    // Proxy to an array from another global.
+    assertEq(Array.isArray(new Proxy(array, {})), true);
+
+    // Other-global proxy to an array from that selfsame global.
+    assertEq(Array.isArray(new otherGlobal.Proxy(array, {})), true);
 }
 
-revocable.revoke();
-assertEq(Array.isArray(revocable.proxy), false);
-assertEq(Array.isArray(proxy), false);
+crossGlobalTest();
 
-var global = newGlobal();
-var array = global.Array();
-assertEq(Array.isArray(array), true);
-assertEq(Array.isArray(new Proxy(array, {})), true);
-assertEq(Array.isArray(new global.Proxy(array, {})), true);
-
-reportCompare(true, true);
+if (typeof reportCompare === "function")
+    reportCompare(true, true);