Bug 1046245 - enumerateDevices w/non-blocking e10s, nsICryptoHMAC, clear cookies, lambdas. r=keeler, florian, billm, jesup
authorJan-Ivar Bruaroey <jib@mozilla.com>
Tue, 03 Mar 2015 09:51:05 -0500
changeset 265303 05ed094a0ab2fa9b6709b8e093eb9315b940066e
parent 265302 7d61e44c267104661fd787f3dfd2da38d04d7c36
child 265304 76f9de2a6a4fd5d60f3f2672bde9231a6383fcd4
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, florian, billm, jesup
bugs1046245
milestone39.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 1046245 - enumerateDevices w/non-blocking e10s, nsICryptoHMAC, clear cookies, lambdas. r=keeler, florian, billm, jesup
browser/base/content/sanitize.js
dom/media/MediaManager.cpp
dom/media/MediaManager.h
dom/media/VideoUtils.cpp
dom/media/VideoUtils.h
dom/media/nsIMediaManager.idl
dom/media/systemservices/MediaChild.cpp
dom/media/systemservices/MediaChild.h
dom/media/systemservices/MediaParent.cpp
dom/media/systemservices/MediaParent.h
dom/media/systemservices/MediaUtils.cpp
dom/media/systemservices/MediaUtils.h
dom/media/systemservices/PMedia.ipdl
dom/media/systemservices/moz.build
ipc/glue/BackgroundChildImpl.cpp
ipc/glue/BackgroundChildImpl.h
ipc/glue/BackgroundParentImpl.cpp
ipc/glue/BackgroundParentImpl.h
ipc/glue/PBackground.ipdl
security/manager/ssl/src/nsNSSModule.cpp
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -194,16 +194,21 @@ Sanitizer.prototype = {
               cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
           }
         }
         else {
           // Remove everything
           cookieMgr.removeAll();
         }
 
+        // Clear deviceIds. Done asynchronously (returns before complete).
+        let mediaMgr = Components.classes["@mozilla.org/mediaManagerService;1"]
+                                 .getService(Ci.nsIMediaManagerService);
+        mediaMgr.sanitizeDeviceIds(this.range && this.range[0]);
+
         // Clear plugin data.
         const phInterface = Ci.nsIPluginHost;
         const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
         let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
 
         // Determine age range in seconds. (-1 means clear all.) We don't know
         // that this.range[1] is actually now, so we compute age range based
         // on the lower bound. If this.range results in a negative age, do
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -21,29 +21,34 @@
 #include "nsISupportsArray.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIIDNService.h"
 #include "nsNetUtil.h"
 #include "nsPrincipal.h"
+#include "nsICryptoHash.h"
+#include "nsICryptoHMAC.h"
+#include "nsIKeyModule.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIInputStream.h"
 #include "nsILineInputStream.h"
 #include "mozilla/Types.h"
 #include "mozilla/PeerIdentity.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/GetUserMediaRequestBinding.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Base64.h"
+#include "mozilla/media/MediaChild.h"
 #include "MediaTrackConstraints.h"
-
+#include "VideoUtils.h"
 #include "Latency.h"
 
 // For PR_snprintf
 #include "prprf.h"
 
 #include "nsJSUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsIUUIDGenerator.h"
@@ -321,60 +326,111 @@ public:
     : mDevices(aDevices)
     , mWindowID(aWindowID)
     , mManager(MediaManager::GetInstance())
   {
     mOnSuccess.swap(aOnSuccess);
     mOnFailure.swap(aOnFailure);
   }
 
+  nsresult
+  AnonymizeId(nsAString& aId, const nsACString& aOriginKey)
+  {
+    nsresult rv;
+    nsCOMPtr<nsIKeyObjectFactory> factory =
+      do_GetService("@mozilla.org/security/keyobjectfactory;1", &rv);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    nsCString rawKey;
+    rv = Base64Decode(aOriginKey, rawKey);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    nsCOMPtr<nsIKeyObject> key;
+    rv = factory->KeyFromString(nsIKeyObject::HMAC, rawKey, getter_AddRefs(key));
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    nsCOMPtr<nsICryptoHMAC> hasher =
+      do_CreateInstance(NS_CRYPTO_HMAC_CONTRACTID, &rv);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    rv = hasher->Init(nsICryptoHMAC::SHA256, key);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    NS_ConvertUTF16toUTF8 id(aId);
+    rv = hasher->Update(reinterpret_cast<const uint8_t*> (id.get()), id.Length());
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    nsCString mac;
+    rv = hasher->Finish(true, mac);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    aId = NS_ConvertUTF8toUTF16(mac);
+    return NS_OK;
+  }
+
   NS_IMETHOD
   Run()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
     // Only run if window is still on our active list.
     if (!mManager->IsWindowStillActive(mWindowID)) {
       return NS_OK;
     }
 
     nsCOMPtr<nsIWritableVariant> devices =
       do_CreateInstance("@mozilla.org/variant;1");
 
-    int32_t len = mDevices->Length();
+    size_t len = mDevices->Length();
     if (len == 0) {
       // XXX
       // We should in the future return an empty array, and dynamically add
       // devices to the dropdowns if things are hotplugged while the
       // requester is up.
       nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
       if (window) {
         nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
             NS_LITERAL_STRING("NotFoundError"));
         mOnFailure->OnError(error);
       }
       return NS_OK;
     }
 
     nsTArray<nsIMediaDevice*> tmp(len);
-    for (int32_t i = 0; i < len; i++) {
-      tmp.AppendElement(mDevices->ElementAt(i));
+    for (auto& device : *mDevices) {
+      if (!mOriginKey.IsEmpty()) {
+        nsString id;
+        device->GetId(id);
+        AnonymizeId(id, mOriginKey);
+        device->SetId(id);
+      }
+      tmp.AppendElement(device);
     }
 
     devices->SetAsArray(nsIDataType::VTYPE_INTERFACE,
                         &NS_GET_IID(nsIMediaDevice),
                         mDevices->Length(),
                         const_cast<void*>(
                           static_cast<const void*>(tmp.Elements())
                         ));
 
     mOnSuccess->OnSuccess(devices);
     return NS_OK;
   }
 
+  nsCString mOriginKey;
 private:
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   nsAutoPtr<nsTArray<nsRefPtr<MediaDevice>>> mDevices;
   uint64_t mWindowID;
   nsRefPtr<MediaManager> mManager;
 };
 
@@ -1153,17 +1209,17 @@ public:
     NS_DispatchToMainThread(runnable);
     // Do after ErrorCallbackRunnable Run()s, as it checks active window list
     NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, mListener));
   }
 
   void
   Run()
   {
-    NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+    MOZ_ASSERT(!NS_IsMainThread());
     MOZ_ASSERT(mOnSuccess);
     MOZ_ASSERT(mOnFailure);
 
     MediaEngine* backend = mBackend;
     // Was a backend provided?
     if (!backend) {
       backend = mManager->GetBackend(mWindowID);
     }
@@ -1357,319 +1413,43 @@ public:
     return NS_OK;
   }
 
 private:
   nsAutoPtr<GetUserMediaTask> mTask;
 };
 #endif
 
