Bug 1371259 part 3. Change UnwrapObject<> and the UNWRAP_OBJECT macro to allow passing in mutable object or value handles for the thing being unwrapped, and do so at various callsites. r=peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 10 Jul 2017 16:05:24 -0400
changeset 416825 d58add6deeb122dc92760eb100c7068306be6a7f
parent 416824 9c3018012352ca80f3ce4d132e6ed61cb2c813ba
child 416826 2338abec8626ded38b149b611f2ecfc7aa2466c8
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1371259
milestone56.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 1371259 part 3. Change UnwrapObject<> and the UNWRAP_OBJECT macro to allow passing in mutable object or value handles for the thing being unwrapped, and do so at various callsites. r=peterv I did audit all UNWRAP_OBJECT callers to make sure that the lifetimes of all the temporary Rooted or the RefPtrs they unwrap into are long enough.
dom/base/StructuredCloneHolder.cpp
dom/base/nsDOMWindowUtils.cpp
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/WebIDLGlobalNameHash.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/media/WebVTTListener.cpp
dom/messagechannel/MessagePort.cpp
dom/notification/Notification.cpp
dom/presentation/PresentationSessionInfo.cpp
dom/promise/Promise.cpp
dom/workers/ScriptLoader.cpp
dom/workers/ServiceWorker.cpp
dom/workers/ServiceWorkerScriptCache.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerScope.cpp
dom/xbl/nsXBLProtoImplField.cpp
dom/xslt/xpath/XPathExpression.cpp
js/xpconnect/src/ExportHelpers.cpp
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/XPCWrappedJSClass.cpp
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -441,56 +441,58 @@ StructuredCloneHolder::ReadFullySerializ
   return nullptr;
 }
 
 /* static */ bool
 StructuredCloneHolder::WriteFullySerializableObjects(JSContext* aCx,
                                                      JSStructuredCloneWriter* aWriter,
                                                      JS::Handle<JSObject*> aObj)
 {
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+
   // See if this is a ImageData object.
   {
     ImageData* imageData = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, aObj, imageData))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, &obj, imageData))) {
       return WriteStructuredCloneImageData(aCx, aWriter, imageData);
     }
   }
 
   // Handle URLSearchParams cloning
   {
     URLSearchParams* usp = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(URLSearchParams, aObj, usp))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(URLSearchParams, &obj, usp))) {
       return JS_WriteUint32Pair(aWriter, SCTAG_DOM_URLSEARCHPARAMS, 0) &&
              usp->WriteStructuredClone(aWriter);
     }
   }
 
   // Handle Key cloning
   {
     CryptoKey* key = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(CryptoKey, aObj, key))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(CryptoKey, &obj, key))) {
       return JS_WriteUint32Pair(aWriter, SCTAG_DOM_WEBCRYPTO_KEY, 0) &&
              key->WriteStructuredClone(aWriter);
     }
   }
 
 #ifdef MOZ_WEBRTC
   {
     // Handle WebRTC Certificate cloning
     RTCCertificate* cert = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, aObj, cert))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, &obj, cert))) {
       MOZ_ASSERT(NS_IsMainThread());
       return JS_WriteUint32Pair(aWriter, SCTAG_DOM_RTC_CERTIFICATE, 0) &&
              cert->WriteStructuredClone(aWriter);
     }
   }
 #endif
 
