Backed out changeset 072f8d4a9964 (bug 1355109) for causing crashes with various extensions. a=lizzard
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 26 Sep 2017 14:12:25 -0400
changeset 383096 39aaf54972cb11a63815a96b532786133baa95bc
parent 383091 b30ae5a455367f336ded1052081e4c54a139fa81
child 383117 7d15bc419c6cd7e9f3b4d41370c3b0e5990c8d1b
push id95491
push userkwierso@gmail.com
push dateWed, 27 Sep 2017 00:14:56 +0000
treeherdermozilla-inbound@792d4db07bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslizzard
bugs1355109
milestone58.0a1
backs out072f8d4a9964129a06d774a5698f7f9f8128c66c
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
Backed out changeset 072f8d4a9964 (bug 1355109) for causing crashes with various extensions. a=lizzard
js/src/jit/BaselineCacheIRCompiler.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/CacheIRCompiler.cpp
js/src/jit/CacheIRCompiler.h
js/src/jit/IonCacheIRCompiler.cpp
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/tests/chrome/chrome.ini
js/xpconnect/tests/chrome/test_xrayic.xul
js/xpconnect/tests/mochitest/file_xrayic.html
js/xpconnect/tests/mochitest/mochitest.ini
js/xpconnect/wrappers/XrayWrapper.cpp
js/xpconnect/wrappers/XrayWrapper.h
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -288,52 +288,16 @@ BaselineCacheIRCompiler::emitGuardCompar
     Address addr(stubAddress(reader.stubOffset()));
     masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
     masm.loadPtr(Address(scratch, ObjectGroup::offsetOfCompartment()), scratch);
     masm.branchPtr(Assembler::NotEqual, addr, scratch, failure->label());
     return true;
 }
 
 bool
-BaselineCacheIRCompiler::emitGuardAnyClass()
-{
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
-    AutoScratchRegister scratch(allocator, masm);
-
-    FailurePath* failure;
-    if (!addFailurePath(&failure))
-        return false;
-
-    Address testAddr(stubAddress(reader.stubOffset()));
-
-    masm.loadObjGroup(obj, scratch);
-    masm.loadPtr(Address(scratch, ObjectGroup::offsetOfClasp()), scratch);
-    masm.branchPtr(Assembler::NotEqual, testAddr, scratch, failure->label());
-    return true;
-}
-
-bool
-BaselineCacheIRCompiler::emitGuardHasProxyHandler()
-{
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
-    AutoScratchRegister scratch(allocator, masm);
-
-    FailurePath* failure;
-    if (!addFailurePath(&failure))
-        return false;
-
-    Address testAddr(stubAddress(reader.stubOffset()));
-    masm.loadPtr(testAddr, scratch);
-
-    Address handlerAddr(obj, ProxyObject::offsetOfHandler());
-    masm.branchPtr(Assembler::NotEqual, handlerAddr, scratch, failure->label());
-    return true;
-}
-
-bool
 BaselineCacheIRCompiler::emitGuardSpecificObject()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
@@ -399,64 +363,16 @@ BaselineCacheIRCompiler::emitGuardSpecif
         return false;
 
     Address addr(stubAddress(reader.stubOffset()));
     masm.branchPtr(Assembler::NotEqual, addr, sym, failure->label());
     return true;
 }
 
 bool
-BaselineCacheIRCompiler::emitGuardXrayExpandoShapeAndDefaultProto()
-{
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
-    bool hasExpando = reader.readBool();
-    Address shapeWrapperAddress(stubAddress(reader.stubOffset()));
-
-    AutoScratchRegister scratch(allocator, masm);
-    Maybe<AutoScratchRegister> scratch2;
-    if (hasExpando)
-        scratch2.emplace(allocator, masm);
-
-    FailurePath* failure;
-    if (!addFailurePath(&failure))
-        return false;
-
-    masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch);
-    Address holderAddress(scratch, sizeof(Value) * GetXrayJitInfo()->xrayHolderSlot);
-    Address expandoAddress(scratch, NativeObject::getFixedSlotOffset(GetXrayJitInfo()->holderExpandoSlot));
-
-    if (hasExpando) {
-        masm.branchTestObject(Assembler::NotEqual, holderAddress, failure->label());
-        masm.unboxObject(holderAddress, scratch);
-        masm.branchTestObject(Assembler::NotEqual, expandoAddress, failure->label());
-        masm.unboxObject(expandoAddress, scratch);
-
-        // Unwrap the expando before checking its shape.
-        masm.loadPtr(Address(scratch, ProxyObject::offsetOfReservedSlots()), scratch);
-        masm.unboxObject(Address(scratch, detail::ProxyReservedSlots::offsetOfPrivateSlot()), scratch);
-
-        masm.loadPtr(shapeWrapperAddress, scratch2.ref());
-        LoadShapeWrapperContents(masm, scratch2.ref(), scratch2.ref(), failure->label());
-        masm.branchTestObjShape(Assembler::NotEqual, scratch, scratch2.ref(), failure->label());
-
-        // The reserved slots on the expando should all be in fixed slots.
-        Address protoAddress(scratch, NativeObject::getFixedSlotOffset(GetXrayJitInfo()->expandoProtoSlot));
-        masm.branchTestUndefined(Assembler::NotEqual, protoAddress, failure->label());
-    } else {
-        Label done;
-        masm.branchTestObject(Assembler::NotEqual, holderAddress, &done);
-        masm.unboxObject(holderAddress, scratch);
-        masm.branchTestObject(Assembler::Equal, expandoAddress, failure->label());
-        masm.bind(&done);
-    }
-
-    return true;
-}
-
-bool
 BaselineCacheIRCompiler::emitLoadFixedSlotResult()
 {
     AutoOutputRegister output(*this);
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
 
     masm.load32(stubAddress(reader.stubOffset()), scratch);
     masm.loadValue(BaseIndex(obj, scratch, TimesOne), output.valueReg());
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -11,17 +11,16 @@
 
 #include "jit/BaselineCacheIRCompiler.h"
 #include "jit/BaselineIC.h"
 #include "jit/CacheIRSpewer.h"
 
 #include "vm/SelfHosting.h"
 #include "jsobjinlines.h"
 
-#include "jit/MacroAssembler-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::DebugOnly;
 using mozilla::Maybe;
@@ -200,18 +199,16 @@ GetPropIRGenerator::tryAttachStub()
             if (tryAttachTypedObject(obj, objId, id))
                 return true;
             if (tryAttachModuleNamespace(obj, objId, id))
                 return true;
             if (tryAttachWindowProxy(obj, objId, id))
                 return true;
             if (tryAttachCrossCompartmentWrapper(obj, objId, id))
                 return true;
-            if (tryAttachXrayCrossCompartmentWrapper(obj, objId, id))
-                return true;
             if (tryAttachFunction(obj, objId, id))
                 return true;
             if (tryAttachProxy(obj, objId, id))
                 return true;
 
             trackNotAttached();
             return false;
         }
@@ -867,17 +864,17 @@ GetPropIRGenerator::tryAttachCrossCompar
                 preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
             else
                 preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
         }
     }
 
     maybeEmitIdGuard(id);
     writer.guardIsProxy(objId);
-    writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
+    writer.guardIsCrossCompartmentWrapper(objId);
 
     // Load the object wrapped by the CCW
     ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
 
     // If the compartment of the wrapped object is different we should fail.
     writer.guardCompartment(wrapperTargetId, wrappedGlobal, unwrapped->compartment());
 
     ObjOperandId unwrappedId = wrapperTargetId;
