Bug 1237504 - Refactor proxy slot layout to allow proxies to have more than 2 slots. r=bz,jonco
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 28 Apr 2017 14:12:28 +0200
changeset 570251 42a3fcaa99ef4039ec76bfc967ff475d04f486da
parent 570250 349adb24a5108b2197ed5d6f5c6b3e3672a6b853
child 570252 34f2d83ee7734d23c612039fc5ff364a26b2540c
push id56441
push userdgottwald@mozilla.com
push dateFri, 28 Apr 2017 16:50:59 +0000
reviewersbz, jonco
bugs1237504
milestone55.0a1
Bug 1237504 - Refactor proxy slot layout to allow proxies to have more than 2 slots. r=bz,jonco The patch makes the following proxy changes: * The number of slots in ProxyValueArray is now dynamic and depends on the number of reserved slots we get from the Class. * "Extra slots" was renamed to "Reserved slots" to make this clearer. * All proxy Classes now have 2 reserved slots, but it should be easy to change that for proxy Classes that need more than 2 slots. * Proxies now store a pointer to these slots and this means GetReservedSlot and SetReservedSlot can be used on proxies as well. We no longer need GetReservedOrProxyPrivateSlot and SetReservedOrProxyPrivateSlot. And some changes to make DOM Proxies work with this: * We now store the C++ object in the first reserved slot (DOM_OBJECT_SLOT) instead of in the proxy's private slot. This is pretty nice because it matches what we do for non-proxy DOM objects. * We now store the expando in the proxy's private slot so I removed GetDOMProxyExpandoSlot and changed the IC code to get the expando from the private slot instead.
dom/base/nsGlobalWindow.cpp
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/DOMJSProxyHandler.cpp
dom/bindings/DOMJSProxyHandler.h
js/ipc/WrapperOwner.cpp
js/public/Proxy.h
js/src/jit/BaselineCacheIRCompiler.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIRCompiler.cpp
js/src/jit/IonCacheIRCompiler.cpp
js/src/jsapi-tests/testBug604087.cpp
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsgc.cpp
js/src/jsobj.cpp
js/src/proxy/Proxy.cpp
js/src/proxy/ScriptedProxyHandler.cpp
js/src/vm/EnvironmentObject.cpp
js/src/vm/EnvironmentObject.h
js/src/vm/ProxyObject.cpp
js/src/vm/ProxyObject.h
js/src/vm/Shape.h
js/xpconnect/src/Sandbox.cpp
js/xpconnect/wrappers/XrayWrapper.cpp
js/xpconnect/wrappers/XrayWrapper.h
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1111,17 +1111,17 @@ public:
   static void ObjectMoved(JSObject *obj, const JSObject *old);
 
   static const nsOuterWindowProxy singleton;
 
 protected:
   static nsGlobalWindow* GetOuterWindow(JSObject *proxy)
   {
     nsGlobalWindow* outerWindow = nsGlobalWindow::FromSupports(
-      static_cast<nsISupports*>(js::GetProxyExtra(proxy, 0).toPrivate()));
+      static_cast<nsISupports*>(js::GetProxyReservedSlot(proxy, 0).toPrivate()));
     MOZ_ASSERT_IF(outerWindow, outerWindow->IsOuterWindow());
     return outerWindow;
   }
 
   // False return value means we threw an exception.  True return value
   // but false "found" means we didn't have a subframe at that index.
   bool GetSubframeWindow(JSContext *cx, JS::Handle<JSObject*> proxy,
                          JS::Handle<jsid> id,
@@ -1725,17 +1725,17 @@ nsGlobalWindow::~nsGlobalWindow()
   }
 #endif
 
   MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug, ("DOMWINDOW %p destroyed", this));
 
   if (IsOuterWindow()) {
     JSObject *proxy = GetWrapperMaybeDead();
     if (proxy) {
-      js::SetProxyExtra(proxy, 0, js::PrivateValue(nullptr));
+      js::SetProxyReservedSlot(proxy, 0, js::PrivateValue(nullptr));
     }
 
     // An outer window is destroyed with inner windows still possibly
     // alive, iterate through the inner windows and null out their
     // back pointer to this outer, and pull them out of the list of
     // inner windows.
 
     nsGlobalWindow *w;
@@ -3139,17 +3139,17 @@ nsGlobalWindow::SetNewDocument(nsIDocume
 
     mInnerWindow = newInnerWindow->AsInner();
 
     if (!GetWrapperPreserveColor()) {
       JS::Rooted<JSObject*> outer(cx,
         NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
       NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE);
 
-      js::SetProxyExtra(outer, 0, js::PrivateValue(ToSupports(this)));
+      js::SetProxyReservedSlot(outer, 0, js::PrivateValue(ToSupports(this)));
 
       // Inform the nsJSContext, which is the canonical holder of the outer.
       mContext->SetWindowProxy(outer);
       mContext->DidInitializeContext();
 
       SetWrapper(mContext->GetWindowProxy());
     } else {
       JS::ExposeObjectToActiveJS(newInnerGlobal);
@@ -3157,26 +3157,26 @@ nsGlobalWindow::SetNewDocument(nsIDocume
         NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
       if (!outerObject) {
         NS_ERROR("out of memory");
         return NS_ERROR_FAILURE;
       }
 
       JS::Rooted<JSObject*> obj(cx, GetWrapperPreserveColor());
 
-      js::SetProxyExtra(obj, 0, js::PrivateValue(nullptr));
-      js::SetProxyExtra(outerObject, 0, js::PrivateValue(nullptr));
+      js::SetProxyReservedSlot(obj, 0, js::PrivateValue(nullptr));
+      js::SetProxyReservedSlot(outerObject, 0, js::PrivateValue(nullptr));
 
       outerObject = xpc::TransplantObject(cx, obj, outerObject);
       if (!outerObject) {
         NS_ERROR("unable to transplant wrappers, probably OOM");
         return NS_ERROR_FAILURE;
       }
 
-      js::SetProxyExtra(outerObject, 0, js::PrivateValue(ToSupports(this)));
+      js::SetProxyReservedSlot(outerObject, 0, js::PrivateValue(ToSupports(this)));
 
       SetWrapper(outerObject);
 
       MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(outerObject) == newInnerGlobal);
 
       // Inform the nsJSContext, which is the canonical holder of the outer.
       mContext->SetWindowProxy(outerObject);
     }
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -2200,19 +2200,19 @@ ReparentWrapper(JSContext* aCx, JS::Hand
   }
 
   // We've set up |newobj|, so we make it own the native by setting its reserved
   // slot and nulling out the reserved slot of |obj|.
   //
   // NB: It's important to do this _after_ copying the properties to
   // propertyHolder. Otherwise, an object with |foo.x === foo| will
   // crash when JS_CopyPropertiesFrom tries to call wrap() on foo.x.
-  js::SetReservedOrProxyPrivateSlot(newobj, DOM_OBJECT_SLOT,
-                                    js::GetReservedOrProxyPrivateSlot(aObj, DOM_OBJECT_SLOT));
-  js::SetReservedOrProxyPrivateSlot(aObj, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
+  js::SetReservedSlot(newobj, DOM_OBJECT_SLOT,
+                      js::GetReservedSlot(aObj, DOM_OBJECT_SLOT));
+  js::SetReservedSlot(aObj, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
 
   aObj = xpc::TransplantObject(aCx, aObj, newobj);
   if (!aObj) {
     MOZ_CRASH();
   }
 
   nsWrapperCache* cache = nullptr;
   CallQueryInterface(native, &cache);
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -130,32 +130,32 @@ static_assert(DOM_OBJECT_SLOT == 0,
               "Expect bad things");
 template <class T>
 inline T*
 UnwrapDOMObject(JSObject* obj)
 {
   MOZ_ASSERT(IsDOMClass(js::GetObjectClass(obj)),
              "Don't pass non-DOM objects to this function");
 
-  JS::Value val = js::GetReservedOrProxyPrivateSlot(obj, DOM_OBJECT_SLOT);
+  JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
   return static_cast<T*>(val.toPrivate());
 }
 
 template <class T>
 inline T*
 UnwrapPossiblyNotInitializedDOMObject(JSObject* obj)
 {
   // This is used by the OjectMoved JSClass hook which can be called before
   // JS_NewObject has returned and so before we have a chance to set
   // DOM_OBJECT_SLOT to anything useful.
 
   MOZ_ASSERT(IsDOMClass(js::GetObjectClass(obj)),
              "Don't pass non-DOM objects to this function");
 
-  JS::Value val = js::GetReservedOrProxyPrivateSlot(obj, DOM_OBJECT_SLOT);
+  JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
   if (val.isUndefined()) {
     return nullptr;
   }
   return static_cast<T*>(val.toPrivate());
 }
 
 inline const DOMJSClass*
 GetDOMClass(const js::Class* clasp)
@@ -2674,33 +2674,33 @@ public:
   explicit BindingJSObjectCreator(JSContext* aCx)
     : mReflector(aCx)
   {
   }
 
   ~BindingJSObjectCreator()
   {
     if (mReflector) {
-      js::SetReservedOrProxyPrivateSlot(mReflector, DOM_OBJECT_SLOT,
-                                        JS::UndefinedValue());
+      js::SetReservedSlot(mReflector, DOM_OBJECT_SLOT, JS::UndefinedValue());
     }
   }
 
   void
   CreateProxyObject(JSContext* aCx, const js::Class* aClass,
                     const DOMProxyHandler* aHandler,
                     JS::Handle<JSObject*> aProto, T* aNative,
+                    JS::Handle<JS::Value> aExpandoValue,
                     JS::MutableHandle<JSObject*> aReflector)
   {
     js::ProxyOptions options;
     options.setClass(aClass);
-    JS::Rooted<JS::Value> proxyPrivateVal(aCx, JS::PrivateValue(aNative));
-    aReflector.set(js::NewProxyObject(aCx, aHandler, proxyPrivateVal, aProto,
+    aReflector.set(js::NewProxyObject(aCx, aHandler, aExpandoValue, aProto,
                                       options));
     if (aReflector) {
+      js::SetProxyReservedSlot(aReflector, DOM_OBJECT_SLOT, JS::PrivateValue(aNative));
       mNative = aNative;
       mReflector = aReflector;
     }
   }
 
   void
   CreateObject(JSContext* aCx, const JSClass* aClass,
                JS::Handle<JSObject*> aProto,
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3494,31 +3494,31 @@ class CGConstructorEnabled(CGAbstractMet
 
 
 def CreateBindingJSObject(descriptor, properties):
     objDecl = "BindingJSObjectCreator<%s> creator(aCx);\n" % descriptor.nativeType
 
     # We don't always need to root obj, but there are a variety
     # of cases where we do, so for simplicity, just always root it.
     if descriptor.proxy:
-        create = dedent(
-            """
+        if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+            expandoValue = "JS::PrivateValue(&aObject->mExpandoAndGeneration)"
+        else:
+            expandoValue = "JS::UndefinedValue()"
+        create = fill(
+            """
+            JS::Rooted<JS::Value> expandoValue(aCx, ${expandoValue});
             creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
-                                      proto, aObject, aReflector);
+                                      proto, aObject, expandoValue, aReflector);
             if (!aReflector) {
               return false;
             }
 
-            """)
-        if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
-            create += dedent("""
-                js::SetProxyExtra(aReflector, JSPROXYSLOT_EXPANDO,
-                                  JS::PrivateValue(&aObject->mExpandoAndGeneration));
-
-                """)
+            """,
+            expandoValue=expandoValue)
     else:
         create = dedent(
             """
             creator.CreateObject(aCx, sClass.ToJSClass(), proto, aObject, aReflector);
             if (!aReflector) {
               return false;
             }
             """)
@@ -11502,17 +11502,17 @@ class CGProxyUnwrap(CGAbstractMethod):
         return fill(
             """
             MOZ_ASSERT(js::IsProxy(obj));
             if (js::GetProxyHandler(obj) != DOMProxyHandler::getInstance()) {
               MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(obj));
               obj = js::UncheckedUnwrap(obj);
             }
             MOZ_ASSERT(IsProxy(obj));
-            return static_cast<${type}*>(js::GetProxyPrivate(obj).toPrivate());
+            return static_cast<${type}*>(js::GetProxyReservedSlot(obj, DOM_OBJECT_SLOT).toPrivate());
             """,
             type=self.descriptor.nativeType)
 
 
 class CGDOMJSProxyHandler_getOwnPropDescriptor(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -29,17 +29,17 @@ DefineStaticJSVals(JSContext* cx)
 }
 
 const char DOMProxyHandler::family = 0;
 
 js::DOMProxyShadowsResult
 DOMProxyShadows(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id)
 {
   JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
-  JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO);
+  JS::Value v = js::GetProxyPrivate(proxy);
   bool isOverrideBuiltins = !v.isObject() && !v.isUndefined();
   if (expando) {
     bool hasOwn;
     if (!JS_AlreadyHasOwnPropertyById(cx, expando, id, &hasOwn))
       return js::ShadowCheckFailed;
 
     if (hasOwn) {
       return isOverrideBuiltins ?
@@ -59,28 +59,28 @@ DOMProxyShadows(JSContext* cx, JS::Handl
   return hasOwn ? js::Shadows : js::DoesntShadowUnique;
 }
 
 // Store the information for the specialized ICs.
 struct SetDOMProxyInformation
 {
   SetDOMProxyInformation() {
     js::SetDOMProxyInformation((const void*) &DOMProxyHandler::family,
-                               JSPROXYSLOT_EXPANDO, DOMProxyShadows);
+                               DOMProxyShadows);
   }
 };
 
 SetDOMProxyInformation gSetDOMProxyInformation;
 
 // static
 void
 DOMProxyHandler::ClearExternalRefsForWrapperRelease(JSObject* obj)
 {
   MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
-  JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+  JS::Value v = js::GetProxyPrivate(obj);
   if (v.isUndefined()) {
     // No expando.
     return;
   }
 
   // See EnsureExpandoObject for the work we're trying to undo here.
 
   if (v.isObject()) {
@@ -97,23 +97,23 @@ DOMProxyHandler::ClearExternalRefsForWra
   expandoAndGeneration->expando = UndefinedValue();
 }
 
 // static
 JSObject*
 DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj)
 {
   MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
-  JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+  JS::Value v = js::GetProxyPrivate(obj);
   if (v.isUndefined()) {
     return nullptr;
   }
 
   if (v.isObject()) {
-    js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, UndefinedValue());
+    js::SetProxyPrivate(obj, UndefinedValue());
     xpc::ObjectScope(obj)->RemoveDOMExpandoObject(obj);
   } else {
     js::ExpandoAndGeneration* expandoAndGeneration =
       static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
     v = expandoAndGeneration->expando;
     if (v.isUndefined()) {
       return nullptr;
     }
@@ -137,17 +137,17 @@ DOMProxyHandler::GetAndClearExpandoObjec
   return &v.toObject();
 }
 
 // static
 JSObject*
 DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JS::Handle<JSObject*> obj)
 {
   NS_ASSERTION(IsDOMProxy(obj), "expected a DOM proxy object");
-  JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+  JS::Value v = js::GetProxyPrivate(obj);
   if (v.isObject()) {
     return &v.toObject();
   }
 
   js::ExpandoAndGeneration* expandoAndGeneration;
   if (!v.isUndefined()) {
     expandoAndGeneration = static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
     if (expandoAndGeneration->expando.isObject()) {
@@ -173,17 +173,17 @@ DOMProxyHandler::EnsureExpandoObject(JSC
     return expando;
   }
 
   if (!xpc::ObjectScope(obj)->RegisterDOMExpandoObject(obj)) {
     return nullptr;
   }
 
   cache->SetPreservingWrapper(true);
-  js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando));
+  js::SetProxyPrivate(obj, ObjectValue(*expando));
 
   return expando;
 }
 
 bool
 DOMProxyHandler::preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
                                    JS::ObjectOpResult& result) const
 {
@@ -317,17 +317,17 @@ DOMProxyHandler::setCustom(JSContext* cx
   return true;
 }
 
 //static
 JSObject *
 DOMProxyHandler::GetExpandoObject(JSObject *obj)
 {
   MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
-  JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+  JS::Value v = js::GetProxyPrivate(obj);
   if (v.isObject()) {
     return &v.toObject();
   }
 
   if (v.isUndefined()) {
     return nullptr;
   }
 
@@ -338,24 +338,22 @@ DOMProxyHandler::GetExpandoObject(JSObje
 }
 
 void
 ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const
 {
   DOMProxyHandler::trace(trc, proxy);
 
   MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
-  JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO);
+  JS::Value v = js::GetProxyPrivate(proxy);
   MOZ_ASSERT(!v.isObject(), "Should not have expando object directly!");
 
-  if (v.isUndefined()) {
-    // This can happen if we GC while creating our object, before we get a
-    // chance to set up its JSPROXYSLOT_EXPANDO slot.
-    return;
-  }
+  // The proxy's private slot is set when we allocate the proxy,
+  // so it cannot be |undefined|.
+  MOZ_ASSERT(!v.isUndefined());
 
   js::ExpandoAndGeneration* expandoAndGeneration =
     static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
   JS::TraceEdge(trc, &expandoAndGeneration->expando,
                 "Shadowing DOM proxy expando");
 }
 
 } // namespace dom
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -9,45 +9,39 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Likely.h"
 
 #include "jsapi.h"
 #include "js/Proxy.h"
 #include "nsString.h"
 
-#define DOM_PROXY_OBJECT_SLOT js::PROXY_PRIVATE_SLOT
-
 namespace mozilla {
 namespace dom {
 
-enum {
-  /**
-   * DOM proxies have an extra slot for the expando object at index
-   * JSPROXYSLOT_EXPANDO.
-   *
-   * The expando object is a plain JSObject whose properties correspond to
-   * "expandos" (custom properties set by the script author).
-   *
-   * The exact value stored in the JSPROXYSLOT_EXPANDO slot depends on whether
-   * the interface is annotated with the [OverrideBuiltins] extended attribute.
-   *
-   * If it is, the proxy is initialized with a PrivateValue, which contains a
-   * pointer to a js::ExpandoAndGeneration object; this contains a pointer to
-   * the actual expando object as well as the "generation" of the object.  The
-   * proxy handler will trace the expando object stored in the
-   * js::ExpandoAndGeneration while the proxy itself is alive.
-   *
-   * If it is not, the proxy is initialized with an UndefinedValue. In
-   * EnsureExpandoObject, it is set to an ObjectValue that points to the
-   * expando object directly. (It is set back to an UndefinedValue only when
-   * the object is about to die.)
-   */
-  JSPROXYSLOT_EXPANDO = 0
-};
+/**
+ * DOM proxies store the expando object in the private slot.
+ *
+ * The expando object is a plain JSObject whose properties correspond to
+ * "expandos" (custom properties set by the script author).
+ *
+ * The exact value stored in the proxy's private slot depends on whether the
+ * interface is annotated with the [OverrideBuiltins] extended attribute.
+ *
+ * If it is, the proxy is initialized with a PrivateValue, which contains a
+ * pointer to a js::ExpandoAndGeneration object; this contains a pointer to
+ * the actual expando object as well as the "generation" of the object.  The
+ * proxy handler will trace the expando object stored in the
+ * js::ExpandoAndGeneration while the proxy itself is alive.
+ *
+ * If it is not, the proxy is initialized with an UndefinedValue. In
+ * EnsureExpandoObject, it is set to an ObjectValue that points to the
+ * expando object directly. (It is set back to an UndefinedValue only when
+ * the object is about to die.)
+ */
 
 template<typename T> struct Prefable;
 
 class BaseDOMProxyHandler : public js::BaseProxyHandler
 {
 public:
   explicit constexpr BaseDOMProxyHandler(const void* aProxyFamily, bool aHasPrototype = false)
     : js::BaseProxyHandler(aProxyFamily, aHasPrototype)
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -54,24 +54,24 @@ WrapperOwner::WrapperOwner()
   : inactive_(false)
 {
 }
 
 static inline AuxCPOWData*
 AuxCPOWDataOf(JSObject* obj)
 {
     MOZ_ASSERT(IsCPOW(obj));
-    return static_cast<AuxCPOWData*>(GetProxyExtra(obj, 1).toPrivate());
+    return static_cast<AuxCPOWData*>(GetProxyReservedSlot(obj, 1).toPrivate());
 }
 
 static inline WrapperOwner*
 OwnerOf(JSObject* obj)
 {
     MOZ_ASSERT(IsCPOW(obj));
-    return reinterpret_cast<WrapperOwner*>(GetProxyExtra(obj, 0).toPrivate());
+    return reinterpret_cast<WrapperOwner*>(GetProxyReservedSlot(obj, 0).toPrivate());
 }
 
 ObjectId
 WrapperOwner::idOfUnchecked(JSObject* obj)
 {
     MOZ_ASSERT(IsCPOW(obj));
 
     AuxCPOWData* aux = AuxCPOWDataOf(obj);
@@ -1213,18 +1213,18 @@ WrapperOwner::fromRemoteObjectVariant(JS
         incref();
 
         AuxCPOWData* aux = new AuxCPOWData(objId,
                                            objVar.isCallable(),
                                            objVar.isConstructor(),
                                            objVar.isDOMObject(),
                                            objVar.objectTag());
 
-        SetProxyExtra(obj, 0, PrivateValue(this));
-        SetProxyExtra(obj, 1, PrivateValue(aux));
+        SetProxyReservedSlot(obj, 0, PrivateValue(this));
+        SetProxyReservedSlot(obj, 1, PrivateValue(aux));
     }
 
     if (!JS_WrapObject(cx, &obj))
         return nullptr;
     return obj;
 }
 
 JSObject*
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -362,52 +362,97 @@ class JS_FRIEND_API(BaseProxyHandler)
 extern JS_FRIEND_DATA(const js::Class* const) ProxyClassPtr;
 
 inline bool IsProxy(const JSObject* obj)
 {
     return GetObjectClass(obj)->isProxy();
 }
 
 namespace detail {
-const uint32_t PROXY_EXTRA_SLOTS = 2;
 
-// Layout of the values stored by a proxy. Note that API clients require the
-// private slot to be the first slot in the proxy's values, so that the private
-// slot can be accessed in the same fashion as the first reserved slot, via
-// {Get,Set}ReservedOrProxyPrivateSlot.
+// Proxy slot layout
+// -----------------
+//
+// Every proxy has a ProxyValueArray that contains the following Values:
+//
+// - The private slot.
+// - The reserved slots. The number of slots is determined by the proxy's Class.
+//
+// Proxy objects store a pointer to the reserved slots (ProxyReservedSlots*).
+// The ProxyValueArray and the private slot can be accessed using
+// ProxyValueArray::fromReservedSlots or ProxyDataLayout::values.
+//
+// Storing a pointer to ProxyReservedSlots instead of ProxyValueArray has a
+// number of advantages. In particular, it means js::GetReservedSlot and
+// js::SetReservedSlot can be used with both proxies and native objects. This
+// works because the ProxyReservedSlots* pointer is stored where native objects
+// store their dynamic slots pointer.
+
+struct ProxyReservedSlots
+{
+    Value slots[1];
+
+    static inline int offsetOfPrivateSlot();
+
+    void init(size_t nreserved) {
+        for (size_t i = 0; i < nreserved; i++)
+            slots[i] = JS::UndefinedValue();
+    }
+
+    ProxyReservedSlots(const ProxyReservedSlots&) = delete;
+    void operator=(const ProxyReservedSlots&) = delete;
+};
 
 struct ProxyValueArray
 {
     Value privateSlot;
-    Value extraSlots[PROXY_EXTRA_SLOTS];
+    ProxyReservedSlots reservedSlots;
 
-    ProxyValueArray()
-      : privateSlot(JS::UndefinedValue())
-    {
-        for (size_t i = 0; i < PROXY_EXTRA_SLOTS; i++)
-            extraSlots[i] = JS::UndefinedValue();
+    void init(size_t nreserved) {
+        privateSlot = JS::UndefinedValue();
+        reservedSlots.init(nreserved);
     }
 
-    static size_t offsetOfPrivateSlot() {
-        return offsetof(ProxyValueArray, privateSlot);
+    static size_t sizeOf(size_t nreserved) {
+        return offsetOfReservedSlots() + nreserved * sizeof(Value);
+    }
+    static MOZ_ALWAYS_INLINE ProxyValueArray* fromReservedSlots(ProxyReservedSlots* slots) {
+        uintptr_t p = reinterpret_cast<uintptr_t>(slots);
+        return reinterpret_cast<ProxyValueArray*>(p - offsetOfReservedSlots());
+    }
+    static size_t offsetOfReservedSlots() {
+        return offsetof(ProxyValueArray, reservedSlots);
     }
+
+    ProxyValueArray(const ProxyValueArray&) = delete;
+    void operator=(const ProxyValueArray&) = delete;
 };
 
+/* static */ inline int
+ProxyReservedSlots::offsetOfPrivateSlot()
+{
+    return -int(ProxyValueArray::offsetOfReservedSlots()) + offsetof(ProxyValueArray, privateSlot);
+}
+
 // All proxies share the same data layout. Following the object's shape and
 // type, the proxy has a ProxyDataLayout structure with a pointer to an array
 // of values and the proxy's handler. This is designed both so that proxies can
 // be easily swapped with other objects (via RemapWrapper) and to mimic the
 // layout of other objects (proxies and other objects have the same size) so
 // that common code can access either type of object.
 //
 // See GetReservedOrProxyPrivateSlot below.
 struct ProxyDataLayout
 {
-    ProxyValueArray* values;
+    ProxyReservedSlots* reservedSlots;
     const BaseProxyHandler* handler;
+
+    MOZ_ALWAYS_INLINE ProxyValueArray* values() const {
+        return ProxyValueArray::fromReservedSlots(reservedSlots);
+    }
 };
 
 const uint32_t ProxyDataOffset = 2 * sizeof(void*);
 
 inline ProxyDataLayout*
 GetProxyDataLayout(JSObject* obj)
 {
     MOZ_ASSERT(IsProxy(obj));
@@ -427,80 +472,72 @@ inline const BaseProxyHandler*
 GetProxyHandler(const JSObject* obj)
 {
     return detail::GetProxyDataLayout(obj)->handler;
 }
 
 inline const Value&
 GetProxyPrivate(const JSObject* obj)
 {
-    return detail::GetProxyDataLayout(obj)->values->privateSlot;
+    return detail::GetProxyDataLayout(obj)->values()->privateSlot;
 }
 
 inline JSObject*
 GetProxyTargetObject(JSObject* obj)
 {
     return GetProxyPrivate(obj).toObjectOrNull();
 }
 
 inline const Value&
-GetProxyExtra(const JSObject* obj, size_t n)
+GetProxyReservedSlot(const JSObject* obj, size_t n)
 {
-    MOZ_ASSERT(n < detail::PROXY_EXTRA_SLOTS);
-    return detail::GetProxyDataLayout(obj)->values->extraSlots[n];
+    MOZ_ASSERT(n < JSCLASS_RESERVED_SLOTS(GetObjectClass(obj)));
+    return detail::GetProxyDataLayout(obj)->reservedSlots->slots[n];
 }
 
 inline void
 SetProxyHandler(JSObject* obj, const BaseProxyHandler* handler)
 {
     detail::GetProxyDataLayout(obj)->handler = handler;
 }
 
 JS_FRIEND_API(void)
 SetValueInProxy(Value* slot, const Value& value);
 
 inline void
-SetProxyExtra(JSObject* obj, size_t n, const Value& extra)
+SetProxyReservedSlot(JSObject* obj, size_t n, const Value& extra)
 {
-    MOZ_ASSERT(n < detail::PROXY_EXTRA_SLOTS);
-    Value* vp = &detail::GetProxyDataLayout(obj)->values->extraSlots[n];
+    MOZ_ASSERT(n < JSCLASS_RESERVED_SLOTS(GetObjectClass(obj)));
+    Value* vp = &detail::GetProxyDataLayout(obj)->reservedSlots->slots[n];
 
     // Trigger a barrier before writing the slot.
     if (vp->isGCThing() || extra.isGCThing())
         SetValueInProxy(vp, extra);
     else
         *vp = extra;
 }
 
+inline void
+SetProxyPrivate(JSObject* obj, const Value& value)
+{
+    Value* vp = &detail::GetProxyDataLayout(obj)->values()->privateSlot;
+
+    // Trigger a barrier before writing the slot.
+    if (vp->isGCThing() || value.isGCThing())
+        SetValueInProxy(vp, value);
+    else
+        *vp = value;
+}
+
 inline bool
 IsScriptedProxy(const JSObject* obj)
 {
     return IsProxy(obj) && GetProxyHandler(obj)->isScripted();
 }
 
-inline const Value&
-GetReservedOrProxyPrivateSlot(const JSObject* obj, size_t slot)
-{
-    MOZ_ASSERT(slot == 0);
-    MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(GetObjectClass(obj)) || IsProxy(obj));
-    return reinterpret_cast<const shadow::Object*>(obj)->slotRef(slot);
-}
-
-inline void
-SetReservedOrProxyPrivateSlot(JSObject* obj, size_t slot, const Value& value)
-{
-    MOZ_ASSERT(slot == 0);
-    MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(GetObjectClass(obj)) || IsProxy(obj));
-    shadow::Object* sobj = reinterpret_cast<shadow::Object*>(obj);
-    if (sobj->slotRef(slot).isGCThing() || value.isGCThing())
-        SetReservedOrProxyPrivateSlotWithBarrier(obj, slot, value);
-    else
-        sobj->slotRef(slot) = value;
-}
-
 class MOZ_STACK_CLASS ProxyOptions {
   protected:
     /* protected constructor for subclass */
     explicit ProxyOptions(bool singletonArg, bool lazyProtoArg = false)
       : singleton_(singletonArg),
         lazyProto_(lazyProtoArg),
         clasp_(ProxyClassPtr)
     {}
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -1821,18 +1821,18 @@ BaselineCacheIRCompiler::emitLoadDOMExpa
 
     AutoScratchRegister scratch(allocator, masm);
     ValueOperand output = allocator.defineValueRegister(masm, reader.valOperandId());
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
-    masm.loadPtr(Address(obj, ProxyObject::offsetOfValues()), scratch);
-    Address expandoAddr(scratch, ProxyObject::offsetOfExtraSlotInValues(GetDOMProxyExpandoSlot()));
+    masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch);
+    Address expandoAddr(scratch, detail::ProxyReservedSlots::offsetOfPrivateSlot());
 
     // Load the ExpandoAndGeneration* in the output scratch register and guard
     // it matches the proxy's ExpandoAndGeneration.
     masm.loadPtr(expandoAndGenerationAddr, output.scratchReg());
     masm.branchPrivatePtr(Assembler::NotEqual, expandoAddr, output.scratchReg(), failure->label());
 
     // Guard expandoAndGeneration->generation matches the expected generation.
     masm.branch64(Assembler::NotEqual,
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -798,17 +798,17 @@ IRGenerator::guardDOMProxyExpandoObjectA
     return expandoObjId;
 }
 
 bool
 GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id)
 {
     MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
-    RootedValue expandoVal(cx_, GetProxyExtra(obj, GetDOMProxyExpandoSlot()));
+    RootedValue expandoVal(cx_, GetProxyPrivate(obj));
     RootedObject expandoObj(cx_);
     if (expandoVal.isObject()) {
         expandoObj = &expandoVal.toObject();
     } else {
         MOZ_ASSERT(!expandoVal.isUndefined(),
                    "How did a missing expando manage to shadow things?");
         auto expandoAndGeneration = static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
         MOZ_ASSERT(expandoAndGeneration);
@@ -868,17 +868,17 @@ GetPropIRGenerator::tryAttachDOMProxySha
 // Callers are expected to have already guarded on the shape of the
 // object, which guarantees the object is a DOM proxy.
 static void
 CheckDOMProxyExpandoDoesNotShadow(CacheIRWriter& writer, JSObject* obj, jsid id,
                                   ObjOperandId objId)
 {
     MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
-    Value expandoVal = GetProxyExtra(obj, GetDOMProxyExpandoSlot());
+    Value expandoVal = GetProxyPrivate(obj);
 
     ValOperandId expandoId;
     if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
         auto expandoAndGeneration = static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
         expandoId = writer.loadDOMExpandoValueGuardGeneration(objId, expandoAndGeneration);
         expandoVal = expandoAndGeneration->expando;
     } else {
         expandoId = writer.loadDOMExpandoValue(objId);
@@ -3159,17 +3159,17 @@ SetPropIRGenerator::tryAttachDOMProxyUns
 }
 
 bool
 SetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id,
                                              ValOperandId rhsId)
 {
     MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
-    RootedValue expandoVal(cx_, GetProxyExtra(obj, GetDOMProxyExpandoSlot()));
+    RootedValue expandoVal(cx_, GetProxyPrivate(obj));
     RootedObject expandoObj(cx_);
     if (expandoVal.isObject()) {
         expandoObj = &expandoVal.toObject();
     } else {
         MOZ_ASSERT(!expandoVal.isUndefined(),
                    "How did a missing expando manage to shadow things?");
         auto expandoAndGeneration = static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
         MOZ_ASSERT(expandoAndGeneration);
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1571,44 +1571,44 @@ CacheIRCompiler::emitLoadEnclosingEnviro
 }
 
 bool
 CacheIRCompiler::emitLoadWrapperTarget()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     Register reg = allocator.defineRegister(masm, reader.objOperandId());
 
-    masm.loadPtr(Address(obj, ProxyObject::offsetOfValues()), reg);
-    masm.unboxObject(Address(reg, detail::ProxyValueArray::offsetOfPrivateSlot()), reg);
+    masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), reg);
+    masm.unboxObject(Address(reg, detail::ProxyReservedSlots::offsetOfPrivateSlot()), reg);
     return true;
 }
 
 bool
 CacheIRCompiler::emitLoadDOMExpandoValue()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     ValueOperand val = allocator.defineValueRegister(masm, reader.valOperandId());
 
-    masm.loadPtr(Address(obj, ProxyObject::offsetOfValues()), val.scratchReg());
+    masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), val.scratchReg());
     masm.loadValue(Address(val.scratchReg(),
-                           ProxyObject::offsetOfExtraSlotInValues(GetDOMProxyExpandoSlot())),
+                           detail::ProxyReservedSlots::offsetOfPrivateSlot()),
                    val);
     return true;
 }
 
 bool
 CacheIRCompiler::emitLoadDOMExpandoValueIgnoreGeneration()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     ValueOperand output = allocator.defineValueRegister(masm, reader.valOperandId());
 
     // Determine the expando's Address.
     Register scratch = output.scratchReg();
-    masm.loadPtr(Address(obj, ProxyObject::offsetOfValues()), scratch);
-    Address expandoAddr(scratch, ProxyObject::offsetOfExtraSlotInValues(GetDOMProxyExpandoSlot()));
+    masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch);
+    Address expandoAddr(scratch, detail::ProxyReservedSlots::offsetOfPrivateSlot());
 
 #ifdef DEBUG
     // Private values are stored as doubles, so assert we have a double.
     Label ok;
     masm.branchTestDouble(Assembler::Equal, expandoAddr, &ok);
     masm.assumeUnreachable("DOM expando is not a PrivateValue!");
     masm.bind(&ok);
 #endif
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -1980,18 +1980,18 @@ IonCacheIRCompiler::emitLoadDOMExpandoVa
     AutoScratchRegister scratch1(allocator, masm);
     AutoScratchRegister scratch2(allocator, masm);
     ValueOperand output = allocator.defineValueRegister(masm, reader.valOperandId());
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
-    masm.loadPtr(Address(obj, ProxyObject::offsetOfValues()), scratch1);
-    Address expandoAddr(scratch1, ProxyObject::offsetOfExtraSlotInValues(GetDOMProxyExpandoSlot()));
+    masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch1);
+    Address expandoAddr(scratch1, detail::ProxyReservedSlots::offsetOfPrivateSlot());
 
     // Guard the ExpandoAndGeneration* matches the proxy's ExpandoAndGeneration.
     masm.loadValue(expandoAddr, output);
     masm.branchTestValue(Assembler::NotEqual, output, PrivateValue(expandoAndGeneration),
                          failure->label());
 
     // Guard expandoAndGeneration->generation matches the expected generation.
     masm.movePtr(ImmPtr(expandoAndGeneration), output.scratchReg());
--- a/js/src/jsapi-tests/testBug604087.cpp
+++ b/js/src/jsapi-tests/testBug604087.cpp
@@ -69,25 +69,25 @@ BEGIN_TEST(testBug604087)
                                                          JS::FireOnNewGlobalHook, globalOptions));
     CHECK(compartment3 != nullptr);
     JS::RootedObject compartment4(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
                                                          JS::FireOnNewGlobalHook, globalOptions));
     CHECK(compartment4 != nullptr);
 
     JS::RootedObject c2wrapper(cx, wrap(cx, outerObj, compartment2));
     CHECK(c2wrapper);
