Bug 1321303 - Part 6: Implement browsingData.removePluginData, r=mak
authorBob Silverberg <bsilverberg@mozilla.com>
Mon, 19 Dec 2016 16:55:02 -0500
changeset 377745 95426f1151bcfe155c32bb7b4af580012d04fe3b
parent 377744 4b819370879335a9dddac438e6ad692b6ed7ceca
child 377746 8a161e6bae182cd3c39a0b25ebdd18103b3d39db
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1321303
milestone53.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 1321303 - Part 6: Implement browsingData.removePluginData, r=mak MozReview-Commit-ID: EpHceeFKMf7
browser/components/extensions/ext-browsingData.js
browser/components/extensions/schemas/browsing_data.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_browsingData_pluginData.js
browser/components/extensions/test/browser/file_clearplugindata.html
--- a/browser/components/extensions/ext-browsingData.js
+++ b/browser/components/extensions/ext-browsingData.js
@@ -75,16 +75,20 @@ function clearDownloads(options) {
 function clearFormData(options) {
   return sanitizer.items.formdata.clear(makeRange(options));
 }
 
 function clearHistory(options) {
   return sanitizer.items.history.clear(makeRange(options));
 }
 
+function clearPluginData(options) {
+  return sanitizer.items.pluginData.clear(makeRange(options));
+}
+
 function doRemoval(options, dataToRemove, extension) {
   if (options.originTypes &&
       (options.originTypes.protectedWeb || options.originTypes.extension)) {
     return Promise.reject(
       {message: "Firefox does not support protectedWeb or extension as originTypes."});
   }
 
   let removalPromises = [];
@@ -102,16 +106,19 @@ function doRemoval(options, dataToRemove
           removalPromises.push(clearDownloads(options));
           break;
         case "formData":
           removalPromises.push(clearFormData(options));
           break;
         case "history":
           removalPromises.push(clearHistory(options));
           break;
+        case "pluginData":
+          removalPromises.push(clearPluginData(options));
+          break;
         default:
           invalidDataTypes.push(dataType);
       }
     }
   }
   if (extension && invalidDataTypes.length) {
     extension.logger.warn(
       `Firefox does not support dataTypes: ${invalidDataTypes.toString()}.`);
@@ -162,11 +169,14 @@ extensions.registerSchemaAPI("browsingDa
         return doRemoval(options, {downloads: true});
       },
       removeFormData(options) {
         return doRemoval(options, {formData: true});
       },
       removeHistory(options) {
         return doRemoval(options, {history: true});
       },
+      removePluginData(options) {
+        return doRemoval(options, {pluginData: true});
+      },
     },
   };
 });
