Bug 1477432 - Part 1: Move xpc_ nsJSID methods to a future-proof API, r=mccr8
authorNika Layzell <nika@thelayzells.com>
Sat, 14 Jul 2018 19:29:54 -0400
changeset 446896 2e250aa206d11319cfaab13e9d57c1095940668c
parent 446895 8ab318a0ae56110d72fac3dd73ba2ce617943049
child 446897 404c4d583acdf448fe77a514e90de26dd5d5893f
push id35053
push userapavel@mozilla.com
push dateSat, 17 Nov 2018 11:27:54 +0000
treeherdermozilla-central@e4deec61fc8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1477432
milestone65.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 1477432 - Part 1: Move xpc_ nsJSID methods to a future-proof API, r=mccr8 The new API tries to be more generic, taking and producing JS::Values. It also supports creating the more specialized IID and CID types. Differential Revision: https://phabricator.services.mozilla.com/D2278
js/ipc/JavaScriptShared.cpp
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCConvert.cpp
js/xpconnect/src/XPCJSID.cpp
js/xpconnect/src/XPCVariant.cpp
js/xpconnect/src/XPCWrappedJSClass.cpp
js/xpconnect/src/XPCWrappedNative.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/src/xpcpublic.h
--- a/js/ipc/JavaScriptShared.cpp
+++ b/js/ipc/JavaScriptShared.cpp
@@ -231,19 +231,19 @@ JavaScriptShared::toVariant(JSContext* c
       {
         RootedObject obj(cx, from.toObjectOrNull());
         if (!obj) {
             MOZ_ASSERT(from.isNull());
             *to = NullVariant();
             return true;
         }
 
-        if (xpc_JSObjectIsID(cx, obj)) {
+        Maybe<nsID> id = xpc::JSValue2ID(cx, from);
+        if (id) {
             JSIID iid;
-            const nsID* id = xpc_JSObjectToID(cx, obj);
             ConvertID(*id, &iid);
             *to = iid;
             return true;
         }
 
         ObjectVariant objVar;
         if (!toObjectVariant(cx, obj, &objVar)) {
             return false;
@@ -343,24 +343,17 @@ JavaScriptShared::fromVariant(JSContext*
           return true;
         }
 
         case JSVariant::TJSIID:
         {
           nsID iid;
           const JSIID& id = from.get_JSIID();
           ConvertID(id, &iid);
-
-          RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
-          JSObject* obj = xpc_NewIDObject(cx, global, iid);
-          if (!obj) {
-              return false;
-          }
-          to.set(ObjectValue(*obj));
-          return true;
+          return xpc::ID2JSValue(cx, iid, to);
         }
 
         default:
           MOZ_CRASH("NYI");
           return false;
     }
 }
 
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -1212,22 +1212,19 @@ nsXPCComponents_ID::CallOrConstruct(nsIX
 
     nsID id;
     if (!id.Parse(bytes.get())) {
         return ThrowAndFail(NS_ERROR_XPC_BAD_ID_STRING, cx, _retval);
     }
 
     // make the new object and return it.
 
-    JSObject* newobj = xpc_NewIDObject(cx, obj, id);
-    if (!newobj) {
+    if (!xpc::ID2JSValue(cx, id, args.rval())) {
         return NS_ERROR_UNEXPECTED;
     }
-
-    args.rval().setObject(*newobj);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_ID::HasInstance(nsIXPConnectWrappedNative* wrapper,
                                 JSContext* cx, JSObject* obj,
                                 HandleValue val, bool* bp, bool* _retval)
 {
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -189,24 +189,17 @@ XPCConvert::NativeData2JS(MutableHandleV
     case nsXPTType::T_IID:
     {
         nsID* iid2 = *static_cast<nsID* const*>(s);
         if (!iid2) {
             d.setNull();
             return true;
         }
 
-        RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
-        JSObject* obj = xpc_NewIDObject(cx, scope, *iid2);
-        if (!obj) {
-            return false;
-        }
-
-        d.setObject(*obj);
-        return true;
+        return xpc::ID2JSValue(cx, *iid2, d);
     }
 
     case nsXPTType::T_ASTRING:
     {
         const nsAString* p = static_cast<const nsAString*>(s);
         if (!p || p->IsVoid()) {
             d.setNull();
             return true;
@@ -578,35 +571,23 @@ XPCConvert::JSData2Native(JSContext* cx,
     }
     case nsXPTType::T_JSVAL :
         *((Value*)d) = s;
         break;
     case nsXPTType::T_VOID:
         XPC_LOG_ERROR(("XPCConvert::JSData2Native : void* params not supported"));
         NS_ERROR("void* params not supported");
         return false;
+
     case nsXPTType::T_IID:
-    {
-        const nsID* pid = nullptr;
-
-        // There's no good reason to pass a null IID.
-        if (s.isNullOrUndefined()) {
-            if (pErr) {
-                *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
-            }
-            return false;
+        if (Maybe<nsID> id = xpc::JSValue2ID(cx, s)) {
+            *((const nsID**)d) = id.ref().Clone();
+            return true;
         }
-
-        if (!s.isObject() ||
-            !(pid = xpc_JSObjectToID(cx, &s.toObject()))) {
-            return false;
-        }
-        *((const nsID**)d) = pid->Clone();
-        return true;
-    }
+        return false;
 
     case nsXPTType::T_ASTRING:
     {
         nsAString* ws = (nsAString*)d;
         if (s.isUndefined() || s.isNull()) {
             ws->SetIsVoid(true);
             return true;
         }
--- a/js/xpconnect/src/XPCJSID.cpp
+++ b/js/xpconnect/src/XPCJSID.cpp
@@ -613,70 +613,61 @@ nsJSCID::NewID(const char* str)
 
     RefPtr<nsJSCID> idObj = new nsJSCID();
     if (NS_FAILED(idObj->Initialize(str))) {
         return nullptr;
     }
     return idObj.forget();
 }
 
-static const nsID*
-GetIIDArg(uint32_t argc, const JS::Value& val, JSContext* cx)
+static Maybe<nsID>
+GetIIDArg(uint32_t argc, JS::HandleValue val, JSContext* cx)
 {
-    const nsID* iid;
-
     // If an IID was passed in then use it
     if (argc) {
-        JSObject* iidobj;
-        if (val.isPrimitive() ||
-            !(iidobj = val.toObjectOrNull()) ||
-            !(iid = xpc_JSObjectToID(cx, iidobj))) {
-            return nullptr;
-        }
-    } else
-        iid = &NS_GET_IID(nsISupports);
-
-    return iid;
+        return xpc::JSValue2ID(cx, val);
+    }
+    return Some(NS_GET_IID(nsISupports));
 }
 
 NS_IMETHODIMP
 nsJSCID::CreateInstance(HandleValue iidval, JSContext* cx,
                         uint8_t optionalArgc, MutableHandleValue retval)
 {
     if (!mDetails->IsValid()) {
         return NS_ERROR_XPC_BAD_CID;
     }
 
     if (NS_FAILED(nsXPConnect::SecurityManager()->CanCreateInstance(cx, mDetails->ID()))) {
         NS_ERROR("how are we not being called from chrome here?");
         return NS_OK;
     }
 
     // If an IID was passed in then use it
-    const nsID* iid = GetIIDArg(optionalArgc, iidval, cx);
+    Maybe<nsID> iid = GetIIDArg(optionalArgc, iidval, cx);
     if (!iid) {
         return NS_ERROR_XPC_BAD_IID;
     }
 
     nsCOMPtr<nsIComponentManager> compMgr;
     nsresult rv = NS_GetComponentManager(getter_AddRefs(compMgr));
     if (NS_FAILED(rv)) {
         return NS_ERROR_UNEXPECTED;
     }
 
     nsCOMPtr<nsISupports> inst;
-    rv = compMgr->CreateInstance(mDetails->ID(), nullptr, *iid, getter_AddRefs(inst));
+    rv = compMgr->CreateInstance(mDetails->ID(), nullptr, iid.ref(), getter_AddRefs(inst));
     MOZ_ASSERT(NS_FAILED(rv) || inst, "component manager returned success, but instance is null!");
 
     NS_ENSURE_SUCCESS(rv, NS_ERROR_XPC_CI_RETURNED_FAILURE);
     if (!inst) {
       return NS_ERROR_XPC_CI_RETURNED_FAILURE;
     }
 
-    rv = nsContentUtils::WrapNative(cx, inst, iid, retval);
+    rv = nsContentUtils::WrapNative(cx, inst, iid.ptr(), retval);
     if (NS_FAILED(rv) || retval.isPrimitive()) {
         return NS_ERROR_XPC_CANT_CREATE_WN;
     }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJSCID::GetService(HandleValue iidval, JSContext* cx, uint8_t optionalArgc,
@@ -688,38 +679,38 @@ nsJSCID::GetService(HandleValue iidval, 
 
     if (NS_FAILED(nsXPConnect::SecurityManager()->CanCreateInstance(cx, mDetails->ID()))) {
         MOZ_ASSERT(JS_IsExceptionPending(cx),
                    "security manager vetoed GetService without setting exception");
         return NS_OK;
     }
 
     // If an IID was passed in then use it
-    const nsID* iid = GetIIDArg(optionalArgc, iidval, cx);
+    Maybe<nsID> iid = GetIIDArg(optionalArgc, iidval, cx);
     if (!iid) {
         return NS_ERROR_XPC_BAD_IID;
     }
 
     nsCOMPtr<nsIServiceManager> svcMgr;
     nsresult rv = NS_GetServiceManager(getter_AddRefs(svcMgr));
     if (NS_FAILED(rv)) {
         return rv;
     }
 
     nsCOMPtr<nsISupports> srvc;
-    rv = svcMgr->GetService(mDetails->ID(), *iid, getter_AddRefs(srvc));
+    rv = svcMgr->GetService(mDetails->ID(), iid.ref(), getter_AddRefs(srvc));
     MOZ_ASSERT(NS_FAILED(rv) || srvc, "service manager returned success, but service is null!");
 
     NS_ENSURE_SUCCESS(rv, NS_ERROR_XPC_GS_RETURNED_FAILURE);
     if (!srvc) {
         return NS_ERROR_XPC_GS_RETURNED_FAILURE;
     }
 
     RootedValue v(cx);
-    rv = nsContentUtils::WrapNative(cx, srvc, iid, &v);
+    rv = nsContentUtils::WrapNative(cx, srvc, iid.ptr(), &v);
     if (NS_FAILED(rv) || !v.isObject()) {
         return NS_ERROR_XPC_CANT_CREATE_WN;
     }
 
     retval.set(v);
     return NS_OK;
 }
 
@@ -777,66 +768,79 @@ nsJSCID::HasInstance(nsIXPConnectWrapped
             }
         }
     }
 
     return NS_OK;
 }
 
 /***************************************************************************/
-// additional utilities...
+// Public Methods
+
+namespace xpc {
 
-JSObject*
-xpc_NewIDObject(JSContext* cx, HandleObject scope, const nsID& aID)
+Maybe<nsID>
+JSValue2ID(JSContext* aCx, JS::HandleValue aVal)
 {
-    RootedObject obj(cx);
+    if (!aVal.isObject()) {
+        return Nothing();
+    }
 
-    nsCOMPtr<nsIJSID> iid = nsJSID::NewID(aID);
-    if (iid) {
-        nsIXPConnect* xpc = nsIXPConnect::XPConnect();
-        if (xpc) {
-            xpc->WrapNative(cx, scope, static_cast<nsISupports*>(iid),
-                            NS_GET_IID(nsIJSID), obj.address());
-        }
+    JS::RootedObject obj(aCx, js::CheckedUnwrap(&aVal.toObject()));
+    if (!obj || !IS_WN_REFLECTOR(obj)) {
+        return Nothing();
     }
-    return obj;
+
+    XPCWrappedNative* wrapper = XPCWrappedNative::Get(obj);
+    if (!wrapper || !(wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSID)) ||
+                      wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSIID)) ||
+                      wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSCID)))) {
+        return Nothing();
+    }
+
+    nsIJSID* jsid = static_cast<nsIJSID*>(wrapper->GetIdentityObject());
+    return Some(*jsid->GetID());
 }
 
-// note: returned pointer is only valid while |obj| remains alive!
-const nsID*
-xpc_JSObjectToID(JSContext* cx, JSObject* obj)
+bool
+ID2JSValue(JSContext* aCx, const nsID& aId, JS::MutableHandleValue aVal)
 {
-    if (!cx || !obj) {
-        return nullptr;
-    }
-
-    // NOTE: this call does NOT addref
-    XPCWrappedNative* wrapper = nullptr;
-    obj = js::CheckedUnwrap(obj);
-    if (obj && IS_WN_REFLECTOR(obj)) {
-        wrapper = XPCWrappedNative::Get(obj);
-    }
-    if (wrapper &&
-        (wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSID))  ||
-         wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSIID)) ||
-         wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSCID)))) {
-        return ((nsIJSID*)wrapper->GetIdentityObject())->GetID();
-    }
-    return nullptr;
+    nsCOMPtr<nsIJSID> jsid = nsJSID::NewID(aId);
+    return jsid && NS_SUCCEEDED(
+        nsContentUtils::WrapNative(aCx, jsid, &NS_GET_IID(nsIJSID), aVal));
 }
 
 bool