-    c2wrapper->as<js::ProxyObject>().setExtra(0, js::Int32Value(2));
+    c2wrapper->as<js::ProxyObject>().setReservedSlot(0, js::Int32Value(2));
 
     JS::RootedObject c3wrapper(cx, wrap(cx, outerObj, compartment3));
     CHECK(c3wrapper);
-    c3wrapper->as<js::ProxyObject>().setExtra(0, js::Int32Value(3));
+    c3wrapper->as<js::ProxyObject>().setReservedSlot(0, js::Int32Value(3));
 
     JS::RootedObject c4wrapper(cx, wrap(cx, outerObj, compartment4));
     CHECK(c4wrapper);
-    c4wrapper->as<js::ProxyObject>().setExtra(0, js::Int32Value(4));
+    c4wrapper->as<js::ProxyObject>().setReservedSlot(0, js::Int32Value(4));
     compartment4 = c4wrapper = nullptr;
 
     JS::RootedObject next(cx);
     {
         JSAutoCompartment ac(cx, compartment2);
         next = js::Wrapper::New(cx, compartment2, &js::Wrapper::singleton, options);
         CHECK(next);
     }
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -541,24 +541,22 @@ JS_FRIEND_API(bool)
 js::GetOriginalEval(JSContext* cx, HandleObject scope, MutableHandleObject eval)
 {
     assertSameCompartment(cx, scope);
     Rooted<GlobalObject*> global(cx, &scope->global());
     return GlobalObject::getOrCreateEval(cx, global, eval);
 }
 
 JS_FRIEND_API(void)