-// A file in the profile dir is used to persist mOriginUuids used to anonymize
-// deviceIds to be unique per origin, to avoid them being supercookies.
+class SanitizeDeviceIdsTask : public Task
+{
+public:
+  explicit SanitizeDeviceIdsTask(int64_t aSinceWhen)
+  : mSinceWhen(aSinceWhen) {}
 
-#define ORIGINUUIDS_FILE "enumerate_devices.txt"
-#define ORIGINUUIDS_VERSION "1"
-
-#define HMAC_LENGTH 20
+  void // NS_IMETHOD
+  Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    nsRefPtr<media::ChildPledge<bool>> p =
+        mozilla::media::SanitizeOriginKeys(mSinceWhen); // we fire and forget
+  }
+private:
+  int64_t mSinceWhen;
+};
 
 /**
  * Similar to GetUserMediaTask, but used for the chrome-only
  * GetUserMediaDevices function. Enumerates a list of audio & video devices,
  * wraps them up in nsIMediaDevice objects and returns it to the success
  * callback.
  *
  * All code in this class runs on the MediaManager thread.
  */
 class GetUserMediaDevicesTask : public Task
 {
-  class OriginUuidsLoader
-  {
-    static unsigned char*
-    unconst_uchar_cast(const char *s) {
-      return reinterpret_cast<unsigned char*>(const_cast<char*>(s));
-    }
-
-    // Cribbed from nricectx.cpp
-    static nsresult
-    hmac_sha1(const char *key, int keyl, const char *buf, int bufl,
-              unsigned char *result)
-    {
-      const CK_MECHANISM_TYPE mech = CKM_SHA_1_HMAC;
-      PK11SlotInfo *slot = 0;
-      MOZ_ASSERT(keyl > 0);
-      SECItem keyi = { siBuffer, unconst_uchar_cast(key),
-                       static_cast<unsigned int>(keyl) };
-      PK11SymKey *skey = 0;
-      PK11Context *hmac_ctx = 0;
-      SECStatus status;
-      unsigned int hmac_len;
-      SECItem param = { siBuffer, nullptr, 0 };
-      nsresult rv = NS_ERROR_UNEXPECTED;
-
-      slot = PK11_GetInternalKeySlot();
-      if (!slot) {
-        goto abort;
-      }
-      skey = PK11_ImportSymKey(slot, mech, PK11_OriginUnwrap, CKA_SIGN, &keyi,
-                               nullptr);
-      if (!skey) {
-        goto abort;
-      }
-
-      hmac_ctx = PK11_CreateContextBySymKey(mech, CKA_SIGN, skey, &param);
-      if (!hmac_ctx) {
-        goto abort;
-      }
-      status = PK11_DigestBegin(hmac_ctx);
-      if (status != SECSuccess) {
-        goto abort;
-      }
-      status = PK11_DigestOp(hmac_ctx, unconst_uchar_cast(buf), bufl);
-      if (status != SECSuccess) {
-        goto abort;
-      }
-      status = PK11_DigestFinal(hmac_ctx, result, &hmac_len, HMAC_LENGTH);
-      if (status != SECSuccess) {
-        goto abort;
-      }
-      MOZ_ASSERT(hmac_len == HMAC_LENGTH);
-      rv = NS_OK;
-
-    abort:
-      if (hmac_ctx) {
-        PK11_DestroyContext(hmac_ctx, PR_TRUE);
-      }
-      if (skey) {
-        PK11_FreeSymKey(skey);
-      }
-      if (slot) {
-        PK11_FreeSlot(slot);
-      }
-      return rv;
-    }
-
-  public:
-    OriginUuidsLoader()
-    : mUnsaved(false), mManager(MediaManager::GetInstance()) {}
-
-    nsresult
-    AnonymizeId(nsAString& aId, const nsACString& origin, bool aInPrivateBrowsing)
-    {
-      // The persistent deviceId would be a supercookie if we returned it. See
-      // http://w3c.github.io/mediacapture-main/getusermedia.html#attributes-8
-      //
-      // 1. Get (or create) a persistent uuid for this origin.
-      // 2. Return hmac_sha1(uuid, id) - an anonymized id unique to origin.
-
-      static bool loaded = false;
-      if (!loaded) {
-        Load();
-        loaded = true;
-      }
-      OriginUuid* originUuid;
-      if (!mManager->mOriginUuids.Get(origin, &originUuid)) {
-        nsresult rv;
-        nsCOMPtr<nsIUUIDGenerator> uuidgen =
-            do_GetService("@mozilla.org/uuid-generator;1", &rv);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        nsID nsid;
-        rv = uuidgen->GenerateUUIDInPlace(&nsid);
-        NS_ENSURE_SUCCESS(rv, rv);
-        char uuid[NSID_LENGTH];
-        nsid.ToProvidedString(uuid);
-
-        originUuid = new OriginUuid(uuid, aInPrivateBrowsing);
-        mManager->mOriginUuids.Put(origin, originUuid);
-        mUnsaved = true;
-      }
-
-      unsigned char mac[HMAC_LENGTH];
-      NS_ConvertUTF16toUTF8 id(aId);
-      hmac_sha1(originUuid->mUuid.get(), originUuid->mUuid.Length(),
-                id.get(), id.Length(), mac);
-
-      char hex[sizeof(mac) * 2 + 1];
-      auto& m = mac;
-      PR_snprintf(hex, sizeof(hex), // Use first 16 bytes of hmac as id
-                  "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x"
-                  "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x",
-                  m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7],
-                  m[8], m[9], m[10],m[11], m[12], m[13], m[14], m[15]);
-      aId = NS_ConvertUTF8toUTF16(hex);
-      return NS_OK;
-    }
-
-    already_AddRefed<nsIFile>
-    GetFile()
-    {
-      MOZ_ASSERT(mManager->mProfileDir);
-      nsCOMPtr<nsIFile> file;
-      nsresult rv = mManager->mProfileDir->Clone(getter_AddRefs(file));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return nullptr;
-      }
-      file->Append(NS_LITERAL_STRING(ORIGINUUIDS_FILE));
-      return file.forget();
-    }
-
-    // Format of file is (first line is version #):
-    //
-    // 1
-    // {54b1d3ad-18ad-0546-a551-80c1bf425057} http://fiddle.jshell.net
-    // {f9c2a045-53b4-0546-bdd0-b96f41850f8a} http://mozilla.github.io
-    // etc.
-
-    nsresult Read()
-    {
-      nsCOMPtr<nsIFile> file = GetFile();
-      if (NS_WARN_IF(!file)) {
-        return NS_ERROR_UNEXPECTED;
-      }
-      bool exists;
-      nsresult rv = file->Exists(&exists);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      if (!exists) {
-        return NS_OK;
-      }
-
-      nsCOMPtr<nsIInputStream> stream;
-      rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      nsCOMPtr<nsILineInputStream> i = do_QueryInterface(stream);
-      MOZ_ASSERT(i);
-
-      nsCString line;
-      bool hasMoreLines;
-      rv = i->ReadLine(line, &hasMoreLines);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      if (!line.EqualsLiteral(ORIGINUUIDS_VERSION)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      while (hasMoreLines) {
-        rv = i->ReadLine(line, &hasMoreLines); \
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-        int32_t f = line.FindChar(' ');
-        // Ignore any line that has no space, per format in the comment above.
-        if (f >= 0) {
-          const nsACString& uuid = Substring(line, 0, f);
-          const nsACString& origin = Substring(line, f+1);
-          mManager->mOriginUuids.Put(origin, new OriginUuid(uuid, false));
-        }
-      }
-      return NS_OK;
-    }
-
-    static PLDHashOperator
-    HashWriter(const nsACString& aOrigin, OriginUuid* aOriginUuid, void *aUserArg)
-    {
-      auto* stream = static_cast<nsIOutputStream *>(aUserArg);
-
-      if (!aOriginUuid->mPrivateBrowsing) {
-        nsCString buffer;
-        buffer.Append(aOriginUuid->mUuid);
-        buffer.Append(' ');
-        buffer.Append(aOrigin);
-        buffer.Append('\n');
-
-        uint32_t count;
-        nsresult rv = stream->Write(buffer.Data(), buffer.Length(), &count);
-        if (NS_WARN_IF(NS_FAILED(rv)) || count != buffer.Length()) {
-          return PL_DHASH_STOP;
-        }
-      }
-      return PL_DHASH_NEXT;
-    }
-
-    nsresult
-    Write()
-    {
-      nsCOMPtr<nsIFile> file = GetFile();
-      if (NS_WARN_IF(!file)) {
-        return NS_ERROR_UNEXPECTED;
-      }
-
-      nsCOMPtr<nsIOutputStream> stream;
-      nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      nsAutoCString buffer;
-      buffer.AppendLiteral(ORIGINUUIDS_VERSION);
-      buffer.Append('\n');
-
-      uint32_t count;
-      rv = stream->Write(buffer.Data(), buffer.Length(), &count);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      if (count != buffer.Length()) {
-        return NS_ERROR_UNEXPECTED;
-      }
-      mManager->mOriginUuids.EnumerateRead(HashWriter, stream.get());
-
-      nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream);
-      MOZ_ASSERT(safeStream);
-
-      rv = safeStream->Finish();
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      return NS_OK;
-    }
-
-    nsresult Load()
-    {
-      nsresult rv = Read();
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        Delete();
-      }
-      return rv;
-    }
-
-    nsresult Save()
-    {
-      nsresult rv = Write();
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Failed to write data for EnumerateDevices id-persistence.");
-        Delete();
-      }
-      return rv;
-    }
-
-    nsresult Delete()
-    {
-      nsCOMPtr<nsIFile> file = GetFile();
-      if (NS_WARN_IF(!file)) {
-        return NS_ERROR_UNEXPECTED;
-      }
-      nsresult rv = file->Remove(false);
-      if (rv == NS_ERROR_FILE_NOT_FOUND) {
-        return NS_OK;
-      }
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-      return NS_OK;
-    }
-
-    bool mUnsaved;
-  private:
-    nsRefPtr<MediaManager> mManager;
-  };
-
 public:
   GetUserMediaDevicesTask(
     const MediaStreamConstraints& aConstraints,
     already_AddRefed<nsIGetUserMediaDevicesSuccessCallback> aOnSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aOnFailure,
     uint64_t aWindowId, nsACString& aAudioLoopbackDev,
     nsACString& aVideoLoopbackDev, bool aPrivileged, const nsACString& aOrigin,
     bool aInPrivateBrowsing, bool aUseFakeDevices)