-xpc_JSObjectIsID(JSContext* cx, JSObject* obj)
+IfaceID2JSValue(JSContext* aCx, const nsXPTInterfaceInfo& aInfo,
+                JS::MutableHandleValue aVal)
 {
-    MOZ_ASSERT(cx && obj, "bad param");
-    // NOTE: this call does NOT addref
-    XPCWrappedNative* wrapper = nullptr;
-    obj = js::CheckedUnwrap(obj);
-    if (obj && IS_WN_REFLECTOR(obj)) {
-        wrapper = XPCWrappedNative::Get(obj);
-    }
-    return wrapper &&
-           (wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSID))  ||
-            wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSIID)) ||
-            wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSCID)));
+    nsCOMPtr<nsIJSIID> jsid = nsJSIID::NewID(&aInfo);
+    return jsid && NS_SUCCEEDED(
+        nsContentUtils::WrapNative(aCx, jsid, &NS_GET_IID(nsIJSIID), aVal));
 }
 
+bool
+ClassID2JSValue(JSContext* aCx, const nsCID& aId, JS::MutableHandleValue aVal)
+{
+    char idstr[NSID_LENGTH];
+    aId.ToProvidedString(idstr);
 
+    nsCOMPtr<nsIJSCID> jsid = nsJSCID::NewID(idstr);
+    return jsid && NS_SUCCEEDED(
+        nsContentUtils::WrapNative(aCx, jsid, &NS_GET_IID(nsIJSCID), aVal));
+}
+
+bool
+ContractID2JSValue(JSContext* aCx, const nsACString& aContract,
+                   JS::MutableHandleValue aVal)
+{
+    if (aContract.IsEmpty() || aContract.First() == '{') {
+        return false;
+    }
+
+    nsCOMPtr<nsIJSCID> jsid =
+        nsJSCID::NewID(PromiseFlatCString(aContract).get());
+    return jsid && NS_SUCCEEDED(
+        nsContentUtils::WrapNative(aCx, jsid, &NS_GET_IID(nsIJSCID), aVal));
+}
+
+} // namespace xpc
+
--- a/js/xpconnect/src/XPCVariant.cpp
+++ b/js/xpconnect/src/XPCVariant.cpp
@@ -196,17 +196,17 @@ XPCArrayHomogenizer::GetTypeForArray(JSC
 
             bool isArray;
             if (!JS_IsArrayObject(cx, jsobj, &isArray)) {
                 return false;
             }
 
             if (isArray) {
                 type = tArr;
-            } else if (xpc_JSObjectIsID(cx, jsobj)) {
+            } else if (xpc::JSValue2ID(cx, val)) {
                 type = tID;
             } else {
                 type = tISup;
             }
         }
 
         MOZ_ASSERT(state != tErr, "bad state table!");
         MOZ_ASSERT(type  != tErr, "bad type!");
