Bug 1060179 - Store and retrieve EME node id. r=jesup
authorChris Pearce <cpearce@mozilla.com>
Mon, 13 Oct 2014 11:53:44 +1300
changeset 233252 27ae9275cd05c8922a4f3fff4192cf99962536d7
parent 233251 d0ad1569d85c52e665cd341ec604b1d7f49e3e95
child 233253 77fcb81f442b5acf0c5dd122abe2916227d42b71
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup
bugs1060179
milestone35.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 1060179 - Store and retrieve EME node id. r=jesup
content/media/gmp/GMPService.cpp
content/media/gmp/GMPService.h
content/media/gmp/GMPStorageParent.cpp
content/media/gmp/mozIGeckoMediaPluginService.idl
--- a/content/media/gmp/GMPService.cpp
+++ b/content/media/gmp/GMPService.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "GMPService.h"
+#include "prio.h"
 #include "prlog.h"
 #include "GMPParent.h"
 #include "GMPVideoDecoderParent.h"
 #include "nsIObserverService.h"
 #include "GeckoChildProcessHost.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SyncRunnable.h"
@@ -21,16 +22,21 @@
 #include "GMPAudioDecoderParent.h"
 #include "nsComponentManagerUtils.h"
 #include "mozilla/Preferences.h"
 #include "runnable_utils.h"
 #include "VideoUtils.h"
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
 #include "mozilla/Sandbox.h"
 #endif
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsHashKeys.h"
+#include "nsIFile.h"
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 #ifdef PR_LOGGING
@@ -150,34 +156,51 @@ GeckoMediaPluginService::GeckoMediaPlugi
 }
 
 GeckoMediaPluginService::~GeckoMediaPluginService()
 {
   MOZ_ASSERT(mPlugins.IsEmpty());
   MOZ_ASSERT(mAsyncShutdownPlugins.IsEmpty());
 }
 
-void
+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)));
 
   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.
+  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mStorageBaseDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mStorageBaseDir->AppendNative(NS_LITERAL_CSTRING("gmp"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mStorageBaseDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+  if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) {
+    return rv;
+  }
+
   // Kick off scanning for plugins
   nsCOMPtr<nsIThread> thread;
-  unused << GetThread(getter_AddRefs(thread));
+  return GetThread(getter_AddRefs(thread));
 }
 
 void
 AbortWaitingForGMPAsyncShutdown(nsITimer* aTimer, void* aClosure)
 {
   NS_WARNING("Timed out waiting for GMP async shutdown!");
   nsRefPtr<GeckoMediaPluginService> service = sSingletonService.get();
   if (service) {
@@ -851,32 +874,214 @@ GeckoMediaPluginService::ReAddOnGMPThrea
   mPlugins.RemoveElement(aOld);
 
   // Schedule aOld to be destroyed.  We can't destroy it from here since we
   // may be inside ActorDestroyed() for it.
   NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld));
 }
 
 NS_IMETHODIMP
