Bug 1060179 - Ensure GMP storage respects private browsing mode. r=ehsan,jesup
authorChris Pearce <cpearce@mozilla.com>
Mon, 13 Oct 2014 11:53:44 +1300
changeset 210039 bd41106643fe8b7bfcf7cc447c337099f6627497
parent 210038 77fcb81f442b5acf0c5dd122abe2916227d42b71
child 210040 b3de4b118cdd569b9dc3215b9755824de694e9da
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersehsan, jesup
bugs1060179
milestone35.0a1
Bug 1060179 - Ensure GMP storage respects private browsing mode. r=ehsan,jesup
content/media/gmp/GMPParent.cpp
content/media/gmp/GMPService.cpp
content/media/gmp/GMPService.h
content/media/gmp/GMPStorageParent.cpp
content/media/gmp/GMPStorageParent.h
content/media/gmp/mozIGeckoMediaPluginService.idl
--- a/content/media/gmp/GMPParent.cpp
+++ b/content/media/gmp/GMPParent.cpp
@@ -706,18 +706,22 @@ GMPParent::DeallocPGMPStorageParent(PGMP
 {
   GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor);
   p->Shutdown();
   mStorage.RemoveElement(p);
   return true;
 }
 
 bool
-GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* actor)
+GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* aActor)
 {
+  GMPStorageParent* p  = (GMPStorageParent*)aActor;
+  if (NS_WARN_IF(NS_FAILED(p->Init()))) {
+    return false;
+  }
   return true;
 }
 
 bool
 GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
 {
   return true;
 }
--- a/content/media/gmp/GMPService.cpp
+++ b/content/media/gmp/GMPService.cpp
@@ -165,16 +165,17 @@ nsresult
 GeckoMediaPluginService::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
   MOZ_ASSERT(obsService);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "profile-change-teardown", false)));
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)));
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "last-pb-context-exited", false)));
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs) {
     prefs->AddObserver("media.gmp.plugin.crash", this, false);
   }
 
   // Directory service is main thread only, so cache the profile dir here
   // so that we can use it off main thread.
@@ -307,16 +308,23 @@ GeckoMediaPluginService::Observe(nsISupp
       // See bug 1057908.
       MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default || mShuttingDown);
       mGMPThread.swap(gmpThread);
     }
 
     if (gmpThread) {
       gmpThread->Shutdown();
     }