@@ -306,30 +306,26 @@ bool XPCVariant::InitializeData(JSContex
         mozilla::Range<char16_t> destChars(mData.u.wstr.mWStringValue, length);
         if (!JS_CopyStringChars(cx, destChars, str)) {
             return false;
         }
 
         MOZ_ASSERT(mData.u.wstr.mWStringValue[length] == '\0');
         return true;
     }
+    if (Maybe<nsID> id = xpc::JSValue2ID(cx, val)) {
+        mData.SetFromID(id.ref());
+        return true;
+    }
 
     // leaving only JSObject...
     MOZ_ASSERT(val.isObject(), "invalid type of jsval!");
 
     RootedObject jsobj(cx, &val.toObject());
 
-    // Let's see if it is a xpcJSID.
-
-    const nsID* id = xpc_JSObjectToID(cx, jsobj);
-    if (id) {
-        mData.SetFromID(*id);
-        return true;
-    }
-
     // Let's see if it is a js array object.
 
     uint32_t len;
 
     bool isArray;
     if (!JS_IsArrayObject(cx, jsobj, &isArray) ||
         (isArray && !JS_GetArrayLength(cx, jsobj, &len)))
     {
--- a/js/xpconnect/src/XPCWrappedJSClass.cpp
+++ b/js/xpconnect/src/XPCWrappedJSClass.cpp
@@ -169,20 +169,19 @@ JSObject*
 nsXPCWrappedJSClass::CallQueryInterfaceOnJSObject(JSContext* cx,
                                                   JSObject* jsobjArg,
                                                   HandleObject scope,
                                                   REFNSIID aIID)
 {
     js::AssertSameCompartment(scope, jsobjArg);
 
     RootedObject jsobj(cx, jsobjArg);
-    JSObject* id;
+    RootedValue arg(cx);
     RootedValue retval(cx);
     RootedObject retObj(cx);
-    bool success = false;
     RootedValue fun(cx);
 
     // In bug 503926, we added a security check to make sure that we don't
     // invoke content QI functions. In the modern world, this is probably
     // unnecessary, because invoking QI involves passing an IID object to
     // content, which will fail. But we do a belt-and-suspenders check to
     // make sure that content can never trigger the rat's nest of code below.
     // Once we completely turn off XPConnect for the web, this can definitely
@@ -219,65 +218,61 @@ nsXPCWrappedJSClass::CallQueryInterfaceO
     dom::MozQueryInterface* mozQI = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(MozQueryInterface, &fun, mozQI))) {
         if (mozQI->QueriesTo(aIID)) {
             return jsobj.get();
         }
         return nullptr;
     }
 