-  if (NS_IsMainThread() && xpc::IsReflector(aObj)) {
-    nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(aObj);
+  if (NS_IsMainThread() && xpc::IsReflector(obj)) {
+    nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(obj);
     nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(base);
     if (principal) {
       auto nsjsprincipals = nsJSPrincipals::get(principal);
       return nsjsprincipals->write(aCx, aWriter);
     }
   }
 
   // Don't know what this is
@@ -1006,72 +1008,74 @@ bool
 StructuredCloneHolder::CustomWriteHandler(JSContext* aCx,
                                           JSStructuredCloneWriter* aWriter,
                                           JS::Handle<JSObject*> aObj)
 {
   if (!mSupportsCloning) {
     return false;
   }
 
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+
   // See if this is a File/Blob object.
   {
     Blob* blob = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
       return WriteBlob(aWriter, blob, this);
     }
   }
 
   // See if this is a Directory object.
   {
     Directory* directory = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, aObj, directory))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, &obj, directory))) {
       return WriteDirectory(aWriter, directory);
     }
   }
 
   // See if this is a FileList object.
   {
     FileList* fileList = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, &obj, fileList))) {
       return WriteFileList(aWriter, fileList, this);
     }
   }
 
   // See if this is a FormData object.
   {
     FormData* formData = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, aObj, formData))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, &obj, formData))) {
       return WriteFormData(aWriter, formData, this);
     }
   }
 
   // See if this is an ImageBitmap object.
   if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
       mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) {
     ImageBitmap* imageBitmap = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, aObj, imageBitmap))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, &obj, imageBitmap))) {
       return ImageBitmap::WriteStructuredClone(aWriter,
                                                GetSurfaces(),
                                                imageBitmap);
     }
   }
 
   // See if this is a StructuredCloneBlob object.
   {
     StructuredCloneBlob* holder = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, aObj, holder))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, &obj, holder))) {
       return holder->WriteStructuredClone(aCx, aWriter, this);
     }
   }
 
   // See if this is a WasmModule.
   if ((mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
        mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) &&
-      JS::IsWasmModuleObject(aObj)) {
-    RefPtr<JS::WasmModule> module = JS::GetWasmModule(aObj);
+      JS::IsWasmModuleObject(obj)) {
+    RefPtr<JS::WasmModule> module = JS::GetWasmModule(obj);
     MOZ_ASSERT(module);
 
     return WriteWasmModule(aWriter, module, this);
   }
 
   {
     nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(aObj);
     nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(base);
@@ -1169,19 +1173,21 @@ StructuredCloneHolder::CustomWriteTransf
                                                   JS::TransferableOwnership* aOwnership,
                                                   void** aContent,
                                                   uint64_t* aExtraData)
 {
   if (!mSupportsTransferring) {
     return false;
   }
 
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+
   {
     MessagePort* port = nullptr;
-    nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port);
+    nsresult rv = UNWRAP_OBJECT(MessagePort, &obj, port);
     if (NS_SUCCEEDED(rv)) {
       // We use aExtraData to store the index of this new port identifier.
       *aExtraData = mPortIdentifiers.Length();
       MessagePortIdentifier* identifier = mPortIdentifiers.AppendElement();
 
       port->CloneAndDisentangle(*identifier);
 
       *aTag = SCTAG_DOM_MAP_MESSAGEPORT;
@@ -1189,32 +1195,32 @@ StructuredCloneHolder::CustomWriteTransf
       *aContent = nullptr;
 
       return true;
     }
 
     if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
         mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) {
       OffscreenCanvas* canvas = nullptr;
-      rv = UNWRAP_OBJECT(OffscreenCanvas, aObj, canvas);
+      rv = UNWRAP_OBJECT(OffscreenCanvas, &obj, canvas);
       if (NS_SUCCEEDED(rv)) {
         MOZ_ASSERT(canvas);
 
         *aExtraData = 0;
         *aTag = SCTAG_DOM_CANVAS;
         *aOwnership = JS::SCTAG_TMO_CUSTOM;
         *aContent = canvas->ToCloneData();
         MOZ_ASSERT(*aContent);
         canvas->SetNeutered();
 
         return true;
       }
 
       ImageBitmap* bitmap = nullptr;
-      rv = UNWRAP_OBJECT(ImageBitmap, aObj, bitmap);
+      rv = UNWRAP_OBJECT(ImageBitmap, &obj, bitmap);
       if (NS_SUCCEEDED(rv)) {
         MOZ_ASSERT(bitmap);
 
         *aExtraData = 0;
         *aTag = SCTAG_DOM_IMAGEBITMAP;
         *aOwnership = JS::SCTAG_TMO_CUSTOM;
         *aContent = bitmap->ToCloneData().release();
         MOZ_ASSERT(*aContent);
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3222,26 +3222,26 @@ NS_IMETHODIMP
 nsDOMWindowUtils::GetFileId(JS::Handle<JS::Value> aFile, JSContext* aCx,
                             int64_t* _retval)
 {
   if (aFile.isPrimitive()) {
     *_retval = -1;
     return NS_OK;
   }
 
-  JSObject* obj = aFile.toObjectOrNull();
+  JS::Rooted<JSObject*> obj(aCx, aFile.toObjectOrNull());
 
   IDBMutableFile* mutableFile = nullptr;
-  if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, obj, mutableFile))) {
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
     *_retval = mutableFile->GetFileId();
     return NS_OK;
   }
 
   Blob* blob = nullptr;
-  if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, obj, blob))) {
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
     *_retval = blob->GetFileId();
     return NS_OK;
   }
 
   *_retval = -1;
   return NS_OK;
 }
 