-js::SetReservedOrProxyPrivateSlotWithBarrier(JSObject* obj, size_t slot, const js::Value& value)
+js::SetReservedSlotWithBarrier(JSObject* obj, size_t slot, const js::Value& value)
 {
-    if (IsProxy(obj)) {
-        MOZ_ASSERT(slot == 0);
-        obj->as<ProxyObject>().setSameCompartmentPrivate(value);
-    } else {
+    if (IsProxy(obj))
+        obj->as<ProxyObject>().setReservedSlot(slot, value);
+    else
         obj->as<NativeObject>().setSlot(slot, value);
-    }
 }
 
 void
 js::SetPreserveWrapperCallback(JSContext* cx, PreserveWrapperCallback callback)
 {
     cx->runtime()->preserveWrapperCallback = callback;
 }
 
@@ -1293,40 +1291,32 @@ js::SetDOMCallbacks(JSContext* cx, const
 
 JS_FRIEND_API(const DOMCallbacks*)
 js::GetDOMCallbacks(JSContext* cx)
 {
     return cx->runtime()->DOMcallbacks;
 }
 
 static const void* gDOMProxyHandlerFamily = nullptr;
-static uint32_t gDOMProxyExpandoSlot = 0;
 static DOMProxyShadowsCheck gDOMProxyShadowsCheck;
 
 JS_FRIEND_API(void)
-js::SetDOMProxyInformation(const void* domProxyHandlerFamily, uint32_t domProxyExpandoSlot,
+js::SetDOMProxyInformation(const void* domProxyHandlerFamily,
                            DOMProxyShadowsCheck domProxyShadowsCheck)
 {
     gDOMProxyHandlerFamily = domProxyHandlerFamily;
-    gDOMProxyExpandoSlot = domProxyExpandoSlot;
     gDOMProxyShadowsCheck = domProxyShadowsCheck;
 }
 
 const void*
 js::GetDOMProxyHandlerFamily()
 {
     return gDOMProxyHandlerFamily;
 }
 
-uint32_t
-js::GetDOMProxyExpandoSlot()
-{
-    return gDOMProxyExpandoSlot;
-}
-
 DOMProxyShadowsCheck
 js::GetDOMProxyShadowsCheck()
 {
     return gDOMProxyShadowsCheck;
 }
 
 bool
 js::detail::IdMatchesAtom(jsid id, JSAtom* atom)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -360,16 +360,17 @@ extern JS_FRIEND_DATA(const js::ObjectOp
     }
 
 #define PROXY_CLASS_WITH_EXT(name, flags, extPtr)                                       \
     {                                                                                   \
         name,                                                                           \
         js::Class::NON_NATIVE |                                                         \
             JSCLASS_IS_PROXY |                                                          \
             JSCLASS_DELAY_METADATA_BUILDER |                                            \
+            JSCLASS_HAS_RESERVED_SLOTS(2) |                                             \
             flags,                                                                      \
         &js::ProxyClassOps,                                                             \
         JS_NULL_CLASS_SPEC,                                                             \
         extPtr,                                                                         \
         &js::ProxyObjectOps                                                             \
     }
 
 #define PROXY_CLASS_DEF(name, flags) \
@@ -717,33 +718,43 @@ inline void*
 GetObjectPrivate(JSObject* obj)
 {
     MOZ_ASSERT(GetObjectClass(obj)->flags & JSCLASS_HAS_PRIVATE);
     const shadow::Object* nobj = reinterpret_cast<const shadow::Object*>(obj);
     void** addr = reinterpret_cast<void**>(&nobj->fixedSlots()[nobj->numFixedSlots()]);
     return *addr;
 }
 
+/**
+ * Get the value stored in an object's reserved slot. This can be used with
+ * both native objects and proxies, but if |obj| is known to be a proxy
+ * GetProxyReservedSlot is a bit more efficient.
+ */
 inline const JS::Value&
 GetReservedSlot(JSObject* obj, size_t slot)
 {
     MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(GetObjectClass(obj)));
     return reinterpret_cast<const shadow::Object*>(obj)->slotRef(slot);
 }
 
 JS_FRIEND_API(void)
-SetReservedOrProxyPrivateSlotWithBarrier(JSObject* obj, size_t slot, const JS::Value& value);
-
+SetReservedSlotWithBarrier(JSObject* obj, size_t slot, const JS::Value& value);
+
+/**
+ * Store a value in an object's reserved slot. This can be used with
+ * both native objects and proxies, but if |obj| is known to be a proxy
+ * SetProxyReservedSlot is a bit more efficient.
+ */
 inline void
 SetReservedSlot(JSObject* obj, size_t slot, const JS::Value& value)
 {
     MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(GetObjectClass(obj)));
     shadow::Object* sobj = reinterpret_cast<shadow::Object*>(obj);
     if (sobj->slotRef(slot).isGCThing() || value.isGCThing())
-        SetReservedOrProxyPrivateSlotWithBarrier(obj, slot, value);
+        SetReservedSlotWithBarrier(obj, slot, value);
     else
         sobj->slotRef(slot) = value;
 }
 
 JS_FRIEND_API(uint32_t)
 GetObjectSlotSpan(JSObject* obj);
 
 inline const JS::Value&