@@ -1683,17 +1463,17 @@ public:
     , mPrivileged(aPrivileged)
     , mOrigin(aOrigin)
     , mInPrivateBrowsing(aInPrivateBrowsing)
     , mUseFakeDevices(aUseFakeDevices) {}
 
   void // NS_IMETHOD
   Run()
   {
-    NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+    MOZ_ASSERT(!NS_IsMainThread());
 
     nsRefPtr<MediaEngine> backend;
     if (mConstraints.mFake || mUseFakeDevices)
       backend = new MediaEngineDefault(mConstraints.mFakeTracks);
     else
       backend = mManager->GetBackend(mWindowId);
 
     typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet;
@@ -1712,49 +1492,36 @@ public:
       nsTArray<nsRefPtr<AudioDevice>> sources;
       GetSources(backend, GetInvariant(mConstraints.mAudio),
                  &MediaEngine::EnumerateAudioDevices, sources,
                  mLoopbackAudioDevice.get());
       for (auto& source : sources) {
         result->AppendElement(source);
       }
     }
-
-    nsresult rv = NS_OK;
-    if (!mPrivileged) {
-      OriginUuidsLoader loader;
-      for (auto& source : *result) {
-        nsString id;
-        source->GetId(id);
-        rv = loader.AnonymizeId(id, mOrigin, mInPrivateBrowsing);
-        if (NS_FAILED(rv)) {
-          break;
-        }
-        source->SetId(id);
-      }
-      if (loader.mUnsaved) {
-        loader.Save();
-      }
+    nsRefPtr<DeviceSuccessCallbackRunnable> runnable
+        (new DeviceSuccessCallbackRunnable(mWindowId, mOnSuccess, mOnFailure,
+                                           result.forget()));
+    if (mPrivileged) {
+      NS_DispatchToMainThread(runnable);
+    } else {
+      // Get persistent origin-unique uuid to anonymize deviceIds back on main.
+      //
+      // GetOriginKey is an async API that returns a pledge (as promise-like
+      // pattern). We use .Then() to pass in a lambda to run back on this
+      // thread once GetOriginKey resolves asynchronously . The "runnable"
+      // nsRefPtr is "captured" (passed by value) into the lambda.
+      nsRefPtr<media::ChildPledge<nsCString>> p =
+          media::GetOriginKey(mOrigin, mInPrivateBrowsing);
+      p->Then([runnable](nsCString result) mutable {
+        runnable->mOriginKey = result;
+        NS_DispatchToMainThread(runnable);
+      }, [](nsresult rv){});
     }
-    if (NS_SUCCEEDED(rv)) {
-      NS_DispatchToMainThread(new DeviceSuccessCallbackRunnable(mWindowId,
-                                                                mOnSuccess,
-                                                                mOnFailure,
-                                                                result.forget()));
-    } else {
-      nsRefPtr<MediaMgrError> error = new
-          MediaMgrError(NS_LITERAL_STRING("InternalError"),
-                        NS_LITERAL_STRING("Unexpected error"));
-      NS_DispatchToMainThread(
-        new ErrorCallbackRunnable<nsIGetUserMediaDevicesSuccessCallback>(mOnSuccess,
-                                                                         mOnFailure,
-                                                                         *error,
-                                                                         mWindowId));
-    }
-    // DeviceSuccessCallbackRunnable should have taken these.
+    // One of the Runnables have taken these.
     MOZ_ASSERT(!mOnSuccess && !mOnFailure);
   }
 
 private:
   MediaStreamConstraints mConstraints;
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   nsRefPtr<MediaManager> mManager;
@@ -1791,16 +1558,26 @@ MediaManager::MediaManager()
   LOG(("%s: default prefs: %dx%d @%dfps (min %d)", __FUNCTION__,
        mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
 }
 
 NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver)
 
 /* static */ StaticRefPtr<MediaManager> MediaManager::sSingleton;
 