@@ -891,124 +888,16 @@ GetPropIRGenerator::tryAttachCrossCompar
 
     EmitReadSlotResult(writer, unwrapped, holder, shape, unwrappedId);
     EmitReadSlotReturn(writer, unwrapped, holder, shape, /* wrapResult = */ true);
 
     trackAttached("CCWSlot");
     return true;
 }
 
-static bool
-GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray, MutableHandleObject wrapper)
-{
-    Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot);
-    if (v.isObject()) {
-        NativeObject* holder = &v.toObject().as<NativeObject>();
-        v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot);
-        if (v.isObject()) {
-            RootedNativeObject expando(cx, &UncheckedUnwrap(&v.toObject())->as<NativeObject>());
-            wrapper.set(NewWrapperWithObjectShape(cx, expando));
-            return wrapper != nullptr;
-        }
-    }
-    wrapper.set(nullptr);
-    return true;
-}
-
-bool
-GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId,
-                                                         HandleId id)
-{
-    if (!IsProxy(obj))
-        return false;
-
-    XrayJitInfo* info = GetXrayJitInfo();
-    if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj)))
-        return false;
-
-    if (!info->globalHasExclusiveExpandos(cx_->global()))
-        return false;
-
-    RootedObject target(cx_, UncheckedUnwrap(obj));
-
-    RootedObject expandoShapeWrapper(cx_);
-    if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) {
-        cx_->recoverFromOutOfMemory();
-        return false;
-    }
-
-    // Look for a getter we can call on the xray or its prototype chain.
-    Rooted<PropertyDescriptor> desc(cx_);
-    RootedObject holder(cx_, obj);
-    AutoObjectVector prototypes(cx_);
-    AutoObjectVector prototypeExpandoShapeWrappers(cx_);
-    while (true) {
-        if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) {
-            cx_->clearPendingException();
-            return false;
-        }
-        if (desc.object())
-            break;
-        if (!GetPrototype(cx_, holder, &holder)) {
-            cx_->clearPendingException();
-            return false;
-        }
-        if (!holder || !IsProxy(holder) || !info->isCrossCompartmentXray(GetProxyHandler(holder)))
-            return false;
-        RootedObject prototypeExpandoShapeWrapper(cx_);
-        if (!GetXrayExpandoShapeWrapper(cx_, holder, &prototypeExpandoShapeWrapper) ||
-            !prototypes.append(holder) ||
-            !prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper))
-        {
-            cx_->recoverFromOutOfMemory();
-            return false;
-        }
-    }
-    if (!desc.isAccessorDescriptor())
-        return false;
-
-    RootedObject getter(cx_, desc.getterObject());
-    if (!getter || !getter->is<JSFunction>() || !getter->as<JSFunction>().isNative())
-        return false;
-
-    maybeEmitIdGuard(id);
-    writer.guardIsProxy(objId);
-    writer.guardHasProxyHandler(objId, GetProxyHandler(obj));
-
-    // Load the object wrapped by the CCW
-    ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
-
-    // Test the wrapped object's class. The properties held by xrays or their
-    // prototypes will be invariant for objects of a given class, except for
-    // changes due to xray expandos or xray prototype mutations.
-    writer.guardAnyClass(wrapperTargetId, target->getClass());
-
-    // Make sure the expandos on the xray and its prototype chain match up with
-    // what we expect. The expando shape needs to be consistent, to ensure it
-    // has not had any shadowing properties added, and the expando cannot have
-    // any custom prototype (xray prototypes are stable otherwise).
-    //
-    // We can only do this for xrays with exclusive access to their expandos
-    // (as we checked earlier), which store a pointer to their expando
-    // directly. Xrays in other compartments may share their expandos with each
-    // other and a VM call is needed just to find the expando.
-    writer.guardXrayExpandoShapeAndDefaultProto(objId, expandoShapeWrapper);
-    for (size_t i = 0; i < prototypes.length(); i++) {
-        JSObject* proto = prototypes[i];
-        ObjOperandId protoId = writer.loadObject(proto);
-        writer.guardXrayExpandoShapeAndDefaultProto(protoId, prototypeExpandoShapeWrappers[i]);
-    }
-
-    writer.callNativeGetterResult(objId, &getter->as<JSFunction>());
-    writer.typeMonitorResult();
-
-    trackAttached("XrayGetter");
-    return true;
-}
-
 bool
 GetPropIRGenerator::tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id,
                                           bool handleDOMProxies)
 {
     MOZ_ASSERT(obj->is<ProxyObject>());
 
     writer.guardIsProxy(objId);
 
@@ -4371,45 +4260,8 @@ CompareIRGenerator::trackNotAttached()
         LockGuard<Mutex> guard(sp.lock());
         sp.beginCache(guard, *this);
         sp.valueProperty(guard, "lhs", lhsVal_);
         sp.valueProperty(guard, "rhs", rhsVal_);
         sp.endCache(guard);
     }
 #endif
 }
-
-// Class which holds a shape pointer for use when caches might reference data in other zones.
-static const Class shapeContainerClass = {
-    "ShapeContainer",
-    JSCLASS_HAS_RESERVED_SLOTS(1)
-};
-
-static const size_t SHAPE_CONTAINER_SLOT = 0;
-
-JSObject*
-jit::NewWrapperWithObjectShape(JSContext* cx, HandleNativeObject obj)
-{
-    MOZ_ASSERT(cx->compartment() != obj->compartment());
-
-    RootedObject wrapper(cx);
-    {
-        AutoCompartment ac(cx, obj);
-        wrapper = NewObjectWithClassProto(cx, &shapeContainerClass, nullptr);
-        if (!obj)
-            return nullptr;
-        wrapper->as<NativeObject>().setSlot(SHAPE_CONTAINER_SLOT, PrivateGCThingValue(obj->lastProperty()));
-    }
-    if (!JS_WrapObject(cx, &wrapper))
-        return nullptr;
-    MOZ_ASSERT(IsWrapper(wrapper));
-    return wrapper;
-}
-
-void
-jit::LoadShapeWrapperContents(MacroAssembler& masm, Register obj, Register dst, Label* failure)
-{
-    masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), dst);
-    Address privateAddr(dst, detail::ProxyReservedSlots::offsetOfPrivateSlot());
-    masm.branchTestObject(Assembler::NotEqual, privateAddr, failure);
-    masm.unboxObject(privateAddr, dst);
-    masm.unboxNonDouble(Address(dst, NativeObject::getFixedSlotOffset(SHAPE_CONTAINER_SLOT)), dst);
-}
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -166,39 +166,36 @@ extern const char* CacheKindNames[];
     _(GuardIsObjectOrNull)                \
     _(GuardIsString)                      \
     _(GuardIsSymbol)                      \
     _(GuardIsInt32Index)                  \
     _(GuardType)                          \
     _(GuardShape)                         \
     _(GuardGroup)                         \
     _(GuardProto)                         \
-    _(GuardClass)                         /* Guard an object class, per GuardClassKind */ \
-    _(GuardAnyClass)                      /* Guard an arbitrary class for an object */ \
+    _(GuardClass)                         \
     _(GuardCompartment)                   \
     _(GuardIsNativeFunction)              \
     _(GuardIsProxy)                       \
-    _(GuardHasProxyHandler)               \
+    _(GuardIsCrossCompartmentWrapper)     \
     _(GuardNotDOMProxy)                   \
     _(GuardSpecificObject)                \
     _(GuardSpecificAtom)                  \
     _(GuardSpecificSymbol)                \
     _(GuardSpecificInt32Immediate)        \
     _(GuardNoDetachedTypedObjects)        \
     _(GuardMagicValue)                    \
     _(GuardFrameHasNoArgumentsObject)     \
     _(GuardNoDenseElements)               \
     _(GuardNoUnboxedExpando)              \
     _(GuardAndLoadUnboxedExpando)         \
     _(GuardAndGetIndexFromString)         \
     _(GuardAndGetIterator)                \
     _(GuardHasGetterSetter)               \
     _(GuardGroupHasUnanalyzedNewScript)   \
