Bug 1363001 - Implement browsingData.removeDownloads WebExtension API method on android. r?grisha, bsilverberg
MozReview-Commit-ID: FjjnfYjsJez
--- a/mobile/android/components/extensions/ext-browsingData.js
+++ b/mobile/android/components/extensions/ext-browsingData.js
@@ -1,14 +1,16 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
Cu.import("resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+ "resource://gre/modules/Sanitizer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences",
"resource://gre/modules/SharedPreferences.jsm");
this.browsingData = class extends ExtensionAPI {
getAPI(context) {
return {
@@ -49,12 +51,16 @@ this.browsingData = class extends Extens
dataToRemove[name] = dataTrue.indexOf(`${PREF_KEY_PREFIX}${item}`) > -1;
// Firefox doesn't have the same concept of dataRemovalPermitted
// as Chrome, so it will always be true.
dataRemovalPermitted[name] = true;
}
return Promise.resolve({options, dataToRemove, dataRemovalPermitted});
},
+
+ async removeDownloads(options) {
+ return Sanitizer.clearItem("downloadHistory", options.since);
+ },
},
};
}
};
\ No newline at end of file
--- a/mobile/android/components/extensions/schemas/browsing_data.json
+++ b/mobile/android/components/extensions/schemas/browsing_data.json
@@ -242,17 +242,16 @@
}
]
},
{
"name": "removeDownloads",
"description": "Clears the browser's list of downloaded files (<em>not</em> the downloaded files themselves).",
"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
@@ -1,11 +1,12 @@
[DEFAULT]
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_downloads.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_downloads.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>BrowsingData Settings 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 {Downloads} = Cu.import("resource://gre/modules/Downloads.jsm", {});
+
+const OLD_NAMES = {[Downloads.PUBLIC]: "old-public", [Downloads.PRIVATE]: "old-private"};
+const RECENT_NAMES = {[Downloads.PUBLIC]: "recent-public", [Downloads.PRIVATE]: "recent-private"};
+const REFERENCE_DATE = new Date();
+const OLD_DATE = new Date(Number(REFERENCE_DATE) - 10000);
+
+async function downloadExists(list, path) {
+ let listArray = await list.getAll();
+ return listArray.some(i => i.target.path == path);
+}
+
+async function checkDownloads(expectOldExists = true, expectRecentExists = true) {
+ for (let listType of [Downloads.PUBLIC, Downloads.PRIVATE]) {
+ let downloadsList = await Downloads.getList(listType);
+ is(
+ (await downloadExists(downloadsList, OLD_NAMES[listType])),
+ expectOldExists,
+ `Fake old download ${(expectOldExists) ? "was found" : "was removed"}.`);
+ is(
+ (await downloadExists(downloadsList, RECENT_NAMES[listType])),
+ expectRecentExists,
+ `Fake recent download ${(expectRecentExists) ? "was found" : "was removed"}.`);
+ }
+}
+
+async function setupDownloads() {
+ let downloadsList = await Downloads.getList(Downloads.ALL);
+ await downloadsList.removeFinished();
+
+ for (let listType of [Downloads.PUBLIC, Downloads.PRIVATE]) {
+ downloadsList = await Downloads.getList(listType);
+ let download = await Downloads.createDownload({
+ source: {
+ url: "https://bugzilla.mozilla.org/show_bug.cgi?id=1363001",
+ isPrivate: listType == Downloads.PRIVATE},
+ target: OLD_NAMES[listType],
+ });
+ download.startTime = OLD_DATE;
+ download.canceled = true;
+ await downloadsList.add(download);
+
+ download = await Downloads.createDownload({
+ source: {
+ url: "https://bugzilla.mozilla.org/show_bug.cgi?id=1363001",
+ isPrivate: listType == Downloads.PRIVATE},
+ target: RECENT_NAMES[listType],
+ });
+ download.startTime = REFERENCE_DATE;
+ download.canceled = true;
+ await downloadsList.add(download);
+ }
+
+ // Confirm everything worked.
+ downloadsList = await Downloads.getList(Downloads.ALL);
+ is((await downloadsList.getAll()).length, 4, "4 fake downloads added.");
+ checkDownloads();
+}
+
+add_task(async function testDownloads() {
+ function background() {
+ browser.test.onMessage.addListener(async (msg, options) => {
+ await browser.browsingData.removeDownloads(options);
+ browser.test.sendMessage("downloadsRemoved");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background,
+ manifest: {
+ permissions: ["browsingData"],
+ },
+ });
+
+ async function testRemovalMethod(method) {
+ // Clear downloads with no since value.
+ await setupDownloads();
+ extension.sendMessage(method, {});
+ await extension.awaitMessage("downloadsRemoved");
+ await checkDownloads(false, false);
+
+ // Clear downloads with recent since value.
+ await setupDownloads();
+ extension.sendMessage(method, {since: REFERENCE_DATE});
+ await extension.awaitMessage("downloadsRemoved");
+ await checkDownloads(true, false);
+
+ // Clear downloads with old since value.
+ await setupDownloads();
+ extension.sendMessage(method, {since: REFERENCE_DATE - 100000});
+ await extension.awaitMessage("downloadsRemoved");
+ await checkDownloads(false, false);
+ }
+
+ await extension.startup();
+
+ await testRemovalMethod("removeDownloads");
+
+ await extension.unload();
+});
+
+</script>
+</body>
+</html>
+
--- a/mobile/android/modules/Sanitizer.jsm
+++ b/mobile/android/modules/Sanitizer.jsm
@@ -28,27 +28,43 @@ XPCOMUtils.defineLazyModuleGetter(this,
function dump(a) {
Services.console.logStringMessage(a);
}
this.EXPORTED_SYMBOLS = ["Sanitizer"];
function Sanitizer() {}
Sanitizer.prototype = {
- clearItem: function (aItemName)
+ _clear: function(aItemName, startTime)
{
let item = this.items[aItemName];
let canClear = item.canClear;
if (typeof canClear == "function") {
canClear(function clearCallback(aCanClear) {
if (aCanClear)
- return item.clear();
+ return item.clear(startTime);
});
} else if (canClear) {
- return item.clear();
+ return item.clear(startTime);
+ }
+ },
+
+ clearItem: function (aItemName, startTime)
+ {
+ // Only a subset of items support deletion with startTime.
+ if (typeof startTime != "undefined") {
+ switch (aItemName) {
+ case 'downloadHistory':
+ this._clear(aItemName, startTime);
+ break;
+ default:
+ this._clear(aItemName);
+ }
+ } else {
+ this._clear(aItemName);
}
},
items: {
cache: {
clear: function ()
{
return new Promise(function(resolve, reject) {
@@ -245,16 +261,50 @@ Sanitizer.prototype = {
handleResult: function(aResult) { count = aResult; },
handleError: function(aError) { Cu.reportError(aError); },
handleCompletion: function(aReason) { aCallback(aReason == 0 && count > 0); }
};
FormHistory.count({}, countDone);
}
},
+ downloadHistory: {
+ clear: Task.async(function* (startTime) {
+ let refObj = {};
+ TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
+
+ // Initialize startTime to 0, if it is undefined
+ if (typeof startTime == "undefined")
+ startTime = 0;
+
+ let list = yield Downloads.getList(Downloads.ALL);
+ let downloads = yield list.getAll();
+ var finalizePromises = [];
+
+ for (let download of downloads) {
+ // Delete history items visisted after startTime
+ if (download.startTime.getTime() >= startTime) {
+ if (download.stopped && (!download.hasPartialData || download.error)) {
+ yield list.remove(download);
+ finalizePromises.push(download.finalize(true).then(() => null, Cu.reportError));
+ }
+ }
+ }
+
+ yield Promise.all(finalizePromises);
+ yield DownloadIntegration.forceSave();
+ TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
+ }),
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
downloadFiles: {
clear: Task.async(function* () {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
let list = yield Downloads.getList(Downloads.ALL);
let downloads = yield list.getAll();
var finalizePromises = [];