Bug 1521907 part 3. Start using CheckedUnwrapStatic/Dynamic in bindings. r=peterv
☠☠ backed out by 0afc21b5734a ☠ ☠
authorBoris Zbarsky <bzbarsky@mit.edu>
Thu, 31 Jan 2019 15:50:06 +0000
changeset 456457 ca65b46b0d3708a115042dd4f5484bdc7a269a6b
parent 456456 b3daf5ca3d1199a9e41b4564b1aa7f560039ab0b
child 456458 192152fe986a8a5fcacd1de95965bcbecd908030
push id111652
push userncsoregi@mozilla.com
push dateFri, 01 Feb 2019 22:14:41 +0000
treeherdermozilla-inbound@a36422c1abbc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1521907
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1521907 part 3. Start using CheckedUnwrapStatic/Dynamic in bindings. r=peterv The basic idea for the changes around UnwrapObjectInternal and its callers (UnwrapObject, UNWRAP_OBJECT, etc) is to add a parameter to the guts of the object-unwrapping code in bindings which can be either a JSContext* or nullptr (statically typed). Then we test which type it is and do either a CheckedUnwrapDynamic or CheckedUnwrapStatic. Since the type is known at compile time, there is no actual runtime check; the compiler just emits a call to the right thing directly (verified by examining the assembly output on Linux). The rest of the changes are mostly propagating through that template parameter, adding static asserts to make sure people don't accidentally pass nullptr while trying to unwrap to a type that might be a WindowProxy or Location, etc. There are also some changes to places that were calling CheckedUnwrap directly to use either the static or dynamic version, as needed. Differential Revision: https://phabricator.services.mozilla.com/D17883
dom/base/nsContentUtils.cpp
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/Date.h
dom/bindings/Exceptions.cpp
dom/bindings/WebIDLGlobalNameHash.cpp
dom/bindings/test/TestFunctions.cpp
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCConvert.cpp
js/xpconnect/src/XPCJSRuntime.cpp
xpcom/reflect/xptinfo/xptcodegen.py
xpcom/reflect/xptinfo/xptinfo.h
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -10291,18 +10291,19 @@ NS_IMPL_ISUPPORTS(nsContentUtils::UserIn
   ExtractExceptionValues<mozilla::dom::prototypes::id::T,         \
                          T##_Binding::NativeType, T>(__VA_ARGS__) \
       .isOk()
 
 template <prototypes::ID PrototypeID, class NativeType, typename T>
 static Result<Ok, nsresult> ExtractExceptionValues(
     JSContext* aCx, JS::HandleObject aObj, nsAString& aSourceSpecOut,
     uint32_t* aLineOut, uint32_t* aColumnOut, nsString& aMessageOut) {
+  AssertStaticUnwrapOK<PrototypeID>();
   RefPtr<T> exn;
-  MOZ_TRY((UnwrapObject<PrototypeID, NativeType>(aObj, exn)));
+  MOZ_TRY((UnwrapObject<PrototypeID, NativeType>(aObj, exn, nullptr)));
 
   exn->GetFilename(aCx, aSourceSpecOut);
   if (!aSourceSpecOut.IsEmpty()) {
     *aLineOut = exn->LineNumber(aCx);
     *aColumnOut = exn->ColumnNumber();
   }
 
   exn->GetName(aMessageOut);
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1216,19 +1216,19 @@ bool QueryInterface(JSContext* cx, unsig
   if (!args.thisv().isObject()) {
     JS_ReportErrorASCII(cx, "QueryInterface called on incompatible non-object");
     return false;
   }
 
   // Get the object. It might be a security wrapper, in which case we do a
   // checked unwrap.
   JS::Rooted<JSObject*> origObj(cx, &args.thisv().toObject());
-  JS::Rooted<JSObject*> obj(cx,
-                            js::CheckedUnwrap(origObj,
-                                              /* stopAtWindowProxy = */ false));
+  JS::Rooted<JSObject*> obj(
+      cx, js::CheckedUnwrapDynamic(origObj, cx,
+                                   /* stopAtWindowProxy = */ false));
   if (!obj) {
     JS_ReportErrorASCII(cx, "Permission denied to access object");
     return false;
   }
 
   nsCOMPtr<nsISupports> native = UnwrapDOMObjectToISupports(obj);
   if (!native) {
     return Throw(cx, NS_ERROR_FAILURE);
@@ -2256,17 +2256,18 @@ void UpdateReflectorGlobal(JSContext* aC
   }
 }
 
 GlobalObject::GlobalObject(JSContext* aCx, JSObject* aObject)
     : mGlobalJSObject(aCx), mCx(aCx), mGlobalObject(nullptr) {
   MOZ_ASSERT(mCx);
   JS::Rooted<JSObject*> obj(aCx, aObject);
   if (js::IsWrapper(obj)) {
-    obj = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+    // aCx correctly represents the current global here.
+    obj = js::CheckedUnwrapDynamic(obj, aCx, /* stopAtWindowProxy = */ false);
     if (!obj) {
       // We should never end up here on a worker thread, since there shouldn't
       // be any security wrappers to worry about.
       if (!MOZ_LIKELY(NS_IsMainThread())) {
         MOZ_CRASH();
       }
 
       Throw(aCx, NS_ERROR_XPC_SECURITY_MANAGER_VETO);
@@ -2364,20 +2365,21 @@ bool InterfaceHasInstance(JSContext* cx,
   // OrdinaryHasInstance).
   if (!args.thisv().isObject()) {
     args.rval().setBoolean(false);
     return true;
   }
 
   // If "this" doesn't have a DOMIfaceAndProtoJSClass, it's not a DOM
   // constructor, so just fall back to OrdinaryHasInstance.  But note that we
-  // should CheckedUnwrap here, because otherwise we won't get the right
-  // answers.
-  JS::Rooted<JSObject*> thisObj(cx,
-                                js::CheckedUnwrap(&args.thisv().toObject()));
+  // should CheckedUnwrapStatic here, because otherwise we won't get the right
+  // answers.  The static version is OK, because we're looking for DOM
+  // constructors, which are not cross-origin objects.
+  JS::Rooted<JSObject*> thisObj(
+      cx, js::CheckedUnwrapStatic(&args.thisv().toObject()));
   if (!thisObj) {
     // Just fall back on the normal thing, in case it still happens to work.
     return CallOrdinaryHasInstance(cx, args);
   }
 
   const js::Class* thisClass = js::GetObjectClass(thisObj);
 
   if (!IsDOMIfaceAndProtoClass(thisClass)) {
@@ -2440,18 +2442,20 @@ bool InterfaceIsInstance(JSContext* cx, 
 
   // If "this" isn't a DOM constructor or is a constructor for an interface
   // without a prototype, return false.
   if (!args.thisv().isObject()) {
     args.rval().setBoolean(false);
     return true;
   }
 
-  JS::Rooted<JSObject*> thisObj(cx,
-                                js::CheckedUnwrap(&args.thisv().toObject()));
+  // CheckedUnwrapStatic is fine, since we're just interested in finding out
+  // whether this is a DOM constructor.
+  JS::Rooted<JSObject*> thisObj(
+      cx, js::CheckedUnwrapStatic(&args.thisv().toObject()));
   if (!thisObj) {
     args.rval().setBoolean(false);
     return true;
   }
 
   const js::Class* thisClass = js::GetObjectClass(thisObj);
   if (!IsDOMIfaceAndProtoClass(thisClass)) {
     args.rval().setBoolean(false);
@@ -2811,16 +2815,19 @@ namespace binding_detail {
  *                   the native (which is why aSelf is a reference to a void*).
  *                   The ThisPolicy user should use the this JSObject* to
  *                   determine what C++ class aSelf contains. aObj is used to
  *                   keep the reflector object alive while self is being used,
  *                   so its value before and after the UnwrapThisObject call
  *                   could be different (if aObj was wrapped). The return value
  *                   is an nsresult, which will signal if an error occurred.
  *
+ *                   This is passed a JSContext for dynamic unwrapping purposes,
+ *                   but should not throw exceptions on that JSContext.
+ *
  * HandleInvalidThis: If the |this| is not valid (wrong type of value, wrong
  *                    object, etc), decide what to do about it.  Returns a
  *                    boolean to return from the JSNative (false for failure,
  *                    true for succcess).
  */
 struct NormalThisPolicy {
   // This needs to be inlined because it's called on no-exceptions fast-paths.
   static MOZ_ALWAYS_INLINE bool HasValidThisValue(const JS::CallArgs& aArgs) {
@@ -2838,22 +2845,22 @@ struct NormalThisPolicy {
       const JS::CallArgs& aArgs) {
     return &aArgs.thisv().toObject();
   }
 
   static MOZ_ALWAYS_INLINE JSObject* MaybeUnwrapThisObject(JSObject* aObj) {
     return aObj;
   }
 
-  static MOZ_ALWAYS_INLINE nsresult
-  UnwrapThisObject(JS::MutableHandle<JSObject*> aObj, void*& aSelf,
-                   prototypes::ID aProtoID, uint32_t aProtoDepth) {
+  static MOZ_ALWAYS_INLINE nsresult UnwrapThisObject(
+      JS::MutableHandle<JSObject*> aObj, JSContext* aCx, void*& aSelf,
+      prototypes::ID aProtoID, uint32_t aProtoDepth) {
     binding_detail::MutableObjectHandleWrapper wrapper(aObj);
     return binding_detail::UnwrapObjectInternal<void, true>(
-        wrapper, aSelf, aProtoID, aProtoDepth);
+        wrapper, aSelf, aProtoID, aProtoDepth, aCx);
   }
 
   static bool HandleInvalidThis(JSContext* aCx, JS::CallArgs& aArgs,
                                 bool aSecurityError, prototypes::ID aProtoId) {
     return ThrowInvalidThis(aCx, aArgs, aSecurityError, aProtoId);
   }
 };
 
@@ -2911,42 +2918,46 @@ struct CrossOriginThisPolicy : public Ma
     // CheckedUnwrap it, and either succeed or get a security error as needed.
     return aObj;
   }
 
   // After calling UnwrapThisObject aSelf can contain one of 2 types, depending
   // on whether aObj is a proxy with a RemoteObjectProxy handler or a (maybe
   // wrapped) normal WebIDL reflector. The generated binding code relies on this
   // and uses IsRemoteObjectProxy to determine what type aSelf points to.
-  static MOZ_ALWAYS_INLINE nsresult
-  UnwrapThisObject(JS::MutableHandle<JSObject*> aObj, void*& aSelf,
-                   prototypes::ID aProtoID, uint32_t aProtoDepth) {
+  static MOZ_ALWAYS_INLINE nsresult UnwrapThisObject(
+      JS::MutableHandle<JSObject*> aObj, JSContext* aCx, void*& aSelf,
+      prototypes::ID aProtoID, uint32_t aProtoDepth) {
     binding_detail::MutableObjectHandleWrapper wrapper(aObj);
     // We need to pass false here, because if aObj doesn't have a DOMJSClass
     // it might be a remote proxy object, and we don't want to throw in that
     // case (even though unwrapping would fail).
     nsresult rv = binding_detail::UnwrapObjectInternal<void, false>(
-        wrapper, aSelf, aProtoID, aProtoDepth);
+        wrapper, aSelf, aProtoID, aProtoDepth, nullptr);
     if (NS_SUCCEEDED(rv)) {
       return rv;
     }
 
     if (js::IsWrapper(wrapper)) {
-      JSObject* unwrappedObj =
-          js::CheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false);
+      // We want CheckedUnwrapDynamic here: aCx represents the Realm we are in
+      // right now, so we want to check whether that Realm should be able to
+      // access the object.  And this object can definitely be a WindowProxy, so
+      // we need he dynamic check.
+      JSObject* unwrappedObj = js::CheckedUnwrapDynamic(
+          wrapper, aCx, /* stopAtWindowProxy = */ false);
       if (!unwrappedObj) {
         return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
       }
 
       // At this point we want to keep "unwrappedObj" alive, because we don't
       // hold a strong reference in "aSelf".
       wrapper = unwrappedObj;
 
       return binding_detail::UnwrapObjectInternal<void, false>(
-          wrapper, aSelf, aProtoID, aProtoDepth);
+          wrapper, aSelf, aProtoID, aProtoDepth, nullptr);
     }
 
     if (!IsRemoteObjectProxy(wrapper, aProtoID)) {
       return NS_ERROR_XPC_BAD_CONVERT_JS;
     }
     aSelf = RemoteObjectProxyBase::GetNative(wrapper);
     return NS_OK;
   }
@@ -3002,17 +3013,17 @@ bool GenericGetter(JSContext* cx, unsign
 
   // NOTE: we want to leave obj in its initial compartment, so don't want to
   // pass it to UnwrapObjectInternal.  Also, the thing we pass to
   // UnwrapObjectInternal may be affected by our ThisPolicy.
   JS::Rooted<JSObject*> rootSelf(cx, ThisPolicy::MaybeUnwrapThisObject(obj));
   void* self;
   {
     nsresult rv =
-        ThisPolicy::UnwrapThisObject(&rootSelf, self, protoID, info->depth);
+        ThisPolicy::UnwrapThisObject(&rootSelf, cx, self, protoID, info->depth);
     if (NS_FAILED(rv)) {
       bool ok = ThisPolicy::HandleInvalidThis(
           cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, protoID);
       return ExceptionPolicy::HandleException(cx, args, info, ok);
     }
   }
 
   MOZ_ASSERT(info->type() == JSJitInfo::Getter);
@@ -3058,17 +3069,17 @@ bool GenericSetter(JSContext* cx, unsign
 
   // NOTE: we want to leave obj in its initial compartment, so don't want to
   // pass it to UnwrapObject.  Also the thing we pass to UnwrapObjectInternal
   // may be affected by our ThisPolicy.
   JS::Rooted<JSObject*> rootSelf(cx, ThisPolicy::MaybeUnwrapThisObject(obj));
   void* self;
   {
     nsresult rv =
-        ThisPolicy::UnwrapThisObject(&rootSelf, self, protoID, info->depth);
+        ThisPolicy::UnwrapThisObject(&rootSelf, cx, self, protoID, info->depth);
     if (NS_FAILED(rv)) {
       return ThisPolicy::HandleInvalidThis(
           cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, protoID);
     }
   }
   if (args.length() == 0) {
     return ThrowNoSetterArg(cx, args, protoID);
   }
@@ -3107,17 +3118,17 @@ bool GenericMethod(JSContext* cx, unsign
 
   // NOTE: we want to leave obj in its initial compartment, so don't want to
   // pass it to UnwrapObjectInternal.  Also, the thing we pass to
   // UnwrapObjectInternal may be affected by our ThisPolicy.
   JS::Rooted<JSObject*> rootSelf(cx, ThisPolicy::MaybeUnwrapThisObject(obj));
   void* self;
   {
     nsresult rv =
-        ThisPolicy::UnwrapThisObject(&rootSelf, self, protoID, info->depth);
+        ThisPolicy::UnwrapThisObject(&rootSelf, cx, self, protoID, info->depth);
     if (NS_FAILED(rv)) {
       bool ok = ThisPolicy::HandleInvalidThis(
           cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, protoID);
       return ExceptionPolicy::HandleException(cx, args, info, ok);
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Method);
   JSJitMethodOp method = info->method;
@@ -3421,18 +3432,19 @@ bool GetDesiredProto(JSContext* aCx, con
   // slow JS_GetProperty call.
   JS::Rooted<JSObject*> newTarget(aCx, &aCallArgs.newTarget().toObject());
   JS::Rooted<JSObject*> originalNewTarget(aCx, newTarget);
   // See whether we have a known DOM constructor here, such that we can take a
   // fast path.
   prototypes::ID protoID = GetProtoIdForNewtarget(newTarget);
   if (protoID == prototypes::id::_ID_Count) {
     // We might still have a cross-compartment wrapper for a known DOM
-    // constructor.
-    newTarget = js::CheckedUnwrap(newTarget);
+    // constructor.  CheckedUnwrapStatic is fine here, because we're looking for
+    // DOM constructors and those can't be cross-origin objects.
+    newTarget = js::CheckedUnwrapStatic(newTarget);
     if (newTarget && newTarget != originalNewTarget) {
       protoID = GetProtoIdForNewtarget(newTarget);
     }
   }
 
   if (protoID != prototypes::id::_ID_Count) {
     ProtoAndIfaceCache& protoAndIfaceCache =
         *GetProtoAndIfaceCache(JS::GetNonCCWObjectGlobal(newTarget));
@@ -3558,18 +3570,22 @@ bool HTMLConstructor(JSContext* aCx, uns
     return Throw(aCx, NS_ERROR_UNEXPECTED);
   }
 
   // Step 2.
 
   // The newTarget might be a cross-compartment wrapper. Get the underlying
   // object so we can do the spec's object-identity checks.  If we ever stop
   // unwrapping here, carefully audit uses of newTarget below!
+  //
+  // Note that the ES spec enforces that newTarget is always a constructor (in
+  // the sense of having a [[Construct]]), so it's not a cross-origin object and
+  // we can use CheckedUnwrapStatic.
   JS::Rooted<JSObject*> newTarget(
-      aCx, js::CheckedUnwrap(&args.newTarget().toObject()));
+      aCx, js::CheckedUnwrapStatic(&args.newTarget().toObject()));
   if (!newTarget) {
     return ThrowErrorMessage(aCx, MSG_ILLEGAL_CONSTRUCTOR);
   }
 
   // Enter the compartment of our underlying newTarget object, so we end
   // up comparing to the constructor object for our interface from that global.
   // XXXbz This is not what the spec says to do, and it's not super-clear to me
   // at this point why we're doing it.  Why not just compare |newTarget| and
@@ -3634,17 +3650,19 @@ bool HTMLConstructor(JSContext* aCx, uns
       cb = HTMLElement_Binding::GetConstructorObject;
     }
 
     // We want to get the constructor from our global's realm, not the
     // caller realm.
     JSAutoRealm ar(aCx, global.Get());
     JS::Rooted<JSObject*> constructor(aCx, cb(aCx));
 
-    if (constructor != js::CheckedUnwrap(callee)) {
+    // CheckedUnwrapStatic is OK here, since our callee is callable, hence not a
+    // cross-origin object.
+    if (constructor != js::CheckedUnwrapStatic(callee)) {
       return ThrowErrorMessage(aCx, MSG_ILLEGAL_CONSTRUCTOR);
     }
   } else {
     if (ns == kNameSpaceID_XHTML) {
       // Step 5.
       // If the definition is for a customized built-in element, the localName
       // should be one of the ones defined in the specification for this
       // interface.
@@ -3667,17 +3685,19 @@ bool HTMLConstructor(JSContext* aCx, uns
     // We want to get the constructor from our global's realm, not the
     // caller realm.
     JSAutoRealm ar(aCx, global.Get());
     JS::Rooted<JSObject*> constructor(aCx, cb(aCx));
     if (!constructor) {
       return false;
     }
 
-    if (constructor != js::CheckedUnwrap(callee)) {
+    // CheckedUnwrapStatic is OK here, since our callee is callable, hence not a
+    // cross-origin object.
+    if (constructor != js::CheckedUnwrapStatic(callee)) {
       return ThrowErrorMessage(aCx, MSG_ILLEGAL_CONSTRUCTOR);
     }
   }
 
   // Step 6.
   JS::Rooted<JSObject*> desiredProto(aCx);
   if (!GetDesiredProto(aCx, args, &desiredProto)) {
     return false;
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -153,20 +153,32 @@ inline nsISupports* UnwrapDOMObjectToISu
 inline bool IsDOMObject(JSObject* obj) {
   return IsDOMClass(js::GetObjectClass(obj));
 }
 
 // There are two valid ways to use UNWRAP_OBJECT: Either obj needs to
 // be a MutableHandle<JSObject*>, or value needs to be a strong-reference
 // smart pointer type (OwningNonNull or RefPtr or nsCOMPtr), in which case obj
 // can be anything that converts to JSObject*.
-#define UNWRAP_OBJECT(Interface, obj, value)                                 \
+//
+// This can't be used with Window, EventTarget, or Location as the "Interface"
+// argument (and will fail a static_assert if you try to do that).  Use
+// UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT to unwrap to those interfaces.
+#define UNWRAP_OBJECT(Interface, obj, value)                        \
+  mozilla::dom::binding_detail::UnwrapObjectWithCrossOriginAsserts< \
+      mozilla::dom::prototypes::id::Interface,                      \
+      mozilla::dom::Interface##_Binding::NativeType>(obj, value)
+
+// UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT is just like UNWRAP_OBJECT but requires a
+// JSContext in a Realm that represents "who is doing the unwrapping?" to
+// properly unwrap the object.
+#define UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Interface, obj, value, cx)          \
   mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface,        \
                              mozilla::dom::Interface##_Binding::NativeType>( \
-      obj, value)
+      obj, value, cx)
 
 // Test whether the given object is an instance of the given interface.
 #define IS_INSTANCE_OF(Interface, obj)                                       \
   mozilla::dom::IsInstanceOf<mozilla::dom::prototypes::id::Interface,        \
                              mozilla::dom::Interface##_Binding::NativeType>( \
       obj)
 
 // Unwrap the given non-wrapper object.  This can be used with any obj that
@@ -190,21 +202,33 @@ inline bool IsDOMObject(JSObject* obj) {
 // MutableHandle<JSObject*>, with an assignment operator that sets the handle to
 // the given object, or U needs to be a strong-reference smart pointer type
 // (OwningNonNull or RefPtr or nsCOMPtr), or the value being stored in "value"
 // must not escape past being tested for falsiness immediately after the
 // UnwrapObjectInternal call.
 //
 // If mayBeWrapper is false, obj can just be a JSObject*, and U anything that a
 // T* can be assigned to.
+//
+// CxType is in practice allowed to be either decltype(nullptr) or JSContext*.
+// If it's decltype(nullptr) we will do a CheckedUnwrapStatic and it's the
+// caller's responsibility to make sure they're not trying to work with Window
+// or Location objects.  Otherwise we'll do a CheckedUnwrapDynamic.  This all
+// only matters if mayBeWrapper is true; if it's false just pass nullptr for
+// the cx arg.
 namespace binding_detail {
-template <class T, bool mayBeWrapper, typename U, typename V>
+template <class T, bool mayBeWrapper, typename U, typename V, typename CxType>
 MOZ_ALWAYS_INLINE nsresult UnwrapObjectInternal(V& obj, U& value,
                                                 prototypes::ID protoID,
-                                                uint32_t protoDepth) {
+                                                uint32_t protoDepth,
+                                                CxType cx) {
+  static_assert(IsSame<CxType, JSContext*>::value ||
+                    IsSame<CxType, decltype(nullptr)>::value,
+                "Unexpected CxType");
+
   /* First check to see whether we have a DOM object */
   const DOMJSClass* domClass = GetDOMClass(obj);
   if (domClass) {
     /* This object is a DOM object.  Double-check that it is safely
        castable to T by checking whether it claims to inherit from the
        class identified by protoID. */
     if (domClass->mInterfaceChain[protoDepth] == protoID) {
       value = UnwrapDOMObject<T>(obj);
@@ -213,33 +237,49 @@ MOZ_ALWAYS_INLINE nsresult UnwrapObjectI
   }
 
   /* Maybe we have a security wrapper or outer window? */
   if (!mayBeWrapper || !js::IsWrapper(obj)) {
     /* Not a DOM object, not a wrapper, just bail */
     return NS_ERROR_XPC_BAD_CONVERT_JS;
   }
 
-  JSObject* unwrappedObj =
-      js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+  JSObject* unwrappedObj;
+  if (IsSame<CxType, decltype(nullptr)>::value) {
+    unwrappedObj = js::CheckedUnwrapStatic(obj);
+  } else {
+    unwrappedObj =
+        js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false);
+  }
   if (!unwrappedObj) {
     return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
   }
-  MOZ_ASSERT(!js::IsWrapper(unwrappedObj));
+
+  if (IsSame<CxType, decltype(nullptr)>::value) {
+    // We might still have a windowproxy here.  But it shouldn't matter, because
+    // that's not what the caller is looking for, so we're going to fail out
+    // anyway below once we do the recursive call to ourselves with wrapper
+    // unwrapping disabled.
+    MOZ_ASSERT(!js::IsWrapper(unwrappedObj) || js::IsWindowProxy(unwrappedObj));
+  } else {
+    // We shouldn't have a wrapper by now.
+    MOZ_ASSERT(!js::IsWrapper(unwrappedObj));
+  }
+
   // Recursive call is OK, because now we're using false for mayBeWrapper and
   // we never reach this code if that boolean is false, so can't keep calling
   // ourselves.
   //
   // Unwrap into a temporary pointer, because in general unwrapping into
   // something of type U might trigger GC (e.g. release the value currently
   // stored in there, with arbitrary consequences) and invalidate the
   // "unwrappedObj" pointer.
   T* tempValue = nullptr;
   nsresult rv = UnwrapObjectInternal<T, false>(unwrappedObj, tempValue, protoID,
-                                               protoDepth);
+                                               protoDepth, nullptr);
   if (NS_SUCCEEDED(rv)) {
     // It's very important to not update "obj" with the "unwrappedObj" value
     // until we know the unwrap has succeeded.  Otherwise, in a situation in
     // which we have an overload of object and primitive we could end up
     // converting to the primitive from the unwrappedObj, whereas we want to do
     // it from the original object.
     obj = unwrappedObj;
     // And now assign to "value"; at this point we don't care if a GC happens
@@ -280,73 +320,104 @@ struct MutableValueHandleWrapper {
 
  private:
   JS::MutableHandle<JS::Value> mHandle;
 };
 
 }  // namespace binding_detail
 
 // UnwrapObject overloads that ensure we have a MutableHandle to keep it alive.
-template <prototypes::ID PrototypeID, class T, typename U>
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
 MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle<JSObject*> obj,
-                                        U& value) {
+                                        U& value, CxType cx) {
   binding_detail::MutableObjectHandleWrapper wrapper(obj);
   return binding_detail::UnwrapObjectInternal<T, true>(
-      wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+      wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
 }
 
-template <prototypes::ID PrototypeID, class T, typename U>
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
 MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle<JS::Value> obj,
-                                        U& value) {
+                                        U& value, CxType cx) {
   MOZ_ASSERT(obj.isObject());
   binding_detail::MutableValueHandleWrapper wrapper(obj);
   return binding_detail::UnwrapObjectInternal<T, true>(
-      wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+      wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
 }
 
 // UnwrapObject overloads that ensure we have a strong ref to keep it alive.
-template <prototypes::ID PrototypeID, class T, typename U>
-MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, RefPtr<U>& value) {
-  return binding_detail::UnwrapObjectInternal<T, true>(
-      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
-}
-
-template <prototypes::ID PrototypeID, class T, typename U>
-MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, nsCOMPtr<U>& value) {
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, RefPtr<U>& value,
+                                        CxType cx) {
   return binding_detail::UnwrapObjectInternal<T, true>(
-      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
 }
 
-template <prototypes::ID PrototypeID, class T, typename U>
-MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj,
-                                        OwningNonNull<U>& value) {
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, nsCOMPtr<U>& value,
+                                        CxType cx) {
   return binding_detail::UnwrapObjectInternal<T, true>(
-      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
+}
+
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, OwningNonNull<U>& value,
+                                        CxType cx) {
+  return binding_detail::UnwrapObjectInternal<T, true>(
+      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
 }
 
 // An UnwrapObject overload that just calls one of the JSObject* ones.
-template <prototypes::ID PrototypeID, class T, typename U>
-MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::Handle<JS::Value> obj, U& value) {
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::Handle<JS::Value> obj, U& value,
+                                        CxType cx) {
   MOZ_ASSERT(obj.isObject());
-  return UnwrapObject<PrototypeID, T>(&obj.toObject(), value);
+  return UnwrapObject<PrototypeID, T>(&obj.toObject(), value, cx);
 }
 
+template <prototypes::ID PrototypeID>
+MOZ_ALWAYS_INLINE void AssertStaticUnwrapOK() {
+  static_assert(PrototypeID != prototypes::id::Window,
+                "Can't do static unwrap of WindowProxy; use "
+                "UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a cross-origin-object "
+                "aware version of IS_INSTANCE_OF");
+  static_assert(PrototypeID != prototypes::id::EventTarget,
+                "Can't do static unwrap of WindowProxy (which an EventTarget "
+                "might be); use UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a "
+                "cross-origin-object aware version of IS_INSTANCE_OF");
+  static_assert(PrototypeID != prototypes::id::Location,
+                "Can't do static unwrap of Location; use "
+                "UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a cross-origin-object "
+                "aware version of IS_INSTANCE_OF");
+}
+
+namespace binding_detail {
+// This function is just here so we can do some static asserts in a centralized
+// place instead of putting them in every single UnwrapObject overload.
+template <prototypes::ID PrototypeID, class T, typename U, typename V>
+MOZ_ALWAYS_INLINE nsresult UnwrapObjectWithCrossOriginAsserts(V&& obj,
+                                                              U& value) {
+  AssertStaticUnwrapOK<PrototypeID>();
+  return UnwrapObject<PrototypeID, T>(obj, value, nullptr);
+}
+}  // namespace binding_detail
+
 template <prototypes::ID PrototypeID, class T>
 MOZ_ALWAYS_INLINE bool IsInstanceOf(JSObject* obj) {
+  AssertStaticUnwrapOK<PrototypeID>();
   void* ignored;
   nsresult unwrapped = binding_detail::UnwrapObjectInternal<T, true>(
-      obj, ignored, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+      obj, ignored, PrototypeID, PrototypeTraits<PrototypeID>::Depth, nullptr);
   return NS_SUCCEEDED(unwrapped);
 }
 
 template <prototypes::ID PrototypeID, class T, typename U>
 MOZ_ALWAYS_INLINE nsresult UnwrapNonWrapperObject(JSObject* obj, U& value) {
   MOZ_ASSERT(!js::IsWrapper(obj));
   return binding_detail::UnwrapObjectInternal<T, false>(
-      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+      obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, nullptr);
 }
 
 MOZ_ALWAYS_INLINE bool IsConvertibleToDictionary(JS::Handle<JS::Value> val) {
   return val.isNullOrUndefined() || val.isObject();
 }
 
 // The items in the protoAndIfaceCache are indexed by the prototypes::id::ID,
 // constructors::id::ID and namedpropertiesobjects::id::ID enums, in that order.
@@ -1086,17 +1157,20 @@ inline bool WrapNewBindingNonWrapperCach
     // before we call JS_WrapValue.
     Maybe<JSAutoRealm> ar;
     // Maybe<Handle> doesn't so much work, and in any case, adding
     // more Maybe (one for a Rooted and one for a Handle) adds more
     // code (and branches!) than just adding a single rooted.
     JS::Rooted<JSObject*> scope(cx, scopeArg);
     JS::Rooted<JSObject*> proto(cx, givenProto);
     if (js::IsWrapper(scope)) {
-      scope = js::CheckedUnwrap(scope, /* stopAtWindowProxy = */ false);
+      // We are working in the Realm of cx and will be producing our reflector
+      // there, so we need to succeed if that realm has access to the scope.
+      scope =
+          js::CheckedUnwrapDynamic(scope, cx, /* stopAtWindowProxy = */ false);
       if (!scope) return false;
       ar.emplace(cx, scope);
       if (!JS_WrapObject(cx, &proto)) {
         return false;
       }
     }
 
     MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
@@ -1135,17 +1209,20 @@ inline bool WrapNewBindingNonWrapperCach
     // before we call JS_WrapValue.
     Maybe<JSAutoRealm> ar;
     // Maybe<Handle> doesn't so much work, and in any case, adding
     // more Maybe (one for a Rooted and one for a Handle) adds more
     // code (and branches!) than just adding a single rooted.
     JS::Rooted<JSObject*> scope(cx, scopeArg);
     JS::Rooted<JSObject*> proto(cx, givenProto);
     if (js::IsWrapper(scope)) {
-      scope = js::CheckedUnwrap(scope, /* stopAtWindowProxy = */ false);
+      // We are working in the Realm of cx and will be producing our reflector
+      // there, so we need to succeed if that realm has access to the scope.
+      scope =
+          js::CheckedUnwrapDynamic(scope, cx, /* stopAtWindowProxy = */ false);
       if (!scope) return false;
       ar.emplace(cx, scope);
       if (!JS_WrapObject(cx, &proto)) {
         return false;
       }
     }
 
     MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2984,19 +2984,24 @@ class CGCollectJSONAttributesMethod(CGAb
                         { // scope for "temp"
                           $*{getAndDefine}
                         }
                         """,
                         getAndDefine=getAndDefine)
         ret += 'return true;\n'
 
         if needUnwrappedObj:
+            # If we started allowing cross-origin objects here, we'd need to
+            # use CheckedUnwrapDynamic and figure out whether it makes sense.
+            # But in practice no one is trying to add toJSON methods to those,
+            # so let's just guard against it.
+            assert not self.descriptor.isMaybeCrossOriginObject()
             ret= fill(
                 """
-                JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrap(obj));
+                JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrapStatic(obj));
                 if (!unwrappedObj) {
                   // How did that happen?  We managed to get called with that
                   // object as "this"!  Just give up on sanity.
                   return false;
                 }
 
                 $*{ret}
                 """,
@@ -4366,17 +4371,18 @@ class CastableObjectUnwrapper():
     def __str__(self):
         substitution = self.substitution.copy()
         substitution["codeOnFailure"] %= {
             'securityError': 'rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO'
         }
         return fill(
             """
             {
-              nsresult rv = UnwrapObject<${protoID}, ${type}>(${mutableSource}, ${target});
+              // Our JSContext should be in the right global to do unwrapping in.
+              nsresult rv = UnwrapObject<${protoID}, ${type}>(${mutableSource}, ${target}, cx);
               if (NS_FAILED(rv)) {
                 $*{codeOnFailure}
               }
             }
             """,
             **substitution)
 
 
@@ -5478,18 +5484,18 @@ def getJSToNativeConversionInfo(type, de
         #    the JS implementation.  But if the JS implementation returned
         #    a page-side Promise (which is a totally sane thing to do, and
         #    in fact the right thing to do given that this return value is
         #    going right to content script) then we don't want to
         #    Promise.resolve with our current compartment Promise, because
         #    that will wrap it up in a chrome-side Promise, which is
         #    decidedly _not_ what's desired here.  So in that case we
         #    should really unwrap the return value and use the global of
-        #    the result.  CheckedUnwrap should be good enough for that; if
-        #    it fails, then we're failing unwrap while in a
+        #    the result.  CheckedUnwrapStatic should be good enough for that;
+        #    if it fails, then we're failing unwrap while in a
         #    system-privileged compartment, so presumably we have a dead
         #    object wrapper.  Just error out.  Do NOT fall back to using
         #    the current compartment instead: that will return a
         #    system-privileged rejected (because getting .then inside
         #    resolve() failed) Promise to the caller, which they won't be
         #    able to touch.  That's not helpful.  If we error out, on the
         #    other hand, they will get a content-side rejected promise.
         #    Same thing if the value returned is not even an object.
@@ -5500,17 +5506,17 @@ def getJSToNativeConversionInfo(type, de
             # which we don't really want here.
             assert exceptionCode == "aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n"
             getPromiseGlobal = fill(
                 """
                 if (!$${val}.isObject()) {
                   aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}"));
                   return nullptr;
                 }
-                JSObject* unwrappedVal = js::CheckedUnwrap(&$${val}.toObject());
+                JSObject* unwrappedVal = js::CheckedUnwrapStatic(&$${val}.toObject());
                 if (!unwrappedVal) {
                   // A slight lie, but not much of one, for a dead object wrapper.
                   aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}"));
                   return nullptr;
                 }
                 globalObj = JS::GetNonCCWObjectGlobal(unwrappedVal);
                 """,
                 sourceDescription=sourceDescription)
@@ -7742,21 +7748,26 @@ class CGPerSignatureCall(CGThing):
         elif descriptor.interface.isJSImplemented():
             if not idlNode.isStatic():
                 needsUnwrap = True
                 needsUnwrappedVar = True
                 argsPost.append("(unwrappedObj ? js::GetNonCCWObjectRealm(*unwrappedObj) : js::GetContextRealm(cx))")
         elif needScopeObject(returnType, arguments, self.extendedAttributes,
                              descriptor.wrapperCache, True,
                              idlNode.getExtendedAttribute("StoreInSlot")):
+            # If we ever end up with APIs like this on cross-origin objects,
+            # figure out how the CheckedUnwrapDynamic bits should work.  Chances
+            # are, just calling it with "cx" is fine...  For now, though, just
+            # assert that it does not matter.
+            assert not descriptor.isMaybeCrossOriginObject()
             # The scope object should always be from the relevant
             # global.  Make sure to unwrap it as needed.
             cgThings.append(CGGeneric(dedent(
                 """
-                JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrap(obj));
+                JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrapStatic(obj));
                 // Caller should have ensured that "obj" can be unwrapped already.
                 MOZ_DIAGNOSTIC_ASSERT(unwrappedObj);
                 """)))
             argsPre.append("unwrappedObj")
 
         if needsUnwrap and needsUnwrappedVar:
             # We cannot assign into obj because it's a Handle, not a
             # MutableHandle, so we need a separate Rooted.
@@ -7821,17 +7832,19 @@ class CGPerSignatureCall(CGThing):
         if needsUnwrap:
             # Something depends on having the unwrapped object, so unwrap it now.
             xraySteps = []
             # XXXkhuey we should be able to MOZ_ASSERT that ${obj} is
             # not null.
             xraySteps.append(
                 CGGeneric(fill(
                     """
-                    ${obj} = js::CheckedUnwrap(${obj});
+                    // Since our object is an Xray, we can just CheckedUnwrapStatic:
+                    // we know Xrays have no dynamic unwrap behavior.
+                    ${obj} = js::CheckedUnwrapStatic(${obj});
                     if (!${obj}) {
                       return false;
                     }
                     """,
                     obj=unwrappedVar)))
             if isConstructor:
                 # If we're called via an xray, we need to enter the underlying
                 # object's compartment and then wrap up all of our arguments into
--- a/dom/bindings/Date.h
+++ b/dom/bindings/Date.h
@@ -28,17 +28,17 @@ class Date {
   // Returns an integer in the range [-8.64e15, +8.64e15] (-0 excluded), *or*
   // returns NaN.  DO NOT ASSUME THIS IS FINITE!
   double ToDouble() const { return mMsecSinceEpoch.toDouble(); }
 
   void SetTimeStamp(JS::ClippedTime aMilliseconds) {
     mMsecSinceEpoch = aMilliseconds;
   }
 
-  // Can return false if CheckedUnwrap fails.  This will NOT throw;
+  // Can return false if unboxing fails.  This will NOT throw;
   // callers should do it as needed.
   bool SetTimeStamp(JSContext* aCx, JSObject* aObject);
 
   bool ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const;
 
  private:
   JS::ClippedTime mMsecSinceEpoch;
 };
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -37,19 +37,21 @@ static void ThrowExceptionValueIfSafe(JS
   if (!exnVal.isObject()) {
     JS_SetPendingException(aCx, exnVal);
     return;
   }
 
   JS::Rooted<JSObject*> exnObj(aCx, &exnVal.toObject());
   MOZ_ASSERT(js::IsObjectInContextCompartment(exnObj, aCx),
              "exnObj needs to be in the right compartment for the "
-             "CheckedUnwrap thing to make sense");
+             "CheckedUnwrapDynamic thing to make sense");
 
-  if (js::CheckedUnwrap(exnObj)) {
+  // aCx's current Realm is where we're throwing, so using it in the
+  // CheckedUnwrapDynamic check makes sense.
+  if (js::CheckedUnwrapDynamic(exnObj, aCx)) {
     // This is an object we're allowed to work with, so just go ahead and throw
     // it.
     JS_SetPendingException(aCx, exnVal);
     return;
   }
 
   // We could probably Throw(aCx, NS_ERROR_UNEXPECTED) here, and it would do the
   // right thing due to there not being an existing exception on the runtime at
--- a/dom/bindings/WebIDLGlobalNameHash.cpp
+++ b/dom/bindings/WebIDLGlobalNameHash.cpp
@@ -68,34 +68,40 @@ bool WebIDLGlobalNameHash::DefineIfEnabl
   if (!entry) {
     *aFound = false;
     return true;
   }
 
   *aFound = true;
 
   ConstructorEnabled checkEnabledForScope = entry->mEnabled;
-  // We do the enabled check on the current compartment of aCx, but for the
+  // We do the enabled check on the current Realm of aCx, but for the
   // actual object we pass in the underlying object in the Xray case.  That
   // way the callee can decide whether to allow access based on the caller
   // or the window being touched.
+  //
+  // Using aCx to represent the current Realm for CheckedUnwrapDynamic
+  // purposes is OK here, because that's the Realm where we plan to do
+  // our property-defining.
   JS::Rooted<JSObject*> global(
-      aCx, js::CheckedUnwrap(aObj, /* stopAtWindowProxy = */ false));
+      aCx,
+      js::CheckedUnwrapDynamic(aObj, aCx, /* stopAtWindowProxy = */ false));
   if (!global) {
     return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR);
   }
 
   {
     // It's safe to pass "&global" here, because we've already unwrapped it, but
     // for general sanity better to not have debug code even having the
     // appearance of mutating things that opt code uses.
 #ifdef DEBUG
     JS::Rooted<JSObject*> temp(aCx, global);
     DebugOnly<nsGlobalWindowInner*> win;
-    MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, &temp, win)));
+    MOZ_ASSERT(NS_SUCCEEDED(
+        UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window, &temp, win, aCx)));
 #endif
   }
 
   if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
     return true;
   }
 
   // The DOM constructor resolve machinery interacts with Xrays in tricky
--- a/dom/bindings/test/TestFunctions.cpp
+++ b/dom/bindings/test/TestFunctions.cpp
@@ -96,17 +96,17 @@ already_AddRefed<Promise> TestFunctions:
 int32_t TestFunctions::One() const { return 1; }
 
 int32_t TestFunctions::Two() const { return 2; }
 
 bool TestFunctions::ObjectFromAboutBlank(JSContext* aCx, JSObject* aObj) {
   // We purposefully don't use WindowOrNull here, because we want to
   // demonstrate the incorrect behavior we get, not just fail some asserts.
   RefPtr<nsGlobalWindowInner> win;
-  UNWRAP_OBJECT(Window, aObj, win);
+  UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window, aObj, win, aCx);
   if (!win) {
     return false;
   }
 
   Document* doc = win->GetDoc();
   if (!doc) {
     return false;
   }
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -1592,19 +1592,19 @@ nsXPCComponents_Utils::ImportGlobalPrope
   // Ensure we're working in the scripted caller's realm. This is not guaranteed
   // to be the current realm because we switch realms when calling cross-realm
   // functions.
   RootedObject global(cx, JS::GetScriptedCallerGlobal(cx));
   MOZ_ASSERT(global);
   js::AssertSameCompartment(cx, global);
   JSAutoRealm ar(cx, global);
 
-  // Don't allow doing this if the global is a Window
+  // Don't allow doing this if the global is a Window.
   nsGlobalWindowInner* win;
-  if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, &global, win))) {
+  if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, win))) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   GlobalProperties options;
   NS_ENSURE_TRUE(aPropertyList.isObject(), NS_ERROR_INVALID_ARG);
 
   RootedObject propertyList(cx, &aPropertyList.toObject());
   bool isArray;
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -802,17 +802,17 @@ bool XPCConvert::JSData2Native(JSContext
         return true;
       }
 
       // Can't handle non-JSObjects
       if (!s.isObject()) {
         return false;
       }
 
-      nsresult err = type.GetDOMObjectInfo().Unwrap(s, (void**)d);
+      nsresult err = type.GetDOMObjectInfo().Unwrap(s, (void**)d, cx);
       if (pErr) {
         *pErr = err;
       }
       return NS_SUCCEEDED(err);
     }
 
     case nsXPTType::T_PROMISE: {
       nsIGlobalObject* glob = CurrentNativeGlobal(cx);
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2245,17 +2245,17 @@ class XPCJSRuntimeStats : public JS::Run
     extras->pathPrefix.AssignLiteral("explicit/js-non-window/zones/");
 
     // Get some global in this zone.
     Rooted<Realm*> realm(cx, js::GetAnyRealmInZone(zone));
     if (realm) {
       RootedObject global(cx, JS::GetRealmGlobalOrNull(realm));
       if (global) {
         RefPtr<nsGlobalWindowInner> window;
-        if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, window))) {
+        if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) {
           // The global is a |window| object.  Use the path prefix that
           // we should have already created for it.
           if (mTopWindowPaths->Get(window->WindowID(), &extras->pathPrefix))
             extras->pathPrefix.AppendLiteral("/js-");
         }
       }
     }
 
@@ -2273,17 +2273,17 @@ class XPCJSRuntimeStats : public JS::Run
     GetRealmName(realm, rName, &mAnonymizeID, /* replaceSlashes = */ true);
 
     // Get the realm's global.
     AutoSafeJSContext cx;
     bool needZone = true;
     RootedObject global(cx, JS::GetRealmGlobalOrNull(realm));
     if (global) {
       RefPtr<nsGlobalWindowInner> window;
-      if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, window))) {
+      if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) {
         // The global is a |window| object.  Use the path prefix that
         // we should have already created for it.
         if (mWindowPaths->Get(window->WindowID(), &extras->jsPathPrefix)) {
           extras->domPathPrefix.Assign(extras->jsPathPrefix);
           extras->domPathPrefix.AppendLiteral("/dom/");
           extras->jsPathPrefix.AppendLiteral("/js-");
           needZone = false;
         } else {
--- a/xpcom/reflect/xptinfo/xptcodegen.py
+++ b/xpcom/reflect/xptinfo/xptcodegen.py
@@ -441,20 +441,20 @@ def link_to_cpp(interfaces, fd):
     fd.write("""
 #include "xptinfo.h"
 #include "mozilla/PerfectHash.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/dom/BindingUtils.h"
 
 // These template methods are specialized to be used in the sDOMObjects table.
 template<mozilla::dom::prototypes::ID PrototypeID, typename T>
-static nsresult UnwrapDOMObject(JS::HandleValue aHandle, void** aObj)
+static nsresult UnwrapDOMObject(JS::HandleValue aHandle, void** aObj, JSContext* aCx)
 {
   RefPtr<T> p;
-  nsresult rv = mozilla::dom::UnwrapObject<PrototypeID, T>(aHandle, p);
+  nsresult rv = mozilla::dom::UnwrapObject<PrototypeID, T>(aHandle, p, aCx);
   p.forget(aObj);
   return rv;
 }
 
 template<typename T>
 static bool WrapDOMObject(JSContext* aCx, void* aObj, JS::MutableHandleValue aHandle)
 {
   return mozilla::dom::GetOrCreateDOMReflector(aCx, reinterpret_cast<T*>(aObj), aHandle);
--- a/xpcom/reflect/xptinfo/xptinfo.h
+++ b/xpcom/reflect/xptinfo/xptinfo.h
@@ -517,31 +517,31 @@ struct nsXPTConstantInfo {
 static_assert(sizeof(nsXPTConstantInfo) == 8, "wrong size");
 
 /**
  * Object representing the information required to wrap and unwrap DOMObjects.
  *
  * This object will not live in rodata as it contains relocations.
  */
 struct nsXPTDOMObjectInfo {
-  nsresult Unwrap(JS::HandleValue aHandle, void** aObj) const {
-    return mUnwrap(aHandle, aObj);
+  nsresult Unwrap(JS::HandleValue aHandle, void** aObj, JSContext* aCx) const {
+    return mUnwrap(aHandle, aObj, aCx);
   }
 
   bool Wrap(JSContext* aCx, void* aObj, JS::MutableHandleValue aHandle) const {
     return mWrap(aCx, aObj, aHandle);
   }
 
   void Cleanup(void* aObj) const { return mCleanup(aObj); }
 
   ////////////////////////////////////////////////////////////////
   // Ensure these fields are in the same order as xptcodegen.py //
   ////////////////////////////////////////////////////////////////
 
-  nsresult (*mUnwrap)(JS::HandleValue aHandle, void** aObj);
+  nsresult (*mUnwrap)(JS::HandleValue aHandle, void** aObj, JSContext* aCx);
   bool (*mWrap)(JSContext* aCx, void* aObj, JS::MutableHandleValue aHandle);
   void (*mCleanup)(void* aObj);
 };
 
 namespace xpt {
 namespace detail {
 
 // The UntypedTArray type allows low-level access from XPConnect to nsTArray