-    _(GuardIndexIsNonNegative)            \
-    _(GuardXrayExpandoShapeAndDefaultProto) \
     _(LoadStackValue)                     \
     _(LoadObject)                         \
     _(LoadProto)                          \
     _(LoadEnclosingEnvironment)           \
     _(LoadWrapperTarget)                  \
                                           \
     _(MegamorphicLoadSlotResult)          \
     _(MegamorphicLoadSlotByValueResult)   \
@@ -353,24 +350,16 @@ enum class GuardClassKind : uint8_t
     Array,
     UnboxedArray,
     MappedArguments,
     UnmappedArguments,
     WindowProxy,
     JSFunction,
 };
 
-// Some ops refer to shapes that might be in other zones. Instead of putting
-// cross-zone pointers in the caches themselves (which would complicate tracing
-// enormously), these ops instead contain wrappers for objects in the target
-// zone, which refer to the actual shape via a reserved slot.
-JSObject* NewWrapperWithObjectShape(JSContext* cx, HandleNativeObject obj);
-
-void LoadShapeWrapperContents(MacroAssembler& masm, Register obj, Register dst, Label* failure);
-
 // Class to record CacheIR + some additional metadata for code generation.
 class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
 {
     CompactBufferWriter buffer_;
 
     uint32_t nextOperandId_;
     uint32_t nextInstructionId_;
     uint32_t numInputOperands_;
@@ -529,48 +518,39 @@ class MOZ_RAII CacheIRWriter : public JS
     }
     void guardIsObjectOrNull(ValOperandId val) {
         writeOpWithOperandId(CacheOp::GuardIsObjectOrNull, val);
     }
     void guardShape(ObjOperandId obj, Shape* shape) {
         writeOpWithOperandId(CacheOp::GuardShape, obj);
         addStubField(uintptr_t(shape), StubField::Type::Shape);
     }
-    void guardXrayExpandoShapeAndDefaultProto(ObjOperandId obj, JSObject* shapeWrapper) {
-        writeOpWithOperandId(CacheOp::GuardXrayExpandoShapeAndDefaultProto, obj);
-        buffer_.writeByte(uint32_t(!!shapeWrapper));        addStubField(uintptr_t(shapeWrapper), StubField::Type::JSObject);
-    }
     void guardGroup(ObjOperandId obj, ObjectGroup* group) {
         writeOpWithOperandId(CacheOp::GuardGroup, obj);
         addStubField(uintptr_t(group), StubField::Type::ObjectGroup);
     }
     void guardProto(ObjOperandId obj, JSObject* proto) {
         writeOpWithOperandId(CacheOp::GuardProto, obj);
         addStubField(uintptr_t(proto), StubField::Type::JSObject);
     }
     void guardClass(ObjOperandId obj, GuardClassKind kind) {
         static_assert(sizeof(GuardClassKind) == sizeof(uint8_t),
                       "GuardClassKind must fit in a byte");
         writeOpWithOperandId(CacheOp::GuardClass, obj);
         buffer_.writeByte(uint32_t(kind));
     }
-    void guardAnyClass(ObjOperandId obj, const Class* clasp) {
-        writeOpWithOperandId(CacheOp::GuardAnyClass, obj);
-        addStubField(uintptr_t(clasp), StubField::Type::RawWord);
-    }
     void guardIsNativeFunction(ObjOperandId obj, JSNative nativeFunc) {
         writeOpWithOperandId(CacheOp::GuardIsNativeFunction, obj);
         writePointer(JS_FUNC_TO_DATA_PTR(void*, nativeFunc));
     }
     void guardIsProxy(ObjOperandId obj) {
         writeOpWithOperandId(CacheOp::GuardIsProxy, obj);
     }
-    void guardHasProxyHandler(ObjOperandId obj, const void* handler) {
-        writeOpWithOperandId(CacheOp::GuardHasProxyHandler, obj);
-        addStubField(uintptr_t(handler), StubField::Type::RawWord);
+    void guardIsCrossCompartmentWrapper(ObjOperandId obj) {
+        writeOpWithOperandId(CacheOp::GuardIsCrossCompartmentWrapper, obj);
     }
     void guardNotDOMProxy(ObjOperandId obj) {
         writeOpWithOperandId(CacheOp::GuardNotDOMProxy, obj);
     }
     void guardSpecificObject(ObjOperandId obj, JSObject* expected) {
         writeOpWithOperandId(CacheOp::GuardSpecificObject, obj);
         addStubField(uintptr_t(expected), StubField::Type::JSObject);
     }
@@ -629,20 +609,16 @@ class MOZ_RAII CacheIRWriter : public JS
         writeOpWithOperandId(CacheOp::GuardHasGetterSetter, obj);
         addStubField(uintptr_t(shape), StubField::Type::Shape);
     }
     void guardGroupHasUnanalyzedNewScript(ObjectGroup* group) {
         writeOp(CacheOp::GuardGroupHasUnanalyzedNewScript);
         addStubField(uintptr_t(group), StubField::Type::ObjectGroup);
     }
 