+  } else if (!strcmp("last-pb-context-exited", aTopic)) {
+    // When Private Browsing mode exits, all we need to do is clear
+    // mTempNodeIds. This drops all the node ids we've cached in memory
+    // for PB origin-pairs. If we try to open an origin-pair for non-PB
+    // mode, we'll get the NodeId salt stored on-disk, and if we try to
+    // open a PB mode origin-pair, we'll re-generate new salt.
+    mTempNodeIds.Clear();
   }
   return NS_OK;
 }
 
 // always call with getter_AddRefs, because it does
 NS_IMETHODIMP
 GeckoMediaPluginService::GetThread(nsIThread** aThread)
 {
@@ -954,40 +962,78 @@ ReadFromFile(nsIFile* aPath,
   if (NS_WARN_IF(len != size)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+GeckoMediaPluginService::IsPersistentStorageAllowed(const nsACString& aNodeId,
+                                                    bool* aOutAllowed)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+  NS_ENSURE_ARG(aOutAllowed);
+  *aOutAllowed = mPersistentStorageAllowed.Get(aNodeId);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 GeckoMediaPluginService::GetNodeId(const nsAString& aOrigin,
                                    const nsAString& aTopLevelOrigin,
                                    bool aInPrivateBrowsing,
                                    nsACString& aOutId)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s: (%s, %s), %s", __CLASS__, __FUNCTION__,
        NS_ConvertUTF16toUTF8(aOrigin).get(),
        NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
        (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing")));
 
   nsresult rv;
   const uint32_t NodeIdSaltLength = 32;
 
-  if (aInPrivateBrowsing ||
-      aOrigin.EqualsLiteral("null") ||
+  if (aOrigin.EqualsLiteral("null") ||
       aOrigin.IsEmpty() ||
       aTopLevelOrigin.EqualsLiteral("null") ||
       aTopLevelOrigin.IsEmpty()) {
-    // Non-persistent session; just generate a random node id.
+    // At least one of the (origin, topLevelOrigin) is null or empty;
+    // probably a local file. Generate a random node id, and don't store
+    // it so that the GMP's storage is temporary and not shared.
     nsAutoCString salt;
     rv = GenerateRandomPathName(salt, NodeIdSaltLength);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
     aOutId = salt;
-    return rv;
+    mPersistentStorageAllowed.Put(salt, false);
+    return NS_OK;
+  }
+
+  const uint32_t hash = AddToHash(HashString(aOrigin),
+                                  HashString(aTopLevelOrigin));
+
+  if (aInPrivateBrowsing) {
+    // For PB mode, we store the node id, indexed by the origin pair,
+    // so that if the same origin pair is opened in this session, it gets
+    // the same node id.
+    nsCString* salt = nullptr;
+    if (!(salt = mTempNodeIds.Get(hash))) {
+      // No salt stored, generate and temporarily store some for this id.
+      nsAutoCString newSalt;
+      rv = GenerateRandomPathName(newSalt, NodeIdSaltLength);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      salt = new nsCString(newSalt);
+      mTempNodeIds.Put(hash, salt);
+      mPersistentStorageAllowed.Put(*salt, false);
+    }
+    aOutId = *salt;
+    return NS_OK;
   }
 
   // Otherwise, try to see if we've previously generated and stored salt
   // for this origin pair.
   nsCOMPtr<nsIFile> path; // $profileDir/gmp/
   rv = GetStorageDir(getter_AddRefs(path));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -999,18 +1045,16 @@ GeckoMediaPluginService::GetNodeId(const
   }
 
   // $profileDir/gmp/id/
   rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
   if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  uint32_t hash = AddToHash(HashString(aOrigin),
-                            HashString(aTopLevelOrigin));
   nsAutoCString hashStr;
   hashStr.AppendInt((int64_t)hash);
 
   // $profileDir/gmp/id/$hash
   rv = path->AppendNative(hashStr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -1074,14 +1118,15 @@ GeckoMediaPluginService::GetNodeId(const
                       salt,
                       NodeIdSaltLength);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   aOutId = salt;
+  mPersistentStorageAllowed.Put(salt, true);
 
   return NS_OK;
 }
 
 } // namespace gmp
 } // namespace mozilla
--- a/content/media/gmp/GMPService.h
+++ b/content/media/gmp/GMPService.h
@@ -12,16 +12,18 @@
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Monitor.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIThread.h"
 #include "nsThreadUtils.h"
 #include "nsITimer.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
 
 template <class> struct already_AddRefed;
 
 namespace mozilla {
 namespace gmp {
 
 class GMPParent;
 
@@ -108,14 +110,22 @@ private:
   };
 
   MainThreadOnly<bool> mWaitingForPluginsAsyncShutdown;
 
   nsTArray<nsRefPtr<GMPParent>> mAsyncShutdownPlugins; // GMP Thread only.
   nsCOMPtr<nsITimer> mAsyncShutdownTimeout; // GMP Thread only.
 
   nsCOMPtr<nsIFile> mStorageBaseDir;
+
+  // Hashes of (origin,topLevelOrigin) to the node id for
+  // non-persistent sessions.
+  nsClassHashtable<nsUint32HashKey, nsCString> mTempNodeIds;
+
+  // Hashes node id to whether that node id is allowed to store data
+  // persistently on disk.
+  nsDataHashtable<nsCStringHashKey, bool> mPersistentStorageAllowed;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPService_h_
--- a/content/media/gmp/GMPStorageParent.cpp
+++ b/content/media/gmp/GMPStorageParent.cpp
@@ -7,16 +7,22 @@
 #include "mozilla/SyncRunnable.h"
 #include "plhash.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "GMPParent.h"
 #include "gmp-storage.h"
 #include "mozilla/unused.h"
+#include "nsTHashtable.h"
+#include "nsDataHashtable.h"
+#include "prio.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 #ifdef PR_LOGGING
@@ -77,24 +83,16 @@ GetGMPStorageDir(nsIFile** aTempDir, con
     return rv;
   }
 
   tmpFile.forget(aTempDir);
 
   return NS_OK;
 }
 
-GMPStorageParent::GMPStorageParent(const nsCString& aNodeId,
-                                   GMPParent* aPlugin)
-  : mNodeId(aNodeId)
-  , mPlugin(aPlugin)
-  , mShutdown(false)
-{
-}
-
 enum OpenFileMode  { ReadWrite, Truncate };
 
 nsresult
 OpenStorageFile(const nsCString& aRecordName,
                 const nsCString& aNodeId,
                 const OpenFileMode aMode,
                 PRFileDesc** aOutFD)
 {
@@ -113,159 +111,319 @@ OpenStorageFile(const nsCString& aRecord
   auto mode = PR_RDWR | PR_CREATE_FILE;
   if (aMode == Truncate) {
     mode |= PR_TRUNCATE;
   }
 
   return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
 }
 
+PLDHashOperator
+CloseFile(const nsACString& key, PRFileDesc*& entry, void* cx)
+{
+  if (PR_Close(entry) != PR_SUCCESS) {
+    NS_WARNING("GMPDiskStorage Failed to clsose file.");
+  }
+  return PL_DHASH_REMOVE;
+}
+
+class GMPDiskStorage : public GMPStorage {
+public:
+  GMPDiskStorage(const nsCString& aNodeId)
+    : mNodeId(aNodeId)
+  {
+  }
+  ~GMPDiskStorage() {
+    mFiles.Enumerate(CloseFile, nullptr);
+    MOZ_ASSERT(!mFiles.Count());
+  }
+
+  virtual GMPErr Open(const nsCString& aRecordName) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(!IsOpen(aRecordName));
+    PRFileDesc* fd = nullptr;
+    if (NS_FAILED(OpenStorageFile(aRecordName, mNodeId, ReadWrite, &fd))) {
+      NS_WARNING("Failed to open storage file.");
+      return GMPGenericErr;
+    }
+    mFiles.Put(aRecordName, fd);
+    return GMPNoErr;
+  }
+
+  virtual bool IsOpen(const nsCString& aRecordName) MOZ_OVERRIDE {
+    return mFiles.Contains(aRecordName);
+  }
+
+  virtual GMPErr Read(const nsCString& aRecordName,
+                      nsTArray<uint8_t>& aOutBytes) MOZ_OVERRIDE
+  {
+    PRFileDesc* fd = mFiles.Get(aRecordName);
+    if (!fd) {
+      return GMPGenericErr;
+    }
+
+    int32_t len = PR_Seek(fd, 0, PR_SEEK_END);
+    PR_Seek(fd, 0, PR_SEEK_SET);
+
+    if (len > GMP_MAX_RECORD_SIZE) {
+      // Refuse to read big records.
+      return GMPQuotaExceededErr;
+    }
+    aOutBytes.SetLength(len);
+    auto bytesRead = PR_Read(fd, aOutBytes.Elements(), len);
+    return (bytesRead == len) ? GMPNoErr : GMPGenericErr;
+  }
+
+  virtual GMPErr Write(const nsCString& aRecordName,
+                       const nsTArray<uint8_t>& aBytes) MOZ_OVERRIDE
+  {
+    PRFileDesc* fd = mFiles.Get(aRecordName);
+    if (!fd) {
+      return GMPGenericErr;
+    }
+
+    // Write operations overwrite the entire record. So re-open the file
+    // in truncate mode, to clear its contents.
+    PR_Close(fd);
+    mFiles.Remove(aRecordName);
+    if (NS_FAILED(OpenStorageFile(aRecordName, mNodeId, Truncate, &fd))) {
+      return GMPGenericErr;
+    }
+    mFiles.Put(aRecordName, fd);
+
+    int32_t bytesWritten = PR_Write(fd, aBytes.Elements(), aBytes.Length());
+    return (bytesWritten == (int32_t)aBytes.Length()) ? GMPNoErr : GMPGenericErr;
+  }
+
+  virtual void Close(const nsCString& aRecordName) MOZ_OVERRIDE
+  {
+    PRFileDesc* fd = mFiles.Get(aRecordName);
+    if (fd) {
+      if (PR_Close(fd) == PR_SUCCESS) {
+        mFiles.Remove(aRecordName);
+      } else {
+        NS_WARNING("GMPDiskStorage Failed to clsose file.");
+      }
+    }
+  }
+
+private:
+  nsDataHashtable<nsCStringHashKey, PRFileDesc*> mFiles;
+  const nsAutoCString mNodeId;
+};
+
+class GMPMemoryStorage : public GMPStorage {
+public:
+  virtual GMPErr Open(const nsCString& aRecordName) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(!IsOpen(aRecordName));
+
+    Record* record = nullptr;
+    if (!mRecords.Get(aRecordName, &record)) {
+      record = new Record();
+      mRecords.Put(aRecordName, record);
+    }
+    record->mIsOpen = true;
+    return GMPNoErr;
+  }
+
+  virtual bool IsOpen(const nsCString& aRecordName) MOZ_OVERRIDE {
+    Record* record = nullptr;
+    if (!mRecords.Get(aRecordName, &record)) {
+      return false;
+    }
+    return record->mIsOpen;
+  }
+
+  virtual GMPErr Read(const nsCString& aRecordName,
+                      nsTArray<uint8_t>& aOutBytes) MOZ_OVERRIDE
+  {
+    Record* record = nullptr;
+    if (!mRecords.Get(aRecordName, &record)) {
+      return GMPGenericErr;
+    }
+    aOutBytes = record->mData;
+    return GMPNoErr;
+  }
+
+  virtual GMPErr Write(const nsCString& aRecordName,
+                       const nsTArray<uint8_t>& aBytes) MOZ_OVERRIDE
+  {
+    Record* record = nullptr;
+    if (!mRecords.Get(aRecordName, &record)) {
+      return GMPClosedErr;
+    }
+    record->mData = aBytes;
+    return GMPNoErr;
+  }
+
+  virtual void Close(const nsCString& aRecordName) MOZ_OVERRIDE
+  {
+    Record* record = nullptr;
+    if (!mRecords.Get(aRecordName, &record)) {
+      return;
+    }
+    if (!record->mData.Length()) {
+      // Record is empty, delete.
+      mRecords.Remove(aRecordName);
+    } else {
+      record->mIsOpen = false;
+    }
+  }
+
+private:
+
+  struct Record {
+    Record() : mIsOpen(false) {}
+    nsTArray<uint8_t> mData;
+    bool mIsOpen;
+  };
+
+  nsClassHashtable<nsCStringHashKey, Record> mRecords;
+};
+
+GMPStorageParent::GMPStorageParent(const nsCString& aNodeId,
+                                   GMPParent* aPlugin)
+  : mNodeId(aNodeId)
+  , mPlugin(aPlugin)
+  , mShutdown(false)
+{
+}
+
+nsresult
+GMPStorageParent::Init()
+{
+  if (NS_WARN_IF(mNodeId.IsEmpty())) {
+    return NS_ERROR_FAILURE;
+  }
+  nsCOMPtr<mozIGeckoMediaPluginService> mps =
+    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  if (NS_WARN_IF(!mps)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  bool persistent = false;
+  if (NS_WARN_IF(NS_FAILED(mps->IsPersistentStorageAllowed(mNodeId, &persistent)))) {
+    return NS_ERROR_FAILURE;
+  }
+  if (persistent) {
+    mStorage = MakeUnique<GMPDiskStorage>(mNodeId);
+  } else {
+    mStorage = MakeUnique<GMPMemoryStorage>();
+  }
+
+  return NS_OK;
+}
+
 bool
 GMPStorageParent::RecvOpen(const nsCString& aRecordName)
 {
   if (mShutdown) {
-    return true;
+    return false;
   }
 
   if (mNodeId.EqualsLiteral("null")) {
     // Refuse to open storage if the page is opened from local disk,
     // or shared across origin.
     NS_WARNING("Refusing to open storage for null NodeId");
     unused << SendOpenComplete(aRecordName, GMPGenericErr);
     return true;
   }
 
-  if (aRecordName.IsEmpty() || mFiles.Contains(aRecordName)) {
+  if (aRecordName.IsEmpty()) {
+    unused << SendOpenComplete(aRecordName, GMPGenericErr);
+    return true;
+  }
+
+  if (mStorage->IsOpen(aRecordName)) {
     unused << SendOpenComplete(aRecordName, GMPRecordInUse);
     return true;
   }
 
-  PRFileDesc* fd = nullptr;
-  nsresult rv = OpenStorageFile(aRecordName, mNodeId, ReadWrite, &fd);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to open storage file.");
-    unused << SendOpenComplete(aRecordName, GMPGenericErr);
-    return true;
-  }
-
-  mFiles.Put(aRecordName, fd);
-  unused << SendOpenComplete(aRecordName, GMPNoErr);
+  auto err = mStorage->Open(aRecordName);
+  MOZ_ASSERT(GMP_FAILED(err) || mStorage->IsOpen(aRecordName));
+  unused << SendOpenComplete(aRecordName, err);
 
   return true;
 }
 
 bool
 GMPStorageParent::RecvRead(const nsCString& aRecordName)
 {
   LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
 
   if (mShutdown) {
-    return true;
-  }
-
-  PRFileDesc* fd = mFiles.Get(aRecordName);
-  nsTArray<uint8_t> data;
-  if (!fd) {
-    unused << SendReadComplete(aRecordName, GMPClosedErr, data);
-    return true;
+    return false;
   }
 
-  int32_t len = PR_Seek(fd, 0, PR_SEEK_END);
-  PR_Seek(fd, 0, PR_SEEK_SET);
-
-  if (len > GMP_MAX_RECORD_SIZE) {
-    // Refuse to read big records.
-    unused << SendReadComplete(aRecordName, GMPQuotaExceededErr, data);
-    return true;
+  nsTArray<uint8_t> data;
+  if (!mStorage->IsOpen(aRecordName)) {
+    unused << SendReadComplete(aRecordName, GMPClosedErr, data);
+  } else {
+    unused << SendReadComplete(aRecordName, mStorage->Read(aRecordName, data), data);
   }
-  data.SetLength(len);
-  auto bytesRead = PR_Read(fd, data.Elements(), len);
-  auto res = (bytesRead == len) ? GMPNoErr : GMPGenericErr;
-  unused << SendReadComplete(aRecordName, res, data);
 
   return true;
 }
 
 bool
 GMPStorageParent::RecvWrite(const nsCString& aRecordName,
                             const InfallibleTArray<uint8_t>& aBytes)
 {
   LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
 
   if (mShutdown) {
+    return false;
+  }
+
+  if (!mStorage->IsOpen(aRecordName)) {
+    unused << SendWriteComplete(aRecordName, GMPClosedErr);
     return true;
   }
+
   if (aBytes.Length() > GMP_MAX_RECORD_SIZE) {
     unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr);
     return true;
   }
 
-  PRFileDesc* fd = mFiles.Get(aRecordName);
-  if (!fd) {
-    unused << SendWriteComplete(aRecordName, GMPGenericErr);
-    return true;
-  }
+  unused << SendWriteComplete(aRecordName, mStorage->Write(aRecordName, aBytes));
 
-  // Write operations overwrite the entire record. So re-open the file
-  // in truncate mode, to clear its contents.
-  PR_Close(fd);
-  mFiles.Remove(aRecordName);
-  if (NS_FAILED(OpenStorageFile(aRecordName, mNodeId, Truncate, &fd))) {
-    unused << SendWriteComplete(aRecordName, GMPGenericErr);
-    return true;
-  }
-  mFiles.Put(aRecordName, fd);
-
-  int32_t bytesWritten = PR_Write(fd, aBytes.Elements(), aBytes.Length());
-  auto res = (bytesWritten == (int32_t)aBytes.Length()) ? GMPNoErr : GMPGenericErr;
-  unused << SendWriteComplete(aRecordName, res);
   return true;
 }
 
 bool
 GMPStorageParent::RecvClose(const nsCString& aRecordName)
 {
   LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
 
   if (mShutdown) {
     return true;
   }
 
-  PRFileDesc* fd = mFiles.Get(aRecordName);
-  if (!fd) {
-    return true;
-  }
-  PR_Close(fd);
-  mFiles.Remove(aRecordName);
+  mStorage->Close(aRecordName);
+
   return true;
 }
 
 void
 GMPStorageParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
   Shutdown();
 }
 
-PLDHashOperator
-CloseFile(const nsACString& key, PRFileDesc*& entry, void* cx)
-{
-  PR_Close(entry);
-  return PL_DHASH_REMOVE;
-}
-
 void
 GMPStorageParent::Shutdown()
 {
   LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
 
   if (mShutdown) {
     return;
   }
   mShutdown = true;
   unused << SendShutdown();
 
-  mFiles.Enumerate(CloseFile, nullptr);
-  MOZ_ASSERT(!mFiles.Count());
+  mStorage = nullptr;
+
 }
 
 } // namespace gmp
 } // namespace mozilla
