Bug 1111787 - Part 1: Delete the directory for the origin pair when "forget this site" is invoked. r=cpearce
authorJW Wang <jwwang@mozilla.com>
Mon, 05 Jan 2015 19:00:00 -0500
changeset 238994 7ff735f32aac001514341b16d2275c72b16f320b
parent 238993 d894b312beb2e98f4f20cd150e588d0e695b9871
child 238995 20298762b610fe8726459f0d17a7cefafc071c5c
push id7472
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 20:36:27 +0000
treeherdermozilla-aurora@300ca104f8fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1111787
milestone37.0a1
Bug 1111787 - Part 1: Delete the directory for the origin pair when "forget this site" is invoked. r=cpearce
dom/media/gmp/GMPService.cpp
dom/media/gmp/GMPService.h
dom/media/gmp/mozIGeckoMediaPluginService.idl
toolkit/forgetaboutsite/ForgetAboutSite.jsm
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -27,16 +27,17 @@
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
 #include "mozilla/SandboxInfo.h"
 #endif
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsHashKeys.h"
 #include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 #ifdef PR_LOGGING
@@ -58,16 +59,17 @@ GetGMPLog()
 
 #ifdef __CLASS__
 #undef __CLASS__
 #endif
 #define __CLASS__ "GMPService"
 
 namespace gmp {
 
+static const uint32_t NodeIdSaltLength = 32;
 static StaticRefPtr<GeckoMediaPluginService> sSingletonService;
 
 class GMPServiceCreateHelper MOZ_FINAL : public nsRunnable
 {
   nsRefPtr<GeckoMediaPluginService> mService;
 
 public:
   static already_AddRefed<GeckoMediaPluginService>
@@ -318,28 +320,35 @@ GeckoMediaPluginService::Observe(nsISupp
   } 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();
   } else if (!strcmp("gmp-clear-storage", aTopic)) {
-    nsCOMPtr<nsIThread> thread;
-    nsresult rv = GetThread(getter_AddRefs(thread));
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    thread->Dispatch(
-      NS_NewRunnableMethod(this, &GeckoMediaPluginService::ClearStorage),
-      NS_DISPATCH_NORMAL);
+    nsresult rv = GMPDispatch(
+      NS_NewRunnableMethod(this, &GeckoMediaPluginService::ClearStorage));
+    NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
+nsresult
+GeckoMediaPluginService::GMPDispatch(nsIRunnable* event, uint32_t flags)
+{
+  nsCOMPtr<nsIRunnable> r(event);
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = GetThread(getter_AddRefs(thread));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return thread->Dispatch(r, flags);
+}
+
 // always call with getter_AddRefs, because it does
 NS_IMETHODIMP
 GeckoMediaPluginService::GetThread(nsIThread** aThread)
 {
   MOZ_ASSERT(aThread);
 
   // This can be called from any thread.
   MutexAutoLock lock(mMutex);
@@ -626,38 +635,24 @@ GeckoMediaPluginService::PathRunnable::R
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::AddPluginDirectory(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<nsIThread> thread;
-  nsresult rv = GetThread(getter_AddRefs(thread));
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  nsCOMPtr<nsIRunnable> r = new PathRunnable(this, aDirectory, true);
-  thread->Dispatch(r, NS_DISPATCH_NORMAL);
-  return NS_OK;
+  return GMPDispatch(new PathRunnable(this, aDirectory, true));
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::RemovePluginDirectory(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<nsIThread> thread;
-  nsresult rv = GetThread(getter_AddRefs(thread));
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  nsCOMPtr<nsIRunnable> r = new PathRunnable(this, aDirectory, false);
-  thread->Dispatch(r, NS_DISPATCH_NORMAL);
-  return NS_OK;
+  return GMPDispatch(new PathRunnable(this, aDirectory, false));
 }
 
 class DummyRunnable : public nsRunnable {
 public:
   NS_IMETHOD Run() { return NS_OK; }
 };
 
 NS_IMETHODIMP
@@ -671,22 +666,18 @@ GeckoMediaPluginService::HasPluginForAPI
   const char* env = nullptr;
   if (!mScannedPluginOnDisk && (env = PR_GetEnv("MOZ_GMP_PATH")) && *env) {
     // We have a MOZ_GMP_PATH environment variable which may specify the
     // location of plugins to load, and we haven't yet scanned the disk to
     // see if there are plugins there. Get the GMP thread, which will
     // cause an event to be dispatched to which scans for plugins. We
     // dispatch a sync event to the GMP thread here in order to wait until
     // after the GMP thread has scanned any paths in MOZ_GMP_PATH.
-    nsCOMPtr<nsIThread> thread;
-    nsresult rv = GetThread(getter_AddRefs(thread));
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    thread->Dispatch(new DummyRunnable(), NS_DISPATCH_SYNC);
+    nsresult rv = GMPDispatch(new DummyRunnable(), NS_DISPATCH_SYNC);
+    NS_ENSURE_SUCCESS(rv, rv);
     MOZ_ASSERT(mScannedPluginOnDisk, "Should have scanned MOZ_GMP_PATH by now");
   }
 
   {
     MutexAutoLock lock(mMutex);
     nsCString api(aAPI);
     GMPParent* gmp = FindPluginForAPIFrom(0, api, *aTags, nullptr);
     *aResult = (gmp != nullptr);
@@ -974,16 +965,24 @@ ReadFromFile(nsIFile* aPath,
   PR_Close(f);
   if (NS_WARN_IF(len != size)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+static nsresult
+ReadSalt(nsIFile* aPath, nsCString& aOutData)
+{
+  return ReadFromFile(aPath, NS_LITERAL_CSTRING("salt"),
+                      aOutData, NodeIdSaltLength);
+
+}
+
 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;
@@ -1002,17 +1001,16 @@ GeckoMediaPluginService::GetNodeId(const
        (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing")));
 
 #ifdef MOZ_WIDGET_GONK
   NS_WARNING("GeckoMediaPluginService::GetNodeId Not implemented on B2G");
   return NS_ERROR_NOT_IMPLEMENTED;
 #endif
 
   nsresult rv;
-  const uint32_t NodeIdSaltLength = 32;
 
   if (aOrigin.EqualsLiteral("null") ||
       aOrigin.IsEmpty() ||
       aTopLevelOrigin.EqualsLiteral("null") ||
       aTopLevelOrigin.IsEmpty()) {
     // 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.
@@ -1126,31 +1124,119 @@ GeckoMediaPluginService::GetNodeId(const
     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);
+    rv = ReadSalt(path, salt);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   aOutId = salt;
   mPersistentStorageAllowed.Put(salt, true);
 
   return NS_OK;
 }
 
+static bool
+MatchOrigin(nsIFile* aPath, const nsACString& aMatch)
+{
+  // http://en.wikipedia.org/wiki/Domain_Name_System#Domain_name_syntax
+  static const uint32_t MaxDomainLength = 253;
+
+  nsresult rv;
+  nsCString str;
+  rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("origin"), str, MaxDomainLength);
+  if (NS_SUCCEEDED(rv) && aMatch.Equals(str)) {
+    return true;
+  }
+  rv = ReadFromFile(aPath, NS_LITERAL_CSTRING("topLevelOrigin"), str, MaxDomainLength);
+  if (NS_SUCCEEDED(rv) && aMatch.Equals(str)) {
+    return true;
+  }
+  return false;
+}
+
+void
+GeckoMediaPluginService::ForgetThisSiteOnGMPThread(const nsACString& aOrigin)
+{
+#define ERR_RET(x) NS_ENSURE_SUCCESS_VOID(x)
+#define ERR_CONT(x) if (NS_FAILED(x)) { continue; }
+
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+  LOGD(("%s::%s: origin=%s", __CLASS__, __FUNCTION__, aOrigin.Data()));
+
+  nsresult rv;
+  nsCOMPtr<nsIFile> path;
+
+  // $profileDir/gmp/
+  ERR_RET(GetStorageDir(getter_AddRefs(path)));
+
+  // $profileDir/gmp/id/
+  ERR_RET(path->AppendNative(NS_LITERAL_CSTRING("id")));
+
+  // Iterate all sub-folders of $profileDir/gmp/id/
+  nsCOMPtr<nsISimpleEnumerator> iter;
+  ERR_RET(path->GetDirectoryEntries(getter_AddRefs(iter)));
+
+  bool hasMore = false;
+  nsTArray<nsCString> nodeIDsToClear;
+  while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
+    nsCOMPtr<nsISupports> supports;
+    ERR_CONT(iter->GetNext(getter_AddRefs(supports)));
+
+    // $profileDir/gmp/id/$hash
+    nsCOMPtr<nsIFile> dirEntry(do_QueryInterface(supports, &rv));
+    ERR_CONT(rv);
+
+    // Skip non-directory files.
+    bool isDirectory = false;
+    ERR_CONT(dirEntry->IsDirectory(&isDirectory));
+    if (!isDirectory) {
+      continue;
+    }
+
+    // Check if origin or topLevelOrigin match the origin being forgotten.
+    if (!MatchOrigin(dirEntry, aOrigin)) {
+      continue;
+    }
+
+    // Keep node IDs to clear data/plugins associated with them later.
+    nsAutoCString salt;
+    if (NS_SUCCEEDED(ReadSalt(dirEntry, salt))) {
+      nodeIDsToClear.AppendElement(salt);
+    }
+    // Now we can remove the directory for the origin pair.
+    if (NS_FAILED(dirEntry->Remove(true))) {
+      NS_WARNING("Failed to delete the directory for the origin pair");
+    }
+  }
+
+  // TODO: Clear plugins/data associated with the node IDs in nodeIDsToClear
+  // in next patch.
+  nodeIDsToClear.Clear();
+
+#undef ERR_RET
+#undef ERR_CONT
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::ForgetThisSite(const nsAString& aOrigin)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return GMPDispatch(NS_NewRunnableMethodWithArg<nsCString>(
+      this, &GeckoMediaPluginService::ForgetThisSiteOnGMPThread,
+      NS_ConvertUTF16toUTF8(aOrigin)));
+}
+
 class StorageClearedTask : public nsRunnable {
 public:
   NS_IMETHOD Run() {
     MOZ_ASSERT(NS_IsMainThread());
     nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
     MOZ_ASSERT(obsService);
     if (obsService) {
       obsService->NotifyObservers(nullptr, "gmp-clear-storage-complete", nullptr);
--- a/dom/media/gmp/GMPService.h
+++ b/dom/media/gmp/GMPService.h
@@ -47,16 +47,18 @@ public:
   void AsyncShutdownComplete(GMPParent* aParent);
   void AbortAsyncShutdown();
 
   int32_t AsyncShutdownTimeoutMs();
 
 private:
   ~GeckoMediaPluginService();
 
+  nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL);
+
   void ClearStorage();
 
   GMPParent* SelectPluginForAPI(const nsACString& aNodeId,
                                 const nsCString& aAPI,
                                 const nsTArray<nsCString>& aTags);
   GMPParent* FindPluginForAPIFrom(size_t aSearchStartIndex,
                                   const nsCString& aAPI,
                                   const nsTArray<nsCString>& aTags,
@@ -69,16 +71,18 @@ private:
   void LoadFromEnvironment();
   void ProcessPossiblePlugin(nsIFile* aDir);
 
   void AddOnGMPThread(const nsAString& aSearchDir);
   void RemoveOnGMPThread(const nsAString& aSearchDir);
 
   nsresult SetAsyncShutdownTimeout();
 
+  void ForgetThisSiteOnGMPThread(const nsACString& aOrigin);
+
 protected:
   friend class GMPParent;
   void ReAddOnGMPThread(nsRefPtr<GMPParent>& aOld);
 private:
   GMPParent* ClonePlugin(const GMPParent* aOriginal);
 
   class PathRunnable : public nsRunnable
   {
--- a/dom/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/dom/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(657443a4-6b5d-4181-98b0-56997b35bd57)]
+[scriptable, uuid(4aaf58d3-181f-4325-9a2f-41172d995a70)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
 
   /**
    * The GMP thread. Callable from any thread.
    */
   readonly attribute nsIThread thread;
 
@@ -92,16 +92,21 @@ interface mozIGeckoMediaPluginService : 
   /**
    * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
    */
   ACString getNodeId(in AString origin,
                      in AString topLevelOrigin,
                      in bool inPrivateBrowsingMode);
 
   /**
+   * Clears storage data associated with the origin.
+   */
+  void forgetThisSite(in AString origin);
+
+  /**
    * 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.
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -75,16 +75,22 @@ this.ForgetAboutSite = {
     let cm = Cc["@mozilla.org/cookiemanager;1"].
              getService(Ci.nsICookieManager2);
     let enumerator = cm.getCookiesFromHost(aDomain);
     while (enumerator.hasMoreElements()) {
       let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
       cm.remove(cookie.host, cookie.name, cookie.path, false);
     }
 
+    // EME
+    let (mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
+               getService(Ci.mozIGeckoMediaPluginService)) {
+      mps.forgetThisSite(aDomain);
+    }
+
     // Plugin data
     const phInterface = Ci.nsIPluginHost;
     const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
     let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
     let tags = ph.getPluginTags();
     for (let i = 0; i < tags.length; i++) {
       try {
         ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1);