Bug 1422365 - Introduce nsIClearDataService - part 15 - permissions and preferences, r=johannh
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 01 Jun 2018 14:30:50 +0200
changeset 420849 144e01c7cfc470c80d422c0dd1202b725021583c
parent 420848 ebdf4bc31e581e48bb2fa009694c9fa410efcbd5
child 420850 3725c0472caac21e8c6d60a6097c55548e9a9ff7
push id103898
push useramarchesini@mozilla.com
push dateFri, 01 Jun 2018 12:31:55 +0000
treeherdermozilla-inbound@ee1e13b50338 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh
bugs1422365
milestone62.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 1422365 - Introduce nsIClearDataService - part 15 - permissions and preferences, r=johannh
browser/modules/Sanitizer.jsm
toolkit/components/cleardata/ClearDataService.js
toolkit/components/cleardata/nsIClearDataService.idl
toolkit/forgetaboutsite/ForgetAboutSite.jsm
toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
--- a/browser/modules/Sanitizer.jsm
+++ b/browser/modules/Sanitizer.jsm
@@ -428,57 +428,30 @@ var Sanitizer = {
     },
 
     siteSettings: {
       async clear(range) {
         let seenException;
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS", refObj);
 
-        let startDateMS = range ? range[0] / 1000 : null;
-
-        try {
-          // Clear site-specific permissions like "Allow this site to open popups"
-          // we ignore the "end" range and hope it is now() - none of the
-          // interfaces used here support a true range anyway.
-          if (startDateMS == null) {
-            Services.perms.removeAll();
-          } else {
-            Services.perms.removeAllSince(startDateMS);
-          }
-        } catch (ex) {
-          seenException = ex;
-        }
-
-        try {
-          // Clear site-specific settings like page-zoom level
-          let cps = Cc["@mozilla.org/content-pref/service;1"]
-                      .getService(Ci.nsIContentPrefService2);
-          if (startDateMS == null) {
-            cps.removeAllDomains(null);
-          } else {
-            cps.removeAllDomainsSince(startDateMS, null);
-          }
-        } catch (ex) {
-          seenException = ex;
-        }
+        await clearData(range, Ci.nsIClearDataService.CLEAR_PERMISSIONS |
+                               Ci.nsIClearDataService.CLEAR_PREFERENCES |
+                               Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS);
 
         try {
           // Clear site security settings - no support for ranges in this
           // interface either, so we clearAll().
           let sss = Cc["@mozilla.org/ssservice;1"]
                       .getService(Ci.nsISiteSecurityService);
           sss.clearAll();
         } catch (ex) {
           seenException = ex;
         }
 
-        // Clear all push notification subscriptions
-        await clearData(range, Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS);
-
         TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
         if (seenException) {
           throw seenException;
         }
       }
     },
 
     openWindows: {
--- a/toolkit/components/cleardata/ClearDataService.js
+++ b/toolkit/components/cleardata/ClearDataService.js
@@ -478,16 +478,86 @@ const AuthCacheCleaner = {
   deleteAll() {
     return new Promise(aResolve => {
       Services.obs.notifyObservers(null, "net:clear-active-logins");
       aResolve();
     });
   },
 };
 
+const PermissionsCleaner = {
+  deleteByHost(aHost, aOriginAttributes) {
+    return new Promise(aResolve => {
+      let enumerator = Services.perms.enumerator;
+      while (enumerator.hasMoreElements()) {
+        let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+        try {
+          if (hasRootDomain(perm.principal.URI.host, aHost)) {
+            Services.perms.removePermission(perm);
+          }
+        } catch (ex) {
+          // Ignore entry
+        }
+      }
+
+      aResolve();
+    });
+  },
+
+  deleteByRange(aFrom, aTo) {
+    Services.perms.removeAllSince(aFrom / 1000);
+    return Promise.resolve();
+  },
+
+  deleteAll() {
+    Services.perms.removeAll();
+    return Promise.resolve();
+  },
+};
+
+const PreferencesCleaner = {
+  deleteByHost(aHost, aOriginAttributes) {
+    return new Promise((aResolve, aReject) => {
+      let cps2 = Cc["@mozilla.org/content-pref/service;1"]
+                   .getService(Ci.nsIContentPrefService2);
+      cps2.removeBySubdomain(aHost, null, {
+        handleCompletion: aReason => {
+          // Notify other consumers, including extensions
+          Services.obs.notifyObservers(null, "browser:purge-domain-data",
+                                       aHost);
+          if (aReason === cps2.COMPLETE_ERROR) {
+            aReject();
+          } else {
+            aResolve();
+          }
+        },
+        handleError() {}
+      });
+    });
+  },
+
+  deleteByRange(aFrom, aTo) {
+    return new Promise(aResolve => {
+      let cps2 = Cc["@mozilla.org/content-pref/service;1"]
+                   .getService(Ci.nsIContentPrefService2);
+      cps2.removeAllDomainsSince(aFrom / 1000, null);
+      aResolve();
+    });
+  },
+
+  deleteAll() {
+    return new Promise(aResolve => {
+      let cps2 = Cc["@mozilla.org/content-pref/service;1"]
+                   .getService(Ci.nsIContentPrefService2);
+      cps2.removeAllDomains(null);
+      aResolve();
+    });
+  },
+};
+
 // Here the map of Flags-Cleaner.
 const FLAGS_MAP = [
  { flag: Ci.nsIClearDataService.CLEAR_COOKIES,
    cleaner: CookieCleaner },
 
  { flag: Ci.nsIClearDataService.CLEAR_NETWORK_CACHE,
    cleaner: NetworkCacheCleaner },
 
@@ -524,16 +594,22 @@ const FLAGS_MAP = [
  { flag: Ci.nsIClearDataService.CLEAR_SESSION_HISTORY,
    cleaner: SessionHistoryCleaner, },
 
  { flag: Ci.nsIClearDataService.CLEAR_AUTH_TOKENS,
    cleaner: AuthTokensCleaner, },
 
  { flag: Ci.nsIClearDataService.CLEAR_AUTH_CACHE,
    cleaner: AuthCacheCleaner, },
+
+ { flag: Ci.nsIClearDataService.CLEAR_PERMISSIONS,
+   cleaner: PermissionsCleaner, },
+
+ { flag: Ci.nsIClearDataService.CLEAR_CONTENT_PREFERENCES,
+   cleaner: PreferencesCleaner, },
 ];
 
 this.ClearDataService = function() {};
 
 ClearDataService.prototype = Object.freeze({
   classID: Components.ID("{0c06583d-7dd8-4293-b1a5-912205f779aa}"),
   QueryInterface: ChromeUtils.generateQI([Ci.nsIClearDataService]),
   _xpcom_factory: XPCOMUtils.generateSingletonFactory(ClearDataService),
--- a/toolkit/components/cleardata/nsIClearDataService.idl
+++ b/toolkit/components/cleardata/nsIClearDataService.idl
@@ -153,20 +153,28 @@ interface nsIClearDataService : nsISuppo
    */
   const uint32_t CLEAR_AUTH_TOKENS = 1 << 13;
 
   /**
    * Login cache
    */
   const uint32_t CLEAR_AUTH_CACHE = 1 << 14;
 
+  /**
+   * Site permissions
+   */
+  const uint32_t CLEAR_PERMISSIONS = 1 << 15;
+
+  /**
+   * Site preferences
+   */
+  const uint32_t CLEAR_CONTENT_PREFERENCES = 1 << 16;
+
   /* TODO
   const uint32_t CLEAR_EME = 1 << 4;
-  const uint32_t CLEAR_PERMISSIONS = 1 << 7;
-  const uint32_t CLEAR_CONTENT_PREFERENCES = 1 << 9;
   const uint32_t CLEAR_HSTS = 1 << 12;
   const uint32_t CLEAR_HPKP = 1 << 13;
   const uint32_t CLEAR_FORMDATA = 1 << 16;
   */
 
   /**
    * Use this value to delete all the data.
    */
@@ -180,17 +188,16 @@ interface nsIClearDataService : nsISuppo
   /**
    * Delete all the possible caches.
    * TODO: add CLEAR_PREDICTOR_CACHE ?
    */
   const uint32_t CLEAR_ALL_CACHES = CLEAR_NETWORK_CACHE | CLEAR_IMAGE_CACHE;
 
   /**
    * Delete all DOM storages
-   * TODO: add CLEAR_FORMDATA | CLEAR_SESSION_HISTORY;
    */
   const uint32_t CLEAR_DOM_STORAGES = CLEAR_APPCACHE | CLEAR_DOM_QUOTA | CLEAR_DOM_PUSH_NOTIFICATIONS;
 };
 
 /**
  * This is a companion interface for
  * nsIClearDataService::deleteDataFromPrincipal().
  */
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -5,40 +5,16 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 
 var EXPORTED_SYMBOLS = ["ForgetAboutSite"];
 
-/**
- * Returns true if the string passed in is part of the root domain of the
- * current string.  For example, if this is "www.mozilla.org", and we pass in
- * "mozilla.org", this will return true.  It would return false the other way
- * around.
- */
-function hasRootDomain(str, aDomain) {
-  let index = str.indexOf(aDomain);
-  // If aDomain is not found, we know we do not have it as a root domain.
-  if (index == -1)
-    return false;
-
-  // If the strings are the same, we obviously have a match.
-  if (str == aDomain)
-    return true;
-
-  // Otherwise, we have aDomain as our root domain iff the index of aDomain is
-  // aDomain.length subtracted from our length and (since we do not have an
-  // exact match) the character before the index is a dot or slash.
-  let prevChar = str[index - 1];
-  return (index == (str.length - aDomain.length)) &&
-         (prevChar == "." || prevChar == "/");
-}
-
 var ForgetAboutSite = {
   async removeDataFromDomain(aDomain) {
     let promises = [];
 
     ["http://", "https://"].forEach(scheme => {
       promises.push(new Promise(resolve => {
         Services.clearData.deleteDataFromHost(aDomain, true /* user request */,
                                               Ci.nsIClearDataService.CLEAR_ALL,
@@ -50,50 +26,16 @@ var ForgetAboutSite = {
     promises.push((async function() {
       let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
                 getService(Ci.mozIGeckoMediaPluginChromeService);
       mps.forgetThisSite(aDomain, JSON.stringify({}));
     })().catch(ex => {
       throw new Error("Exception thrown while clearing Encrypted Media Extensions: " + ex);
     }));
 
-    // Permissions
-    // Enumerate all of the permissions, and if one matches, remove it
-    let enumerator = Services.perms.enumerator;
-    while (enumerator.hasMoreElements()) {
-      let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
-      promises.push(new Promise((resolve, reject) => {
-        try {
-          if (hasRootDomain(perm.principal.URI.host, aDomain)) {
-            Services.perms.removePermission(perm);
-          }
-        } catch (ex) {
-          // Ignore entry
-        } finally {
-          resolve();
-        }
-      }));
-    }
-
-    // Content Preferences
-    promises.push((async function() {
-      let cps2 = Cc["@mozilla.org/content-pref/service;1"].
-                 getService(Ci.nsIContentPrefService2);
-      cps2.removeBySubdomain(aDomain, null, {
-        handleCompletion: (reason) => {
-          // Notify other consumers, including extensions
-          Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain);
-          if (reason === cps2.COMPLETE_ERROR) {
-            throw new Error("Exception occured while clearing content preferences");
-          }
-        },
-        handleError() {}
-      });
-    })());
-
     // HSTS and HPKP
     promises.push((async function() {
       let sss = Cc["@mozilla.org/ssservice;1"].
                 getService(Ci.nsISiteSecurityService);
       for (let type of [Ci.nsISiteSecurityService.HEADER_HSTS,
                         Ci.nsISiteSecurityService.HEADER_HPKP]) {
         // Also remove HSTS/HPKP information for subdomains by enumerating the
         // information in the site security service.
--- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -333,43 +333,47 @@ function waitForPurgeNotification() {
 }
 
 // Content Preferences
 async function test_content_preferences_cleared_with_direct_match() {
   const TEST_URI = Services.io.newURI("http://mozilla.org");
   Assert.equal(false, await preference_exists(TEST_URI));
   await add_preference(TEST_URI);
   Assert.ok(await preference_exists(TEST_URI));
+  let promisePurgeNotification = waitForPurgeNotification();
   await ForgetAboutSite.removeDataFromDomain("mozilla.org");
-  await waitForPurgeNotification();
+  await promisePurgeNotification;
   Assert.equal(false, await preference_exists(TEST_URI));
 }
 
 async function test_content_preferences_cleared_with_subdomain() {
   const TEST_URI = Services.io.newURI("http://www.mozilla.org");
   Assert.equal(false, await preference_exists(TEST_URI));
   await add_preference(TEST_URI);
   Assert.ok(await preference_exists(TEST_URI));
+  let promisePurgeNotification = waitForPurgeNotification();
   await ForgetAboutSite.removeDataFromDomain("mozilla.org");
-  await waitForPurgeNotification();
+  await promisePurgeNotification;
   Assert.equal(false, await preference_exists(TEST_URI));
 }
 
 async function test_content_preferences_not_cleared_with_uri_contains_domain() {
   const TEST_URI = Services.io.newURI("http://ilovemozilla.org");
   Assert.equal(false, await preference_exists(TEST_URI));
   await add_preference(TEST_URI);
   Assert.ok(await preference_exists(TEST_URI));
+  let promisePurgeNotification = waitForPurgeNotification();
   await ForgetAboutSite.removeDataFromDomain("mozilla.org");
-  await waitForPurgeNotification();
+  await promisePurgeNotification;
   Assert.ok(await preference_exists(TEST_URI));
 
   // Reset state
+  promisePurgeNotification = waitForPurgeNotification();
   await ForgetAboutSite.removeDataFromDomain("ilovemozilla.org");
-  await waitForPurgeNotification();
+  await promisePurgeNotification;
   Assert.equal(false, await preference_exists(TEST_URI));
 }
 
 function push_registration_exists(aURL, ps) {
   return new Promise(resolve => {
     let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(aURL);
     return ps.getSubscription(aURL, principal, (status, record) => {
       if (!Components.isSuccessCode(status)) {
@@ -478,18 +482,19 @@ async function test_storage_cleared() {
   for (let i = 0; i < s.length; ++i) {
     let storage = s[i];
     storage.setItem("test", "value" + i);
     Assert.equal(storage.length, 1);
     Assert.equal(storage.key(0), "test");
     Assert.equal(storage.getItem("test"), "value" + i);
   }
 
+  let promisePurgeNotification = waitForPurgeNotification();
   await ForgetAboutSite.removeDataFromDomain("mozilla.org");
-  await waitForPurgeNotification();
+  await promisePurgeNotification;
 
   Assert.equal(s[0].getItem("test"), null);
   Assert.equal(s[0].length, 0);
   Assert.equal(s[1].getItem("test"), null);
   Assert.equal(s[1].length, 0);
   Assert.equal(s[2].getItem("test"), "value2");
   Assert.equal(s[2].length, 1);
 }