-    if ((id = xpc_NewIDObject(cx, scope, aIID))) {
-        // Throwing NS_NOINTERFACE is the prescribed way to fail QI from JS. It
-        // is not an exception that is ever worth reporting, but we don't want
-        // to eat all exceptions either.
+    if (!xpc::ID2JSValue(cx, aIID, &arg)) {
+        return nullptr;
+    }
 
-        {
-            RootedValue arg(cx, JS::ObjectValue(*id));
-            success = JS_CallFunctionValue(cx, jsobj, fun, HandleValueArray(arg), &retval);
-        }
+    // Throwing NS_NOINTERFACE is the prescribed way to fail QI from JS. It is
+    // not an exception that is ever worth reporting, but we don't want to eat
+    // all exceptions either.
 
-        if (!success && JS_IsExceptionPending(cx)) {
-            RootedValue jsexception(cx, NullValue());
+    bool success =
+        JS_CallFunctionValue(cx, jsobj, fun, HandleValueArray(arg), &retval);
+    if (!success && JS_IsExceptionPending(cx)) {
+        RootedValue jsexception(cx, NullValue());
 
-            if (JS_GetPendingException(cx, &jsexception)) {
-                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, &exceptionObj, e);
+        if (JS_GetPendingException(cx, &jsexception)) {
+            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, &exceptionObj, e);
 
-                    if (e && e->GetResult() == NS_NOINTERFACE) {
-                        JS_ClearPendingException(cx);
-                    }
-                } else if (jsexception.isNumber()) {
-                    nsresult rv;
-                    // JS often throws an nsresult.
-                    if (jsexception.isDouble()) {
-                        // Visual Studio 9 doesn't allow casting directly from
-                        // a double to an enumeration type, contrary to
-                        // 5.2.9(10) of C++11, so add an intermediate cast.
-                        rv = (nsresult)(uint32_t)(jsexception.toDouble());
-                    } else {
-                        rv = (nsresult)(jsexception.toInt32());
-                    }
+                if (e && e->GetResult() == NS_NOINTERFACE) {
+                    JS_ClearPendingException(cx);
+                }
+            } else if (jsexception.isNumber()) {
+                nsresult rv;
+                // JS often throws an nsresult.
+                if (jsexception.isDouble())
+                    // Visual Studio 9 doesn't allow casting directly from
+                    // a double to an enumeration type, contrary to
+                    // 5.2.9(10) of C++11, so add an intermediate cast.
+                    rv = (nsresult)(uint32_t)(jsexception.toDouble());
+                else
+                    rv = (nsresult)(jsexception.toInt32());
 
-                    if (rv == NS_NOINTERFACE) {
-                        JS_ClearPendingException(cx);
-                    }
-                }
+                if (rv == NS_NOINTERFACE)
+                    JS_ClearPendingException(cx);
             }
-        } else if (!success) {
-            NS_WARNING("QI hook ran OOMed - this is probably a bug!");
         }
