Bug 1546296 - Correctly clear Service Workers by hostname. r=baku
authorJohann Hofmann <jhofmann@mozilla.com>
Mon, 13 May 2019 21:32:53 +0000
changeset 532486 8e51c8d00793669923134baaf44f32b552c6c991
parent 532485 2b0045289ea3ff901ce697c2bb51389bca055074
child 532487 cc187a665ccff9167c72e46a0b9ca73c04b7bcbe
push id11268
push usercsabou@mozilla.com
push dateTue, 14 May 2019 15:24:22 +0000
treeherdermozilla-beta@5fb7fcd568d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1546296
milestone68.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 1546296 - Correctly clear Service Workers by hostname. r=baku Differential Revision: https://phabricator.services.mozilla.com/D30455
toolkit/components/cleardata/ClearDataService.jsm
toolkit/components/cleardata/SiteDataTestUtils.jsm
toolkit/components/cleardata/moz.build
toolkit/components/cleardata/tests/browser/browser.ini
toolkit/components/cleardata/tests/browser/browser_serviceworkers.js
toolkit/components/cleardata/tests/browser/worker.js
--- a/toolkit/components/cleardata/ClearDataService.jsm
+++ b/toolkit/components/cleardata/ClearDataService.jsm
@@ -357,23 +357,20 @@ const QuotaCleaner = {
     // localStorage: The legacy LocalStorage implementation that will
     // eventually be removed depends on this observer notification to clear by
     // host.  Some other subsystems like Reporting headers depend on this too.
     Services.obs.notifyObservers(null, "extension:purge-localStorage", aHost);
 
     // Clear sessionStorage
     Services.obs.notifyObservers(null, "browser:purge-sessionStorage", aHost);
 
-    let exceptionThrown = false;
-
     // ServiceWorkers: they must be removed before cleaning QuotaManager.
-    return Promise.all([
-      ServiceWorkerCleanUp.removeFromHost("http://" + aHost).catch(_ => { exceptionThrown = true; }),
-      ServiceWorkerCleanUp.removeFromHost("https://" + aHost).catch(_ => { exceptionThrown = true; }),
-    ]).then(() => {
+    return ServiceWorkerCleanUp.removeFromHost(aHost)
+      .then(_ => /* exceptionThrown = */ false, _ => /* exceptionThrown = */ true)
+      .then(exceptionThrown => {
         // QuotaManager: In the event of a failure, we call reject to propagate
         // the error upwards.
 
         // delete data from both HTTP and HTTPS sites
         let httpURI = Services.io.newURI("http://" + aHost);
         let httpsURI = Services.io.newURI("https://" + aHost);
         let httpPrincipal = Services.scriptSecurityManager
                                      .createCodebasePrincipal(httpURI, aOriginAttributes);
--- a/toolkit/components/cleardata/SiteDataTestUtils.jsm
+++ b/toolkit/components/cleardata/SiteDataTestUtils.jsm
@@ -97,22 +97,26 @@ var SiteDataTestUtils = {
   addServiceWorker(path) {
     let uri = Services.io.newURI(path);
     // Register a dummy ServiceWorker.
     return BrowserTestUtils.withNewTab(uri.prePath, async function(browser) {
       return ContentTask.spawn(browser, {path}, async ({path: p}) => {
         // eslint-disable-next-line no-undef
         let r = await content.navigator.serviceWorker.register(p);
         return new Promise(resolve => {
-          let worker = r.installing;
-          worker.addEventListener("statechange", () => {
-            if (worker.state === "installed") {
-              resolve();
-            }
-          });
+          let worker = r.installing || r.waiting || r.active;
+          if (worker.state == "activated") {
+            resolve();
+          } else {
+            worker.addEventListener("statechange", () => {
+              if (worker.state == "activated") {
+                resolve();
+              }
+            });
+          }
         });
       });
     });
   },
 
   _getCacheStorage(where, lci) {
     switch (where) {
       case "disk": return Services.cache2.diskCacheStorage(lci, false);
@@ -162,16 +166,70 @@ var SiteDataTestUtils = {
       if (sw.principal.origin == origin) {
         return true;
       }
     }
     return false;
   },
 
   /**
+   * Waits for a ServiceWorker to be registered.
+   *
+   * @param {String} the url of the ServiceWorker to wait for
+   *
+   * @returns a Promise that resolves when a ServiceWorker at the
+   *          specified location has been registered.
+   */
+  promiseServiceWorkerRegistered(url) {
+    if (!(url instanceof Ci.nsIURI)) {
+      url = Services.io.newURI(url);
+    }
+
+    return new Promise(resolve => {
+      let listener = {
+        onRegister: registration => {
+          if (registration.principal.URI.host != url.host) {
+            return;
+          }
+          swm.removeListener(listener);
+          resolve(registration);
+        },
+      };
+      swm.addListener(listener);
+    });
+  },
+
+  /**
+   * Waits for a ServiceWorker to be unregistered.
+   *
+   * @param {String} the url of the ServiceWorker to wait for
+   *
+   * @returns a Promise that resolves when a ServiceWorker at the
+   *          specified location has been unregistered.
+   */
+  promiseServiceWorkerUnregistered(url) {
+    if (!(url instanceof Ci.nsIURI)) {
+      url = Services.io.newURI(url);
+    }
+
+    return new Promise(resolve => {
+      let listener = {
+        onUnregister: registration => {
+          if (registration.principal.URI.host != url.host) {
+            return;
+          }
+          swm.removeListener(listener);
+          resolve(registration);
+        },
+      };
+      swm.addListener(listener);
+    });
+  },
+
+  /**
    * Gets the current quota usage for the specified origin.
    *
    * @returns a Promise that resolves to an integer with the total
    *          amount of disk usage by a given origin.
    */
   getQuotaUsage(origin) {
     return new Promise(resolve => {
       let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
--- a/toolkit/components/cleardata/moz.build
+++ b/toolkit/components/cleardata/moz.build
@@ -20,14 +20,16 @@ EXTRA_JS_MODULES += [
 ]
 
 XPCOM_MANIFESTS += [
     'components.conf',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
+BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Data Sanitization')
 
 FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/toolkit/components/cleardata/tests/browser/browser.ini
@@ -0,0 +1,2 @@
+[browser_serviceworkers.js]
+support-files = worker.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/cleardata/tests/browser/browser_serviceworkers.js
@@ -0,0 +1,103 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {SiteDataTestUtils} = ChromeUtils.import("resource://testing-common/SiteDataTestUtils.jsm");
+
+async function addServiceWorker(origin) {
+  let swURL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + "worker.js";
+
+  let registered = SiteDataTestUtils.promiseServiceWorkerRegistered(swURL);
+  await SiteDataTestUtils.addServiceWorker(swURL);
+  await registered;
+
+  ok(true, `${origin} has a service worker`);
+}
+
+add_task(async function setup() {
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.testing.enabled", true],
+  ]});
+});
+
+add_task(async function test_deleteFromHost() {
+  await addServiceWorker("https://example.com");
+  await addServiceWorker("https://example.org");
+
+  let unregistered = SiteDataTestUtils.promiseServiceWorkerUnregistered("https://example.com");
+  await new Promise(aResolve => {
+    Services.clearData.deleteDataFromHost("example.com", true, Ci.nsIClearDataService.CLEAR_DOM_QUOTA, value => {
+      Assert.equal(value, 0);
+      aResolve();
+    });
+  });
+  await unregistered;
+
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://example.com"), "example.com has no service worker");
+  ok(SiteDataTestUtils.hasServiceWorkers("https://example.org"), "example.org has a service worker");
+
+  unregistered = SiteDataTestUtils.promiseServiceWorkerUnregistered("https://example.org");
+  await new Promise(aResolve => {
+    Services.clearData.deleteDataFromHost("example.org", true, Ci.nsIClearDataService.CLEAR_DOM_QUOTA, value => {
+      Assert.equal(value, 0);
+      aResolve();
+    });
+  });
+  await unregistered;
+
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://example.org"), "example.org has no service worker");
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://example.com"), "example.com has no service worker");
+});
+
+add_task(async function test_deleteFromPrincipal() {
+  await addServiceWorker("https://test1.example.com");
+  await addServiceWorker("https://test1.example.org");
+
+  let unregistered = SiteDataTestUtils.promiseServiceWorkerUnregistered("https://test1.example.com");
+  let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("https://test1.example.com/");
+  await new Promise(aResolve => {
+    Services.clearData.deleteDataFromPrincipal(principal, true, Ci.nsIClearDataService.CLEAR_DOM_QUOTA, value => {
+      Assert.equal(value, 0);
+      aResolve();
+    });
+  });
+  await unregistered;
+
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://test1.example.com"), "test1.example.com has no service worker");
+  ok(SiteDataTestUtils.hasServiceWorkers("https://test1.example.org"), "test1.example.org has a service worker");
+
+  unregistered = SiteDataTestUtils.promiseServiceWorkerUnregistered("https://test1.example.org");
+  principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("https://test1.example.org/");
+  await new Promise(aResolve => {
+    Services.clearData.deleteDataFromPrincipal(principal, true, Ci.nsIClearDataService.CLEAR_DOM_QUOTA, value => {
+      Assert.equal(value, 0);
+      aResolve();
+    });
+  });
+  await unregistered;
+
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://test1.example.org"), "test1.example.org has no service worker");
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://test1.example.com"), "test1.example.com has no service worker");
+});
+
+add_task(async function test_deleteAll() {
+  await addServiceWorker("https://test2.example.com");
+  await addServiceWorker("https://test2.example.org");
+
+  let unregistered = SiteDataTestUtils.promiseServiceWorkerUnregistered("https://test2.example.com");
+  await new Promise(aResolve => {
+    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_DOM_QUOTA, value => {
+      Assert.equal(value, 0);
+      aResolve();
+    });
+  });
+  await unregistered;
+
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://test2.example.com"), "test2.example.com has no service worker");
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://test2.example.org"), "test2.example.org has no service worker");
+
+  await SiteDataTestUtils.clear();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/cleardata/tests/browser/worker.js
@@ -0,0 +1,1 @@
+// Empty script for testing service workers