Bug 1388428 - Extend browsingData to restrict removing localStorage to a given list of hostnames; r?bsilverberg,janv draft
authorThomas Wisniewski <wisniewskit@gmail.com>
Mon, 25 Sep 2017 16:47:38 -0400
changeset 670897 37773efe14cbf97e350b4d4d95e1c8e31b569b7c
parent 670875 70158e4e215d784d1391db5e517b18727f4b3683
child 733355 1525aef02073fdeee63f4ab24e033e0606f6697e
push id81759
push userwisniewskit@gmail.com
push dateWed, 27 Sep 2017 04:15:03 +0000
reviewersbsilverberg, janv
bugs1388428
milestone58.0a1
Bug 1388428 - Extend browsingData to restrict removing localStorage to a given list of hostnames; r?bsilverberg,janv MozReview-Commit-ID: ELgjLHsVLkv
browser/components/extensions/ext-browsingData.js
browser/components/extensions/schemas/browsing_data.json
browser/components/extensions/test/browser/browser_ext_browsingData_localStorage.js
dom/storage/LocalStorageManager.cpp
dom/storage/StorageObserver.cpp
dom/storage/StorageObserver.h
--- a/browser/components/extensions/ext-browsingData.js
+++ b/browser/components/extensions/ext-browsingData.js
@@ -102,17 +102,27 @@ const clearIndexedDB = async function(op
       resolve();
     });
   });
 
   return Promise.all(promises);
 };
 
 const clearLocalStorage = async function(options) {
-  Services.obs.notifyObservers(null, "extension:purge-localStorage");
+  if (options.since) {
+    return Promise.reject(
+      {message: "Firefox does not support clearing localStorage with 'since'."});
+  }
+  if (options.hostnames) {
+    for (let hostname of options.hostnames) {
+      Services.obs.notifyObservers(null, "extension:purge-localStorage", hostname);
+    }
+  } else {
+    Services.obs.notifyObservers(null, "extension:purge-localStorage");
+  }
 };
 
 const clearPasswords = async function(options) {
   let loginManager = Services.logins;
   let yieldCounter = 0;
 
   if (options.since) {
     // Iterate through the logins and delete any updated after our cutoff.
--- a/browser/components/extensions/schemas/browsing_data.json
+++ b/browser/components/extensions/schemas/browsing_data.json
@@ -31,17 +31,17 @@
             "$ref": "extensionTypes.Date",
             "optional": true,
             "description": "Remove data accumulated on or after this date, represented in milliseconds since the epoch (accessible via the <code>getTime</code> method of the JavaScript <code>Date</code> object). If absent, defaults to 0 (which would remove all browsing data)."
           },
           "hostnames": {
             "type": "array",
             "items": {"type": "string", "format": "hostname"},
             "optional": true,
-            "description": "Only remove data associated with these hostnames (only applies to cookies)."
+            "description": "Only remove data associated with these hostnames (only applies to cookies and localStorage)."
           },
           "originTypes": {
             "type": "object",
             "optional": true,
             "description": "An object whose properties specify which origin types ought to be cleared. If this object isn't specified, it defaults to clearing only \"unprotected\" origins. Please ensure that you <em>really</em> want to remove application data before adding 'protectedWeb' or 'extensions'.",
             "properties": {
               "unprotectedWeb": {
                 "type": "boolean",
--- a/browser/components/extensions/test/browser/browser_ext_browsingData_localStorage.js
+++ b/browser/components/extensions/test/browser/browser_ext_browsingData_localStorage.js
@@ -34,16 +34,27 @@ add_task(async function testLocalStorage
 
     function sendMessageToTabs(tabs, message) {
       return Promise.all(
         tabs.map(tab => { return browser.tabs.sendMessage(tab.id, message); }));
     }
 
     let tabs = await openTabs();
 
+    browser.test.assertRejects(
+      browser.browsingData.removeLocalStorage({since: Date.now()}),
+      "Firefox does not support clearing localStorage with 'since'.",
+      "Expected error received when using unimplemented parameter 'since'."
+    );
+
+    await sendMessageToTabs(tabs, "resetLocalStorage");
+    await browser.browsingData.removeLocalStorage({hostnames: ["example.com"]});
+    await browser.tabs.sendMessage(tabs[0].id, "checkLocalStorageCleared");
+    await browser.tabs.sendMessage(tabs[1].id, "checkLocalStorageSet");
+
     await sendMessageToTabs(tabs, "resetLocalStorage");
     await sendMessageToTabs(tabs, "checkLocalStorageSet");
     await browser.browsingData.removeLocalStorage({});
     await sendMessageToTabs(tabs, "checkLocalStorageCleared");
 
     await sendMessageToTabs(tabs, "resetLocalStorage");
     await sendMessageToTabs(tabs, "checkLocalStorageSet");
     await browser.browsingData.remove({}, {localStorage: true});
--- a/dom/storage/LocalStorageManager.cpp
+++ b/dom/storage/LocalStorageManager.cpp
@@ -370,22 +370,27 @@ LocalStorageManager::Observe(const char*
 {
   OriginAttributesPattern pattern;
   if (!pattern.Init(aOriginAttributesPattern)) {
     NS_ERROR("Cannot parse origin attributes pattern");
     return NS_ERROR_FAILURE;
   }
 
   // Clear everything, caches + database
-  if (!strcmp(aTopic, "cookie-cleared") ||
-      !strcmp(aTopic, "extension:purge-localStorage-caches")) {
+  if (!strcmp(aTopic, "cookie-cleared")) {
     ClearCaches(LocalStorageCache::kUnloadComplete, pattern, EmptyCString());
     return NS_OK;
   }
 
+  // Clear everything, caches + database
+  if (!strcmp(aTopic, "extension:purge-localStorage-caches")) {
+    ClearCaches(LocalStorageCache::kUnloadComplete, pattern, aOriginScope);
+    return NS_OK;
+  }
+
   // Clear from caches everything that has been stored
   // while in session-only mode
   if (!strcmp(aTopic, "session-only-cleared")) {
     ClearCaches(LocalStorageCache::kUnloadSession, pattern, aOriginScope);
     return NS_OK;
   }
 
   // Clear everything (including so and pb data) from caches and database
--- a/dom/storage/StorageObserver.cpp
+++ b/dom/storage/StorageObserver.cpp
@@ -148,16 +148,60 @@ StorageObserver::Notify(const char* aTop
 }
 
 void
 StorageObserver::NoteBackgroundThread(nsIEventTarget* aBackgroundThread)
 {
   mBackgroundThread = aBackgroundThread;
 }
 
+nsresult
+StorageObserver::ClearMatchingOrigin(const char16_t* aData,
+                                     nsACString& aOriginScope)
+{
+  nsresult rv;
+
+  NS_ConvertUTF16toUTF8 domain(aData);
+
+  nsAutoCString convertedDomain;
+  nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
+  if (converter) {
+    // Convert the domain name to the ACE format
+    rv = converter->ConvertUTF8toACE(domain, convertedDomain);
+  } else {
+    // In case the IDN service is not available, this is the best we can come
+    // up with!
+    rv = NS_EscapeURL(domain,
+                      esc_OnlyNonASCII | esc_AlwaysCopy,
+                      convertedDomain,
+                      fallible);
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCString originScope;
+  rv = CreateReversedDomain(convertedDomain, originScope);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (XRE_IsParentProcess()) {
+    StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+    if (NS_WARN_IF(!storageChild)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    storageChild->SendClearMatchingOrigin(originScope);
+  }
+
+  aOriginScope = originScope;
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 StorageObserver::Observe(nsISupports* aSubject,
                          const char* aTopic,
                          const char16_t* aData)
 {
   nsresult rv;
 
   // Start the thread that opens the database.
@@ -271,62 +315,51 @@ StorageObserver::Observe(nsISupports* aS
 
     Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
            originScope);
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "extension:purge-localStorage")) {
-    StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
-    if (NS_WARN_IF(!storageChild)) {
-      return NS_ERROR_FAILURE;
-    }
+    const char topic[] = "extension:purge-localStorage-caches";
+
+    if (aData) {
+      nsCString originScope;
+      rv = ClearMatchingOrigin(aData, originScope);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
 
-    storageChild->AsyncClearAll();
+      Notify(topic, EmptyString(), originScope);
+    } else {
+      StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+      if (NS_WARN_IF(!storageChild)) {
+        return NS_ERROR_FAILURE;
+      }
 
-    if (XRE_IsParentProcess()) {
-      storageChild->SendClearAll();
+      storageChild->AsyncClearAll();
+
+      if (XRE_IsParentProcess()) {
+        storageChild->SendClearAll();
+      }
+
+      Notify(topic);
     }
 
-    Notify("extension:purge-localStorage-caches");
-
     return NS_OK;
   }
 
   // Clear everything (including so and pb data) from caches and database
-  // for the gived domain and subdomains.
+  // for the given domain and subdomains.
   if (!strcmp(aTopic, "browser:purge-domain-data")) {
-    // Convert the domain name to the ACE format
-    nsAutoCString aceDomain;
-    nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
-    if (converter) {
-      rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain);
-      NS_ENSURE_SUCCESS(rv, rv);
-    } else {
-      // In case the IDN service is not available, this is the best we can come
-      // up with!
-      rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(aData),
-                        esc_OnlyNonASCII | esc_AlwaysCopy,
-                        aceDomain,
-                        fallible);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-
-    nsAutoCString originScope;
-    rv = CreateReversedDomain(aceDomain, originScope);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (XRE_IsParentProcess()) {
-      StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
-      if (NS_WARN_IF(!storageChild)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      storageChild->SendClearMatchingOrigin(originScope);
+    nsCString originScope;
+    rv = ClearMatchingOrigin(aData, originScope);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
     }
 
     Notify("domain-data-cleared", EmptyString(), originScope);
 
     return NS_OK;
   }
 
   // Clear all private-browsing caches
--- a/dom/storage/StorageObserver.h
+++ b/dom/storage/StorageObserver.h
@@ -52,16 +52,20 @@ public:
               const nsACString& aOriginScope = EmptyCString());
 
   void
   NoteBackgroundThread(nsIEventTarget* aBackgroundThread);
 
 private:
   virtual ~StorageObserver() {}
 
+  nsresult
+  ClearMatchingOrigin(const char16_t* aData,
+                      nsACString& aOriginScope);
+
   static void TestingPrefChanged(const char* aPrefName, void* aClosure);
 
   static StorageObserver* sSelf;
 
   nsCOMPtr<nsIEventTarget> mBackgroundThread;
 
   // Weak references
   nsTObserverArray<StorageObserverSink*> mSinks;