+    } else if (!success) {
+        NS_WARNING("QI hook ran OOMed - this is probably a bug!");
+    }
 
-        if (success) {
-            success = JS_ValueToObject(cx, retval, &retObj);
-        }
-    }
+    if (success)
+        success = JS_ValueToObject(cx, retval, &retObj);
 
     return success ? retObj.get() : nullptr;
 }
 
 /***************************************************************************/
 
 static bool
 GetNamedPropertyAsVariantRaw(XPCCallContext& ccx,
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -1447,36 +1447,37 @@ CallMethodHelper::QueryInterfaceFastPath
         return false;
     }
 
     if (!mArgv[0].isObject()) {
         ThrowBadParam(NS_ERROR_XPC_BAD_CONVERT_JS, 0, mCallContext);
         return false;
     }
 
-    const nsID* iid = xpc_JSObjectToID(mCallContext, &mArgv[0].toObject());
+    JS::RootedValue iidarg(mCallContext, mArgv[0]);
+    Maybe<nsID> iid = xpc::JSValue2ID(mCallContext, iidarg);
     if (!iid) {
         ThrowBadParam(NS_ERROR_XPC_BAD_CONVERT_JS, 0, mCallContext);
         return false;
     }
 
     nsISupports* qiresult = nullptr;
-    mInvokeResult = mCallee->QueryInterface(*iid, (void**) &qiresult);
+    mInvokeResult = mCallee->QueryInterface(iid.ref(), (void**) &qiresult);
 
     if (NS_FAILED(mInvokeResult)) {
         ThrowBadResult(mInvokeResult, mCallContext);
         return false;
     }
 
     RootedValue v(mCallContext, NullValue());
     nsresult err;
     bool success =
         XPCConvert::NativeData2JS(&v, &qiresult,
                                   { nsXPTType::T_INTERFACE_IS },
-                                  iid, 0, &err);
+                                  iid.ptr(), 0, &err);
     NS_IF_RELEASE(qiresult);
 
     if (!success) {
         ThrowBadParam(err, 0, mCallContext);
         return false;
     }
 
     mCallContext.SetRetVal(v);
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -2282,27 +2282,16 @@ protected:
     RefPtr<nsXPCComponents_Exception>   mException;
     RefPtr<nsXPCComponents_Constructor> mConstructor;
     RefPtr<nsXPCComponents_Utils>       mUtils;
 
     friend class XPCWrappedNativeScope;
 };
 
 