-    void guardIndexIsNonNegative(Int32OperandId index) {
-        writeOpWithOperandId(CacheOp::GuardIndexIsNonNegative, index);
-    }
-
     void loadFrameCalleeResult() {
         writeOp(CacheOp::LoadFrameCalleeResult);
     }
     void loadFrameNumActualArgsResult() {
         writeOp(CacheOp::LoadFrameNumActualArgsResult);
     }
     void loadFrameArgumentResult(Int32OperandId index) {
         writeOpWithOperandId(CacheOp::LoadFrameArgumentResult, index);
@@ -1212,17 +1188,16 @@ class MOZ_RAII GetPropIRGenerator : publ
     bool tryAttachNative(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachUnboxed(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachUnboxedExpando(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachTypedObject(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachObjectLength(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachModuleNamespace(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId, HandleId id);
-    bool tryAttachXrayCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachFunction(HandleObject obj, ObjOperandId objId, HandleId id);
 
     bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id,
                                bool handleDOMProxies);
     bool tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId, HandleId id);
     bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id);
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1431,16 +1431,32 @@ CacheIRCompiler::emitGuardIsProxy()
     if (!addFailurePath(&failure))
         return false;
 
     masm.branchTestObjectIsProxy(false, obj, scratch, failure->label());
     return true;
 }
 
 bool
+CacheIRCompiler::emitGuardIsCrossCompartmentWrapper()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    AutoScratchRegister scratch(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    Address handlerAddr(obj, ProxyObject::offsetOfHandler());
+    masm.branchPtr(Assembler::NotEqual, handlerAddr, ImmPtr(&CrossCompartmentWrapper::singleton),
+                   failure->label());
+    return true;
+}
+
+bool
 CacheIRCompiler::emitGuardNotDOMProxy()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
@@ -1908,29 +1924,16 @@ CacheIRCompiler::emitLoadDenseElementRes
     // Hole check.
     BaseObjectElementIndex element(scratch, index);
     masm.branchTestMagic(Assembler::Equal, element, failure->label());
     masm.loadTypedOrValue(element, output);
     return true;
 }
 
 bool
-CacheIRCompiler::emitGuardIndexIsNonNegative()
-{
-    Register index = allocator.useRegister(masm, reader.int32OperandId());
-
-    FailurePath* failure;
-    if (!addFailurePath(&failure))
-        return false;
-
-    masm.branch32(Assembler::LessThan, index, Imm32(0), failure->label());
-    return true;
-}
-
-bool
 CacheIRCompiler::emitLoadDenseElementHoleResult()
 {
     AutoOutputRegister output(*this);
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     Register index = allocator.useRegister(masm, reader.int32OperandId());
     AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
 
     if (!output.hasValue()) {
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -19,25 +19,25 @@ namespace jit {
     _(GuardIsObjectOrNull)                \
     _(GuardIsString)                      \
     _(GuardIsSymbol)                      \
     _(GuardIsInt32Index)                  \
     _(GuardType)                          \
     _(GuardClass)                         \
     _(GuardIsNativeFunction)              \
     _(GuardIsProxy)                       \
+    _(GuardIsCrossCompartmentWrapper)     \
     _(GuardNotDOMProxy)                   \
     _(GuardSpecificInt32Immediate)        \
     _(GuardMagicValue)                    \
     _(GuardNoUnboxedExpando)              \
     _(GuardAndLoadUnboxedExpando)         \
     _(GuardNoDetachedTypedObjects)        \
     _(GuardNoDenseElements)               \
     _(GuardAndGetIndexFromString)         \
-    _(GuardIndexIsNonNegative)            \
     _(LoadProto)                          \
     _(LoadEnclosingEnvironment)           \
     _(LoadWrapperTarget)                  \
     _(LoadDOMExpandoValue)                \
     _(LoadDOMExpandoValueIgnoreGeneration)\
     _(LoadUndefinedResult)                \
     _(LoadBooleanResult)                  \
     _(LoadInt32ArrayLengthResult)         \
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -102,22 +102,16 @@ class MOZ_RAII IonCacheIRCompiler : publ
         return (JS::Symbol*)readStubWord(offset, StubField::Type::Symbol);
     }
     ObjectGroup* groupStubField(uint32_t offset) {
         return (ObjectGroup*)readStubWord(offset, StubField::Type::ObjectGroup);
     }
     JSCompartment* compartmentStubField(uint32_t offset) {
         return (JSCompartment*)readStubWord(offset, StubField::Type::RawWord);
     }
-    const Class* classStubField(uintptr_t offset) {
-        return (const Class*)readStubWord(offset, StubField::Type::RawWord);
-    }
-    const void* proxyHandlerStubField(uintptr_t offset) {
-        return (const void*)readStubWord(offset, StubField::Type::RawWord);
-    }
     jsid idStubField(uint32_t offset) {
         return mozilla::BitwiseCast<jsid>(readStubWord(offset, StubField::Type::Id));
     }
     template <typename T>
     T rawWordStubField(uint32_t offset) {
         static_assert(sizeof(T) == sizeof(uintptr_t), "T must have word size");
         return (T)readStubWord(offset, StubField::Type::RawWord);
     }
@@ -665,47 +659,16 @@ IonCacheIRCompiler::emitGuardCompartment
 
     masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
     masm.loadPtr(Address(scratch, ObjectGroup::offsetOfCompartment()), scratch);
     masm.branchPtr(Assembler::NotEqual, scratch, ImmPtr(compartment), failure->label());
     return true;
 }
 
 bool
-IonCacheIRCompiler::emitGuardAnyClass()
-{
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
-    AutoScratchRegister scratch(allocator, masm);
-
-    const Class* clasp = classStubField(reader.stubOffset());
-
-    FailurePath* failure;
-    if (!addFailurePath(&failure))
-        return false;
-
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, clasp, failure->label());
-    return true;
-}
-
-bool
-IonCacheIRCompiler::emitGuardHasProxyHandler()
-{
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
-    const void* handler = proxyHandlerStubField(reader.stubOffset());
-
-    FailurePath* failure;
-    if (!addFailurePath(&failure))
-        return false;
-
-    Address handlerAddr(obj, ProxyObject::offsetOfHandler());
-    masm.branchPtr(Assembler::NotEqual, handlerAddr, ImmPtr(handler), failure->label());
-    return true;
-}
-
-bool
 IonCacheIRCompiler::emitGuardSpecificObject()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     JSObject* expected = objectStubField(reader.stubOffset());
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
@@ -769,65 +732,16 @@ IonCacheIRCompiler::emitGuardSpecificSym
     if (!addFailurePath(&failure))
         return false;
 
     masm.branchPtr(Assembler::NotEqual, sym, ImmGCPtr(expected), failure->label());
     return true;
 }
 
 bool
-IonCacheIRCompiler::emitGuardXrayExpandoShapeAndDefaultProto()
-{
-    Register obj = allocator.useRegister(masm, reader.objOperandId());
-    bool hasExpando = reader.readBool();
-    JSObject* shapeWrapper = objectStubField(reader.stubOffset());
-    MOZ_ASSERT(hasExpando == !!shapeWrapper);
-
-    AutoScratchRegister scratch(allocator, masm);
-    Maybe<AutoScratchRegister> scratch2;
-    if (hasExpando)
-        scratch2.emplace(allocator, masm);
-
-    FailurePath* failure;
-    if (!addFailurePath(&failure))
-        return false;
-
-    masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch);
-    Address holderAddress(scratch, sizeof(Value) * GetXrayJitInfo()->xrayHolderSlot);
-    Address expandoAddress(scratch, NativeObject::getFixedSlotOffset(GetXrayJitInfo()->holderExpandoSlot));
-
-    if (hasExpando) {
-        masm.branchTestObject(Assembler::NotEqual, holderAddress, failure->label());
-        masm.unboxObject(holderAddress, scratch);
-        masm.branchTestObject(Assembler::NotEqual, expandoAddress, failure->label());
-        masm.unboxObject(expandoAddress, scratch);
-
-        // Unwrap the expando before checking its shape.
-        masm.loadPtr(Address(scratch, ProxyObject::offsetOfReservedSlots()), scratch);
-        masm.unboxObject(Address(scratch, detail::ProxyReservedSlots::offsetOfPrivateSlot()), scratch);
-
-        masm.movePtr(ImmGCPtr(shapeWrapper), scratch2.ref());
-        LoadShapeWrapperContents(masm, scratch2.ref(), scratch2.ref(), failure->label());
-        masm.branchTestObjShape(Assembler::NotEqual, scratch, scratch2.ref(), failure->label());
-
-        // The reserved slots on the expando should all be in fixed slots.
-        Address protoAddress(scratch, NativeObject::getFixedSlotOffset(GetXrayJitInfo()->expandoProtoSlot));
-        masm.branchTestUndefined(Assembler::NotEqual, protoAddress, failure->label());
-    } else {
-        Label done;
-        masm.branchTestObject(Assembler::NotEqual, holderAddress, &done);
-        masm.unboxObject(holderAddress, scratch);
-        masm.branchTestObject(Assembler::Equal, expandoAddress, failure->label());
-        masm.bind(&done);
-    }
-
-    return true;
-}
-
-bool
 IonCacheIRCompiler::emitLoadFixedSlotResult()
 {
     AutoOutputRegister output(*this);
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     int32_t offset = int32StubField(reader.stubOffset());
     masm.loadTypedOrValue(Address(obj, offset), output);
     return true;
 }
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1365,30 +1365,16 @@ js::GetDOMProxyHandlerFamily()
 }
 
 DOMProxyShadowsCheck
 js::GetDOMProxyShadowsCheck()
 {
     return gDOMProxyShadowsCheck;
 }
 