--- a/content/media/gmp/GMPStorageParent.h
+++ b/content/media/gmp/GMPStorageParent.h
@@ -3,45 +3,59 @@
  * 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 GMPStorageParent_h_
 #define GMPStorageParent_h_
 
 #include "mozilla/gmp/PGMPStorageParent.h"
 #include "gmp-storage.h"
-#include "nsTHashtable.h"
-#include "nsDataHashtable.h"
-#include "prio.h"
+#include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 namespace gmp {
 
 class GMPParent;
 
+class GMPStorage {
+public:
+  virtual ~GMPStorage() {}
+
+  virtual GMPErr Open(const nsCString& aRecordName) = 0;
+  virtual bool IsOpen(const nsCString& aRecordName) = 0;
+  virtual GMPErr Read(const nsCString& aRecordName,
+                      nsTArray<uint8_t>& aOutBytes) = 0;
+  virtual GMPErr Write(const nsCString& aRecordName,
+                       const nsTArray<uint8_t>& aBytes) = 0;
+  virtual void Close(const nsCString& aRecordName) = 0;
+};
+
 class GMPStorageParent : public PGMPStorageParent {
 public:
   NS_INLINE_DECL_REFCOUNTING(GMPStorageParent)
-  GMPStorageParent(const nsCString& aNodeId, GMPParent* aPlugin);
+  GMPStorageParent(const nsCString& aNodeId,
+                   GMPParent* aPlugin);
 
+  nsresult Init();
   void Shutdown();
 
 protected:
   virtual bool RecvOpen(const nsCString& aRecordName) MOZ_OVERRIDE;
   virtual bool RecvRead(const nsCString& aRecordName) MOZ_OVERRIDE;
   virtual bool RecvWrite(const nsCString& aRecordName,
                          const InfallibleTArray<uint8_t>& aBytes) MOZ_OVERRIDE;
   virtual bool RecvClose(const nsCString& aRecordName) MOZ_OVERRIDE;
   virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
 private:
   ~GMPStorageParent() {}
 
-  nsDataHashtable<nsCStringHashKey, PRFileDesc*> mFiles;
-  const nsAutoCString mNodeId;
+  UniquePtr<GMPStorage> mStorage;
+
+  const nsCString mNodeId;
   nsRefPtr<GMPParent> mPlugin;
   bool mShutdown;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPStorageParent_h_
--- a/content/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/content/media/gmp/mozIGeckoMediaPluginService.idl
@@ -21,17 +21,17 @@ class GMPVideoHost;
 [ptr] native GMPVideoDecoderProxy(GMPVideoDecoderProxy);
 [ptr] native GMPVideoEncoderProxy(GMPVideoEncoderProxy);
 [ptr] native GMPVideoHost(GMPVideoHost);
 [ptr] native MessageLoop(MessageLoop);
 [ptr] native TagArray(nsTArray<nsCString>);
 [ptr] native GMPDecryptorProxy(GMPDecryptorProxy);
 [ptr] native GMPAudioDecoderProxy(GMPAudioDecoderProxy);
 
-[scriptable, uuid(e5cde76d-f926-4b3f-84ff-62864c7a750a)]
+[scriptable, uuid(b350d3b6-00c9-4602-bdfe-84c2be8d1e7a)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
 
   /**
    * The GMP thread. Callable from any thread.
    */
   readonly attribute nsIThread thread;
 
@@ -94,13 +94,20 @@ interface mozIGeckoMediaPluginService : 
   /**
    * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
    */
   ACString getNodeId(in AString origin,
                      in AString topLevelOrigin,
                      in bool inPrivateBrowsingMode);
 
   /**
+   * Returns true if the given node id is allowed to store things
+   * persistently on disk. Private Browsing and local content are not
+   * allowed to store persistent data.
+   */
+  bool isPersistentStorageAllowed(in ACString nodeId);
+
+  /**
    * Returns the directory to use as the base for storing data about GMPs.
    */
   nsIFile getStorageDir();
 
 };