@@ -1284,21 +1295,20 @@ typedef enum DOMProxyShadowsResult {
   DoesntShadow,
   DoesntShadowUnique,
   ShadowsViaDirectExpando,
   ShadowsViaIndirectExpando
 } DOMProxyShadowsResult;
 typedef DOMProxyShadowsResult
 (* DOMProxyShadowsCheck)(JSContext* cx, JS::HandleObject object, JS::HandleId id);
 JS_FRIEND_API(void)
-SetDOMProxyInformation(const void* domProxyHandlerFamily, uint32_t domProxyExpandoSlot,
+SetDOMProxyInformation(const void* domProxyHandlerFamily,
                        DOMProxyShadowsCheck domProxyShadowsCheck);
 
 const void* GetDOMProxyHandlerFamily();
-uint32_t GetDOMProxyExpandoSlot();
 DOMProxyShadowsCheck GetDOMProxyShadowsCheck();
 inline bool DOMProxyIsShadowing(DOMProxyShadowsResult result) {
     return result == Shadows ||
            result == ShadowsViaDirectExpando ||
            result == ShadowsViaIndirectExpando;
 }
 
 /* Implemented in jsdate.cpp. */
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -4672,28 +4672,28 @@ GCRuntime::getNextSweepGroup()
 static bool
 IsGrayListObject(JSObject* obj)
 {
     MOZ_ASSERT(obj);
     return obj->is<CrossCompartmentWrapperObject>() && !IsDeadProxyObject(obj);
 }
 
 /* static */ unsigned
-ProxyObject::grayLinkExtraSlot(JSObject* obj)
+ProxyObject::grayLinkReservedSlot(JSObject* obj)
 {
     MOZ_ASSERT(IsGrayListObject(obj));
     return 1;
 }
 
 #ifdef DEBUG
 static void
 AssertNotOnGrayList(JSObject* obj)
 {
     MOZ_ASSERT_IF(IsGrayListObject(obj),
-                  GetProxyExtra(obj, ProxyObject::grayLinkExtraSlot(obj)).isUndefined());
+                  GetProxyReservedSlot(obj, ProxyObject::grayLinkReservedSlot(obj)).isUndefined());
 }
 #endif
 
 static void
 AssertNoWrappersInGrayList(JSRuntime* rt)
 {
 #ifdef DEBUG
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
@@ -4711,41 +4711,41 @@ CrossCompartmentPointerReferent(JSObject
 {
     MOZ_ASSERT(IsGrayListObject(obj));
     return &obj->as<ProxyObject>().private_().toObject();
 }
 
 static JSObject*
 NextIncomingCrossCompartmentPointer(JSObject* prev, bool unlink)
 {
-    unsigned slot = ProxyObject::grayLinkExtraSlot(prev);
-    JSObject* next = GetProxyExtra(prev, slot).toObjectOrNull();
+    unsigned slot = ProxyObject::grayLinkReservedSlot(prev);
+    JSObject* next = GetProxyReservedSlot(prev, slot).toObjectOrNull();
     MOZ_ASSERT_IF(next, IsGrayListObject(next));
 
     if (unlink)
-        SetProxyExtra(prev, slot, UndefinedValue());
+        SetProxyReservedSlot(prev, slot, UndefinedValue());
 
     return next;
 }
 
 void
 js::DelayCrossCompartmentGrayMarking(JSObject* src)
 {
     MOZ_ASSERT(IsGrayListObject(src));
 
     /* Called from MarkCrossCompartmentXXX functions. */
-    unsigned slot = ProxyObject::grayLinkExtraSlot(src);
+    unsigned slot = ProxyObject::grayLinkReservedSlot(src);
     JSObject* dest = CrossCompartmentPointerReferent(src);
     JSCompartment* comp = dest->compartment();
 
-    if (GetProxyExtra(src, slot).isUndefined()) {
-        SetProxyExtra(src, slot, ObjectOrNullValue(comp->gcIncomingGrayPointers));
+    if (GetProxyReservedSlot(src, slot).isUndefined()) {
+        SetProxyReservedSlot(src, slot, ObjectOrNullValue(comp->gcIncomingGrayPointers));
         comp->gcIncomingGrayPointers = src;
     } else {
-        MOZ_ASSERT(GetProxyExtra(src, slot).isObjectOrNull());
+        MOZ_ASSERT(GetProxyReservedSlot(src, slot).isObjectOrNull());
     }
 
 #ifdef DEBUG
     /*
      * Assert that the object is in our list, also walking the list to check its
      * integrity.
      */
     JSObject* obj = comp->gcIncomingGrayPointers;
@@ -4804,35 +4804,35 @@ MarkIncomingCrossCompartmentPointers(JSR
 }
 
 static bool
 RemoveFromGrayList(JSObject* wrapper)
 {
     if (!IsGrayListObject(wrapper))
         return false;
 
-    unsigned slot = ProxyObject::grayLinkExtraSlot(wrapper);
-    if (GetProxyExtra(wrapper, slot).isUndefined())
+    unsigned slot = ProxyObject::grayLinkReservedSlot(wrapper);
+    if (GetProxyReservedSlot(wrapper, slot).isUndefined())
         return false;  /* Not on our list. */
 
-    JSObject* tail = GetProxyExtra(wrapper, slot).toObjectOrNull();
-    SetProxyExtra(wrapper, slot, UndefinedValue());
+    JSObject* tail = GetProxyReservedSlot(wrapper, slot).toObjectOrNull();
+    SetProxyReservedSlot(wrapper, slot, UndefinedValue());
 
     JSCompartment* comp = CrossCompartmentPointerReferent(wrapper)->compartment();
     JSObject* obj = comp->gcIncomingGrayPointers;
     if (obj == wrapper) {
         comp->gcIncomingGrayPointers = tail;
         return true;
     }
 
     while (obj) {
-        unsigned slot = ProxyObject::grayLinkExtraSlot(obj);
-        JSObject* next = GetProxyExtra(obj, slot).toObjectOrNull();
+        unsigned slot = ProxyObject::grayLinkReservedSlot(obj);
+        JSObject* next = GetProxyReservedSlot(obj, slot).toObjectOrNull();
         if (next == wrapper) {
-            SetProxyExtra(obj, slot, ObjectOrNullValue(tail));
+            SetProxyReservedSlot(obj, slot, ObjectOrNullValue(tail));
             return true;
         }
         obj = next;
     }
 
     MOZ_CRASH("object not found in gray link list");
 }
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1120,22 +1120,24 @@ CopyProxyObject(JSContext* cx, Handle<Pr
         to->setCrossCompartmentPrivate(GetProxyPrivate(from));
     } else {
         RootedValue v(cx, GetProxyPrivate(from));
         if (!cx->compartment()->wrap(cx, &v))
             return false;
         to->setSameCompartmentPrivate(v);
     }
 
+    MOZ_ASSERT(from->numReservedSlots() == to->numReservedSlots());
+
     RootedValue v(cx);
-    for (size_t n = 0; n < js::detail::PROXY_EXTRA_SLOTS; n++) {
-        v = GetProxyExtra(from, n);
+    for (size_t n = 0; n < from->numReservedSlots(); n++) {
+        v = GetProxyReservedSlot(from, n);
         if (!cx->compartment()->wrap(cx, &v))
             return false;
-        SetProxyExtra(to, n, v);
+        SetProxyReservedSlot(to, n, v);
     }
 
     return true;
 }
 
 JSObject*
 js::CloneObject(JSContext* cx, HandleObject obj, Handle<js::TaggedProto> proto)
 {
@@ -1523,23 +1525,68 @@ void
 JSObject::fixDictionaryShapeAfterSwap()
 {
     // Dictionary shapes can point back to their containing objects, so after
     // swapping the guts of those objects fix the pointers up.
     if (isNative() && as<NativeObject>().inDictionaryMode())
         as<NativeObject>().shape_->listp = &as<NativeObject>().shape_;
 }
 
-static void
-RemoveFromStoreBuffer(JSContext* cx, js::detail::ProxyValueArray* values)
+static MOZ_MUST_USE bool
+CopyProxyValuesBeforeSwap(ProxyObject* proxy, Vector<Value>& values)
 {
-    StoreBuffer& sb = cx->zone()->group()->storeBuffer();
-    sb.unputValue(&values->privateSlot);
-    for (size_t i = 0; i < js::detail::PROXY_EXTRA_SLOTS; i++)
-        sb.unputValue(&values->extraSlots[i]);
+    MOZ_ASSERT(values.empty());
+
+    // Remove the GCPtrValues we're about to swap from the store buffer, to
+    // ensure we don't trace bogus values.
+    StoreBuffer& sb = proxy->zone()->group()->storeBuffer();
+
+    // Reserve space for the private slot and the reserved slots.
+    if (!values.reserve(1 + proxy->numReservedSlots()))
+        return false;
+
+    js::detail::ProxyValueArray* valArray = js::detail::GetProxyDataLayout(proxy)->values();
+    sb.unputValue(&valArray->privateSlot);
+    values.infallibleAppend(valArray->privateSlot);
+
+    for (size_t i = 0; i < proxy->numReservedSlots(); i++) {
+        sb.unputValue(&valArray->reservedSlots.slots[i]);
+        values.infallibleAppend(valArray->reservedSlots.slots[i]);
+    }
+
+    return true;
+}
+
+bool
+ProxyObject::initExternalValueArrayAfterSwap(JSContext* cx, const Vector<Value>& values)
+{
+    MOZ_ASSERT(getClass()->isProxy());
+
+    size_t nreserved = numReservedSlots();
+
+    // |values| contains the private slot and the reserved slots.
+    MOZ_ASSERT(values.length() == 1 + nreserved);
+
+    size_t nbytes = js::detail::ProxyValueArray::sizeOf(nreserved);
+
+    auto* valArray =
+        reinterpret_cast<js::detail::ProxyValueArray*>(cx->zone()->pod_malloc<uint8_t>(nbytes));
+    if (!valArray)
+        return false;
+
+    valArray->privateSlot = values[0];
+
+    for (size_t i = 0; i < nreserved; i++)
+        valArray->reservedSlots.slots[i] = values[i + 1];
+
+    // Note: we allocate external slots iff the proxy had an inline
+    // ProxyValueArray, so at this point reservedSlots points into the
+    // old object and we don't have to free anything.
+    data.reservedSlots = &valArray->reservedSlots;
+    return true;
 }
 
 /* Use this method with extreme caution. It trades the guts of two objects. */
 bool
 JSObject::swap(JSContext* cx, HandleObject a, HandleObject b)
 {
     // Ensure swap doesn't cause a finalizer to not be run.
     MOZ_ASSERT(IsBackgroundFinalized(a->asTenured().getAllocKind()) ==
@@ -1635,26 +1682,23 @@ JSObject::swap(JSContext* cx, HandleObje
                     oomUnsafe.crash("JSObject::swap");
             }
         }
 
         // Do the same for proxies storing ProxyValueArray inline.
         ProxyObject* proxyA = a->is<ProxyObject>() ? &a->as<ProxyObject>() : nullptr;
         ProxyObject* proxyB = b->is<ProxyObject>() ? &b->as<ProxyObject>() : nullptr;
 
-        Maybe<js::detail::ProxyValueArray> proxyAVals, proxyBVals;
         if (aIsProxyWithInlineValues) {
-            js::detail::ProxyValueArray* values = js::detail::GetProxyDataLayout(proxyA)->values;
-            proxyAVals.emplace(*values);
-            RemoveFromStoreBuffer(cx, values);
+            if (!CopyProxyValuesBeforeSwap(proxyA, avals))
+                oomUnsafe.crash("CopyProxyValuesBeforeSwap");
         }
         if (bIsProxyWithInlineValues) {
-            js::detail::ProxyValueArray* values = js::detail::GetProxyDataLayout(proxyB)->values;
-            proxyBVals.emplace(*values);
-            RemoveFromStoreBuffer(cx, values);
+            if (!CopyProxyValuesBeforeSwap(proxyB, bvals))
+                oomUnsafe.crash("CopyProxyValuesBeforeSwap");
         }
 
         // Swap the main fields of the objects, whether they are native objects or proxies.
         char tmp[sizeof(JSObject_Slots0)];
         js_memcpy(&tmp, a, sizeof tmp);
         js_memcpy(a, b, sizeof tmp);
         js_memcpy(b, &tmp, sizeof tmp);
 
@@ -1665,21 +1709,21 @@ JSObject::swap(JSContext* cx, HandleObje
             if (!NativeObject::fillInAfterSwap(cx, b.as<NativeObject>(), avals, apriv))
                 oomUnsafe.crash("fillInAfterSwap");
         }
         if (nb) {
             if (!NativeObject::fillInAfterSwap(cx, a.as<NativeObject>(), bvals, bpriv))
                 oomUnsafe.crash("fillInAfterSwap");
         }
         if (aIsProxyWithInlineValues) {
-            if (!b->as<ProxyObject>().initExternalValueArrayAfterSwap(cx, proxyAVals.ref()))
+            if (!b->as<ProxyObject>().initExternalValueArrayAfterSwap(cx, avals))
                 oomUnsafe.crash("initExternalValueArray");
         }
         if (bIsProxyWithInlineValues) {
-            if (!a->as<ProxyObject>().initExternalValueArrayAfterSwap(cx, proxyBVals.ref()))
+            if (!a->as<ProxyObject>().initExternalValueArrayAfterSwap(cx, bvals))
                 oomUnsafe.crash("initExternalValueArray");
         }
     }
 
     // Swapping the contents of two objects invalidates type sets which contain
     // either of the objects, so mark all such sets as unknown.
     MarkObjectGroupUnknownProperties(cx, a->group());
     MarkObjectGroupUnknownProperties(cx, b->group());
@@ -4073,19 +4117,19 @@ JSObject::debugCheckNewObject(ObjectGrou
 
     MOZ_ASSERT_IF(clasp->hasFinalize(), heap == gc::TenuredHeap ||
                                         CanNurseryAllocateFinalizedClass(clasp) ||
                                         clasp->isProxy());
     MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(), heap == gc::TenuredHeap);
 
     MOZ_ASSERT(!group->compartment()->hasObjectPendingMetadata());
 
-    // Non-native classes cannot have reserved slots or private data, and the
-    // objects can't have any fixed slots, for compatibility with
-    // GetReservedOrProxyPrivateSlot.
+    // Non-native classes manage their own data and slots, so numFixedSlots and
+    // slotSpan are always 0. Note that proxy classes can have reserved slots
+    // but they're also not included in numFixedSlots/slotSpan.
     if (!clasp->isNative()) {
-        MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(clasp) == 0);
+        MOZ_ASSERT_IF(!clasp->isProxy(), JSCLASS_RESERVED_SLOTS(clasp) == 0);
         MOZ_ASSERT(!clasp->hasPrivate());
         MOZ_ASSERT_IF(shape, shape->numFixedSlots() == 0);
         MOZ_ASSERT_IF(shape, shape->slotSpan() == 0);
     }
 }
 #endif
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -680,24 +680,27 @@ ProxyObject::trace(JSTracer* trc, JSObje
             MOZ_ASSERT(*p->value().unsafeGet() == ObjectValue(*proxy));
         }
     }
 #endif
 
     // Note: If you add new slots here, make sure to change
     // nuke() to cope.
     TraceCrossCompartmentEdge(trc, obj, proxy->slotOfPrivate(), "private");
-    TraceEdge(trc, proxy->slotOfExtra(0), "extra0");
 
-    /*
-     * The GC can use the second reserved slot to link the cross compartment
-     * wrappers into a linked list, in which case we don't want to trace it.
-     */
-    if (!proxy->is<CrossCompartmentWrapperObject>())
-        TraceEdge(trc, proxy->slotOfExtra(1), "extra1");
+    size_t nreserved = proxy->numReservedSlots();
+    for (size_t i = 0; i < nreserved; i++) {
+        /*
+         * The GC can use the second reserved slot to link the cross compartment
+         * wrappers into a linked list, in which case we don't want to trace it.
+         */
+        if (proxy->is<CrossCompartmentWrapperObject>() && i == 1)
+            continue;
+        TraceEdge(trc, proxy->reservedSlotPtr(i), "proxy_reserved");
+    }
 
     Proxy::trace(trc, obj);
 }
 
 JSObject*
 js::proxy_WeakmapKeyDelegate(JSObject* obj)
 {
     MOZ_ASSERT(obj->is<ProxyObject>());
@@ -709,17 +712,17 @@ proxy_Finalize(FreeOp* fop, JSObject* ob
 {
     // Suppress a bogus warning about finalize().
     JS::AutoSuppressGCAnalysis nogc;
 
     MOZ_ASSERT(obj->is<ProxyObject>());
     obj->as<ProxyObject>().handler()->finalize(fop, obj);
 
     if (!obj->as<ProxyObject>().usingInlineValueArray())
-        js_free(js::detail::GetProxyDataLayout(obj)->values);
+        js_free(js::detail::GetProxyDataLayout(obj)->values());
 }
 
 static void
 proxy_ObjectMoved(JSObject* obj, const JSObject* old)
 {
     MOZ_ASSERT(obj->is<ProxyObject>());
     obj->as<ProxyObject>().handler()->objectMoved(obj, old);
 }
@@ -798,18 +801,18 @@ ProxyObject::renew(const BaseProxyHandle
     MOZ_ASSERT(!IsInsideNursery(this));
     MOZ_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this));
     MOZ_ASSERT(getClass() == &ProxyObject::proxyClass);
     MOZ_ASSERT(!IsWindowProxy(this));
     MOZ_ASSERT(hasDynamicPrototype());
 
     setHandler(handler);
     setCrossCompartmentPrivate(priv);
-    setExtra(0, UndefinedValue());
-    setExtra(1, UndefinedValue());
+    for (size_t i = 0; i < numReservedSlots(); i++)
+        setReservedSlot(i, UndefinedValue());
 }
 
 JS_FRIEND_API(JSObject*)
 js::InitProxyClass(JSContext* cx, HandleObject obj)
 {
     static const JSFunctionSpec static_methods[] = {
         JS_FN("revocable",      proxy_revocable,       2, 0),
         JS_FS_END
--- a/js/src/proxy/ScriptedProxyHandler.cpp
+++ b/js/src/proxy/ScriptedProxyHandler.cpp
@@ -125,17 +125,17 @@ IsCompatiblePropertyDescriptor(JSContext
     return true;
 }
 
 // Get the [[ProxyHandler]] of a scripted proxy.
 /* static */ JSObject*
 ScriptedProxyHandler::handlerObject(const JSObject* proxy)
 {
     MOZ_ASSERT(proxy->as<ProxyObject>().handler() == &ScriptedProxyHandler::singleton);
-    return proxy->as<ProxyObject>().extra(ScriptedProxyHandler::HANDLER_EXTRA).toObjectOrNull();
+    return proxy->as<ProxyObject>().reservedSlot(ScriptedProxyHandler::HANDLER_EXTRA).toObjectOrNull();
 }
 
 // ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 7.3.9 GetMethod,
 // reimplemented for proxy handler trap-getting to produce better error
 // messages.
 static bool
 GetProxyTrap(JSContext* cx, HandleObject handler, HandlePropertyName name, MutableHandleValue func)
 {
@@ -1282,25 +1282,25 @@ ScriptedProxyHandler::boxedValue_unbox(J
     MOZ_CRASH("Should not end up in ScriptedProxyHandler::boxedValue_unbox");
     return false;
 }
 
 bool
 ScriptedProxyHandler::isCallable(JSObject* obj) const
 {
     MOZ_ASSERT(obj->as<ProxyObject>().handler() == &ScriptedProxyHandler::singleton);
-    uint32_t callConstruct = obj->as<ProxyObject>().extra(IS_CALLCONSTRUCT_EXTRA).toPrivateUint32();
+    uint32_t callConstruct = obj->as<ProxyObject>().reservedSlot(IS_CALLCONSTRUCT_EXTRA).toPrivateUint32();
     return !!(callConstruct & IS_CALLABLE);
 }
 
 bool
 ScriptedProxyHandler::isConstructor(JSObject* obj) const
 {
     MOZ_ASSERT(obj->as<ProxyObject>().handler() == &ScriptedProxyHandler::singleton);
-    uint32_t callConstruct = obj->as<ProxyObject>().extra(IS_CALLCONSTRUCT_EXTRA).toPrivateUint32();
+    uint32_t callConstruct = obj->as<ProxyObject>().reservedSlot(IS_CALLCONSTRUCT_EXTRA).toPrivateUint32();
     return !!(callConstruct & IS_CONSTRUCTOR);
 }
 
 const char ScriptedProxyHandler::family = 0;
 const ScriptedProxyHandler ScriptedProxyHandler::singleton;
 
 bool
 IsRevokedScriptedProxy(JSObject* obj)
@@ -1345,23 +1345,23 @@ ProxyCreate(JSContext* cx, CallArgs& arg
     RootedValue priv(cx, ObjectValue(*target));
     JSObject* proxy_ =
         NewProxyObject(cx, &ScriptedProxyHandler::singleton, priv, TaggedProto::LazyProto);
     if (!proxy_)
         return false;
 
     // Step 9 (reordered).
     Rooted<ProxyObject*> proxy(cx, &proxy_->as<ProxyObject>());
-    proxy->setExtra(ScriptedProxyHandler::HANDLER_EXTRA, ObjectValue(*handler));
+    proxy->setReservedSlot(ScriptedProxyHandler::HANDLER_EXTRA, ObjectValue(*handler));
 
     // Step 7.
     uint32_t callable = target->isCallable() ? ScriptedProxyHandler::IS_CALLABLE : 0;
     uint32_t constructor = target->isConstructor() ? ScriptedProxyHandler::IS_CONSTRUCTOR : 0;
-    proxy->setExtra(ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA,
-                    PrivateUint32Value(callable | constructor));
+    proxy->setReservedSlot(ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA,
+                           PrivateUint32Value(callable | constructor));
 
     // Step 10.
     args.rval().setObject(*proxy);
     return true;
 }
 
 bool
 js::proxy(JSContext* cx, unsigned argc, Value* vp)
@@ -1383,17 +1383,17 @@ RevokeProxy(JSContext* cx, unsigned argc
     RootedObject p(cx, func->getExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT).toObjectOrNull());
 
     if (p) {
         func->setExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT, NullValue());
 
         MOZ_ASSERT(p->is<ProxyObject>());
 
         p->as<ProxyObject>().setSameCompartmentPrivate(NullValue());
-        p->as<ProxyObject>().setExtra(ScriptedProxyHandler::HANDLER_EXTRA, NullValue());
+        p->as<ProxyObject>().setReservedSlot(ScriptedProxyHandler::HANDLER_EXTRA, NullValue());
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 bool
 js::proxy_revocable(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -2263,46 +2263,46 @@ DebugEnvironmentProxy::create(JSContext*
 
     RootedValue priv(cx, ObjectValue(env));
     JSObject* obj = NewProxyObject(cx, &DebugEnvironmentProxyHandler::singleton, priv,
                                    nullptr /* proto */);
     if (!obj)
         return nullptr;
 
     DebugEnvironmentProxy* debugEnv = &obj->as<DebugEnvironmentProxy>();
-    debugEnv->setExtra(ENCLOSING_EXTRA, ObjectValue(*enclosing));
-    debugEnv->setExtra(SNAPSHOT_EXTRA, NullValue());
+    debugEnv->setReservedSlot(ENCLOSING_SLOT, ObjectValue(*enclosing));
+    debugEnv->setReservedSlot(SNAPSHOT_SLOT, NullValue());
 
     return debugEnv;
 }
 
 EnvironmentObject&
 DebugEnvironmentProxy::environment() const
 {
     return target()->as<EnvironmentObject>();
 }
 
 JSObject&
 DebugEnvironmentProxy::enclosingEnvironment() const
 {
-    return extra(ENCLOSING_EXTRA).toObject();
+    return reservedSlot(ENCLOSING_SLOT).toObject();
 }
 
 ArrayObject*
 DebugEnvironmentProxy::maybeSnapshot() const
 {
-    JSObject* obj = extra(SNAPSHOT_EXTRA).toObjectOrNull();
+    JSObject* obj = reservedSlot(SNAPSHOT_SLOT).toObjectOrNull();
     return obj ? &obj->as<ArrayObject>() : nullptr;
 }
 
 void
 DebugEnvironmentProxy::initSnapshot(ArrayObject& o)
 {
     MOZ_ASSERT(maybeSnapshot() == nullptr);
-    setExtra(SNAPSHOT_EXTRA, ObjectValue(o));
+    setReservedSlot(SNAPSHOT_SLOT, ObjectValue(o));
 }
 
 bool
 DebugEnvironmentProxy::isForDeclarative() const
 {
     EnvironmentObject& e = environment();
     return e.is<CallObject>() ||
            e.is<VarEnvironmentObject>() ||
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -867,23 +867,23 @@ GetDebugEnvironmentForGlobalLexicalEnvir
 
 /* Provides debugger access to a environment. */
 class DebugEnvironmentProxy : public ProxyObject
 {
     /*
      * The enclosing environment on the dynamic environment chain. This slot is analogous
      * to the ENCLOSING_ENV_SLOT of a EnvironmentObject.
      */
-    static const unsigned ENCLOSING_EXTRA = 0;
+    static const unsigned ENCLOSING_SLOT = 0;
 
     /*
      * NullValue or a dense array holding the unaliased variables of a function
      * frame that has been popped.
      */
-    static const unsigned SNAPSHOT_EXTRA = 1;
+    static const unsigned SNAPSHOT_SLOT = 1;
 
   public:
     static DebugEnvironmentProxy* create(JSContext* cx, EnvironmentObject& env,
                                          HandleObject enclosing);
 
     EnvironmentObject& environment() const;
     JSObject& enclosingEnvironment() const;
 
--- a/js/src/vm/ProxyObject.cpp
+++ b/js/src/vm/ProxyObject.cpp
@@ -10,22 +10,28 @@
 
 #include "proxy/DeadObjectProxy.h"
 
 #include "jsobjinlines.h"
 
 using namespace js;
 
 static gc::AllocKind
-GetProxyGCObjectKind(const BaseProxyHandler* handler, const Value& priv)
+GetProxyGCObjectKind(const Class* clasp, const BaseProxyHandler* handler, const Value& priv)
 {
-    static_assert(sizeof(js::detail::ProxyValueArray) % sizeof(js::HeapSlot) == 0,
-                  "ProxyValueArray must be a multiple of HeapSlot");
+    MOZ_ASSERT(clasp->isProxy());
+
+    uint32_t nreserved = JSCLASS_RESERVED_SLOTS(clasp);
+    MOZ_ASSERT(nreserved > 0);
 
-    uint32_t nslots = sizeof(js::detail::ProxyValueArray) / sizeof(HeapSlot);
+    MOZ_ASSERT(js::detail::ProxyValueArray::sizeOf(nreserved) % sizeof(Value) == 0,
+               "ProxyValueArray must be a multiple of Value");
+
+    uint32_t nslots = js::detail::ProxyValueArray::sizeOf(nreserved) / sizeof(Value);
+    MOZ_ASSERT(nslots <= NativeObject::MAX_FIXED_SLOTS);
 
     gc::AllocKind kind = gc::GetGCObjectKind(nslots);
     if (handler->finalizeInBackground(priv))
         kind = GetBackgroundAllocKind(kind);
 
     return kind;
 }
 
@@ -63,42 +69,45 @@ ProxyObject::New(JSContext* cx, const Ba
         newKind = SingletonObject;
     } else if ((priv.isGCThing() && priv.toGCThing()->isTenured()) ||
                !handler->canNurseryAllocate() ||
                !handler->finalizeInBackground(priv))
     {
         newKind = TenuredObject;
     }
 
-    gc::AllocKind allocKind = GetProxyGCObjectKind(handler, priv);
+    gc::AllocKind allocKind = GetProxyGCObjectKind(clasp, handler, priv);
 
     AutoSetNewObjectMetadata metadata(cx);
     // Note: this will initialize the object's |data| to strange values, but we
     // will immediately overwrite those below.
     ProxyObject* proxy;
     JS_TRY_VAR_OR_RETURN_NULL(cx, proxy, create(cx, clasp, proto, allocKind, newKind));
 
     proxy->setInlineValueArray();
-    new (proxy->data.values) detail::ProxyValueArray;
+
+    detail::ProxyValueArray* values = detail::GetProxyDataLayout(proxy)->values();
+    values->init(proxy->numReservedSlots());
+
     proxy->data.handler = handler;
     proxy->setCrossCompartmentPrivate(priv);
 
     /* Don't track types of properties of non-DOM and non-singleton proxies. */
     if (newKind != SingletonObject && !clasp->isDOMClass())
         MarkObjectGroupUnknownProperties(cx, proxy->group());
 
     return proxy;
 }
 
 gc::AllocKind
 ProxyObject::allocKindForTenure() const
 {
     MOZ_ASSERT(usingInlineValueArray());
     Value priv = const_cast<ProxyObject*>(this)->private_();
-    return GetProxyGCObjectKind(data.handler, priv);
+    return GetProxyGCObjectKind(getClass(), data.handler, priv);
 }
 
 void
 ProxyObject::setCrossCompartmentPrivate(const Value& priv)
 {
     *slotOfPrivate() = priv;
 }
 
@@ -128,17 +137,17 @@ ProxyObject::nuke()
             setHandler(DeadObjectProxy<DeadProxyIsCallableNotConstructor>::singleton());
     } else {
         if (constructor)
             setHandler(DeadObjectProxy<DeadProxyNotCallableIsConstructor>::singleton());
         else
             setHandler(DeadObjectProxy<DeadProxyNotCallableNotConstructor>::singleton());
     }
 
-    // The proxy's extra slots are not cleared and will continue to be
+    // The proxy's reserved slots are not cleared and will continue to be
     // traced. This avoids the possibility of triggering write barriers while
     // nuking proxies in dead compartments which could otherwise cause those
     // compartments to be kept alive. Note that these are slots cannot hold
     // cross compartment pointers, so this cannot cause the target compartment
     // to leak.
 }
 
 /* static */ JS::Result<ProxyObject*, JS::OOM&>
@@ -185,28 +194,15 @@ ProxyObject::create(JSContext* cx, const
         if (!JSObject::setSingleton(cx, pobjRoot))
             return cx->alreadyReportedOOM();
         pobj = pobjRoot;
     }
 
     return pobj;
 }
 
-bool
-ProxyObject::initExternalValueArrayAfterSwap(JSContext* cx, const detail::ProxyValueArray& src)
-{
-    MOZ_ASSERT(getClass()->isProxy());
-
-    auto* values = cx->zone()->new_<detail::ProxyValueArray>(src);
-    if (!values)
-        return false;
-
-    data.values = values;
-    return true;
-}
-
 JS_FRIEND_API(void)
 js::SetValueInProxy(Value* slot, const Value& value)
 {
     // Slots in proxies are not GCPtrValues, so do a cast whenever assigning
     // values to them which might trigger a barrier.
     *reinterpret_cast<GCPtrValue*>(slot) = value;
 }
--- a/js/src/vm/ProxyObject.h
+++ b/js/src/vm/ProxyObject.h
@@ -24,16 +24,18 @@ class ProxyObject : public ShapedObject
     // GetProxyDataLayout computes the address of this field.
     detail::ProxyDataLayout data;
 
     void static_asserts() {
         static_assert(sizeof(ProxyObject) == sizeof(JSObject_Slots0),
                       "proxy object size must match GC thing size");
         static_assert(offsetof(ProxyObject, data) == detail::ProxyDataOffset,
                       "proxy object layout must match shadow interface");
+        static_assert(offsetof(ProxyObject, data.reservedSlots) == offsetof(shadow::Object, slots),
+                      "Proxy reservedSlots must overlay native object slots field");
     }
 
     static JS::Result<ProxyObject*, JS::OOM&>
     create(JSContext* cx, const js::Class* clasp, Handle<TaggedProto> proto,
            js::gc::AllocKind allocKind, js::NewObjectKind newKind);
 
   public:
     static ProxyObject* New(JSContext* cx, const BaseProxyHandler* handler, HandleValue priv,
@@ -41,87 +43,85 @@ class ProxyObject : public ShapedObject
 
     // Proxies usually store their ProxyValueArray inline in the object.
     // There's one unfortunate exception: when a proxy is swapped with another
     // object, and the sizes don't match, we malloc the ProxyValueArray.
     void* inlineDataStart() const {
         return (void*)(uintptr_t(this) + sizeof(ProxyObject));
     }
     bool usingInlineValueArray() const {
-        return data.values == inlineDataStart();
+        return data.values() == inlineDataStart();
     }
     void setInlineValueArray() {
-        data.values = reinterpret_cast<detail::ProxyValueArray*>(inlineDataStart());
+        data.reservedSlots = &reinterpret_cast<detail::ProxyValueArray*>(inlineDataStart())->reservedSlots;
     }
-    MOZ_MUST_USE bool initExternalValueArrayAfterSwap(JSContext* cx, const detail::ProxyValueArray& src);
+    MOZ_MUST_USE bool initExternalValueArrayAfterSwap(JSContext* cx, const Vector<Value>& values);
 
     const Value& private_() {
         return GetProxyPrivate(this);
     }
 
     void setCrossCompartmentPrivate(const Value& priv);
     void setSameCompartmentPrivate(const Value& priv);
 
     GCPtrValue* slotOfPrivate() {
-        return reinterpret_cast<GCPtrValue*>(&detail::GetProxyDataLayout(this)->values->privateSlot);
+        return reinterpret_cast<GCPtrValue*>(&detail::GetProxyDataLayout(this)->values()->privateSlot);
     }
 
     JSObject* target() const {
         return const_cast<ProxyObject*>(this)->private_().toObjectOrNull();
     }
 
     const BaseProxyHandler* handler() const {
         return GetProxyHandler(const_cast<ProxyObject*>(this));
     }
 
     void setHandler(const BaseProxyHandler* handler) {
         SetProxyHandler(this, handler);
     }
 
-    static size_t offsetOfValues() {
-        return offsetof(ProxyObject, data.values);
+    static size_t offsetOfReservedSlots() {
+        return offsetof(ProxyObject, data.reservedSlots);
     }
     static size_t offsetOfHandler() {
         return offsetof(ProxyObject, data.handler);
     }
-    static size_t offsetOfExtraSlotInValues(size_t slot) {
-        MOZ_ASSERT(slot < detail::PROXY_EXTRA_SLOTS);
-        return offsetof(detail::ProxyValueArray, extraSlots) + slot * sizeof(Value);
+
+    size_t numReservedSlots() const {
+        return JSCLASS_RESERVED_SLOTS(getClass());
+    }
+    const Value& reservedSlot(size_t n) const {
+        return GetProxyReservedSlot(const_cast<ProxyObject*>(this), n);
     }
 
-    const Value& extra(size_t n) const {
-        return GetProxyExtra(const_cast<ProxyObject*>(this), n);
-    }
-
-    void setExtra(size_t n, const Value& extra) {
-        SetProxyExtra(this, n, extra);
+    void setReservedSlot(size_t n, const Value& extra) {
+        SetProxyReservedSlot(this, n, extra);
     }
 
     gc::AllocKind allocKindForTenure() const;
 
   private:
-    GCPtrValue* slotOfExtra(size_t n) {
-        MOZ_ASSERT(n < detail::PROXY_EXTRA_SLOTS);
-        return reinterpret_cast<GCPtrValue*>(&detail::GetProxyDataLayout(this)->values->extraSlots[n]);
+    GCPtrValue* reservedSlotPtr(size_t n) {
+        return reinterpret_cast<GCPtrValue*>(&detail::GetProxyDataLayout(this)->reservedSlots->slots[n]);
     }
 
     static bool isValidProxyClass(const Class* clasp) {
         // Since we can take classes from the outside, make sure that they
         // are "sane". They have to quack enough like proxies for us to belive
         // they should be treated as such.
 
         // Proxy classes are not allowed to have call or construct hooks directly. Their
         // callability is instead decided by handler()->isCallable().
         return clasp->isProxy() &&
                clasp->isTrace(ProxyObject::trace) &&
                !clasp->getCall() && !clasp->getConstruct();
     }
 
   public:
-    static unsigned grayLinkExtraSlot(JSObject* obj);
+    static unsigned grayLinkReservedSlot(JSObject* obj);
 
     void renew(const BaseProxyHandler* handler, const Value& priv);
 
     static void trace(JSTracer* trc, JSObject* obj);
 
     void nuke();
 
     // There is no class_ member to force specialization of JSObject::is<T>().
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -109,17 +109,24 @@
  * For getters/setters, an AccessorShape is allocated. This is a slightly fatter
  * type with extra fields for the getter/setter data.
  *
  * Because many Shapes have similar data, there is actually a secondary type
  * called a BaseShape that holds some of a Shape's data.  Many shapes can share
  * a single BaseShape.
  */
 
-#define JSSLOT_FREE(clasp)  JSCLASS_RESERVED_SLOTS(clasp)
+MOZ_ALWAYS_INLINE size_t
+JSSLOT_FREE(const js::Class* clasp)
+{
+    // Proxy classes have reserved slots, but proxies manage their own slot
+    // layout.
+    MOZ_ASSERT(!clasp->isProxy());
+    return JSCLASS_RESERVED_SLOTS(clasp);
+}
 
 namespace js {
 
 class TenuringTracer;
 
 typedef JSGetterOp GetterOp;
 typedef JSSetterOp SetterOp;
 
@@ -914,17 +921,20 @@ class Shape : public gc::TenuredCell
 
     bool isEmptyShape() const {
         MOZ_ASSERT_IF(JSID_IS_EMPTY(propid_), hasMissingSlot());
         return JSID_IS_EMPTY(propid_);
     }
 
     uint32_t slotSpan(const Class* clasp) const {
         MOZ_ASSERT(!inDictionary());
-        uint32_t free = JSSLOT_FREE(clasp);
+        // Proxy classes have reserved slots, but proxies manage their own slot
+        // layout. This means all non-native object shapes have nfixed == 0 and
+        // slotSpan == 0.
+        uint32_t free = clasp->isProxy() ? 0 : JSSLOT_FREE(clasp);
         return hasMissingSlot() ? free : Max(free, maybeSlot() + 1);
     }
 
     uint32_t slotSpan() const {
         return slotSpan(getObjectClass());
     }
 
     void setSlot(uint32_t slot) {
@@ -1319,21 +1329,16 @@ struct StackShape
     }
 
     bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; }
     bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
 
     uint32_t slot() const { MOZ_ASSERT(hasSlot() && !hasMissingSlot()); return slot_; }
     uint32_t maybeSlot() const { return slot_; }
 
-    uint32_t slotSpan() const {
-        uint32_t free = JSSLOT_FREE(base->clasp_);
-        return hasMissingSlot() ? free : (maybeSlot() + 1);
-    }
-
     void setSlot(uint32_t slot) {
         MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT);
         slot_ = slot;
     }
 
     bool isAccessorShape() const {
         return flags & Shape::ACCESSOR_SHAPE;
     }
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -711,18 +711,18 @@ WrapCallable(JSContext* cx, HandleObject
     RootedValue priv(cx, ObjectValue(*callable));
     // We want to claim to have the same proto as our wrapped callable, so set
     // ourselves up with a lazy proto.
     js::ProxyOptions options;
     options.setLazyProto(true);
     JSObject* obj = js::NewProxyObject(cx, &xpc::sandboxCallableProxyHandler,
                                        priv, nullptr, options);
     if (obj) {
-        js::SetProxyExtra(obj, SandboxCallableProxyHandler::SandboxProxySlot,
-                          ObjectValue(*sandboxProtoProxy));
+        js::SetProxyReservedSlot(obj, SandboxCallableProxyHandler::SandboxProxySlot,
+                                 ObjectValue(*sandboxProtoProxy));
     }
 
     return obj;
 }
 
 template<typename Op>
 bool WrapAccessorFunction(JSContext* cx, Op& op, PropertyDescriptor* desc,
                           unsigned attrFlag, HandleObject sandboxProtoProxy)
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -1313,36 +1313,36 @@ bool CloneExpandoChain(JSContext* cx, JS
     RootedObject src(cx, srcArg);
     return GetXrayTraits(src)->cloneExpandoChain(cx, dst, src);
 }
 } // namespace XrayUtils
 
 static JSObject*
 GetHolder(JSObject* obj)
 {
-    return &js::GetProxyExtra(obj, 0).toObject();
+    return &js::GetProxyReservedSlot(obj, 0).toObject();
 }
 
 JSObject*
 XrayTraits::getHolder(JSObject* wrapper)
 {
     MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper));
-    js::Value v = js::GetProxyExtra(wrapper, 0);
+    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::SetProxyExtra(wrapper, 0, ObjectValue(*holder));
+        js::SetProxyReservedSlot(wrapper, 0, ObjectValue(*holder));
     return holder;
 }
 
 namespace XrayUtils {
 
 bool
 IsXPCWNHolderClass(const JSClass* clasp)
 {
--- a/js/xpconnect/wrappers/XrayWrapper.h
+++ b/js/xpconnect/wrappers/XrayWrapper.h
@@ -583,17 +583,17 @@ public:
 
     virtual bool call(JSContext* cx, JS::Handle<JSObject*> proxy,
                       const JS::CallArgs& args) const override;
 
     static const size_t SandboxProxySlot = 0;
 
     static inline JSObject* getSandboxProxy(JS::Handle<JSObject*> proxy)
     {
-        return &js::GetProxyExtra(proxy, SandboxProxySlot).toObject();
+        return &js::GetProxyReservedSlot(proxy, SandboxProxySlot).toObject();
     }
 };
 
 extern const SandboxCallableProxyHandler sandboxCallableProxyHandler;
 
 class AutoSetWrapperNotShadowing;
 
 /*