+GeckoMediaPluginService::GetStorageDir(nsIFile** aOutFile)
+{
+  if (NS_WARN_IF(!mStorageBaseDir)) {
+    return NS_ERROR_FAILURE;
+  }
+  return mStorageBaseDir->Clone(aOutFile);
+}
+
+static nsresult
+WriteToFile(nsIFile* aPath,
+            const nsCString& aFileName,
+            const nsCString& aData)
+{
+  nsCOMPtr<nsIFile> path;
+  nsresult rv = aPath->Clone(getter_AddRefs(path));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = path->AppendNative(aFileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  PRFileDesc* f = nullptr;
+  rv = path->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, PR_IRWXU, &f);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  int32_t len = PR_Write(f, aData.get(), aData.Length());
+  PR_Close(f);
+  if (NS_WARN_IF(len < 0 || (size_t)len != aData.Length())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+static nsresult
+ReadFromFile(nsIFile* aPath,
+             const nsCString& aFileName,
+             nsCString& aOutData,
+             int32_t aMaxLength)
+{
+  nsCOMPtr<nsIFile> path;
+  nsresult rv = aPath->Clone(getter_AddRefs(path));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = path->AppendNative(aFileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  PRFileDesc* f = nullptr;
+  rv = path->OpenNSPRFileDesc(PR_RDONLY | PR_CREATE_FILE, PR_IRWXU, &f);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  auto size = PR_Seek(f, 0, PR_SEEK_END);
+  PR_Seek(f, 0, PR_SEEK_SET);
+
+  if (size > aMaxLength) {
+    return NS_ERROR_FAILURE;
+  }
+  aOutData.SetLength(size);
+
+  auto len = PR_Read(f, aOutData.BeginWriting(), size);
+  PR_Close(f);
+  if (NS_WARN_IF(len != size)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  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") ||
+      aOrigin.IsEmpty() ||
+      aTopLevelOrigin.EqualsLiteral("null") ||
+      aTopLevelOrigin.IsEmpty()) {
+    // Non-persistent session; just generate a random node id.
+    nsAutoCString salt;
+    rv = GenerateRandomPathName(salt, NodeIdSaltLength);
+    aOutId = salt;
+    return rv;
+  }
+
+  // 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;
+  }
+
+  rv = path->AppendNative(NS_LITERAL_CSTRING("id"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // $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;
+  }
+
+  rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+  if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIFile> saltFile;
+  rv = path->Clone(getter_AddRefs(saltFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = saltFile->AppendNative(NS_LITERAL_CSTRING("salt"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   nsAutoCString salt;
-  nsresult rv = GenerateRandomPathName(salt, 32);
-  NS_ENSURE_SUCCESS(rv, rv);
+  bool exists = false;
+  rv = saltFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  if (!exists) {
+    // No stored salt for this origin. Generate salt, and store it and
+    // the origin on disk.
+    nsresult rv = GenerateRandomPathName(salt, NodeIdSaltLength);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    MOZ_ASSERT(salt.Length() == NodeIdSaltLength);
+
+    // $profileDir/gmp/id/$hash/salt
+    rv = WriteToFile(path, NS_LITERAL_CSTRING("salt"), salt);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    // $profileDir/gmp/id/$hash/origin
+    rv = WriteToFile(path,
+                     NS_LITERAL_CSTRING("origin"),
+                     NS_ConvertUTF16toUTF8(aOrigin));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    // $profileDir/gmp/id/$hash/topLevelOrigin
+    rv = WriteToFile(path,
+                     NS_LITERAL_CSTRING("topLevelOrigin"),
+                     NS_ConvertUTF16toUTF8(aTopLevelOrigin));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+  } else {
+    rv = ReadFromFile(path,
+                      NS_LITERAL_CSTRING("salt"),
+                      salt,
+                      NodeIdSaltLength);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
 
   aOutId = salt;
 
-  // TODO: Store salt, so it can be retrieved in subsequent sessions.
-
   return NS_OK;
 }
 
 } // namespace gmp
 } // namespace mozilla
--- a/content/media/gmp/GMPService.h
+++ b/content/media/gmp/GMPService.h
@@ -27,17 +27,17 @@ class GMPParent;
 
 class GeckoMediaPluginService MOZ_FINAL : public mozIGeckoMediaPluginService
                                         , public nsIObserver
 {
 public:
   static already_AddRefed<GeckoMediaPluginService> GetGeckoMediaPluginService();
 
   GeckoMediaPluginService();
-  void Init();
+  nsresult Init();
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_MOZIGECKOMEDIAPLUGINSERVICE
   NS_DECL_NSIOBSERVER
 
   void AsyncShutdownNeeded(GMPParent* aParent);
   void AsyncShutdownComplete(GMPParent* aParent);
   void AbortAsyncShutdown();
@@ -106,14 +106,16 @@ private:
   private:
     T mValue;
   };
 
   MainThreadOnly<bool> mWaitingForPluginsAsyncShutdown;
 
   nsTArray<nsRefPtr<GMPParent>> mAsyncShutdownPlugins; // GMP Thread only.
   nsCOMPtr<nsITimer> mAsyncShutdownTimeout; // GMP Thread only.
+
+  nsCOMPtr<nsIFile> mStorageBaseDir;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPService_h_
--- a/content/media/gmp/GMPStorageParent.cpp
+++ b/content/media/gmp/GMPStorageParent.cpp
@@ -3,16 +3,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 "GMPStorageParent.h"
 #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"
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
@@ -30,59 +31,47 @@ extern PRLogModuleInfo* GetGMPLog();
 
 #ifdef __CLASS__
 #undef __CLASS__
 #endif
 #define __CLASS__ "GMPParent"
 
 namespace gmp {
 
-class GetTempDirTask : public nsRunnable
-{
-public:
-  NS_IMETHOD Run() {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    nsCOMPtr<nsIFile> tmpFile;
-    nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    tmpFile->GetPath(mPath);
-    return NS_OK;
-  }
-
-  nsString mPath;
-};
-
-// We store the records in files in the system temp dir.
+// We store the records in files in the profile dir.
+// $profileDir/gmp/storage/$nodeId/
 static nsresult
 GetGMPStorageDir(nsIFile** aTempDir, const nsCString& aNodeId)
 {
   if (NS_WARN_IF(!aTempDir)) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  // Directory service is main thread only...
-  nsRefPtr<GetTempDirTask> task = new GetTempDirTask();
-  nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
-  mozilla::SyncRunnable::DispatchToThread(mainThread, task);
+  nsCOMPtr<mozIGeckoMediaPluginService> mps =
+    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  if (NS_WARN_IF(!mps)) {
+    return NS_ERROR_FAILURE;
+  }
 
   nsCOMPtr<nsIFile> tmpFile;
-  nsresult rv = NS_NewLocalFile(task->mPath, false, getter_AddRefs(tmpFile));
+  nsresult rv = mps->GetStorageDir(getter_AddRefs(tmpFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = tmpFile->AppendNative(nsDependentCString("mozilla-gmp-storage"));
+  rv = tmpFile->AppendNative(NS_LITERAL_CSTRING("storage"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+  if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   rv = tmpFile->AppendNative(aNodeId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
   if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
--- a/content/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/content/media/gmp/mozIGeckoMediaPluginService.idl
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "nsISupports.idl"
 #include "nsIThread.idl"
 #include "nsIPrincipal.idl"
+#include "nsIFile.idl"
 
 %{C++
 #include "nsTArray.h"
 #include "nsStringGlue.h"
 class GMPAudioDecoderProxy;
 class GMPDecryptorProxy;
 class GMPVideoDecoderProxy;
 class GMPVideoEncoderProxy;
@@ -20,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(3d811f9f-e1f8-48a5-a385-3657a641ee76)]
+[scriptable, uuid(e5cde76d-f926-4b3f-84ff-62864c7a750a)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
 
   /**
    * The GMP thread. Callable from any thread.
    */
   readonly attribute nsIThread thread;
 
@@ -91,9 +92,15 @@ interface mozIGeckoMediaPluginService : 
   void removePluginDirectory(in AString directory);
 
   /**
    * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
    */
   ACString getNodeId(in AString origin,
                      in AString topLevelOrigin,
                      in bool inPrivateBrowsingMode);
+
+  /**
+   * Returns the directory to use as the base for storing data about GMPs.
+   */
+  nsIFile getStorageDir();
+
 };