-static XrayJitInfo* gXrayJitInfo = nullptr;
-
-JS_FRIEND_API(void)
-js::SetXrayJitInfo(XrayJitInfo* info)
-{
-    gXrayJitInfo = info;
-}
-
-XrayJitInfo*
-js::GetXrayJitInfo()
-{
-    return gXrayJitInfo;
-}
-
 bool
 js::detail::IdMatchesAtom(jsid id, JSAtom* atom)
 {
     return id == INTERNED_STRING_TO_JSID(nullptr, atom);
 }
 
 bool
 js::detail::IdMatchesAtom(jsid id, JSString* atom)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1341,44 +1341,16 @@ SetDOMProxyInformation(const void* domPr
 const void* GetDOMProxyHandlerFamily();
 DOMProxyShadowsCheck GetDOMProxyShadowsCheck();
 inline bool DOMProxyIsShadowing(DOMProxyShadowsResult result) {
     return result == Shadows ||
            result == ShadowsViaDirectExpando ||
            result == ShadowsViaIndirectExpando;
 }
 
-// Callbacks and other information for use by the JITs when optimizing accesses
-// on xray wrappers.
-struct XrayJitInfo {
-    // Test whether a proxy handler is a cross compartment xray with no
-    // security checks.
-    bool (*isCrossCompartmentXray)(const BaseProxyHandler* handler);
-
-    // Test whether xrays with a global object's compartment have expandos of
-    // their own, instead of sharing them with Xrays from other compartments.
-    bool (*globalHasExclusiveExpandos)(JSObject* obj);
-
-    // Proxy reserved slot used by xrays in sandboxes to store their holder
-    // object.
-    size_t xrayHolderSlot;
-
-    // Reserved slot used by xray holders to store the xray's expando object.
-    size_t holderExpandoSlot;
-
-    // Reserved slot used by xray expandos to store a custom prototype.
-    size_t expandoProtoSlot;
-};
-
-JS_FRIEND_API(void)
-SetXrayJitInfo(XrayJitInfo* info);
-
-XrayJitInfo*
-GetXrayJitInfo();
-
 /* Implemented in jsdate.cpp. */
 
 /** Detect whether the internal date value is NaN. */
 extern JS_FRIEND_API(bool)
 DateIsValid(JSContext* cx, JS::HandleObject obj, bool* isValid);
 
 extern JS_FRIEND_API(bool)
 DateGetMsecSinceEpoch(JSContext* cx, JS::HandleObject obj, double* msecSinceEpoch);
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2906,17 +2906,16 @@ XPCJSRuntime::Initialize(JSContext* cx)
     JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
     JS_AddWeakPointerZonesCallback(cx, WeakPointerZonesCallback, this);
     JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback, this);
     JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     js::SetPreserveWrapperCallback(cx, PreserveWrapper);
     JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback);
     JS_SetSetUseCounterCallback(cx, SetUseCounterCallback);
     js::SetWindowProxyClass(cx, &OuterWindowProxyClass);
-    js::SetXrayJitInfo(&gXrayJitInfo);
     JS::SetProcessLargeAllocationFailureCallback(OnLargeAllocationFailureCallback);
 
     // The JS engine needs to keep the source code around in order to implement
     // Function.prototype.toSource(). It'd be nice to not have to do this for
     // chrome code and simply stub out requests for source on it. Life is not so
     // easy, unfortunately. Nobody relies on chrome toSource() working in core
     // browser code, but chrome tests use it. The worst offenders are addons,
     // which like to monkeypatch chrome functions by calling toSource() on them
--- a/js/xpconnect/tests/chrome/chrome.ini
+++ b/js/xpconnect/tests/chrome/chrome.ini
@@ -27,17 +27,16 @@ support-files =
   !/js/xpconnect/tests/mochitest/file_bug860494.html
   !/js/xpconnect/tests/mochitest/file_documentdomain.html
   !/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html
   !/js/xpconnect/tests/mochitest/file_empty.html
   !/js/xpconnect/tests/mochitest/file_exnstack.html
   !/js/xpconnect/tests/mochitest/file_expandosharing.html
   !/js/xpconnect/tests/mochitest/file_nodelists.html
   !/js/xpconnect/tests/mochitest/file_evalInSandbox.html
-  !/js/xpconnect/tests/mochitest/file_xrayic.html
 
 [test_APIExposer.xul]
 [test_bug361111.xul]
 [test_bug448587.xul]
 [test_bug484459.xul]
 skip-if = os == 'win' || os == 'mac' || (os == 'linux' && !debug) # bug 1131110, 1255284
 [test_bug500931.xul]
 [test_bug503926.xul]
@@ -112,10 +111,9 @@ skip-if = os == 'win' || os == 'mac' || 
 [test_scriptSettings.xul]
 [test_watchpoints.xul]
 [test_weakmap_keys_preserved.xul]
 [test_weakmap_keys_preserved2.xul]
 [test_weakmaps.xul]
 [test_weakref.xul]
 [test_windowProxyDeadWrapper.html]
 [test_wrappers.xul]
-[test_xrayic.xul]
 [test_xrayToJS.xul]
deleted file mode 100644
--- a/js/xpconnect/tests/chrome/test_xrayic.xul
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
-<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1355109
--->
-<window title="Mozilla Bug 1355109"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
-
-  <!-- test results are displayed in the html:body -->
-  <body xmlns="http://www.w3.org/1999/xhtml">
-  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=758415"
-     target="_blank">Mozilla Bug 758415</a>
-  </body>
-
-  <!-- test code goes here -->
-  <script type="application/javascript">
-  <![CDATA[
-
-  SimpleTest.waitForExplicitFinish();
-  const Cu = Components.utils;
-
-  // Import our test JSM. We first strip the filename off
-  // the chrome url, then append the jsm filename.
-  var base = /.*\//.exec(window.location.href)[0];
-  Cu.import(base + "file_expandosharing.jsm");
-
-  // Wait for all child frames to load.
-  var gLoadCount = 0;
-  function frameLoaded() {
-    if (++gLoadCount == window.frames.length)
-      go();
-  }
-
-  function go() {
-    testSandbox(1);
-    testSandbox(100);
-    testSandbox(1000);
-    SimpleTest.finish();
-  }
-
-  function testSandbox(iterations) {
-    var sandbox = new Cu.Sandbox(window);
-    sandbox.iframes = document.getElementsByTagName('iframe');
-    Cu.evalInSandbox(testClassName.toSource(), sandbox);
-    Cu.evalInSandbox(testIC.toSource(), sandbox);
-    is(Cu.evalInSandbox("testIC(" + iterations + ");", sandbox), true, "sandbox test");
-  }
-
-  // This is in a separate function to provide a common source location for ICs.
-  function testClassName(obj, expected) {
-    var className = obj.className;
-    if (className != expected)
-        throw new Error("Got " + className + ", expected " + expected);
-  }
-
-  function testIC(iterations) {
-    for (var i = 0; i < this.iframes.length; i++) {
-      var win = this.iframes[i].contentWindow;
-      var spans = win.document.getElementsByTagName('span');
-      for (var j = 0; j < spans.length; j++) {
-        var span = spans[j];
-        for (var k = 0; k < iterations; k++)
-          testClassName(span, "iamaspan");
-        Object.defineProperty(span, "className", { value: "what" });
-        testClassName(span, "what");
-      }
-    }
-    return true;
-  }
-  ]]>
-  </script>
-  <iframe id="inlineFrame1" onload="frameLoaded();" type="content" src="http://test1.example.org/tests/js/xpconnect/tests/mochitest/file_xrayic.html" />
-  <iframe id="inlineFrame2" onload="frameLoaded();" type="content" src="http://test1.example.org/tests/js/xpconnect/tests/mochitest/file_xrayic.html" />
-</window>
deleted file mode 100644
--- a/js/xpconnect/tests/mochitest/file_xrayic.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<script type="application/javascript">
-  function setup() {
-    // Set up targets for sandbox expandos.
-    window.targetDOM = [document.getElementById("hello"), document.getElementById("there")];
-  }
-</script>
-</head>
-<body onload="setup();">
-<span id="hello" class="iamaspan">Hello</span>
-<span id="there" class="iamaspan">There</span>
-</body>
-</html>
--- a/js/xpconnect/tests/mochitest/mochitest.ini
+++ b/js/xpconnect/tests/mochitest/mochitest.ini
@@ -29,17 +29,16 @@ support-files =
   file_doublewrappedcompartments.html
   file_empty.html
   file_evalInSandbox.html
   file_exnstack.html
   file_expandosharing.html
   file_matches.html
   file_nodelists.html
   file_wrappers-2.html