@@ -3249,20 +3249,20 @@ NS_IMETHODIMP
 nsDOMWindowUtils::GetFilePath(JS::HandleValue aFile, JSContext* aCx,
                               nsAString& _retval)
 {
   if (aFile.isPrimitive()) {
     _retval.Truncate();
     return NS_OK;
   }
 
-  JSObject* obj = aFile.toObjectOrNull();
+  JS::Rooted<JSObject*> obj(aCx, aFile.toObjectOrNull());
 
   File* file = nullptr;
-  if (NS_SUCCEEDED(UNWRAP_OBJECT(File, obj, file))) {
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(File, &obj, file))) {
     nsString filePath;
     ErrorResult rv;
     file->GetMozFullPathInternal(filePath, rv);
     if (NS_WARN_IF(rv.Failed())) {
       return rv.StealNSResult();
     }
 
     _retval = filePath;
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1711,19 +1711,20 @@ XrayResolveOwnProperty(JSContext* cx, JS
     // explicitly. So we check if we're running in such a scope, and if so,
     // whether the wrappee is a bound element. If it is, we do a lookup via
     // specialized XBL machinery.
     //
     // While we have to do some sketchy walking through content land, we should
     // be protected by read-only/non-configurable properties, and any functions
     // we end up with should _always_ be living in our own scope (the XBL scope).
     // Make sure to assert that.
+    JS::Rooted<JSObject*> maybeElement(cx, obj);
     Element* element;
     if (xpc::ObjectScope(wrapper)->IsContentXBLScope() &&
-        NS_SUCCEEDED(UNWRAP_OBJECT(Element, obj, element))) {
+        NS_SUCCEEDED(UNWRAP_OBJECT(Element, &maybeElement, element))) {
       if (!nsContentUtils::LookupBindingMember(cx, element, id, desc)) {
         return false;
       }
 
       DEBUG_CheckXBLLookup(cx, desc.address());
 
       if (desc.object()) {
         // XBL properties shouldn't be cached on the holder, as they might be
@@ -2301,24 +2302,25 @@ ReparentWrapper(JSContext* aCx, JS::Hand
       copyTo = aObj;
     }
 
     if (!copyTo || !JS_CopyPropertiesFrom(aCx, copyTo, propertyHolder)) {
       MOZ_CRASH();
     }
   }
 
+  JS::Rooted<JSObject*> maybeObjLC(aCx, aObj);
   nsObjectLoadingContent* htmlobject;
-  nsresult rv = UNWRAP_OBJECT(HTMLObjectElement, aObj, htmlobject);
+  nsresult rv = UNWRAP_OBJECT(HTMLObjectElement, &maybeObjLC, htmlobject);
   if (NS_FAILED(rv)) {
     rv = UnwrapObject<prototypes::id::HTMLEmbedElement,
-                      HTMLSharedObjectElement>(aObj, htmlobject);
+                      HTMLSharedObjectElement>(&maybeObjLC, htmlobject);
     if (NS_FAILED(rv)) {
       rv = UnwrapObject<prototypes::id::HTMLAppletElement,
-                        HTMLSharedObjectElement>(aObj, htmlobject);
+                        HTMLSharedObjectElement>(&maybeObjLC, htmlobject);
       if (NS_FAILED(rv)) {
         htmlobject = nullptr;
       }
     }
   }
   if (htmlobject) {
     htmlobject->SetupProtoChain(aCx, aObj);
   }
@@ -2898,19 +2900,26 @@ GenericBindingGetter(JSContext* cx, unsi
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     return ThrowInvalidThis(cx, args, false, protoID);
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       return ThrowInvalidThis(cx, args,
                               rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                               protoID);
     }
   }
 
   MOZ_ASSERT(info->type() == JSJitInfo::Getter);
@@ -2937,19 +2946,26 @@ GenericPromiseReturningBindingGetter(JSC
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     ThrowInvalidThis(cx, args, false, protoID);
     return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
                                      args.rval());
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       ThrowInvalidThis(cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                        protoID);
       return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
                                        args.rval());
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Getter);
@@ -2974,19 +2990,26 @@ GenericBindingSetter(JSContext* cx, unsi
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     return ThrowInvalidThis(cx, args, false, protoID);
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       return ThrowInvalidThis(cx, args,
                               rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                               protoID);
     }
   }
   if (args.length() == 0) {
     return ThrowNoSetterArg(cx, protoID);
@@ -3009,19 +3032,26 @@ GenericBindingMethod(JSContext* cx, unsi
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     return ThrowInvalidThis(cx, args, false, protoID);
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       return ThrowInvalidThis(cx, args,
                               rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                               protoID);
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Method);
   JSJitMethodOp method = info->method;
@@ -3047,19 +3077,26 @@ GenericPromiseReturningBindingMethod(JSC
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     ThrowInvalidThis(cx, args, false, protoID);
     return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
                                      args.rval());
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       ThrowInvalidThis(cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                        protoID);
       return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
                                        args.rval());
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Method);
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -181,68 +181,218 @@ UnwrapDOMObjectToISupports(JSObject* aOb
 }
 
 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)                                 \
   mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface,        \
     mozilla::dom::Interface##Binding::NativeType>(obj, value)
 
+// 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
+// converts to JSObject*; as long as that JSObject* is live the return value
+// will be valid.
+#define UNWRAP_NON_WRAPPER_OBJECT(Interface, obj, value)                        \
+  mozilla::dom::UnwrapNonWrapperObject<mozilla::dom::prototypes::id::Interface, \
+    mozilla::dom::Interface##Binding::NativeType>(obj, value)
+
 // Some callers don't want to set an exception when unwrapping fails
 // (for example, overload resolution uses unwrapping to tell what sort
 // of thing it's looking at).
 // U must be something that a T* can be assigned to (e.g. T* or an RefPtr<T>).
-template <class T, typename U>
+//
+// The obj argument will be mutated to point to CheckedUnwrap of itself if the
+// passed-in value is not a DOM object and CheckedUnwrap succeeds.
+//
+// If mayBeWrapper is true, there are three valid ways to invoke
+// UnwrapObjectInternal: Either obj needs to be a class wrapping a
+// 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.
+namespace binding_detail {
+template <class T, bool mayBeWrapper, typename U, typename V>
 MOZ_ALWAYS_INLINE nsresult
-UnwrapObject(JSObject* obj, U& value, prototypes::ID protoID,
-             uint32_t protoDepth)
+UnwrapObjectInternal(V& obj, U& value, prototypes::ID protoID,
+                     uint32_t protoDepth)
 {
   /* First check to see whether we have a DOM object */
   const DOMJSClass* domClass = GetDOMClass(obj);
-  if (!domClass) {
-    /* Maybe we have a security wrapper or outer window? */
-    if (!js::IsWrapper(obj)) {
-      /* Not a DOM object, not a wrapper, just bail */
-      return NS_ERROR_XPC_BAD_CONVERT_JS;
-    }
-
-    obj = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
-    if (!obj) {
-      return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
-    }
-    MOZ_ASSERT(!js::IsWrapper(obj));
-    domClass = GetDOMClass(obj);
-    if (!domClass) {
-      /* We don't have a DOM object */
-      return NS_ERROR_XPC_BAD_CONVERT_JS;
+  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);
+      return NS_OK;
     }
   }
 
-  /* 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);
+  /* 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);
+  if (!unwrappedObj) {
+    return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
+  }
+  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.
+  nsresult rv = UnwrapObjectInternal<T, false>(unwrappedObj, value,
+                                               protoID, protoDepth);
+  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;
     return NS_OK;
   }
 
   /* It's the wrong sort of DOM object */
   return NS_ERROR_XPC_BAD_CONVERT_JS;
 }
 