+#ifdef DEBUG
+/* static */ bool
+MediaManager::IsInMediaThread()
+{
+  return sSingleton?
+      (sSingleton->mMediaThread->thread_id() == PlatformThread::CurrentId()) :
+      false;
+}
+#endif
+
 // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
 // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
 // from MediaManager thread.
 /* static */  MediaManager*
 MediaManager::Get() {
   if (!sSingleton) {
     NS_ASSERTION(NS_IsMainThread(), "Only create MediaManager on main thread");
 #ifdef DEBUG
@@ -2218,22 +1995,16 @@ MediaManager::GetUserMediaDevices(nsPIDO
 
 nsresult
 MediaManager::EnumerateDevices(nsPIDOMWindow* aWindow,
                                nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
                                nsIDOMGetUserMediaErrorCallback* aOnFailure)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
-  if (!mProfileDir) {
-    nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
-                                         getter_AddRefs(mProfileDir));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
   MediaStreamConstraints c;
   c.mVideo.SetAsBoolean() = true;
   c.mAudio.SetAsBoolean() = true;
 
   AddWindowID(aWindow->WindowID());
   return GetUserMediaDevices(aWindow, c, aOnSuccess, aOnFailure, 0, false);
 }
 
@@ -2679,16 +2450,27 @@ MediaManager::MediaCaptureWindowState(ns
   LOG(("%s: window %lld capturing %s %s %s %s %s %s", __FUNCTION__, piWin ? piWin->WindowID() : -1,
        *aVideo ? "video" : "", *aAudio ? "audio" : "",
        *aScreenShare ? "screenshare" : "",  *aWindowShare ? "windowshare" : "",
        *aAppShare ? "appshare" : "", *aBrowserShare ? "browsershare" : ""));
 #endif
   return NS_OK;
 }
 
+NS_IMETHODIMP
+MediaManager::SanitizeDeviceIds(int64_t aSinceWhen)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  LOG(("%s: sinceWhen = %llu", __FUNCTION__, aSinceWhen));
+
+  MediaManager::GetMessageLoop()->PostTask(FROM_HERE,
+    new SanitizeDeviceIdsTask(aSinceWhen));
+  return NS_OK;
+}
+
 static void
 StopScreensharingCallback(MediaManager *aThis,
                           uint64_t aWindowID,
                           StreamListeners *aListeners,
                           void *aData)
 {
   if (aListeners) {
     auto length = aListeners->Length();
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -453,30 +453,16 @@ private:
   nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe
   nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
   bool mBool;
   uint64_t mWindowID;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
 };
 
-class OriginUuid
-{
-public:
-  OriginUuid(char *aUuid, bool aPrivateBrowsing)
-  : mPrivateBrowsing(aPrivateBrowsing) {
-    mUuid.Append(aUuid);
-  }
-  OriginUuid(const nsACString& aUuid, bool aPrivateBrowsing)
-  : mUuid(aUuid), mPrivateBrowsing(aPrivateBrowsing) {}
-
-  nsCString mUuid;
-  bool mPrivateBrowsing;
-};
-
 typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
 typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
 
 class MediaDevice : public nsIMediaDevice
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEDIADEVICE
@@ -518,32 +504,31 @@ public:
 };
 
 // we could add MediaManager if needed
 typedef void (*WindowListenerCallback)(MediaManager *aThis,
                                        uint64_t aWindowID,
                                        StreamListeners *aListeners,
                                        void *aData);
 
-class GetUserMediaDevicesTask;
-
 class MediaManager final : public nsIMediaManagerService,
                            public nsIObserver
 {
-  friend GetUserMediaDevicesTask;
-
 public:
   static already_AddRefed<MediaManager> GetInstance();
 
   // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
   // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
   // from MediaManager thread.
   static MediaManager* Get();
   static MediaManager* GetIfExists();
   static MessageLoop* GetMessageLoop();
+#ifdef DEBUG
+  static bool IsInMediaThread();
+#endif
 
   static bool Exists()
   {
     return !!sSingleton;
   }
 
   static nsresult NotifyRecordingStatusChange(nsPIDOMWindow* aWindow,
                                               const nsString& aMsg,
@@ -611,20 +596,16 @@ private:
 
   void StopScreensharing(uint64_t aWindowID);
   void IterateWindowListeners(nsPIDOMWindow *aWindow,
                               WindowListenerCallback aCallback,
                               void *aData);
 
   void StopMediaStreams();
 
-  // ONLY access from MediaManagerThread so we don't need to lock
-  nsClassHashtable<nsCStringHashKey, OriginUuid> mOriginUuids;
-  nsCOMPtr<nsIFile> mProfileDir;
-
   // ONLY access from MainThread so we don't need to lock
   WindowTable mActiveWindows;
   nsClassHashtable<nsStringHashKey, GetUserMediaTask> mActiveCallbacks;
   nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mCallIds;
 
   // Always exists
   nsAutoPtr<base::Thread> mMediaThread;
 
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -256,49 +256,52 @@ ExtractH264CodecDetails(const nsAString&
   // otherwise collect 0 for unknown.
   Telemetry::Accumulate(Telemetry::VIDEO_CANPLAYTYPE_H264_LEVEL,
                         (aLevel >= 10 && aLevel <= 52) ? aLevel : 0);
 
   return true;
 }
 
 nsresult
-GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength)
+GenerateRandomName(nsCString& aOutSalt, uint32_t aLength)
 {
   nsresult rv;
   nsCOMPtr<nsIRandomGenerator> rg =
     do_GetService("@mozilla.org/security/random-generator;1", &rv);
   if (NS_FAILED(rv)) return rv;
 
-  // For each three bytes of random data we will get four bytes of
-  // ASCII. Request a bit more to be safe and truncate to the length
-  // we want at the end.
+  // For each three bytes of random data we will get four bytes of ASCII.
   const uint32_t requiredBytesLength =
-    static_cast<uint32_t>((aLength + 1) / 4 * 3);
+    static_cast<uint32_t>((aLength + 3) / 4 * 3);
 
   uint8_t* buffer;
   rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
   if (NS_FAILED(rv)) return rv;
 
   nsAutoCString temp;
   nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
                                    requiredBytesLength);
   rv = Base64Encode(randomData, temp);
   NS_Free(buffer);
   buffer = nullptr;
   if (NS_FAILED (rv)) return rv;
 
-  temp.Truncate(aLength);
+  aOutSalt = temp;
+  return NS_OK;
+}
+
+nsresult
+GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength)
+{
+  nsresult rv = GenerateRandomName(aOutSalt, aLength);
+  if (NS_FAILED(rv)) return rv;
 
   // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
   // to replace illegal characters -- notably '/'
-  temp.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
-
-  aOutSalt = temp;
-
+  aOutSalt.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
   return NS_OK;
 }
 
 class CreateTaskQueueTask : public nsRunnable {
 public:
   NS_IMETHOD Run() {
     MOZ_ASSERT(NS_IsMainThread());
     mTaskQueue = new MediaTaskQueue(GetMediaThreadPool());
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -256,17 +256,21 @@ enum H264_LEVEL {
 // for more details.
 // Returns false on failure.
 bool
 ExtractH264CodecDetails(const nsAString& aCodecs,
                         int16_t& aProfile,
                         int16_t& aLevel);
 
 // Use a cryptographic quality PRNG to generate raw random bytes
-// and convert that to a base64 string suitable for use as a file or URL
+// and convert that to a base64 string.
+nsresult
+GenerateRandomName(nsCString& aOutSalt, uint32_t aLength);
+
+// This version returns a string suitable for use as a file or URL
 // path. This is based on code from nsExternalAppHandler::SetUpTempFile.
 nsresult
 GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength);
 
 class MediaTaskQueue;
 class FlushableMediaTaskQueue;
 
 already_AddRefed<MediaTaskQueue>
--- a/dom/media/nsIMediaManager.idl
+++ b/dom/media/nsIMediaManager.idl
@@ -7,19 +7,23 @@
 interface nsISupportsArray;
 interface nsIDOMWindow;
 
 %{C++
 #define NS_MEDIAMANAGERSERVICE_CID {0xabc622ea, 0x9655, 0x4123, {0x80, 0xd9, 0x22, 0x62, 0x1b, 0xdd, 0x54, 0x65}}
 #define MEDIAMANAGERSERVICE_CONTRACTID "@mozilla.org/mediaManagerService;1"
 %}
 
-[scriptable, builtinclass, uuid(9b10661f-77b3-47f7-a8de-ee58daaff5d2)]
+[scriptable, builtinclass, uuid(24b23e01-33fd-401f-ba25-6e52658750b0)]
 interface nsIMediaManagerService : nsISupports
 {
   /* return a array of inner windows that have active captures */
   readonly attribute nsISupportsArray activeMediaCaptureWindows;
 
   /* Get the capture state for the given window and all descendant windows (iframes, etc) */
   void mediaCaptureWindowState(in nsIDOMWindow aWindow, out boolean aVideo, out boolean aAudio,
                                [optional] out boolean aScreenShare, [optional] out boolean aWindowShare,
                                [optional] out boolean aAppShare, [optional] out boolean aBrowserShare);
+
+  /* Clear per-orgin list of persistent DeviceIds stored for enumerateDevices
+     sinceTime is milliseconds since 1 January 1970 00:00:00 UTC. 0 = clear all */
+  void sanitizeDeviceIds(in long long sinceWhen);
 };
new file mode 100644
--- /dev/null
+++ b/dom/media/systemservices/MediaChild.cpp
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaChild.h"
+
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/MediaManager.h"
+#include "prlog.h"
+
+#undef LOG
+#if defined(PR_LOGGING)
+PRLogModuleInfo *gMediaChildLog;
+#define LOG(args) PR_LOG(gMediaChildLog, PR_LOG_DEBUG, args)
+#else
+#define LOG(args)
+#endif
+
+
+namespace mozilla {
+namespace media {
+
+static PMediaChild* sChild = nullptr;
+
+template<typename ValueType>
+ChildPledge<ValueType>::ChildPledge()
+{
+  MOZ_ASSERT(MediaManager::IsInMediaThread());
+}
+
+template<typename ValueType> void
+ChildPledge<ValueType>::ActorCreated(PBackgroundChild* aActor)
+{
+  if (!sChild) {
+    // Create PMedia by sending a message to the parent
+    sChild = aActor->SendPMediaConstructor();
+  }
+  Run(static_cast<Child*>(sChild));
+}
+
+template<typename ValueType> void
+ChildPledge<ValueType>::ActorFailed()
+{
+  Pledge<ValueType>::Reject(NS_ERROR_UNEXPECTED);
+}
+
+template<typename ValueType> NS_IMPL_ADDREF(ChildPledge<ValueType>)
+template<typename ValueType> NS_IMPL_RELEASE(ChildPledge<ValueType>)
+template<typename ValueType> NS_INTERFACE_MAP_BEGIN(ChildPledge<ValueType>)
+NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
+NS_INTERFACE_MAP_END
+
+already_AddRefed<ChildPledge<nsCString>>
+GetOriginKey(const nsCString& aOrigin, bool aPrivateBrowsing)
+{
+  class Pledge : public ChildPledge<nsCString>
+  {
+  public:
+    explicit Pledge(const nsCString& aOrigin, bool aPrivateBrowsing)
+    : mOrigin(aOrigin), mPrivateBrowsing(aPrivateBrowsing) {}
+  private:
+    ~Pledge() {}
+    void Run(PMediaChild* aMedia)
+    {
+      aMedia->SendGetOriginKey(mOrigin, mPrivateBrowsing, &mValue);
+      LOG(("GetOriginKey for %s, result:", mOrigin.get(), mValue.get()));
+      Resolve();
+    }
+    const nsCString mOrigin;
+    const bool mPrivateBrowsing;
+  };
+
+  nsRefPtr<ChildPledge<nsCString>> p = new Pledge(aOrigin, aPrivateBrowsing);
+  nsCOMPtr<nsIIPCBackgroundChildCreateCallback> cb = do_QueryObject(p);
+  bool ok = ipc::BackgroundChild::GetOrCreateForCurrentThread(cb);
+  MOZ_RELEASE_ASSERT(ok);
+  return p.forget();
+}
+
+already_AddRefed<ChildPledge<bool>>
+SanitizeOriginKeys(const uint64_t& aSinceWhen)
+{
+  class Pledge : public ChildPledge<bool>
+  {
+  public:
+    explicit Pledge(const uint64_t& aSinceWhen) : mSinceWhen(aSinceWhen) {}
+  private:
+    void Run(PMediaChild* aMedia)
+    {
+      aMedia->SendSanitizeOriginKeys(mSinceWhen);
+      mValue = true;
+      LOG(("SanitizeOriginKeys since %llu", mSinceWhen));
+      Resolve();
+    }
+    const uint64_t mSinceWhen;
+  };
+
+  nsRefPtr<ChildPledge<bool>> p = new Pledge(aSinceWhen);
+  nsCOMPtr<nsIIPCBackgroundChildCreateCallback> cb = do_QueryObject(p);
+  bool ok = ipc::BackgroundChild::GetOrCreateForCurrentThread(cb);
+  MOZ_RELEASE_ASSERT(ok);
+  return p.forget();
+}
+
+Child::Child()
+{
+#if defined(PR_LOGGING)
+  if (!gMediaChildLog) {
+    gMediaChildLog = PR_NewLogModule("MediaChild");
+  }
+#endif
+  LOG(("media::Child: %p", this));
+  MOZ_COUNT_CTOR(Child);
+}
+
+Child::~Child()
+{
+  LOG(("~media::Child: %p", this));
+  sChild = nullptr;
+  MOZ_COUNT_DTOR(Child);
+}
+
+PMediaChild*
+CreateMediaChild()
+{
+  return new Child();
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/dom/media/systemservices/MediaChild.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MediaChild_h
+#define mozilla_MediaChild_h
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/media/PMediaChild.h"
+#include "mozilla/media/PMediaParent.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "MediaUtils.h"
+
+namespace mozilla {
+namespace media {
+
+// media::Child implements proxying to the chrome process for some media-related
+// functions, for the moment just:
+//
+// GetOriginKey() - get a cookie-like persisted unique key for a given origin.
+// SanitizeOriginKeys() - reset persisted unique keys.
+
+// GetOriginKey and SanitizeOriginKeys are asynchronous APIs that return pledges
+// (promise-like objects) with the future value. Use pledge.Then(func) to access.
+
+template<typename ValueType>
+class ChildPledge : public Pledge<ValueType>,
+                    public nsIIPCBackgroundChildCreateCallback
+{
+  NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+  NS_DECL_ISUPPORTS
+public:
+  explicit ChildPledge();
+protected:
+  virtual ~ChildPledge() {}
+  virtual void Run(PMediaChild* aMedia) = 0;
+};
+
+already_AddRefed<ChildPledge<nsCString>>
+GetOriginKey(const nsCString& aOrigin, bool aPrivateBrowsing);
+
+already_AddRefed<ChildPledge<bool>>
+SanitizeOriginKeys(const uint64_t& aSinceWhen);
+
+class Child : public PMediaChild
+{
+public:
+  Child();
+  virtual ~Child();
+};
+
+PMediaChild* CreateMediaChild();
+
+} // namespace media
+} // namespace mozilla
+
+#endif  // mozilla_MediaChild_h
new file mode 100644
--- /dev/null
+++ b/dom/media/systemservices/MediaParent.cpp
@@ -0,0 +1,400 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaParent.h"
+
+#include "mozilla/Base64.h"
+
+#include "MediaUtils.h"
+#include "MediaEngine.h"
+#include "VideoUtils.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsILineInputStream.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsISupportsImpl.h"
+#include "prlog.h"
+
+#undef LOG
+#if defined(PR_LOGGING)
+PRLogModuleInfo *gMediaParentLog;
+#define LOG(args) PR_LOG(gMediaParentLog, PR_LOG_DEBUG, args)
+#else
+#define LOG(args)
+#endif
+
+// A file in the profile dir is used to persist mOriginKeys used to anonymize
+// deviceIds to be unique per origin, to avoid them being supercookies.
+
+#define ORIGINKEYS_FILE "enumerate_devices.txt"
+#define ORIGINKEYS_VERSION "1"
+
+namespace mozilla {
+namespace media {
+
+static ParentSingleton* sParentSingleton = nullptr;
+
+class ParentSingleton : public nsISupports
+{
+  NS_DECL_ISUPPORTS
+
+  class OriginKey
+  {
+  public:
+    static const size_t DecodedLength = 18;
+
+    OriginKey(const nsACString& aKey, int64_t aSecondsStamp)
+    : mKey(aKey)
+    , mSecondsStamp(aSecondsStamp) {}
+
+    nsCString mKey; // Base64 encoded.
+    int64_t mSecondsStamp;
+  };
+
+  class OriginKeysTable
+  {
+  public:
+    OriginKeysTable() {}
+
+    nsresult
+    GetOriginKey(const nsACString& aOrigin, nsCString& result)
+    {
+      OriginKey* key;
+      if (!mKeys.Get(aOrigin, &key)) {
+        nsCString salt; // Make a new one
+        nsresult rv = GenerateRandomName(salt, key->DecodedLength * 4 / 3);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+        key = new OriginKey(salt, PR_Now() / PR_USEC_PER_SEC);
+        mKeys.Put(aOrigin, key);
+      }
+      result = key->mKey;
+      return NS_OK;
+    }
+
+    static PLDHashOperator
+    HashCleaner(const nsACString& aOrigin, nsAutoPtr<OriginKey>& aOriginKey,
+                void *aUserArg)
+    {
+      OriginKey* since = static_cast<OriginKey*>(aUserArg);
+
+      LOG((((aOriginKey->mSecondsStamp >= since->mSecondsStamp)?
+            "%s: REMOVE %lld >= %lld" :
+            "%s: KEEP   %lld < %lld"),
+            __FUNCTION__, aOriginKey->mSecondsStamp, since->mSecondsStamp));
+
+      return (aOriginKey->mSecondsStamp >= since->mSecondsStamp)?
+          PL_DHASH_REMOVE : PL_DHASH_NEXT;
+    }
+
+    void Clear(int64_t aSinceWhen)
+    {
+      // Avoid int64_t* <-> void* casting offset
+      OriginKey since(nsCString(), aSinceWhen  / PR_USEC_PER_SEC);
+      mKeys.Enumerate(HashCleaner, &since);
+    }
+
+  protected:
+    nsClassHashtable<nsCStringHashKey, OriginKey> mKeys;
+  };
+
+  class OriginKeysLoader : public OriginKeysTable
+  {
+  public:
+    OriginKeysLoader()
+    {
+      Load();
+    }
+
+    nsresult
+    GetOriginKey(const nsACString& aOrigin, nsCString& result)
+    {
+      auto before = mKeys.Count();
+      OriginKeysTable::GetOriginKey(aOrigin, result);
+      if (mKeys.Count() != before) {
+        Save();
+      }
+      return NS_OK;
+    }
+
+    already_AddRefed<nsIFile>
+    GetFile()
+    {
+      if (!mProfileDir) {
+        nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                                             getter_AddRefs(mProfileDir));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return nullptr;
+        }
+      }
+      nsCOMPtr<nsIFile> file;
+      nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return nullptr;
+      }
+      file->Append(NS_LITERAL_STRING(ORIGINKEYS_FILE));
+      return file.forget();
+    }
+
+    // Format of file is key secondsstamp origin (first line is version #):
+    //
+    // 1
+    // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424733961 http://fiddle.jshell.net
+    // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424734841 http://mozilla.github.io
+    // etc.
+
+    nsresult Read()
+    {
+      nsCOMPtr<nsIFile> file = GetFile();
+      if (NS_WARN_IF(!file)) {
+        return NS_ERROR_UNEXPECTED;
+      }
+      bool exists;
+      nsresult rv = file->Exists(&exists);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      if (!exists) {
+        return NS_OK;
+      }
+
+      nsCOMPtr<nsIInputStream> stream;
+      rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      nsCOMPtr<nsILineInputStream> i = do_QueryInterface(stream);
+      MOZ_ASSERT(i);
+
+      nsCString line;
+      bool hasMoreLines;
+      rv = i->ReadLine(line, &hasMoreLines);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      if (!line.EqualsLiteral(ORIGINKEYS_VERSION)) {
+        // If version on disk is newer than we can understand then ignore it.
+        return NS_OK;
+      }
+
+      while (hasMoreLines) {
+        rv = i->ReadLine(line, &hasMoreLines);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+        // Read key secondsstamp origin.
+        // Ignore any lines that don't fit format in the comment above exactly.
+        int32_t f = line.FindChar(' ');
+        if (f < 0) {
+          continue;
+        }
+        const nsACString& key = Substring(line, 0, f);
+        const nsACString& s = Substring(line, f+1);
+        f = s.FindChar(' ');
+        if (f < 0) {
+          continue;
+        }
+        int64_t secondsstamp = nsCString(Substring(s, 0, f)).ToInteger64(&rv);
+        if (NS_FAILED(rv)) {
+          continue;
+        }
+        const nsACString& origin = Substring(s, f+1);
+
+        // Validate key
+        if (key.Length() != OriginKey::DecodedLength) {
+          continue;
+        }
+        nsCString dummy;
+        rv = Base64Decode(key, dummy);
+        if (NS_FAILED(rv)) {
+          continue;
+        }
+        mKeys.Put(origin, new OriginKey(key, secondsstamp));
+      }
+      return NS_OK;
+    }
+
+    static PLDHashOperator
+    HashWriter(const nsACString& aOrigin, OriginKey* aOriginKey, void *aUserArg)
+    {
+      auto* stream = static_cast<nsIOutputStream *>(aUserArg);
+
+      nsCString buffer;
+      buffer.Append(aOriginKey->mKey);
+      buffer.Append(' ');
+      buffer.AppendInt(aOriginKey->mSecondsStamp);
+      buffer.Append(' ');
+      buffer.Append(aOrigin);
+      buffer.Append('\n');
+
+      uint32_t count;
+      nsresult rv = stream->Write(buffer.Data(), buffer.Length(), &count);
+      if (NS_WARN_IF(NS_FAILED(rv)) || count != buffer.Length()) {
+        return PL_DHASH_STOP;
+      }
+      return PL_DHASH_NEXT;
+    }
+
+    nsresult
+    Write()
+    {
+      nsCOMPtr<nsIFile> file = GetFile();
+      if (NS_WARN_IF(!file)) {
+        return NS_ERROR_UNEXPECTED;
+      }
+
+      nsCOMPtr<nsIOutputStream> stream;
+      nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      nsAutoCString buffer;
+      buffer.AppendLiteral(ORIGINKEYS_VERSION);
+      buffer.Append('\n');
+
+      uint32_t count;
+      rv = stream->Write(buffer.Data(), buffer.Length(), &count);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      if (count != buffer.Length()) {
+        return NS_ERROR_UNEXPECTED;
+      }
+      mKeys.EnumerateRead(HashWriter, stream.get());
+
+      nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream);
+      MOZ_ASSERT(safeStream);
+
+      rv = safeStream->Finish();
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      return NS_OK;
+    }
+
+    nsresult Load()
+    {
+      nsresult rv = Read();
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        Delete();
+      }
+      return rv;
+    }
+
+    nsresult Save()
+    {
+      nsresult rv = Write();
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        NS_WARNING("Failed to write data for EnumerateDevices id-persistence.");
+        Delete();
+      }
+      return rv;
+    }
+
+    void Clear(int64_t aSinceWhen)
+    {
+      OriginKeysTable::Clear(aSinceWhen);
+      Delete();
+      Save();
+    }
+
+    nsresult Delete()
+    {
+      nsCOMPtr<nsIFile> file = GetFile();
+      if (NS_WARN_IF(!file)) {
+        return NS_ERROR_UNEXPECTED;
+      }
+      nsresult rv = file->Remove(false);
+      if (rv == NS_ERROR_FILE_NOT_FOUND) {
+        return NS_OK;
+      }
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      return NS_OK;
+    }
+
+  private:
+    nsCOMPtr<nsIFile> mProfileDir;
+  };
+
+private:
+  virtual ~ParentSingleton()
+  {
+    sParentSingleton = nullptr;
+    LOG((__FUNCTION__));
+  }
+
+public:
+  static ParentSingleton* Get()
+  {
+    if (!sParentSingleton) {
+      sParentSingleton = new ParentSingleton();
+    }
+    return sParentSingleton;
+  }
+
+  OriginKeysLoader mOriginKeys;
+  OriginKeysTable mPrivateBrowsingOriginKeys;
+};
+
+NS_IMPL_ISUPPORTS0(ParentSingleton)
+
+bool
+Parent::RecvGetOriginKey(const nsCString& aOrigin,
+                         const bool& aPrivateBrowsing,
+                         nsCString* aKey)
+{
+  if (aPrivateBrowsing) {
+    mSingleton->mPrivateBrowsingOriginKeys.GetOriginKey(aOrigin, *aKey);
+  } else {
+    mSingleton->mOriginKeys.GetOriginKey(aOrigin, *aKey);
+  }
+  return true;
+}
+
+bool
+Parent::RecvSanitizeOriginKeys(const uint64_t& aSinceWhen)
+{
+  mSingleton->mPrivateBrowsingOriginKeys.Clear(aSinceWhen);
+  mSingleton->mOriginKeys.Clear(aSinceWhen);
+  return true;
+}
+
+void
+Parent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  // No more IPC from here
+  LOG((__FUNCTION__));
+}
+
+Parent::Parent()
+  : mSingleton(ParentSingleton::Get())
+{
+#if defined(PR_LOGGING)
+  if (!gMediaParentLog)
+    gMediaParentLog = PR_NewLogModule("MediaParent");
+#endif
+  LOG(("media::Parent: %p", this));
+
+  MOZ_COUNT_CTOR(Parent);
+}
+
+Parent::~Parent()
+{
+  LOG(("~media::Parent: %p", this));
+
+  MOZ_COUNT_DTOR(Parent);
+}
+
+PMediaParent* CreateParent()
+{
+  return new Parent();
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/dom/media/systemservices/MediaParent.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MediaParent_h
+#define mozilla_MediaParent_h
+
+#include "MediaChild.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/media/PMediaParent.h"
+
+namespace mozilla {
+namespace media {
+
+// media::Parent implements the chrome-process side of ipc for media::Child APIs
+
+class ParentSingleton;
+
+class Parent : public PMediaParent
+{
+public:
+  virtual bool RecvGetOriginKey(const nsCString& aOrigin,
+                                const bool& aPrivateBrowsing,
+                                nsCString* aKey) override;
+  virtual bool RecvSanitizeOriginKeys(const uint64_t& aSinceWhen) override;
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  Parent();
+  virtual ~Parent();
+private:
+  nsRefPtr<ParentSingleton> mSingleton;
+};
+
+PMediaParent* CreateParent();
+
+} // namespace media
+} // namespace mozilla
+
+#endif  // mozilla_MediaParent_h
new file mode 100644
--- /dev/null
+++ b/dom/media/systemservices/MediaUtils.cpp
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaUtils.h"
+
+#include "mozilla/MediaManager.h"
+
+namespace mozilla {
+namespace media {
+
+template<typename ValueType>
+Pledge<ValueType>::Pledge()
+  : mDone(false)
+  , mResult(NS_OK)
+{
+  MOZ_ASSERT(MediaManager::IsInMediaThread());
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/dom/media/systemservices/MediaUtils.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MediaUtils_h
+#define mozilla_MediaUtils_h
+
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+namespace media {
+
+// A media::Pledge is a promise-like pattern for c++ that takes lambda functions.
+//
+// Asynchronous APIs that proxy to the chrome process and back, may return a
+// pledge to allow callers to use pledge.Then() to specify a lambda function to
+// invoke with the result once the asynchronous API resolves later back on the
+// same thread.
+//
+// An advantage of this pattern is that lambdas allow "capturing" of local
+// variables, much like closures in JavaScript.
+
+template<typename ValueType>
+class Pledge
+{
+  // TODO: Remove workaround once mozilla allows std::function from <functional>
+  class FunctorsBase
+  {
+  public:
+    FunctorsBase() {}
+    virtual void Succeed(const ValueType& result) = 0;
+    virtual void Fail(nsresult rv) = 0;
+    virtual ~FunctorsBase() {};
+  };
+
+public:
+  explicit Pledge();
+
+  template<typename OnSuccessType, typename OnFailureType>
+  void Then(OnSuccessType aOnSuccess, OnFailureType aOnFailure)
+  {
+    class F : public FunctorsBase
+    {
+    public:
+      F(OnSuccessType& aOnSuccess, OnFailureType& aOnFailure)
+        : mOnSuccess(aOnSuccess), mOnFailure(aOnFailure) {}
+
+      void Succeed(const ValueType& result)
+      {
+        mOnSuccess(result);
+      }
+      void Fail(nsresult rv)
+      {
+        mOnFailure(rv);
+      };
+
+      OnSuccessType mOnSuccess;
+      OnFailureType mOnFailure;
+    };
+    mFunctors = new F(aOnSuccess, aOnFailure);
+
+    if (mDone) {
+      if (mResult == NS_OK) {
+        mFunctors->Succeed(mValue);
+      } else {
+        mFunctors->Fail(mResult);
+      }
+    }
+  }
+
+protected:
+  void Resolve()
+  {
+    if (!mDone) {
+      mDone = true;
+      MOZ_ASSERT(mResult == NS_OK);
+      if (mFunctors) {
+        mFunctors->Succeed(mValue);
+      }
+    }
+  }
+
+  void Reject(nsresult rv)
+  {
+    if (!mDone) {
+      mDone = true;
+      mResult = rv;
+      if (mFunctors) {
+        mFunctors->Fail(mResult);
+      }
+    }
+  }
+
+  ValueType mValue;
+private:
+  bool mDone;
+  nsresult mResult;
+  nsAutoPtr<FunctorsBase> mFunctors;
+};
+
+}
+}
+
+#endif // mozilla_MediaUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/media/systemservices/PMedia.ipdl
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+include protocol PBackground;
+
+namespace mozilla {
+namespace media {
+
+sync protocol PMedia
+{
+  manager PBackground;
+
+parent:
+  /**
+   * Returns a persistent unique secret key for each origin.
+   * Has no expiry, but is cleared by age along with cookies.
+   * This is needed by mediaDevices.enumerateDevices() to produce persistent
+   * deviceIds that wont work cross-origin.
+   */
+  sync GetOriginKey(nsCString origin, bool privateBrowsing) returns (nsCString key);
+
+  /**
+   * On clear cookies.
+   */
+  sync SanitizeOriginKeys(uint64_t sinceWhen);
+
+  async __delete__();
+};
+
+} // namespace media
+} // namespace mozilla
--- a/dom/media/systemservices/moz.build
+++ b/dom/media/systemservices/moz.build
@@ -32,13 +32,29 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'coco
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     CXXFLAGS += [
         '-I%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
             'frameworks/wilhelm/include',
             'system/media/wilhelm/include',
         ]
     ]
 
+EXPORTS.mozilla.media += ['MediaChild.h',
+            'MediaParent.h',
+            'MediaUtils.h'
+]
+UNIFIED_SOURCES += ['MediaChild.cpp',
+                    'MediaParent.cpp',
+                    'MediaUtils.cpp'
+]
+IPDL_SOURCES += [
+    'PMedia.ipdl',
+]
+# /dom/base needed for nsGlobalWindow.h in MediaChild.cpp
+LOCAL_INCLUDES += [
+    '/dom/base',
+]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 FAIL_ON_WARNINGS = True
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "BackgroundChildImpl.h"
 
 #include "ActorsChild.h" // IndexedDB
 #include "BroadcastChannelChild.h"
 #include "FileDescriptorSetChild.h"
+#include "mozilla/media/MediaChild.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/dom/PBlobChild.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/ipc/PBackgroundTestChild.h"
 #include "mozilla/layout/VsyncChild.h"
 #include "nsID.h"
@@ -276,16 +277,29 @@ BackgroundChildImpl::AllocPCacheStreamCo
 
 bool
 BackgroundChildImpl::DeallocPCacheStreamControlChild(PCacheStreamControlChild* aActor)
 {
   dom::cache::DeallocPCacheStreamControlChild(aActor);
   return true;
 }
 
+media::PMediaChild*
+BackgroundChildImpl::AllocPMediaChild()
+{
+  return media::CreateMediaChild();
+}
+
+bool
+BackgroundChildImpl::DeallocPMediaChild(media::PMediaChild *aActor)
+{
+  delete aActor;
+  return true;
+}
+
 } // namespace ipc
 } // namespace mozilla
 
 bool
 TestChild::Recv__delete__(const nsCString& aTestArg)
 {
   MOZ_RELEASE_ASSERT(aTestArg == mTestArg,
                      "BackgroundTest message was corrupted!");
--- a/ipc/glue/BackgroundChildImpl.h
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -66,16 +66,22 @@ protected:
 
   virtual PFileDescriptorSetChild*
   AllocPFileDescriptorSetChild(const FileDescriptor& aFileDescriptor)
                                override;
 
   virtual bool
   DeallocPFileDescriptorSetChild(PFileDescriptorSetChild* aActor) override;
 
+  virtual PMediaChild*
+  AllocPMediaChild() override;
+
+  virtual bool
+  DeallocPMediaChild(PMediaChild* aActor) override;
+
   virtual PVsyncChild*
   AllocPVsyncChild() override;
 
   virtual bool
   DeallocPVsyncChild(PVsyncChild* aActor) override;
 
   virtual PBroadcastChannelChild*
   AllocPBroadcastChannelChild(const PrincipalInfo& aPrincipalInfo,
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "BackgroundParentImpl.h"
 
 #include "BroadcastChannelParent.h"
 #include "FileDescriptorSetParent.h"
+#include "mozilla/media/MediaParent.h"
 #include "mozilla/AppProcessChecker.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/PBlobParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/ipc/BlobParent.h"
@@ -355,16 +356,29 @@ BackgroundParentImpl::DeallocPBroadcastC
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   delete static_cast<BroadcastChannelParent*>(aActor);
   return true;
 }
 
+media::PMediaParent*
+BackgroundParentImpl::AllocPMediaParent()
+{
+  return media::CreateParent();
+}
+
+bool
+BackgroundParentImpl::DeallocPMediaParent(media::PMediaParent *aActor)
+{
+  delete aActor;
+  return true;
+}
+
 namespace {
 
 class RegisterServiceWorkerCallback final : public nsRunnable
 {
 public:
   explicit RegisterServiceWorkerCallback(
                                      const ServiceWorkerRegistrationData& aData)
     : mData(aData)
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -79,16 +79,22 @@ protected:
   RecvPBroadcastChannelConstructor(PBroadcastChannelParent* actor,
                                    const PrincipalInfo& aPrincipalInfo,
                                    const nsString& origin,
                                    const nsString& channel) override;
 
   virtual bool
   DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) override;
 
+  virtual PMediaParent*
+  AllocPMediaParent() override;
+
+  virtual bool
+  DeallocPMediaParent(PMediaParent* aActor) override;
+
   virtual bool
   RecvRegisterServiceWorker(const ServiceWorkerRegistrationData& aData)
                             override;
 
   virtual bool
   RecvUnregisterServiceWorker(const PrincipalInfo& aPrincipalInfo,
                               const nsString& aScope) override;
 
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -6,16 +6,17 @@ include protocol PBackgroundIDBFactory;
 include protocol PBackgroundTest;
 include protocol PBlob;
 include protocol PBroadcastChannel;
 include protocol PCache;
 include protocol PCacheStorage;
 include protocol PCacheStreamControl;
 include protocol PFileDescriptorSet;
 include protocol PVsync;
+include protocol PMedia;
 
 include DOMTypes;
 include PBackgroundSharedTypes;
 include PBackgroundIDBSharedTypes;
 include ServiceWorkerRegistrarTypes;
 
 using mozilla::dom::cache::Namespace from "mozilla/dom/cache/Types.h";
 include "mozilla/dom/cache/IPCUtils.h";
@@ -29,24 +30,26 @@ sync protocol PBackground
   manages PBackgroundTest;
   manages PBlob;
   manages PBroadcastChannel;
   manages PCache;
   manages PCacheStorage;
   manages PCacheStreamControl;
   manages PFileDescriptorSet;
   manages PVsync;
+  manages PMedia;
 
 parent:
   // Only called at startup during mochitests to check the basic infrastructure.
   PBackgroundTest(nsCString testArg);
 
   PBackgroundIDBFactory(LoggingInfo loggingInfo);
 
   PVsync();
+  PMedia();
 
   PBroadcastChannel(PrincipalInfo pInfo, nsString origin, nsString channel);
 
   RegisterServiceWorker(ServiceWorkerRegistrationData data);
   UnregisterServiceWorker(PrincipalInfo principalInfo,
                           nsString scope);
   ShutdownServiceWorkerRegistrar();
 
--- a/security/manager/ssl/src/nsNSSModule.cpp
+++ b/security/manager/ssl/src/nsNSSModule.cpp
@@ -164,17 +164,17 @@ static nsresult                         
                                                                               \
     return rv;                                                                \
 }
 
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nssLoadingComponent, nsNSSComponent,
                                         Init)
 
 using namespace mozilla::psm;