-  file_xrayic.html
   inner.html
   test1_bug629331.html
   test2_bug629331.html
 
 [test_bug384632.html]
 [test_bug390488.html]
 [test_bug393269.html]
 [test_bug396851.html]
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -1051,33 +1051,16 @@ GetXrayTraits(JSObject* obj)
  * them.
  *
  * The expando objects should _never_ be exposed to script. The fact that they
  * live in the target compartment is a detail of the implementation, and does
  * not imply that code in the target compartment should be allowed to inspect
  * them. They are private to the origin that placed them.
  */
 
-// Certain globals do not share expandos with other globals. Xrays in these
-// globals cache expandos on the wrapper's holder, as there is only one such
-// wrapper which can create or access the expando. This allows for faster
-// access to the expando, including through JIT inline caches.
-static inline bool
-GlobalHasExclusiveExpandos(JSObject* obj)
-{
-    MOZ_ASSERT(JS_IsGlobalObject(obj));
-    return !strcmp(js::GetObjectJSClass(obj)->name, "Sandbox");
-}
-
-static inline JSObject*
-GetCachedXrayExpando(JSObject* wrapper);
-
-static inline void
-SetCachedXrayExpando(JSObject* holder, JSObject* expandoWrapper);
-
 static nsIPrincipal*
 ObjectPrincipal(JSObject* obj)
 {
     return GetCompartmentPrincipal(js::GetObjectCompartment(obj));
 }
 
 static nsIPrincipal*
 GetExpandoObjectPrincipal(JSObject* expandoObject)
@@ -1098,76 +1081,66 @@ const JSClassOps XrayExpandoObjectClassO
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr,
     ExpandoObjectFinalize
 };
 
 bool
 XrayTraits::expandoObjectMatchesConsumer(JSContext* cx,
                                          HandleObject expandoObject,
-                                         nsIPrincipal* consumerOrigin)
+                                         nsIPrincipal* consumerOrigin,
+                                         HandleObject exclusiveGlobal)
 {
     MOZ_ASSERT(js::IsObjectInContextCompartment(expandoObject, cx));
 
     // First, compare the principals.
     nsIPrincipal* o = GetExpandoObjectPrincipal(expandoObject);
     // Note that it's very important here to ignore document.domain. We
     // pull the principal for the expando object off of the first consumer
     // for a given origin, and freely share the expandos amongst multiple
     // same-origin consumers afterwards. However, this means that we have
     // no way to know whether _all_ consumers have opted in to collaboration
     // by explicitly setting document.domain. So we just mandate that expando
     // sharing is unaffected by it.
     if (!consumerOrigin->Equals(o))
       return false;
 
-    // Certain globals exclusively own the associated expandos, in which case
-    // the caller should have used the cached expando on the wrapper instead.
+    // Sandboxes want exclusive expando objects.
     JSObject* owner = JS_GetReservedSlot(expandoObject,
-                                         JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER)
+                                         JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL)
                                         .toObjectOrNull();
-    return owner == nullptr;
+    if (!owner && !exclusiveGlobal)
+        return true;
+
+    // The exclusive global should always be wrapped in the target's compartment.
+    MOZ_ASSERT(!exclusiveGlobal || js::IsObjectInContextCompartment(exclusiveGlobal, cx));
+    MOZ_ASSERT(!owner || js::IsObjectInContextCompartment(owner, cx));
+    return owner == exclusiveGlobal;
 }
 
 bool
 XrayTraits::getExpandoObjectInternal(JSContext* cx, JSObject* expandoChain,
-                                     HandleObject exclusiveWrapper,
                                      nsIPrincipal* origin,
+                                     JSObject* exclusiveGlobalArg,
                                      MutableHandleObject expandoObject)
 {
     MOZ_ASSERT(!JS_IsExceptionPending(cx));
     expandoObject.set(nullptr);
 
-    // Use the cached expando if this wrapper has exclusive access to it.
-    if (exclusiveWrapper) {
-        JSObject* expandoWrapper = GetCachedXrayExpando(exclusiveWrapper);
-        expandoObject.set(expandoWrapper ? UncheckedUnwrap(expandoWrapper) : nullptr);
-#ifdef DEBUG
-        // Make sure the expando we found is on the target's chain. While we
-        // don't use this chain to look up expandos for the wrapper,
-        // the expando still needs to be on the chain to keep the wrapper and
-        // expando alive.
-        if (expandoObject) {
-            JSObject* head = expandoChain;
-            while (head && head != expandoObject)
-                head = JS_GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull();
-            MOZ_ASSERT(head == expandoObject);
-        }
-#endif
-        return true;
-    }
-
     // The expando object lives in the compartment of the target, so all our
     // work needs to happen there.
+    RootedObject exclusiveGlobal(cx, exclusiveGlobalArg);
     RootedObject head(cx, expandoChain);
     JSAutoCompartment ac(cx, head);
+    if (!JS_WrapObject(cx, &exclusiveGlobal))
+        return false;
 
     // Iterate through the chain, looking for a same-origin object.
     while (head) {
-        if (expandoObjectMatchesConsumer(cx, head, origin)) {
+        if (expandoObjectMatchesConsumer(cx, head, origin, exclusiveGlobal)) {
             expandoObject.set(head);
             return true;
         }
         head = JS_GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull();
     }
 
     // Not found.
     return true;
@@ -1179,53 +1152,37 @@ XrayTraits::getExpandoObject(JSContext* 
 {
     // Return early if no expando object has ever been attached, which is
     // usually the case.
     JSObject* chain = getExpandoChain(target);
     if (!chain)
         return true;
 
     JSObject* consumerGlobal = js::GetGlobalForObjectCrossCompartment(consumer);
-    bool isExclusive = GlobalHasExclusiveExpandos(consumerGlobal);
-    return getExpandoObjectInternal(cx, chain, isExclusive ? consumer : nullptr,
-                                    ObjectPrincipal(consumer), expandoObject);
+    bool isSandbox = !strcmp(js::GetObjectJSClass(consumerGlobal)->name, "Sandbox");
+    return getExpandoObjectInternal(cx, chain, ObjectPrincipal(consumer),
+                                    isSandbox ? consumerGlobal : nullptr,
+                                    expandoObject);
 }
 
-// Wrappers which have exclusive access to the expando on their target object
-// need to be kept alive as long as the target object exists. This is done by
-// keeping the expando in the expando chain on the target (even though it will
-// not be used while looking up the expando for the wrapper), and keeping a
-// strong reference from that expando to the wrapper itself, via the
-// JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER reserved slot. This slot does not
-// point to the wrapper itself, because it is a cross compartment edge and we
-// can't create a wrapper for a wrapper. Instead, the slot points to an
-// instance of the holder class below in the wrapper's compartment, and the
-// wrapper is held via this holder object's reserved slot.
-static const JSClass gWrapperHolderClass = {
-    "XrayExpandoWrapperHolder",
-    JSCLASS_HAS_RESERVED_SLOTS(1)
-};
-static const size_t JSSLOT_WRAPPER_HOLDER_CONTENTS = 0;
-
 JSObject*
 XrayTraits::attachExpandoObject(JSContext* cx, HandleObject target,
-                                HandleObject exclusiveWrapper,
-                                nsIPrincipal* origin)
+                                nsIPrincipal* origin, HandleObject exclusiveGlobal)
 {
     // Make sure the compartments are sane.
     MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx));
-    MOZ_ASSERT_IF(exclusiveWrapper, !js::IsObjectInContextCompartment(exclusiveWrapper, cx));
+    MOZ_ASSERT(!exclusiveGlobal || js::IsObjectInContextCompartment(exclusiveGlobal, cx));
 
     // No duplicates allowed.
 #ifdef DEBUG
     {
         JSObject* chain = getExpandoChain(target);
         if (chain) {
             RootedObject existingExpandoObject(cx);
-            if (getExpandoObjectInternal(cx, chain, exclusiveWrapper, origin, &existingExpandoObject))
+            if (getExpandoObjectInternal(cx, chain, origin, exclusiveGlobal, &existingExpandoObject))
                 MOZ_ASSERT(!existingExpandoObject);
             else
                 JS_ClearPendingException(cx);
         }
     }
 #endif
 
     // Create the expando object.
@@ -1235,41 +1192,19 @@ XrayTraits::attachExpandoObject(JSContex
       JS_NewObjectWithGivenProto(cx, expandoClass, nullptr));
     if (!expandoObject)
         return nullptr;
 
     // AddRef and store the principal.
     NS_ADDREF(origin);
     JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_ORIGIN, JS::PrivateValue(origin));
 