--- a/browser/components/extensions/schemas/browsing_data.json
+++ b/browser/components/extensions/schemas/browsing_data.json
@@ -350,17 +350,16 @@
           }
         ]
       },
       {
         "name": "removePluginData",
         "description": "Clears plugins' data.",
         "type": "function",
         "async": "callback",
-        "unsupported": true,
         "parameters": [
           {
             "$ref": "RemovalOptions",
             "name": "options"
           },
           {
             "name": "callback",
             "type": "function",
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -2,16 +2,17 @@
 support-files =
   head.js
   head_pageAction.js
   head_sessions.js
   context.html
   ctxmenu-image.png
   context_tabs_onUpdated_page.html
   context_tabs_onUpdated_iframe.html
+  file_clearplugindata.html
   file_popup_api_injection_a.html
   file_popup_api_injection_b.html
   file_iframe_document.html
   file_iframe_document.sjs
   file_bypass_cache.sjs
   file_language_fr_en.html
   file_language_ja.html
   file_language_tlh.html
@@ -25,16 +26,17 @@ support-files =
 [browser_ext_browserAction_pageAction_icon.js]
 [browser_ext_browserAction_pageAction_icon_permissions.js]
 [browser_ext_browserAction_popup.js]
 [browser_ext_browserAction_popup_preload.js]
 [browser_ext_browserAction_popup_resize.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_browsingData_formData.js]
 [browser_ext_browsingData_history.js]
+[browser_ext_browsingData_pluginData.js]
 [browser_ext_commands_execute_browser_action.js]
 [browser_ext_commands_execute_page_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_contextMenus.js]
 [browser_ext_contextMenus_checkboxes.js]
 [browser_ext_contextMenus_chrome.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browsingData_pluginData.js
@@ -0,0 +1,136 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(this, "pluginHost",
+                                   "@mozilla.org/plugin/host;1",
+                                   "nsIPluginHost");
+
+// Returns the chrome side nsIPluginTag for the test plugin.
+function getTestPlugin() {
+  let tags = pluginHost.getPluginTags();
+  let plugin = tags.find(tag => tag.name == "Test Plug-in");
+  if (!plugin) {
+    ok(false, "Unable to find plugin");
+  }
+  return plugin;
+}
+
+const TEST_ROOT = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+const TEST_URL = TEST_ROOT + "file_clearplugindata.html";
+const REFERENCE_DATE = Date.now();
+const PLUGIN_TAG = getTestPlugin();
+
+/* Due to layout being async, "PluginBindAttached" may trigger later. This
+   returns a Promise that resolves once we've forced a layout flush, which
+   triggers the PluginBindAttached event to fire. This trick only works if
+   there is some sort of plugin in the page.
+ */
+function promiseUpdatePluginBindings(browser) {
+  return ContentTask.spawn(browser, {}, function* () {
+    let doc = content.document;
+    let elems = doc.getElementsByTagName("embed");
+    if (elems && elems.length > 0) {
+      elems[0].clientTop; // eslint-disable-line no-unused-expressions
+    }
+  });
+}
+
+function stored(needles) {
+  let something = pluginHost.siteHasData(PLUGIN_TAG, null);
+  if (!needles) {
+    return something;
+  }
+
+  if (!something) {
+    return false;
+  }
+
+  if (needles.every(value => pluginHost.siteHasData(PLUGIN_TAG, value))) {
+    return true;
+  }
+
+  return false;
+}
+
+add_task(async function testPluginData() {
+  function background() {
+    browser.test.onMessage.addListener(async(msg, options) => {
+      if (msg == "removePluginData") {
+        await browser.browsingData.removePluginData(options);
+      } else {
+        await browser.browsingData.remove(options, {pluginData: true});
+      }
+      browser.test.sendMessage("pluginDataRemoved");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      permissions: ["browsingData"],
+    },
+  });
+
+  async function testRemovalMethod(method) {
+    // Clear plugin data with no since value.
+
+    // Load page to set data for the plugin.
+    let 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, {});
+    await extension.awaitMessage("pluginDataRemoved");
+
+    ok(!stored(null), "All data cleared");
+    await BrowserTestUtils.removeTab(tab);
+
+    // Clear history 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");
+
+    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");
+    await BrowserTestUtils.removeTab(tab);
+
+    // Clear history 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");
+    await BrowserTestUtils.removeTab(tab);
+  }
+
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("plugins.click_to_play");
+  });
+  PLUGIN_TAG.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+
+  await extension.startup();
+
+  await testRemovalMethod("removePluginData");
+  await testRemovalMethod("remove");
+
+  await extension.unload();
+});
copy from browser/base/content/test/plugins/browser_clearplugindata.html
copy to browser/components/extensions/test/browser/file_clearplugindata.html
--- a/browser/base/content/test/plugins/browser_clearplugindata.html
+++ b/browser/components/extensions/test/browser/file_clearplugindata.html
@@ -3,27 +3,28 @@
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html>
   <head>
     <title>Plugin Clear Site Data sanitize test</title>
 
     <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
 
-    <script type="application/javascript">
-      function testSteps() {
-        // Make sure clearing by timerange is supported.
-        var p = document.getElementById("plugin1");
-        p.setSitesWithDataCapabilities(true);
-
-        p.setSitesWithData(
-          "foo.com:0:5," +
-          "bar.com:0:100," +
-          "baz.com:1:5," +
-          "qux.com:1:100"
-        );
-      }
-    </script>
   </head>
 
-  <body onload="testSteps();"></body>
+  <body></body>
+
+  <script type="application/javascript">
+    "use strict";
+
+    // Make sure clearing by timerange is supported.
+    let p = document.getElementById("plugin1");
+    p.setSitesWithDataCapabilities(true);
+
+    p.setSitesWithData(
+      "foo.com:0:5," +
+      "bar.com:0:100," +
+      "baz.com:1:5," +
+      "qux.com:1:100"
+    );
+  </script>
 
 </html>