Bug 1636784 - Use Services.clearData for clearing cache/plugin data. r=rpl
authorTom Schuster <evilpies@gmail.com>
Thu, 21 May 2020 16:50:29 +0000
changeset 531460 8ca49faffad06e21775062d2ae944c378c22cf68
parent 531459 98080333994e06121f0f0a83741e50b48d11e904
child 531461 fcd7cb8b4cdf9a5ff99df84739f7dc8b3c238a44
push id37439
push userbtara@mozilla.com
push dateThu, 21 May 2020 21:49:34 +0000
treeherdermozilla-central@92c11f0bf14b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrpl
bugs1636784
milestone78.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 1636784 - Use Services.clearData for clearing cache/plugin data. r=rpl This allows us to support clearing by hostname for both. Differential Revision: https://phabricator.services.mozilla.com/D75094
browser/components/extensions/parent/ext-browsingData.js
browser/components/extensions/test/browser/browser_ext_browsingData_pluginData.js
browser/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cache.js
--- a/browser/components/extensions/parent/ext-browsingData.js
+++ b/browser/components/extensions/parent/ext-browsingData.js
@@ -34,19 +34,53 @@ XPCOMUtils.defineLazyServiceGetter(
 const YIELD_PERIOD = 10;
 
 const makeRange = options => {
   return options.since == null
     ? null
     : [PlacesUtils.toPRTime(options.since), PlacesUtils.toPRTime(Date.now())];
 };
 
-const clearCache = () => {
-  // Clearing the cache does not support timestamps.
-  return Sanitizer.items.cache.clear();
+// General implementation for clearing data using Services.clearData.
+// Currently Sanitizer.items uses this under the hood.
+async function clearData(options, flags) {
+  if (options.hostnames) {
+    await Promise.all(
+      options.hostnames.map(
+        host =>
+          new Promise(resolve => {
+            // Set aIsUserRequest to true. This means when the ClearDataService
+            // "Cleaner" implementation doesn't support clearing by host
+            // it will delete all data instead.
+            // This is appropriate for cases like |cache|, which doesn't
+            // support clearing by a time range.
+            // In future when we use this for other data types, we have to
+            // evaluate if that behavior is still acceptable.
+            Services.clearData.deleteDataFromHost(host, true, flags, resolve);
+          })
+      )
+    );
+    return;
+  }
+
+  if (options.since) {
+    const range = makeRange(options);
+    await new Promise(resolve => {
+      Services.clearData.deleteDataInTimeRange(...range, true, flags, resolve);
+    });
+    return;
+  }
+
+  // Don't return the promise here and above to prevent leaking the resolved
+  // value.
+  await new Promise(resolve => Services.clearData.deleteData(flags, resolve));
+}
+
+const clearCache = options => {
+  return clearData(options, Ci.nsIClearDataService.CLEAR_ALL_CACHES);
 };
 
 const clearCookies = async function(options) {
   let cookieMgr = Services.cookies;
   // This code has been borrowed from Sanitizer.jsm.
   let yieldCounter = 0;
 
   if (options.since || options.hostnames) {
@@ -228,17 +262,17 @@ const clearPasswords = async function(op
       if (++yieldCounter % YIELD_PERIOD == 0) {
         await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
       }
     }
   }
 };
 
 const clearPluginData = options => {
-  return Sanitizer.items.pluginData.clear(makeRange(options));
+  return clearData(options, Ci.nsIClearDataService.CLEAR_PLUGIN_DATA);
 };
 
 const clearServiceWorkers = options => {
   if (!options.hostnames) {
     return ServiceWorkerCleanUp.removeAll();
   }
 
   return Promise.all(
@@ -260,17 +294,17 @@ const doRemoval = (options, dataToRemove
   }
 
   let removalPromises = [];
   let invalidDataTypes = [];
   for (let dataType in dataToRemove) {
     if (dataToRemove[dataType]) {
       switch (dataType) {
         case "cache":
-          removalPromises.push(clearCache());
+          removalPromises.push(clearCache(options));
           break;
         case "cookies":
           removalPromises.push(clearCookies(options));
           break;
         case "downloads":
           removalPromises.push(clearDownloads(options));
           break;
         case "formData":
--- a/browser/components/extensions/test/browser/browser_ext_browsingData_pluginData.js
+++ b/browser/components/extensions/test/browser/browser_ext_browsingData_pluginData.js
@@ -91,17 +91,17 @@ add_task(async function testPluginData()
     );
 
     extension.sendMessage(method, {});
     await extension.awaitMessage("pluginDataRemoved");
 
     ok(!stored(null), "All data cleared");
     BrowserTestUtils.removeTab(tab);
 
-    // Clear history with recent since value.
+    // Clear pluginData with recent since value.
 
     // Load page to set data for the plugin.
     tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
     await promiseUpdatePluginBindings(gBrowser.selectedBrowser);
 
     ok(
       stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
       "Data stored for sites"
@@ -110,32 +110,71 @@ add_task(async function testPluginData()
     extension.sendMessage(method, { since: REFERENCE_DATE - 20000 });
     await extension.awaitMessage("pluginDataRemoved");
 
     ok(stored(["bar.com", "qux.com"]), "Data stored for sites");
     ok(!stored(["foo.com"]), "Data cleared for foo.com");
     ok(!stored(["baz.com"]), "Data cleared for baz.com");
     BrowserTestUtils.removeTab(tab);
 
-    // Clear history with old since value.
+    // Clear pluginData with old since value.
 
     // Load page to set data for the plugin.
     tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
     await promiseUpdatePluginBindings(gBrowser.selectedBrowser);
 
     ok(
       stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
       "Data stored for sites"
     );
 
     extension.sendMessage(method, { since: REFERENCE_DATE - 1000000 });
     await extension.awaitMessage("pluginDataRemoved");
 
     ok(!stored(null), "All data cleared");
     BrowserTestUtils.removeTab(tab);
+
+    // Clear pluginData for specific hosts.
+
+    // Load page to set data for the plugin.
+    tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+    await promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+    ok(
+      stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
+      "Data stored for sites"
+    );
+
+    extension.sendMessage(method, { hostnames: ["bar.com", "baz.com"] });
+    await extension.awaitMessage("pluginDataRemoved");
+
+    ok(stored(["foo.com", "qux.com"]), "Data stored for sites");
+    ok(!stored(["bar.com"]), "Data cleared for bar.com");
+    ok(!stored(["baz.com"]), "Data cleared for baz.com");
+    BrowserTestUtils.removeTab(tab);
+
+    // Clear pluginData for no hosts.
+
+    // Load page to set data for the plugin.
+    tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+    await promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+    ok(
+      stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
+      "Data stored for sites"
+    );
+
+    extension.sendMessage(method, { hostnames: [] });
+    await extension.awaitMessage("pluginDataRemoved");
+
+    ok(
+      stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
+      "Data stored for sites"
+    );
+    BrowserTestUtils.removeTab(tab);
   }
 
   PLUGIN_TAG.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
 
   await extension.startup();
 
   await testRemovalMethod("removePluginData");
   await testRemovalMethod("remove");
--- a/browser/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cache.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cache.js
@@ -4,16 +4,20 @@
 "use strict";
 
 ChromeUtils.defineModuleGetter(
   this,
   "setTimeout",
   "resource://gre/modules/Timer.jsm"
 );
 
+const { SiteDataTestUtils } = ChromeUtils.import(
+  "resource://testing-common/SiteDataTestUtils.jsm"
+);
+
 const COOKIE = {
   host: "example.com",
   name: "test_cookie",
   path: "/",
 };
 const COOKIE_NET = {
   host: "example.net",
   name: "test_cookie",
@@ -58,16 +62,38 @@ async function setUpCookies() {
   // Add a cookie which will end up with a more recent creationTime.
   addCookie(COOKIE);
 
   // Add cookies for different domains.
   addCookie(COOKIE_NET);
   addCookie(COOKIE_ORG);
 }
 
+async function setUpCache() {
+  Services.cache2.clear();
+
+  // Add cache entries for different domains.
+  for (const domain of ["example.net", "example.org", "example.com"]) {
+    await SiteDataTestUtils.addCacheEntry(`http://${domain}/`, "disk");
+    await SiteDataTestUtils.addCacheEntry(`http://${domain}/`, "memory");
+  }
+}
+
+function hasCacheEntry(domain) {
+  const disk = SiteDataTestUtils.hasCacheEntry(`http://${domain}/`, "disk");
+  const memory = SiteDataTestUtils.hasCacheEntry(`http://${domain}/`, "memory");
+
+  equal(
+    disk,
+    memory,
+    `For ${domain} either either both or neither caches need to exists.`
+  );
+  return disk;
+}
+
 add_task(async function testCache() {
   function background() {
     browser.test.onMessage.addListener(async msg => {
       if (msg == "removeCache") {
         await browser.browsingData.removeCache({});
       } else {
         await browser.browsingData.remove({}, { cache: true });
       }
@@ -78,22 +104,24 @@ add_task(async function testCache() {
   let extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
       permissions: ["browsingData"],
     },
   });
 
   async function testRemovalMethod(method) {
-    // We can assume the notification works properly, so we only need to observe
-    // the notification to know the cache was cleared.
-    let awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
+    await setUpCache();
+
     extension.sendMessage(method);
-    await awaitNotification;
     await extension.awaitMessage("cacheRemoved");
+
+    ok(!hasCacheEntry("example.net"), "example.net cache was removed");
+    ok(!hasCacheEntry("example.org"), "example.org cache was removed");
+    ok(!hasCacheEntry("example.com"), "example.com cache was removed");
   }
 
   await extension.startup();
 
   await testRemovalMethod("removeCache");
   await testRemovalMethod("remove");
 
   await extension.unload();
@@ -224,63 +252,70 @@ add_task(async function testCacheAndCook
       permissions: ["browsingData"],
     },
   });
 
   await extension.startup();
 
   // Clear cache and cookies with a recent since value.
   await setUpCookies();
-  let awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
+  await setUpCache();
   extension.sendMessage({ since });
-  await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
   ok(
     Services.cookies.cookieExists(
       oldCookie.host,
       oldCookie.path,
       oldCookie.name,
       {}
     ),
     "Old cookie was not removed."
   );
   ok(
     !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}),
     "Recent cookie was removed."
   );
 
+  // Cache does not support |since| and deletes everything!
+  ok(!hasCacheEntry("example.net"), "example.net cache was removed");
+  ok(!hasCacheEntry("example.org"), "example.org cache was removed");
+  ok(!hasCacheEntry("example.com"), "example.com cache was removed");
+
   // Clear cache and cookies with an old since value.
   await setUpCookies();
-  awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
+  await setUpCache();
   extension.sendMessage({ since: since - 100000 });
-  await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
+  // Cache does not support |since| and deletes everything!
+  ok(!hasCacheEntry("example.net"), "example.net cache was removed");
+  ok(!hasCacheEntry("example.org"), "example.org cache was removed");
+  ok(!hasCacheEntry("example.com"), "example.com cache was removed");
+
   ok(
     !Services.cookies.cookieExists(
       oldCookie.host,
       oldCookie.path,
       oldCookie.name,
       {}
     ),
     "Old cookie was removed."
   );
   ok(
     !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}),
     "Recent cookie was removed."
   );
 
   // Clear cache and cookies with hostnames value.
   await setUpCookies();
-  awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
+  await setUpCache();
   extension.sendMessage({
     hostnames: ["example.net", "example.org", "unknown.com"],
   });
-  await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
   ok(
     Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}),
     `Cookie ${COOKIE.name}  was not removed.`
   );
   ok(
     !Services.cookies.cookieExists(
@@ -296,21 +331,24 @@ add_task(async function testCacheAndCook
       COOKIE_ORG.host,
       COOKIE_ORG.path,
       COOKIE_ORG.name,
       {}
     ),
     `Cookie ${COOKIE_ORG.name}  was removed.`
   );
 
+  ok(!hasCacheEntry("example.net"), "example.net cache was removed");
+  ok(!hasCacheEntry("example.org"), "example.org cache was removed");
+  ok(hasCacheEntry("example.com"), "example.com cache was not removed");
+
   // Clear cache and cookies with (empty) hostnames value.
   await setUpCookies();
-  awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
+  await setUpCache();
   extension.sendMessage({ hostnames: [] });
-  await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
   ok(
     Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}),
     `Cookie ${COOKIE.name}  was not removed.`
   );
   ok(
     Services.cookies.cookieExists(
@@ -326,21 +364,24 @@ add_task(async function testCacheAndCook
       COOKIE_ORG.host,
       COOKIE_ORG.path,
       COOKIE_ORG.name,
       {}
     ),
     `Cookie ${COOKIE_ORG.name}  was not removed.`
   );
 
+  ok(hasCacheEntry("example.net"), "example.net cache was not removed");
+  ok(hasCacheEntry("example.org"), "example.org cache was not removed");
+  ok(hasCacheEntry("example.com"), "example.com cache was not removed");
+
   // Clear cache and cookies with both hostnames and since values.
+  await setUpCache();
   await setUpCookies();
-  awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
   extension.sendMessage({ hostnames: ["example.com"], since });
-  await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
   ok(
     Services.cookies.cookieExists(
       oldCookie.host,
       oldCookie.path,
       oldCookie.name,
       {}
@@ -365,21 +406,24 @@ add_task(async function testCacheAndCook
       COOKIE_ORG.host,
       COOKIE_ORG.path,
       COOKIE_ORG.name,
       {}
     ),
     "Cookie with different hostname was not removed"
   );
 
+  ok(hasCacheEntry("example.net"), "example.net cache was not removed");
+  ok(hasCacheEntry("example.org"), "example.org cache was not removed");
+  ok(!hasCacheEntry("example.com"), "example.com cache was removed");
+
   // Clear cache and cookies with no since or hostnames value.
+  await setUpCache();
   await setUpCookies();
-  awaitNotification = TestUtils.topicObserved("cacheservice:empty-cache");
   extension.sendMessage({});
-  await awaitNotification;
   await extension.awaitMessage("cacheAndCookiesRemoved");
 
   ok(
     !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}),
     `Cookie ${COOKIE.name}  was removed.`
   );
   ok(
     !Services.cookies.cookieExists(
@@ -404,10 +448,14 @@ add_task(async function testCacheAndCook
       COOKIE_ORG.host,
       COOKIE_ORG.path,
       COOKIE_ORG.name,
       {}
     ),
     `Cookie ${COOKIE_ORG.name}  was removed.`
   );
 
+  ok(!hasCacheEntry("example.net"), "example.net cache was removed");
+  ok(!hasCacheEntry("example.org"), "example.org cache was removed");
+  ok(!hasCacheEntry("example.com"), "example.com cache was removed");
+
   await extension.unload();
 });