-  
+
 namespace {
 
 // Use the special factory constructor for everything this module implements,
 // because all code could potentially require the NSS library.
 // Our factory constructor takes an additional boolean parameter.
 // Only for the nsNSSComponent, set this to true.
 // All other classes must have this set to false.
 
@@ -194,19 +194,19 @@ NS_NSS_GENERIC_FACTORY_CONSTRUCTOR_BYPRO
                                              nsNSSCertListFakeTransport)
 #ifdef MOZ_XUL
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, nsCertTree)
 #endif
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, nsPkcs11)
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, nsCertPicker)
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nssEnsure, nsNTLMAuthModule, InitTest)
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureChromeOrContent, nsCryptoHash)
-NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, nsCryptoHMAC)
-NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, nsKeyObject)
-NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, nsKeyObjectFactory)
+NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureChromeOrContent, nsCryptoHMAC)
+NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureChromeOrContent, nsKeyObject)
+NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureChromeOrContent, nsKeyObjectFactory)
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, nsDataSignatureVerifier)
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsure, nsRandomGenerator)
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureOnChromeOnly, nsSSLStatus)
 NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(nssEnsureOnChromeOnly, TransportSecurityInfo)
 
 typedef mozilla::psm::NSSErrorsService NSSErrorsService;
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NSSErrorsService, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsNSSVersion)