Backed out changeset 58e45fc51d63. a=lizzard FIREFOX_56_0_BUILD6 FIREFOX_56_0_RELEASE
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 26 Sep 2017 15:06:51 -0400
changeset 694354 8fbf05f4b92125e081984f5e39b559b83e5cc729
parent 694353 86382cd12f85d677d31607ae0c5c07d9bc5f55c9
child 694355 d8c274aa9d42bed5868d1be0487a337f969b51cf
push id88118
push userbmo:edilee@mozilla.com
push dateTue, 07 Nov 2017 18:56:14 +0000
reviewerslizzard
milestone56.0
backs out58e45fc51d63903b92708bbceb5276567a297f71
Backed out changeset 58e45fc51d63. 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,16 +288,52 @@ 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;
 
@@ -363,16 +399,64 @@ 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
@@ -12,16 +12,17 @@
 #include "jit/BaselineCacheIRCompiler.h"
 #include "jit/BaselineIC.h"
 #include "jit/CacheIRSpewer.h"
 #include "jit/IonCaches.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;
@@ -173,16 +174,18 @@ 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;
         }
@@ -745,17 +748,17 @@ GetPropIRGenerator::tryAttachCrossCompar
                 preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
             else
                 preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
         }
     }
 
     maybeEmitIdGuard(id);
     writer.guardIsProxy(objId);
-    writer.guardIsCrossCompartmentWrapper(objId);
+    writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
 
     // 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;
@@ -769,16 +772,124 @@ 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);
 
@@ -3961,8 +4072,45 @@ 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,36 +166,39 @@ extern const char* CacheKindNames[];
     _(GuardIsObjectOrNull)                \
     _(GuardIsString)                      \
     _(GuardIsSymbol)                      \
     _(GuardIsInt32Index)                  \
     _(GuardType)                          \
     _(GuardShape)                         \
     _(GuardGroup)                         \
     _(GuardProto)                         \
-    _(GuardClass)                         \
+    _(GuardClass)                         /* Guard an object class, per GuardClassKind */ \
+    _(GuardAnyClass)                      /* Guard an arbitrary class for an object */ \
     _(GuardCompartment)                   \
     _(GuardIsNativeFunction)              \
     _(GuardIsProxy)                       \
-    _(GuardIsCrossCompartmentWrapper)     \
+    _(GuardHasProxyHandler)               \
     _(GuardNotDOMProxy)                   \
     _(GuardSpecificObject)                \
     _(GuardSpecificAtom)                  \
     _(GuardSpecificSymbol)                \
     _(GuardSpecificInt32Immediate)        \
     _(GuardNoDetachedTypedObjects)        \
     _(GuardMagicValue)                    \
     _(GuardFrameHasNoArgumentsObject)     \
     _(GuardNoDenseElements)               \
     _(GuardNoUnboxedExpando)              \
     _(GuardAndLoadUnboxedExpando)         \
     _(GuardAndGetIndexFromString)         \
     _(GuardAndGetIterator)                \
     _(GuardHasGetterSetter)               \
     _(GuardGroupHasUnanalyzedNewScript)   \
+    _(GuardIndexIsNonNegative)            \
+    _(GuardXrayExpandoShapeAndDefaultProto) \
     _(LoadStackValue)                     \
     _(LoadObject)                         \
     _(LoadProto)                          \
     _(LoadEnclosingEnvironment)           \
     _(LoadWrapperTarget)                  \
                                           \
     _(MegamorphicLoadSlotResult)          \
     _(MegamorphicLoadSlotByValueResult)   \
@@ -348,16 +351,24 @@ 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_;
@@ -516,39 +527,48 @@ 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 guardIsCrossCompartmentWrapper(ObjOperandId obj) {
-        writeOpWithOperandId(CacheOp::GuardIsCrossCompartmentWrapper, obj);
+    void guardHasProxyHandler(ObjOperandId obj, const void* handler) {
+        writeOpWithOperandId(CacheOp::GuardHasProxyHandler, obj);
+        addStubField(uintptr_t(handler), StubField::Type::RawWord);
     }
     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);
     }
@@ -604,16 +624,20 @@ 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);
@@ -1171,16 +1195,17 @@ 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,32 +1431,16 @@ 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;
@@ -1923,16 +1907,29 @@ 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,16 +102,22 @@ 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);
     }
@@ -644,16 +650,47 @@ 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;
@@ -717,16 +754,65 @@ 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
@@ -1300,16 +1300,30 @@ 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
@@ -1314,16 +1314,44 @@ 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
@@ -2839,16 +2839,17 @@ XPCJSRuntime::Initialize(JSContext* cx)
             DoCycleCollectionCallback);
     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::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,16 +27,17 @@ 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]
@@ -110,10 +111,11 @@ 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]
 [test_asyncIteration.xul]
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/chrome/test_xrayic.xul
@@ -0,0 +1,76 @@
+<?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>
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/mochitest/file_xrayic.html
@@ -0,0 +1,15 @@
+<!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,16 +29,17 @@ 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
@@ -1049,16 +1049,33 @@ 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)
@@ -1079,66 +1096,76 @@ const JSClassOps XrayExpandoObjectClassO
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr, nullptr,
     ExpandoObjectFinalize
 };
 
 bool
 XrayTraits::expandoObjectMatchesConsumer(JSContext* cx,
                                          HandleObject expandoObject,
-                                         nsIPrincipal* consumerOrigin,
-                                         HandleObject exclusiveGlobal)
+                                         nsIPrincipal* consumerOrigin)
 {
     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;
 
-    // Sandboxes want exclusive expando objects.
+    // Certain globals exclusively own the associated expandos, in which case
+    // the caller should have used the cached expando on the wrapper instead.
     JSObject* owner = JS_GetReservedSlot(expandoObject,
-                                         JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL)
+                                         JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER)
                                         .toObjectOrNull();
-    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;
+    return owner == nullptr;
 }
 
 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, exclusiveGlobal)) {
+        if (expandoObjectMatchesConsumer(cx, head, origin)) {
             expandoObject.set(head);
             return true;
         }
         head = JS_GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull();
     }
 
     // Not found.
     return true;
@@ -1150,37 +1177,53 @@ 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 isSandbox = !strcmp(js::GetObjectJSClass(consumerGlobal)->name, "Sandbox");
-    return getExpandoObjectInternal(cx, chain, ObjectPrincipal(consumer),
-                                    isSandbox ? consumerGlobal : nullptr,
-                                    expandoObject);
+    bool isExclusive = GlobalHasExclusiveExpandos(consumerGlobal);
+    return getExpandoObjectInternal(cx, chain, isExclusive ? consumer : nullptr,
+                                    ObjectPrincipal(consumer), 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,
-                                nsIPrincipal* origin, HandleObject exclusiveGlobal)
+                                HandleObject exclusiveWrapper,
+                                nsIPrincipal* origin)
 {
     // Make sure the compartments are sane.
     MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx));
-    MOZ_ASSERT(!exclusiveGlobal || js::IsObjectInContextCompartment(exclusiveGlobal, cx));
+    MOZ_ASSERT_IF(exclusiveWrapper, !js::IsObjectInContextCompartment(exclusiveWrapper, cx));
 
     // No duplicates allowed.
 #ifdef DEBUG
     {
         JSObject* chain = getExpandoChain(target);
         if (chain) {
             RootedObject existingExpandoObject(cx);
-            if (getExpandoObjectInternal(cx, chain, origin, exclusiveGlobal, &existingExpandoObject))
+            if (getExpandoObjectInternal(cx, chain, exclusiveWrapper, origin, &existingExpandoObject))
                 MOZ_ASSERT(!existingExpandoObject);
             else
                 JS_ClearPendingException(cx);
         }
     }
 #endif
 
     // Create the expando object.
@@ -1190,19 +1233,41 @@ 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 global, if any.
-    JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL,
-                       ObjectOrNullValue(exclusiveGlobal));
+    // 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);
+    }
 
     // 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);
 
@@ -1218,27 +1283,20 @@ 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) {
-        // 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);
+        JSObject* consumerGlobal = js::GetGlobalForObjectCrossCompartment(wrapper);
+        bool isExclusive = GlobalHasExclusiveExpandos(consumerGlobal);
+        expandoObject = attachExpandoObject(cx, target, isExclusive ? wrapper : nullptr,
+                                            ObjectPrincipal(wrapper));
     }
     return expandoObject;
 }
 
 bool
 XrayTraits::cloneExpandoChain(JSContext* cx, HandleObject dst, HandleObject src)
 {
     MOZ_ASSERT(js::IsObjectInContextCompartment(dst, cx));
@@ -1259,38 +1317,48 @@ XrayTraits::cloneExpandoChain(JSContext*
             nsWrapperCache* cache = nullptr;
             CallQueryInterface(identity, &cache);
             MOZ_ASSERT_IF(cache, cache->PreservingWrapper());
         }
     }
 #endif
 
     while (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));
+        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)));
         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());
@@ -1319,42 +1387,62 @@ 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, 0).toObject();
+    return &js::GetProxyReservedSlot(obj, JSSLOT_XRAY_HOLDER).toObject();
 }
 
-JSObject*
+/* static */ JSObject*
 XrayTraits::getHolder(JSObject* wrapper)
 {
     MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper));
-    js::Value v = js::GetProxyReservedSlot(wrapper, 0);
+    js::Value v = js::GetProxyReservedSlot(wrapper, JSSLOT_XRAY_HOLDER);
     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, 0, ObjectValue(*holder));
+        js::SetProxyReservedSlot(wrapper, JSSLOT_XRAY_HOLDER, 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;
 }
 
@@ -2541,9 +2629,27 @@ 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,44 +100,52 @@ 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
     };
 
-    JSObject* getHolder(JSObject* wrapper);
+    static 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,
-                                      JS::HandleObject exclusiveGlobal);
+                                      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.
     bool getExpandoObjectInternal(JSContext* cx, JSObject* expandoChain,
-                                  nsIPrincipal* origin, JSObject* exclusiveGlobal,
+                                  JS::HandleObject exclusiveWrapper,
+                                  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,
-                                  nsIPrincipal* origin,
-                                  JS::HandleObject exclusiveGlobal);
+                                  JS::HandleObject exclusiveWrapper,
+                                  nsIPrincipal* origin);
 
     XrayTraits(XrayTraits&) = delete;
     const XrayTraits& operator=(XrayTraits&) = delete;
 };
 
 class XPCWrappedNativeXrayTraits : public XrayTraits
 {
 public:
@@ -596,17 +604,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_GLOBAL,
+    JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER,
     JSSLOT_EXPANDO_PROTOTYPE,
     JSSLOT_EXPANDO_COUNT
 };
 
 extern const JSClassOps XrayExpandoObjectClassOps;
 
 /*
  * Clear the given slot on all Xray expandos for the given object.
@@ -619,11 +627,14 @@ 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