-template <prototypes::ID PrototypeID, class T, typename U>
+struct MutableObjectHandleWrapper {
+  explicit MutableObjectHandleWrapper(JS::MutableHandle<JSObject*> aHandle)
+    : mHandle(aHandle)
+  {
+  }
+
+  void operator=(JSObject* aObject)
+  {
+    MOZ_ASSERT(aObject);
+    mHandle.set(aObject);
+  }
+
+  operator JSObject*() const
+  {
+    return mHandle;
+  }
+
+private:
+  JS::MutableHandle<JSObject*> mHandle;
+};
+
+struct MutableValueHandleWrapper {
+  explicit MutableValueHandleWrapper(JS::MutableHandle<JS::Value> aHandle)
+    : mHandle(aHandle)
+  {
+  }
+
+  void operator=(JSObject* aObject)
+  {
+    MOZ_ASSERT(aObject);
+    mHandle.setObject(*aObject);
+  }
+
+  operator JSObject*() const
+  {
+    return &mHandle.toObject();
+  }
+
+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>
+MOZ_ALWAYS_INLINE nsresult
+UnwrapObject(JS::MutableHandle<JSObject*> obj, U& value)
+{
+  binding_detail::MutableObjectHandleWrapper wrapper(obj);
+  return binding_detail::UnwrapObjectInternal<T, true>(
+    wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+}
+
+template<prototypes::ID PrototypeID, class T, typename U>
 MOZ_ALWAYS_INLINE nsresult
-UnwrapObject(JSObject* obj, U& value)
+UnwrapObject(JS::MutableHandle<JS::Value> obj, U& value)
+{
+  MOZ_ASSERT(obj.isObject());
+  binding_detail::MutableValueHandleWrapper wrapper(obj);
+  return binding_detail::UnwrapObjectInternal<T, true>(
+    wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+}
+
+// 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)
+{
+  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, OwningNonNull<U>& value)
 {
-  return UnwrapObject<T>(obj, value, PrototypeID,
-                         PrototypeTraits<PrototypeID>::Depth);
+  return binding_detail::UnwrapObjectInternal<T, true>(
+    obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+}
+
+// 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)
+{
+  MOZ_ASSERT(obj.isObject());
+  return UnwrapObject<PrototypeID, T>(&obj.toObject(), value);
+}
+
+template<prototypes::ID PrototypeID, class T>
+MOZ_ALWAYS_INLINE bool
+IsInstanceOf(JSObject* obj)
+{
+  void* ignored;
+  nsresult unwrapped = binding_detail::UnwrapObjectInternal<T, true>(
+    obj, ignored, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+  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);
 }
 
 MOZ_ALWAYS_INLINE bool
 IsConvertibleToDictionary(JS::Handle<JS::Value> val)
 {
   return val.isNullOrUndefined() || val.isObject();
 }
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -4348,28 +4348,26 @@ class CastableObjectUnwrapper():
                 if (xpc::WrapperFactory::IsXrayWrapper(maybeUncheckedObj)) {
                   maybeUncheckedObj = js::UncheckedUnwrap(maybeUncheckedObj);
                 } else {
                   maybeUncheckedObj = js::CheckedUnwrap(maybeUncheckedObj);
                   if (!maybeUncheckedObj) {
                     $*{codeOnFailure}
                   }
                 }
-                JS::Rooted<JS::Value> maybeUncheckedVal(cx, JS::ObjectValue(*maybeUncheckedObj));
                 """,
                 codeOnFailure=(codeOnFailure % { 'securityError': 'true'}))
-            self.substitution["source"] = "maybeUncheckedVal"
-            self.substitution["mutableSource"] = "&maybeUncheckedVal"
+            self.substitution["source"] = "maybeUncheckedObj"
+            self.substitution["mutableSource"] = "&maybeUncheckedObj"
             xpconnectUnwrap = dedent("""
                 nsresult rv;
                 { // Scope for the JSAutoCompartment, because we only
                   // want to be in that compartment for the UnwrapArg call.
-                  JS::Rooted<JSObject*> source(cx, &${source}.toObject());
-                  JSAutoCompartment ac(cx, source);
-                  rv = UnwrapArg<${type}>(cx, source, getter_AddRefs(objPtr));
+                  JSAutoCompartment ac(cx, ${source});
+                  rv = UnwrapArg<${type}>(cx, ${source}, getter_AddRefs(objPtr));
                 }
                 """)
         else:
             self.substitution["uncheckedObjDecl"] = ""
             self.substitution["uncheckedObjGet"] = ""
             self.substitution["source"] = source
             self.substitution["mutableSource"] = mutableSource
             xpconnectUnwrap = (
@@ -4420,17 +4418,17 @@ class CastableObjectUnwrapper():
         substitution["codeOnFailure"] %= {
             'securityError': 'rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO'
         }
         return fill(
             """
             $*{uncheckedObjDecl}
             {
               $*{uncheckedObjGet}
-              nsresult rv = UnwrapObject<${protoID}, ${type}>(&${source}.toObject(), ${target});
+              nsresult rv = UnwrapObject<${protoID}, ${type}>(${mutableSource}, ${target});
               if (NS_FAILED(rv)) {
                 $*{codeOnFailure}
               }
             }
             """,
             **substitution)
 
 
--- a/dom/bindings/WebIDLGlobalNameHash.cpp
+++ b/dom/bindings/WebIDLGlobalNameHash.cpp
@@ -214,18 +214,24 @@ WebIDLGlobalNameHash::DefineIfEnabled(JS
   // or the window being touched.
   JS::Rooted<JSObject*> global(aCx,
     js::CheckedUnwrap(aObj, /* 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<nsGlobalWindow*> win;
-    MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, win)));
+    MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, &temp, win)));
+#endif
   }
 
   if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
     return true;
   }
 
   // The DOM constructor resolve machinery interacts with Xrays in tricky
   // ways, and there are some asymmetries that are important to understand.
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -206,18 +206,21 @@ StructuredCloneWriteCallback(JSContext* 
     MOZ_ASSERT(!cloneWriteInfo->mOffsetToKeyProp);
     cloneWriteInfo->mOffsetToKeyProp = js::GetSCOffset(aWriter);
 
     uint64_t value = 0;
     // Omit endian swap
     return JS_WriteBytes(aWriter, &value, sizeof(value));
   }
 
+  // UNWRAP_OBJECT calls might mutate this.
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+
   IDBMutableFile* mutableFile;
-  if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, aObj, mutableFile))) {
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
     if (cloneWriteInfo->mDatabase->IsFileHandleDisabled()) {
       return false;
     }
 
     IDBDatabase* database = mutableFile->Database();
     MOZ_ASSERT(database);
 
     // Throw when trying to store IDBMutableFile objects that live in a
@@ -276,17 +279,17 @@ StructuredCloneWriteCallback(JSContext* 
     newFile->mMutableFile = mutableFile;
     newFile->mType = StructuredCloneFile::eMutableFile;
 
     return true;
   }
 
   {
     Blob* blob = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
       ErrorResult rv;
       uint64_t size = blob->GetSize(rv);
       MOZ_ASSERT(!rv.Failed());
 
       size = NativeEndian::swapToLittleEndian(size);
 
       nsString type;
       blob->GetType(type);
--- a/dom/media/WebVTTListener.cpp
+++ b/dom/media/WebVTTListener.cpp
@@ -152,18 +152,19 @@ WebVTTListener::OnDataAvailable(nsIReque
 
 NS_IMETHODIMP
 WebVTTListener::OnCue(JS::Handle<JS::Value> aCue, JSContext* aCx)
 {
   if (!aCue.isObject()) {
     return NS_ERROR_FAILURE;
   }
 
+  JS::Rooted<JSObject*> obj(aCx, &aCue.toObject());
   TextTrackCue* cue = nullptr;
-  nsresult rv = UNWRAP_OBJECT(VTTCue, &aCue.toObject(), cue);
+  nsresult rv = UNWRAP_OBJECT(VTTCue, &obj, cue);
   NS_ENSURE_SUCCESS(rv, rv);
 
   cue->SetTrackElement(mElement);
   mElement->mTrack->AddCue(*cue);
 
   return NS_OK;
 }
 
--- a/dom/messagechannel/MessagePort.cpp
+++ b/dom/messagechannel/MessagePort.cpp
@@ -398,23 +398,23 @@ MessagePort::PostMessage(JSContext* aCx,
                          ErrorResult& aRv)
 {
   // We *must* clone the data here, or the JS::Value could be modified
   // by script
 
   // Here we want to check if the transerable object list contains
   // this port.
   for (uint32_t i = 0; i < aTransferable.Length(); ++i) {
-    JSObject* object = aTransferable[i];
+    JS::Rooted<JSObject*> object(aCx, aTransferable[i]);
     if (!object) {
       continue;
     }
 
     MessagePort* port = nullptr;
-    nsresult rv = UNWRAP_OBJECT(MessagePort, object, port);
+    nsresult rv = UNWRAP_OBJECT(MessagePort, &object, port);
     if (NS_SUCCEEDED(rv) && port == this) {
       aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
       return;
     }
   }
 
   JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
 
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -984,17 +984,17 @@ Notification::SetAlertName()
 // static
 already_AddRefed<Notification>
 Notification::Constructor(const GlobalObject& aGlobal,
                           const nsAString& aTitle,
                           const NotificationOptions& aOptions,
                           ErrorResult& aRv)
 {
   // FIXME(nsm): If the sticky flag is set, throw an error.
-  ServiceWorkerGlobalScope* scope = nullptr;
+  RefPtr<ServiceWorkerGlobalScope> scope;
   UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
   if (scope) {
     aRv.ThrowTypeError<MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER>();
     return nullptr;
   }
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<Notification> notification =
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -1606,17 +1606,17 @@ PresentationPresentingInfo::ResolvedCall
   if (NS_WARN_IF(!obj)) {
     ReplyError(NS_ERROR_DOM_OPERATION_ERR);
     return;
   }
 
   // Start to listen to document state change event |STATE_TRANSFERRING|.
   // Use Element to support both HTMLIFrameElement and nsXULElement.
   Element* frame = nullptr;
-  nsresult rv = UNWRAP_OBJECT(Element, obj, frame);
+  nsresult rv = UNWRAP_OBJECT(Element, &obj, frame);
   if (NS_WARN_IF(!frame)) {
     ReplyError(NS_ERROR_DOM_OPERATION_ERR);
     return;
   }
 
   nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface((nsIFrameLoaderOwner*) frame);
   if (NS_WARN_IF(!owner)) {
     ReplyError(NS_ERROR_DOM_OPERATION_ERR);
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -279,24 +279,23 @@ enum class NativeHandlerTask : int32_t {
   Reject
 };
 
 static bool
 NativeHandlerCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
   JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
 
-  JS::Rooted<JS::Value> v(aCx,
-                          js::GetFunctionNativeReserved(&args.callee(),
-                                                        SLOT_NATIVEHANDLER));
+  JS::Value v = js::GetFunctionNativeReserved(&args.callee(),
+                                              SLOT_NATIVEHANDLER);
   MOZ_ASSERT(v.isObject());
 
+  JS::Rooted<JSObject*> obj(aCx, &v.toObject());
   PromiseNativeHandler* handler = nullptr;
-  if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &v.toObject(),
-                              handler))) {
+  if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &obj, handler))) {
     return Throw(aCx, NS_ERROR_UNEXPECTED);
   }
 
   v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
   NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
 
   if (task == NativeHandlerTask::Resolve) {
     handler->ResolvedCallback(aCx, args.get(0));
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -1520,17 +1520,17 @@ CacheCreator::ResolvedCallback(JSContext
 
   if (!aValue.isObject()) {
     FailLoaders(NS_ERROR_FAILURE);
     return;
   }
 
   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
   Cache* cache = nullptr;
-  nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
+  nsresult rv = UNWRAP_OBJECT(Cache, &obj, cache);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailLoaders(NS_ERROR_FAILURE);
     return;
   }
 
   mCache = cache;
   MOZ_DIAGNOSTIC_ASSERT(mCache);
 
@@ -1666,17 +1666,17 @@ CacheScriptLoader::ResolvedCallback(JSCo
     }
     return;
   }
 
   MOZ_ASSERT(aValue.isObject());
 
   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
   mozilla::dom::Response* response = nullptr;
-  rv = UNWRAP_OBJECT(Response, obj, response);
+  rv = UNWRAP_OBJECT(Response, &obj, response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     Fail(rv);
     return;
   }
 
   InternalHeaders* headers = response->GetInternalHeaders();
 
   IgnoredErrorResult ignored;
--- a/dom/workers/ServiceWorker.cpp
+++ b/dom/workers/ServiceWorker.cpp
@@ -30,19 +30,17 @@ namespace workers {
 
 bool
 ServiceWorkerVisible(JSContext* aCx, JSObject* aObj)
 {
   if (NS_IsMainThread()) {
     return Preferences::GetBool("dom.serviceWorkers.enabled", false);
   }
 
-  ServiceWorkerGlobalScope* scope = nullptr;
-  nsresult rv = UNWRAP_OBJECT(ServiceWorkerGlobalScope, aObj, scope);
-  return NS_SUCCEEDED(rv);
+  return IS_INSTANCE_OF(ServiceWorkerGlobalScope, aObj);
 }
 
 ServiceWorker::ServiceWorker(nsPIDOMWindowInner* aWindow,
                              ServiceWorkerInfo* aInfo)
   : DOMEventTargetHelper(aWindow),
     mInfo(aInfo)
 {
   AssertIsOnMainThread();
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -468,17 +468,17 @@ private:
       JS::Rooted<JS::Value> val(aCx);
       if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &val)) ||
           NS_WARN_IF(!val.isObject())) {
         return;
       }
 
       Request* request;
       JS::Rooted<JSObject*> requestObj(aCx, &val.toObject());
-      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request, requestObj, request)))) {
+      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request, &requestObj, request)))) {
         return;
       };
 
       nsString URL;
       request->GetUrl(URL);
 
       rv = FetchScript(URL, mURL == URL /* aIsMainScript */, mOldCache);
       if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -505,17 +505,17 @@ private:
     }
 
     JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
     if (NS_WARN_IF(!obj)) {
       return;
     }
 
     Cache* cache = nullptr;
-    rv = UNWRAP_OBJECT(Cache, obj, cache);
+    rv = UNWRAP_OBJECT(Cache, &obj, cache);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
 
     // Just to be safe.
     RefPtr<Cache> kungfuDeathGrip = cache;
 
     MOZ_ASSERT(mPendingCount == 0);
@@ -1119,17 +1119,17 @@ CompareCache::ManageValueResult(JSContex
 
   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
   if (NS_WARN_IF(!obj)) {
     Finish(NS_ERROR_FAILURE, false);
     return;
   }
 
   Response* response = nullptr;
-  nsresult rv = UNWRAP_OBJECT(Response, obj, response);
+  nsresult rv = UNWRAP_OBJECT(Response, &obj, response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     Finish(rv, false);
     return;
   }
 
   MOZ_ASSERT(response->Ok());
 
   nsCOMPtr<nsIInputStream> inputStream;
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1045,21 +1045,21 @@ public:
         JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
         NS_ASSERTION(global, "This should never be null!");
 
         nsEventStatus status = nsEventStatus_eIgnore;
         nsIScriptGlobalObject* sgo;
 
         if (aWorkerPrivate) {
           WorkerGlobalScope* globalScope = nullptr;
-          UNWRAP_OBJECT(WorkerGlobalScope, global, globalScope);
+          UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
 
           if (!globalScope) {
             WorkerDebuggerGlobalScope* globalScope = nullptr;
-            UNWRAP_OBJECT(WorkerDebuggerGlobalScope, global, globalScope);
+            UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
 
             MOZ_ASSERT_IF(globalScope, globalScope->GetWrapperPreserveColor() == global);
             if (globalScope || IsDebuggerSandbox(global)) {
               aWorkerPrivate->ReportErrorToDebugger(aReport.mFilename, aReport.mLineNumber,
                                                     aReport.mMessage);
               return;
             }
 
@@ -7107,18 +7107,19 @@ BEGIN_WORKERS_NAMESPACE
 
 WorkerCrossThreadDispatcher*
 GetWorkerCrossThreadDispatcher(JSContext* aCx, const JS::Value& aWorker)
 {
   if (!aWorker.isObject()) {
     return nullptr;
   }
 
+  JS::Rooted<JSObject*> obj(aCx, &aWorker.toObject());
   WorkerPrivate* w = nullptr;
-  UNWRAP_OBJECT(Worker, &aWorker.toObject(), w);
+  UNWRAP_OBJECT(Worker, &obj, w);
   MOZ_ASSERT(w);
   return w->GetCrossThreadDispatcher();
 }
 
 // Force instantiation.
 template class WorkerPrivateParent<WorkerPrivate>;
 
 END_WORKERS_NAMESPACE
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -1099,27 +1099,23 @@ WorkerDebuggerGlobalScope::AbstractMainT
   MOZ_CRASH("AbstractMainThreadFor not supported for workers.");
 }
 
 BEGIN_WORKERS_NAMESPACE
 
 bool
 IsWorkerGlobal(JSObject* object)
 {
-  nsIGlobalObject* globalObject = nullptr;
-  return NS_SUCCEEDED(UNWRAP_OBJECT(WorkerGlobalScope, object,
-                                    globalObject)) && !!globalObject;
+  return IS_INSTANCE_OF(WorkerGlobalScope, object);
 }
 
 bool
 IsDebuggerGlobal(JSObject* object)
 {
-  nsIGlobalObject* globalObject = nullptr;
-  return NS_SUCCEEDED(UNWRAP_OBJECT(WorkerDebuggerGlobalScope, object,
-                                    globalObject)) && !!globalObject;
+  return IS_INSTANCE_OF(WorkerDebuggerGlobalScope, object);
 }
 
 bool
 IsDebuggerSandbox(JSObject* object)
 {
   return SimpleGlobalObject::SimpleGlobalType(object) ==
     SimpleGlobalObject::GlobalType::WorkerDebuggerSandbox;
 }
--- a/dom/xbl/nsXBLProtoImplField.cpp
+++ b/dom/xbl/nsXBLProtoImplField.cpp
@@ -410,18 +410,20 @@ nsXBLProtoImplField::InstallField(JS::Ha
   if (!jsapi.Init(globalObject)) {
     return NS_ERROR_UNEXPECTED;
   }
   MOZ_ASSERT(!::JS_IsExceptionPending(jsapi.cx()),
              "Shouldn't get here when an exception is pending!");
 
   JSAddonId* addonId = MapURIToAddonID(aBindingDocURI);
 
+  // Note: the UNWRAP_OBJECT may mutate boundNode; don't use it after that call.
+  JS::Rooted<JSObject*> boundNode(jsapi.cx(), aBoundNode);
   Element* boundElement = nullptr;
-  rv = UNWRAP_OBJECT(Element, aBoundNode, boundElement);
+  rv = UNWRAP_OBJECT(Element, &boundNode, boundElement);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // First, enter the xbl scope, build the element's scope chain, and use
   // that as the scope chain for the evaluation.
   JS::Rooted<JSObject*> scopeObject(jsapi.cx(),
     xpc::GetScopeForXBLExecution(jsapi.cx(), aBoundNode, addonId));
--- a/dom/xslt/xpath/XPathExpression.cpp
+++ b/dom/xslt/xpath/XPathExpression.cpp
@@ -70,17 +70,17 @@ already_AddRefed<XPathResult>
 XPathExpression::EvaluateWithContext(JSContext* aCx,
                                      nsINode& aContextNode,
                                      uint32_t aContextPosition,
                                      uint32_t aContextSize,
                                      uint16_t aType,
                                      JS::Handle<JSObject*> aInResult,
                                      ErrorResult& aRv)
 {
-    XPathResult* inResult = nullptr;
+    RefPtr<XPathResult> inResult;
     if (aInResult) {
         nsresult rv = UNWRAP_OBJECT(XPathResult, aInResult, inResult);
         if (NS_FAILED(rv) && rv != NS_ERROR_XPC_BAD_CONVERT_JS) {
             aRv.Throw(rv);
             return nullptr;
         }
     }
 
--- a/js/xpconnect/src/ExportHelpers.cpp
+++ b/js/xpconnect/src/ExportHelpers.cpp
@@ -145,18 +145,19 @@ public:
         return nullptr;
     }
 
     bool CustomWriteHandler(JSContext* aCx,
                             JSStructuredCloneWriter* aWriter,
                             JS::Handle<JSObject*> aObj)
     {
         {
+            JS::Rooted<JSObject*> obj(aCx, aObj);
             Blob* blob = nullptr;
-            if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
+            if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
                 BlobImpl* blobImpl = blob->Impl();
                 MOZ_ASSERT(blobImpl);
 
                 if (!mBlobImpls.AppendElement(blobImpl))
                     return false;
 
                 size_t idx = mBlobImpls.Length() - 1;
                 return JS_WriteUint32Pair(aWriter, SCTAG_BLOB, 0) &&
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -1703,22 +1703,20 @@ nsXPCComponents_Exception::CallOrConstru
 NS_IMETHODIMP
 nsXPCComponents_Exception::HasInstance(nsIXPConnectWrappedNative* wrapper,
                                        JSContext * cx, JSObject * obj,
                                        HandleValue val, bool* bp,
                                        bool* _retval)
 {
     using namespace mozilla::dom;
 
-    RootedValue v(cx, val);
     if (bp) {
-        Exception* e;
-        *bp = (v.isObject() &&
-               NS_SUCCEEDED(UNWRAP_OBJECT(Exception, &v.toObject(), e))) ||
-              JSValIsInterfaceOfType(cx, v, NS_GET_IID(nsIException));
+        *bp = (val.isObject() &&
+               IS_INSTANCE_OF(Exception, &val.toObject())) ||
+              JSValIsInterfaceOfType(cx, val, NS_GET_IID(nsIException));
     }
     return NS_OK;
 }
 
 /***************************************************************************/
 // This class is for the thing returned by "new Component.Constructor".
 
 // XXXjband we use this CID for security check, but security system can't see
@@ -2534,17 +2532,17 @@ NS_IMETHODIMP
 nsXPCComponents_Utils::ImportGlobalProperties(HandleValue aPropertyList,
                                               JSContext* cx)
 {
     RootedObject global(cx, CurrentGlobalOrNull(cx));
     MOZ_ASSERT(global);
 
     // Don't allow doing this if the global is a Window
     nsGlobalWindow* win;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, win))) {
+    if (NS_SUCCEEDED(UNWRAP_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/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -509,17 +509,17 @@ CompilationScope()
 
 nsGlobalWindow*
 WindowOrNull(JSObject* aObj)
 {
     MOZ_ASSERT(aObj);
     MOZ_ASSERT(!js::IsWrapper(aObj));
 
     nsGlobalWindow* win = nullptr;
-    UNWRAP_OBJECT(Window, aObj, win);
+    UNWRAP_NON_WRAPPER_OBJECT(Window, aObj, win);
     return win;
 }
 
 nsGlobalWindow*
 WindowGlobalOrNull(JSObject* aObj)
 {
     MOZ_ASSERT(aObj);
     JSObject* glob = js::GetGlobalForObjectCrossCompartment(aObj);
--- a/js/xpconnect/src/XPCWrappedJSClass.cpp
+++ b/js/xpconnect/src/XPCWrappedJSClass.cpp
@@ -247,18 +247,19 @@ nsXPCWrappedJSClass::CallQueryInterfaceO
         if (!success && JS_IsExceptionPending(cx)) {
             RootedValue jsexception(cx, NullValue());
 
             if (JS_GetPendingException(cx, &jsexception)) {
                 nsresult rv;
                 if (jsexception.isObject()) {
                     // XPConnect may have constructed an object to represent a
                     // C++ QI failure. See if that is the case.
+                    JS::Rooted<JSObject*> exceptionObj(cx, &jsexception.toObject());
                     Exception* e = nullptr;
-                    UNWRAP_OBJECT(Exception, &jsexception.toObject(), e);
+                    UNWRAP_OBJECT(Exception, &exceptionObj, e);
 
                     if (e &&
                         NS_SUCCEEDED(e->GetResult(&rv)) &&
                         rv == NS_NOINTERFACE) {
                         JS_ClearPendingException(cx);
                     }
                 } else if (jsexception.isNumber()) {
                     // JS often throws an nsresult.