Bug 1363004 - Implement browsingData.removeFormData webExtension API method on android. r=bsilverberg, grisha
MozReview-Commit-ID: F0hqTCOCA23
--- a/mobile/android/components/extensions/ext-browsingData.js
+++ b/mobile/android/components/extensions/ext-browsingData.js
@@ -92,12 +92,15 @@ this.browsingData = class extends Extens
return clearCookies(options);
},
removeCache(options) {
return Sanitizer.clearItem("cache");
},
async removeDownloads(options) {
return Sanitizer.clearItem("downloadHistory", options.since);
},
+ removeFormData(options) {
+ return Sanitizer.clearItem("formdata", options.since);
+ },
},
};
}
};
--- a/mobile/android/components/extensions/schemas/browsing_data.json
+++ b/mobile/android/components/extensions/schemas/browsing_data.json
@@ -279,17 +279,16 @@
}
]
},
{
"name": "removeFormData",
"description": "Clears the browser's stored form data (autofill).",
"type": "function",
"async": "callback",
- "unsupported": true,
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
--- a/mobile/android/components/extensions/test/mochitest/chrome.ini
+++ b/mobile/android/components/extensions/test/mochitest/chrome.ini
@@ -3,13 +3,14 @@ support-files =
head.js
../../../../../../toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js
tags = webextensions
[test_ext_browserAction_getTitle_setTitle.html]
[test_ext_browserAction_onClicked.html]
[test_ext_browsingData_cookies_cache.html]
[test_ext_browsingData_downloads.html]
+[test_ext_browsingData_formdata.html]
[test_ext_browsingData_settings.html]
[test_ext_options_ui.html]
[test_ext_pageAction_show_hide.html]
[test_ext_pageAction_getPopup_setPopup.html]
skip-if = os == 'android' # bug 1373170
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_browsingData_formdata.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>BrowsingData FormData test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+var {FormHistory} = Cu.import("resource://gre/modules/FormHistory.jsm", {});
+
+const REFERENCE_DATE = Date.now();
+
+function countEntries(fieldname, message, expected) {
+ return new Promise((resolve, reject) => {
+ let callback = {
+ handleResult: result => {
+ is(result, expected, message);
+ resolve();
+ },
+ handleError: reject,
+ };
+
+ FormHistory.count({fieldname}, callback);
+ });
+}
+
+async function setupFormHistory() {
+ function searchEntries(terms, params) {
+ return new Promise((resolve, reject) => {
+ let callback = {
+ handleResult: resolve,
+ handleError: reject,
+ };
+
+ FormHistory.search(terms, params, callback);
+ });
+ }
+
+ function update(changes) {
+ return new Promise((resolve, reject) => {
+ let callback = {
+ handleError: reject,
+ handleCompletion: resolve,
+ };
+ FormHistory.update(changes, callback);
+ });
+ }
+
+ // Make sure we've got a clean DB to start with, then add the entries we'll be testing.
+ await update([
+ {op: "remove"},
+ {
+ op: "add",
+ fieldname: "reference",
+ value: "reference",
+ }, {
+ op: "add",
+ fieldname: "10secondsAgo",
+ value: "10s",
+ }, {
+ op: "add",
+ fieldname: "10minutesAgo",
+ value: "10m",
+ }]);
+
+ // Age the entries to the proper vintage.
+ let timestamp = REFERENCE_DATE * 1000;
+ let result = await searchEntries(["guid"], {fieldname: "reference"});
+ await update({op: "update", firstUsed: timestamp, guid: result.guid});
+
+ timestamp = (REFERENCE_DATE - 10000) * 1000;
+ result = await searchEntries(["guid"], {fieldname: "10secondsAgo"});
+ await update({op: "update", firstUsed: timestamp, guid: result.guid});
+
+ timestamp = (REFERENCE_DATE - 10000 * 60) * 1000;
+ result = await searchEntries(["guid"], {fieldname: "10minutesAgo"});
+ await update({op: "update", firstUsed: timestamp, guid: result.guid});
+
+ // Sanity check.
+ await countEntries("reference", "Checking for 10minutes form history entry creation", 1);
+ await countEntries("10secondsAgo", "Checking for 1hour form history entry creation", 1);
+ await countEntries("10minutesAgo", "Checking for 1hour10minutes form history entry creation", 1);
+}
+
+add_task(async function testFormData() {
+ function background() {
+ browser.test.onMessage.addListener(async (msg, options) => {
+ await browser.browsingData.removeFormData(options);
+ browser.test.sendMessage("formDataRemoved");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background,
+ manifest: {
+ permissions: ["browsingData"],
+ },
+ });
+
+ async function testRemovalMethod(method) {
+ // Clear form data with no since value.
+ await setupFormHistory();
+ extension.sendMessage(method, {});
+ await extension.awaitMessage("formDataRemoved");
+
+ await countEntries("reference", "reference form entry should be deleted.", 0);
+ await countEntries("10secondsAgo", "10secondsAgo form entry should be deleted.", 0);
+ await countEntries("10minutesAgo", "10minutesAgo form entry should be deleted.", 0);
+
+ // Clear form data with recent since value.
+ await setupFormHistory();
+ extension.sendMessage(method, {since: REFERENCE_DATE});
+ await extension.awaitMessage("formDataRemoved");
+
+ await countEntries("reference", "reference form entry should be deleted.", 0);
+ await countEntries("10secondsAgo", "10secondsAgo form entry should still exist.", 1);
+ await countEntries("10minutesAgo", "10minutesAgo form entry should still exist.", 1);
+
+ // Clear form data with old since value.
+ await setupFormHistory();
+ extension.sendMessage(method, {since: REFERENCE_DATE - 1000000});
+ await extension.awaitMessage("formDataRemoved");
+
+ await countEntries("reference", "reference form entry should be deleted.", 0);
+ await countEntries("10secondsAgo", "10secondsAgo form entry should be deleted.", 0);
+ await countEntries("10minutesAgo", "10minutesAgo form entry should be deleted.", 0);
+ }
+
+ await extension.startup();
+
+ await testRemovalMethod("removeFormData");
+
+ await extension.unload();
+});
+
+</script>
+</body>
+</html>
--- a/mobile/android/modules/Sanitizer.jsm
+++ b/mobile/android/modules/Sanitizer.jsm
@@ -38,16 +38,19 @@ Sanitizer.prototype = {
// Those who do not will be rejected with error message.
if (typeof startTime != "undefined") {
switch (aItemName) {
// Normal call to DownloadFiles remove actual data from storage, but our web-extension consumer
// deletes only download history. So, for this reason we are passing a flag 'deleteFiles'.
case "downloadHistory":
this._clear("downloadFiles", { startTime, deleteFiles: false });
break;
+ case "formdata":
+ this._clear(aItemName, { startTime });
+ break;
default:
return Promise.reject({message: `Invalid argument: ${aItemName} does not support startTime argument.`});
}
} else {
this._clear(aItemName);
}
},
@@ -222,22 +225,27 @@ Sanitizer.prototype = {
},
get canClear() {
return true;
}
},
formdata: {
- clear: function() {
+ clear: function({ startTime = 0 } = {}) {
return new Promise(function(resolve, reject) {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
- FormHistory.update({ op: "remove" });
+ // Conver time to microseconds
+ let time = startTime * 1000;
+ FormHistory.update({
+ op: "remove",
+ firstUsedStart: time
+ });
TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
resolve();
});
},
canClear: function(aCallback) {
let count = 0;