-    // Note the exclusive wrapper, if there is one.
-    RootedObject wrapperHolder(cx);
-    if (exclusiveWrapper) {
-        JSAutoCompartment ac(cx, exclusiveWrapper);
-        wrapperHolder = JS_NewObjectWithGivenProto(cx, &gWrapperHolderClass, nullptr);
-        if (!wrapperHolder)
-            return nullptr;
-        JS_SetReservedSlot(wrapperHolder, JSSLOT_WRAPPER_HOLDER_CONTENTS, ObjectValue(*exclusiveWrapper));
-    }
-    if (!JS_WrapObject(cx, &wrapperHolder))
-        return nullptr;
-    JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER,
-                       ObjectOrNullValue(wrapperHolder));
-
-    // Store it on the exclusive wrapper, if there is one.
-    if (exclusiveWrapper) {
-        RootedObject cachedExpandoObject(cx, expandoObject);
-        JSAutoCompartment ac(cx, exclusiveWrapper);
-        if (!JS_WrapObject(cx, &cachedExpandoObject))
-            return nullptr;
-        JSObject* holder = ensureHolder(cx, exclusiveWrapper);
-        if (!holder)
-            return nullptr;
-        SetCachedXrayExpando(holder, cachedExpandoObject);
-    }
+    // Note the exclusive global, if any.
+    JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL,
+                       ObjectOrNullValue(exclusiveGlobal));
 
     // If this is our first expando object, take the opportunity to preserve
     // the wrapper. This keeps our expandos alive even if the Xray wrapper gets
     // collected.
     RootedObject chain(cx, getExpandoChain(target));
     if (!chain)
         preserveWrapper(target);
 
@@ -1285,20 +1220,27 @@ XrayTraits::ensureExpandoObject(JSContex
                                 HandleObject target)
 {
     // Expando objects live in the target compartment.
     JSAutoCompartment ac(cx, target);
     RootedObject expandoObject(cx);
     if (!getExpandoObject(cx, target, wrapper, &expandoObject))
         return nullptr;
     if (!expandoObject) {
-        JSObject* consumerGlobal = js::GetGlobalForObjectCrossCompartment(wrapper);
-        bool isExclusive = GlobalHasExclusiveExpandos(consumerGlobal);
-        expandoObject = attachExpandoObject(cx, target, isExclusive ? wrapper : nullptr,
-                                            ObjectPrincipal(wrapper));
+        // If the object is a sandbox, we don't want it to share expandos with
+        // anyone else, so we tag it with the sandbox global.
+        //
+        // NB: We first need to check the class, _then_ wrap for the target's
+        // compartment.
+        RootedObject consumerGlobal(cx, js::GetGlobalForObjectCrossCompartment(wrapper));
+        bool isSandbox = !strcmp(js::GetObjectJSClass(consumerGlobal)->name, "Sandbox");
+        if (!JS_WrapObject(cx, &consumerGlobal))
+            return nullptr;
+        expandoObject = attachExpandoObject(cx, target, ObjectPrincipal(wrapper),
+                                            isSandbox ? (HandleObject)consumerGlobal : nullptr);
     }
     return expandoObject;
 }
 
 bool
 XrayTraits::cloneExpandoChain(JSContext* cx, HandleObject dst, HandleObject src)
 {
     MOZ_ASSERT(js::IsObjectInContextCompartment(dst, cx));
@@ -1319,48 +1261,38 @@ XrayTraits::cloneExpandoChain(JSContext*
             nsWrapperCache* cache = nullptr;
             CallQueryInterface(identity, &cache);
             MOZ_ASSERT_IF(cache, cache->PreservingWrapper());
         }
     }
 #endif
 
     while (oldHead) {
-        RootedObject exclusiveWrapper(cx);
-        RootedObject wrapperHolder(cx, JS_GetReservedSlot(oldHead,
-                                                          JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER)
-                                                         .toObjectOrNull());
-        if (wrapperHolder) {
-            // The global containing this wrapper holder has an xray for |src|
-            // with expandos. Create an xray in the global for |dst| which
-            // will be associated with a clone of |src|'s expando object.
-            JSAutoCompartment ac(cx, UncheckedUnwrap(wrapperHolder));
-            exclusiveWrapper = dst;
-            if (!JS_WrapObject(cx, &exclusiveWrapper))
-                return false;
-        }
-        RootedObject newHead(cx, attachExpandoObject(cx, dst, exclusiveWrapper,
-                                                     GetExpandoObjectPrincipal(oldHead)));
+        RootedObject exclusive(cx, JS_GetReservedSlot(oldHead,
+                                                      JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL)
+                                                     .toObjectOrNull());
+        if (!JS_WrapObject(cx, &exclusive))
+            return false;
+        RootedObject newHead(cx, attachExpandoObject(cx, dst, GetExpandoObjectPrincipal(oldHead),
+                                                     exclusive));
         if (!JS_CopyPropertiesFrom(cx, newHead, oldHead))
             return false;
         oldHead = JS_GetReservedSlot(oldHead, JSSLOT_EXPANDO_NEXT).toObjectOrNull();
     }
     return true;
 }
 
 void
 ClearXrayExpandoSlots(JSObject* target, size_t slotIndex)
 {
     if (!NS_IsMainThread()) {
         // No Xrays
         return;
     }
 
-    MOZ_ASSERT(slotIndex != JSSLOT_EXPANDO_NEXT);
-    MOZ_ASSERT(slotIndex != JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER);
     MOZ_ASSERT(GetXrayTraits(target) == &DOMXrayTraits::singleton);
     RootingContext* rootingCx = RootingCx();
     RootedObject rootedTarget(rootingCx, target);
     RootedObject head(rootingCx,
                       DOMXrayTraits::singleton.getExpandoChain(rootedTarget));
     while (head) {
         MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(js::GetObjectClass(head)) > slotIndex);
         js::SetReservedSlot(head, slotIndex, UndefinedValue());
@@ -1389,62 +1321,42 @@ namespace XrayUtils {
 bool CloneExpandoChain(JSContext* cx, JSObject* dstArg, JSObject* srcArg)
 {
     RootedObject dst(cx, dstArg);
     RootedObject src(cx, srcArg);
     return GetXrayTraits(src)->cloneExpandoChain(cx, dst, src);
 }
 } // namespace XrayUtils
 
-static const size_t JSSLOT_XRAY_HOLDER = 0;
-
 static JSObject*
 GetHolder(JSObject* obj)
 {
-    return &js::GetProxyReservedSlot(obj, JSSLOT_XRAY_HOLDER).toObject();
+    return &js::GetProxyReservedSlot(obj, 0).toObject();
 }
 
-/* static */ JSObject*
+JSObject*
 XrayTraits::getHolder(JSObject* wrapper)
 {
     MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper));