-/***************************************************************************/
-
-extern JSObject*
-xpc_NewIDObject(JSContext* cx, JS::HandleObject scope, const nsID& aID);
-
-extern const nsID*
-xpc_JSObjectToID(JSContext* cx, JSObject* obj);
-
-extern bool
-xpc_JSObjectIsID(JSContext* cx, JSObject* obj);
-
 /******************************************************************************
  * Handles pre/post script processing.
  */
 class MOZ_RAII AutoScriptEvaluate
 {
 public:
     /**
      * Saves the JSContext as well as initializing our state
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -27,16 +27,17 @@
 #include "nsMathUtils.h"
 #include "nsStringBuffer.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/Preferences.h"
 
 class nsGlobalWindowInner;
 class nsIPrincipal;
 class nsIHandleReportCallback;
+struct nsXPTInterfaceInfo;
 
 namespace mozilla {
 namespace dom {
 class Exception;
 }
 }
 
 typedef void (* xpcGCCallback)(JSGCStatus status);
@@ -728,16 +729,62 @@ DestroyCooperativeContext();
 // Please see JS_YieldCooperativeContext in jsapi.h.
 void
 YieldCooperativeContext();
 
 // Please see JS_ResumeCooperativeContext in jsapi.h.
 void
 ResumeCooperativeContext();
 
+/**
+ * Extract the native nsID object from a JS ID, IfaceID, ClassID, or ContractID
+ * value.
+ *
+ * Returns 'Nothing()' if 'aVal' does is not one of the supported ID types.
+ */
+mozilla::Maybe<nsID> JSValue2ID(JSContext* aCx, JS::HandleValue aVal);
+
+/**
+ * Reflect an ID into JS
+ */
+bool ID2JSValue(JSContext* aCx, const nsID& aId, JS::MutableHandleValue aVal);
+
+/**
+ * Reflect an IfaceID into JS
+ *
+ * This object will expose constants from the selected interface, and support
+ * 'instanceof', in addition to the other methods available on JS ID objects.
+ *
+ * Use 'xpc::JSValue2ID' to unwrap JS::Values created with this function.
+ */
+bool IfaceID2JSValue(JSContext* aCx, const nsXPTInterfaceInfo& aInfo,
+                     JS::MutableHandleValue aVal);
+
+/**
+ * Reflect a ClassID into JS
+ *
+ * This object will expose 'getService' and 'createInstance' methods in addition
+ * to the other methods avaliable on nsID objects.
+ *
+ * Use 'xpc::JSValue2ID' to unwrap JS::Values created with this function.
+ */
+bool ClassID2JSValue(JSContext* aCx, const nsCID& aId,
+                     JS::MutableHandleValue aVal);
+
+/**
+ * Reflect a ContractID into JS
+ *
+ * This object will expose 'getService' and 'createInstance' methods in addition
+ * to the other methods available on nsID objects.
+ *
+ * Use 'xpc::JSValue2ID' to unwrap JS::Values created with this function.
+ */
+bool ContractID2JSValue(JSContext* aCx, const nsACString& aContract,
+                        JS::MutableHandleValue aVal);
+
 } // namespace xpc
 
 namespace mozilla {
 namespace dom {
 
 /**
  * A test for whether WebIDL methods that should only be visible to
  * chrome or XBL scopes should be exposed.