-    js::Value v = js::GetProxyReservedSlot(wrapper, JSSLOT_XRAY_HOLDER);
+    js::Value v = js::GetProxyReservedSlot(wrapper, 0);
     return v.isObject() ? &v.toObject() : nullptr;
 }
 
 JSObject*
 XrayTraits::ensureHolder(JSContext* cx, HandleObject wrapper)
 {
     RootedObject holder(cx, getHolder(wrapper));
     if (holder)
         return holder;
     holder = createHolder(cx, wrapper); // virtual trap.
     if (holder)
-        js::SetProxyReservedSlot(wrapper, JSSLOT_XRAY_HOLDER, ObjectValue(*holder));
+        js::SetProxyReservedSlot(wrapper, 0, ObjectValue(*holder));
     return holder;
 }
 
-static inline JSObject*
-GetCachedXrayExpando(JSObject* wrapper)
-{
-    JSObject* holder = XrayTraits::getHolder(wrapper);
-    if (!holder)
-        return nullptr;
-    Value v = JS_GetReservedSlot(holder, XrayTraits::HOLDER_SLOT_EXPANDO);
-    return v.isObject() ? &v.toObject() : nullptr;
-}
-
-static inline void
-SetCachedXrayExpando(JSObject* holder, JSObject* expandoWrapper)
-{
-    MOZ_ASSERT(js::GetObjectCompartment(holder) ==
-               js::GetObjectCompartment(expandoWrapper));
-    JS_SetReservedSlot(holder, XrayTraits::HOLDER_SLOT_EXPANDO, ObjectValue(*expandoWrapper));
-}
-
 namespace XrayUtils {
 
 bool
 IsXPCWNHolderClass(const JSClass* clasp)
 {
   return clasp == &XPCWrappedNativeXrayTraits::HolderClass;
 }
 
@@ -2630,27 +2542,9 @@ xpc::XrayWrapper<Base, Traits>::singleto
 
 template class PermissiveXrayXPCWN;
 template class SecurityXrayXPCWN;
 template class PermissiveXrayDOM;
 template class SecurityXrayDOM;
 template class PermissiveXrayJS;
 template class PermissiveXrayOpaque;
 
-/*
- * This callback is used by the JS engine to test if a proxy handler is for a
- * cross compartment xray with no security requirements.
- */
-static bool
-IsCrossCompartmentXrayCallback(const js::BaseProxyHandler* handler)
-{
-    return handler == &PermissiveXrayDOM::singleton;
-}
-
-js::XrayJitInfo gXrayJitInfo = {
-    IsCrossCompartmentXrayCallback,
-    GlobalHasExclusiveExpandos,
-    JSSLOT_XRAY_HOLDER,
-    XrayTraits::HOLDER_SLOT_EXPANDO,
-    JSSLOT_EXPANDO_PROTOTYPE
-};
-
 } // namespace xpc
--- a/js/xpconnect/wrappers/XrayWrapper.h
+++ b/js/xpconnect/wrappers/XrayWrapper.h
@@ -100,52 +100,44 @@ public:
     bool getExpandoObject(JSContext* cx, JS::HandleObject target,
                           JS::HandleObject consumer, JS::MutableHandleObject expandObject);
     JSObject* ensureExpandoObject(JSContext* cx, JS::HandleObject wrapper,
                                   JS::HandleObject target);
 
     // Slots for holder objects.
     enum {
         HOLDER_SLOT_CACHED_PROTO = 0,
-        HOLDER_SLOT_EXPANDO = 1,
         HOLDER_SHARED_SLOT_COUNT
     };
 
-    static JSObject* getHolder(JSObject* wrapper);
+    JSObject* getHolder(JSObject* wrapper);
     JSObject* ensureHolder(JSContext* cx, JS::HandleObject wrapper);
     virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) = 0;
 
     JSObject* getExpandoChain(JS::HandleObject obj);
     bool setExpandoChain(JSContext* cx, JS::HandleObject obj, JS::HandleObject chain);
     bool cloneExpandoChain(JSContext* cx, JS::HandleObject dst, JS::HandleObject src);
 
 protected:
     static const JSClass HolderClass;
 
     // Get the JSClass we should use for our expando object.
     virtual const JSClass* getExpandoClass(JSContext* cx,
                                            JS::HandleObject target) const;
 
 private:
     bool expandoObjectMatchesConsumer(JSContext* cx, JS::HandleObject expandoObject,
-                                      nsIPrincipal* consumerOrigin);
-
-    // |expandoChain| is the expando chain in the wrapped object's compartment.
-    // |exclusiveWrapper| is any xray that has exclusive use of the expando.
-    // |cx| may be in any compartment.
+                                      nsIPrincipal* consumerOrigin,
+                                      JS::HandleObject exclusiveGlobal);
     bool getExpandoObjectInternal(JSContext* cx, JSObject* expandoChain,
-                                  JS::HandleObject exclusiveWrapper,
+                                  nsIPrincipal* origin, JSObject* exclusiveGlobal,
+                                  JS::MutableHandleObject expandoObject);
+    JSObject* attachExpandoObject(JSContext* cx, JS::HandleObject target,
                                   nsIPrincipal* origin,
-                                  JS::MutableHandleObject expandoObject);
-
-    // |cx| is in the target's compartment, and |exclusiveWrapper| is any xray
-    // that has exclusive use of the expando.
-    JSObject* attachExpandoObject(JSContext* cx, JS::HandleObject target,
-                                  JS::HandleObject exclusiveWrapper,
-                                  nsIPrincipal* origin);
+                                  JS::HandleObject exclusiveGlobal);
 
     XrayTraits(XrayTraits&) = delete;
     const XrayTraits& operator=(XrayTraits&) = delete;
 };
 
 class XPCWrappedNativeXrayTraits : public XrayTraits
 {
 public:
@@ -604,17 +596,17 @@ class AutoSetWrapperNotShadowing;
 
 /*
  * Slots for Xray expando objects.  See comments in XrayWrapper.cpp for details
  * of how these get used; we mostly want the value of JSSLOT_EXPANDO_COUNT here.
  */
 enum ExpandoSlots {
     JSSLOT_EXPANDO_NEXT = 0,
     JSSLOT_EXPANDO_ORIGIN,
-    JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER,
+    JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL,
     JSSLOT_EXPANDO_PROTOTYPE,
     JSSLOT_EXPANDO_COUNT
 };
 
 extern const JSClassOps XrayExpandoObjectClassOps;
 
 /*
  * Clear the given slot on all Xray expandos for the given object.
@@ -627,14 +619,11 @@ ClearXrayExpandoSlots(JSObject* target, 
 /*
  * Ensure the given wrapper has an expando object and return it.  This can
  * return null on failure.  Will only be called when "wrapper" is an Xray for a
  * DOM object.
  */
 JSObject*
 EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper);
 
-// Information about xrays for use by the JITs.
-extern js::XrayJitInfo gXrayJitInfo;
-
 } // namespace xpc
 
 #endif