Bug 1610641 - Remove Fennec's ext-browsingData. r=mixedpuppy

Differential Revision: https://phabricator.services.mozilla.com/D60580

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Agi Sferro 2020-01-22 15:19:31 +00:00
Родитель a6d7032a66
Коммит 9b30266671
12 изменённых файлов: 0 добавлений и 1631 удалений

Просмотреть файл

@ -77,13 +77,6 @@ extensions.registerModules({
manifest: ["browser_action"],
paths: [["browserAction"]],
},
browsingData: {
url: "chrome://geckoview/content/ext-browsingData.js",
schema: "chrome://geckoview/content/schemas/browsing_data.json",
scopes: ["addon_parent"],
manifest: ["browsing_data"],
paths: [["browsingData"]],
},
pageAction: {
url: "chrome://geckoview/content/ext-pageAction.js",
schema: "chrome://extensions/content/schemas/page_action.json",

Просмотреть файл

@ -1,176 +0,0 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
ChromeUtils.defineModuleGetter(
this,
"Sanitizer",
"resource://gre/modules/Sanitizer.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"Services",
"resource://gre/modules/Services.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"SharedPreferences",
"resource://gre/modules/SharedPreferences.jsm"
);
const clearCache = () => {
// Clearing the cache does not support timestamps.
return Sanitizer.clearItem("cache");
};
const clearCookies = async function(options) {
let cookieMgr = Services.cookies;
let yieldCounter = 0;
const YIELD_PERIOD = 10;
if (options.since) {
// Convert it to microseconds
let since = options.since * 1000;
// Iterate through the cookies and delete any created after our cutoff.
for (let cookie of cookieMgr.cookies) {
if (cookie.creationTime >= since) {
// This cookie was created after our cutoff, clear it.
cookieMgr.remove(
cookie.host,
cookie.name,
cookie.path,
cookie.originAttributes
);
if (++yieldCounter % YIELD_PERIOD == 0) {
await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
}
}
}
} else {
// Remove everything.
cookieMgr.removeAll();
}
};
const clearDownloads = options => {
return Sanitizer.clearItem("downloadHistory", options.since);
};
const clearFormData = options => {
return Sanitizer.clearItem("formdata", options.since);
};
const 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 = [];
let invalidDataTypes = [];
for (let dataType in dataToRemove) {
if (dataToRemove[dataType]) {
switch (dataType) {
case "cache":
removalPromises.push(clearCache());
break;
case "cookies":
removalPromises.push(clearCookies(options));
break;
case "downloads":
removalPromises.push(clearDownloads(options));
break;
case "formData":
removalPromises.push(clearFormData(options));
break;
default:
invalidDataTypes.push(dataType);
}
}
}
if (extension && invalidDataTypes.length) {
extension.logger.warn(
`Firefox does not support dataTypes: ${invalidDataTypes.toString()}.`
);
}
return Promise.all(removalPromises);
};
this.browsingData = class extends ExtensionAPI {
getAPI(context) {
let { extension } = context;
return {
browsingData: {
settings() {
const PREF_DOMAIN = "android.not_a_preference.privacy.clear";
const PREF_KEY_PREFIX = "private.data.";
// The following prefs are the only ones in Firefox that match corresponding
// values used by Chrome when returning settings.
const PREF_LIST = [
"cache",
"history",
"formdata",
"cookies_sessions",
"downloadFiles",
];
let dataTrue = SharedPreferences.forProfile().getSetPref(PREF_DOMAIN);
let name;
let dataToRemove = {};
let dataRemovalPermitted = {};
for (let item of PREF_LIST) {
// The property formData needs a different case than the
// formdata preference.
switch (item) {
case "formdata":
name = "formData";
break;
case "cookies_sessions":
name = "cookies";
break;
case "downloadFiles":
name = "downloads";
break;
default:
name = item;
}
dataToRemove[name] = dataTrue.includes(`${PREF_KEY_PREFIX}${item}`);
// Firefox doesn't have the same concept of dataRemovalPermitted
// as Chrome, so it will always be true.
dataRemovalPermitted[name] = true;
}
// We do not provide option to delete history by time
// so, since value is given 0, which means Everything
return Promise.resolve({
options: { since: 0 },
dataToRemove,
dataRemovalPermitted,
});
},
remove(options, dataToRemove) {
return doRemoval(options, dataToRemove, extension);
},
removeCache(options) {
return doRemoval(options, { cache: true });
},
removeCookies(options) {
return doRemoval(options, { cookies: true });
},
removeDownloads(options) {
return doRemoval(options, { downloads: true });
},
removeFormData(options) {
return doRemoval(options, { formData: true });
},
},
};
}
};

Просмотреть файл

@ -7,7 +7,6 @@ geckoview.jar:
content/ext-c-android.js
content/ext-c-tabs.js
content/ext-browserAction.js
content/ext-browsingData.js
content/ext-pageAction.js
content/ext-tabs.js
content/ext-utils.js

Просмотреть файл

@ -1,421 +0,0 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[
{
"namespace": "manifest",
"types": [
{
"$extend": "Permission",
"choices": [{
"type": "string",
"enum": [
"browsingData"
]
}]
}
]
},
{
"namespace": "browsingData",
"description": "Use the <code>chrome.browsingData</code> API to remove browsing data from a user's local profile.",
"permissions": ["browsingData"],
"types": [
{
"id": "RemovalOptions",
"type": "object",
"description": "Options that determine exactly what data will be removed.",
"properties": {
"since": {
"$ref": "extensionTypes.Date",
"optional": true,
"description": "Remove data accumulated on or after this date, represented in milliseconds since the epoch (accessible via the <code>getTime</code> method of the JavaScript <code>Date</code> object). If absent, defaults to 0 (which would remove all browsing data)."
},
"hostnames": {
"type": "array",
"items": {"type": "string", "format": "hostname"},
"optional": true,
"description": "Only remove data associated with these hostnames (only applies to cookies)."
},
"originTypes": {
"type": "object",
"optional": true,
"description": "An object whose properties specify which origin types ought to be cleared. If this object isn't specified, it defaults to clearing only \"unprotected\" origins. Please ensure that you <em>really</em> want to remove application data before adding 'protectedWeb' or 'extensions'.",
"properties": {
"unprotectedWeb": {
"type": "boolean",
"optional": true,
"description": "Normal websites."
},
"protectedWeb": {
"type": "boolean",
"optional": true,
"description": "Websites that have been installed as hosted applications (be careful!)."
},
"extension": {
"type": "boolean",
"optional": true,
"description": "Extensions and packaged applications a user has installed (be _really_ careful!)."
}
}
}
}
},
{
"id": "DataTypeSet",
"type": "object",
"description": "A set of data types. Missing data types are interpreted as <code>false</code>.",
"properties": {
"cache": {
"type": "boolean",
"optional": true,
"description": "The browser's cache. Note: when removing data, this clears the <em>entire</em> cache: it is not limited to the range you specify."
},
"cookies": {
"type": "boolean",
"optional": true,
"description": "The browser's cookies."
},
"downloads": {
"type": "boolean",
"optional": true,
"description": "The browser's download list."
},
"formData": {
"type": "boolean",
"optional": true,
"description": "The browser's stored form data."
},
"history": {
"type": "boolean",
"optional": true,
"description": "The browser's history."
},
"indexedDB": {
"type": "boolean",
"optional": true,
"description": "Websites' IndexedDB data."
},
"localStorage": {
"type": "boolean",
"optional": true,
"description": "Websites' local storage data."
},
"serverBoundCertificates": {
"type": "boolean",
"optional": true,
"description": "Server-bound certificates."
},
"passwords": {
"type": "boolean",
"optional": true,
"description": "Stored passwords."
},
"pluginData": {
"type": "boolean",
"optional": true,
"description": "Plugins' data."
},
"serviceWorkers": {
"type": "boolean",
"optional": true,
"description": "Service Workers."
}
}
}
],
"functions": [
{
"name": "settings",
"description": "Reports which types of data are currently selected in the 'Clear browsing data' settings UI. Note: some of the data types included in this API are not available in the settings UI, and some UI settings control more than one data type listed here.",
"type": "function",
"async": "callback",
"parameters": [
{
"name": "callback",
"type": "function",
"parameters": [
{
"name": "result",
"type": "object",
"properties": {
"options": {
"$ref": "RemovalOptions"
},
"dataToRemove": {
"$ref": "DataTypeSet",
"description": "All of the types will be present in the result, with values of <code>true</code> if they are both selected to be removed and permitted to be removed, otherwise <code>false</code>."
},
"dataRemovalPermitted": {
"$ref": "DataTypeSet",
"description": "All of the types will be present in the result, with values of <code>true</code> if they are permitted to be removed (e.g., by enterprise policy) and <code>false</code> if not."
}
}
}
]
}
]
},
{
"name": "remove",
"description": "Clears various types of browsing data stored in a user's profile.",
"type": "function",
"async": "callback",
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "dataToRemove",
"$ref": "DataTypeSet",
"description": "The set of data types to remove."
},
{
"name": "callback",
"type": "function",
"description": "Called when deletion has completed.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeAppcache",
"description": "Clears websites' appcache data.",
"type": "function",
"async": "callback",
"unsupported": true,
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when websites' appcache data has been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeCache",
"description": "Clears the browser's cache.",
"type": "function",
"async": "callback",
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when the browser's cache has been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeCookies",
"description": "Clears the browser's cookies and server-bound certificates modified within a particular timeframe.",
"type": "function",
"async": "callback",
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when the browser's cookies and server-bound certificates have been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeDownloads",
"description": "Clears the browser's list of downloaded files (<em>not</em> the downloaded files themselves).",
"type": "function",
"async": "callback",
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when the browser's list of downloaded files has been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeFileSystems",
"description": "Clears websites' file system data.",
"type": "function",
"async": "callback",
"unsupported": true,
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when websites' file systems have been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeFormData",
"description": "Clears the browser's stored form data (autofill).",
"type": "function",
"async": "callback",
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when the browser's form data has been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeHistory",
"description": "Clears the browser's history.",
"type": "function",
"async": "callback",
"unsupported": true,
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when the browser's history has cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeIndexedDB",
"description": "Clears websites' IndexedDB data.",
"type": "function",
"async": "callback",
"unsupported": true,
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when websites' IndexedDB data has been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeLocalStorage",
"description": "Clears websites' local storage data.",
"type": "function",
"async": "callback",
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when websites' local storage has been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removePluginData",
"description": "Clears plugins' data.",
"type": "function",
"async": "callback",
"unsupported": true,
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when plugins' data has been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removePasswords",
"description": "Clears the browser's stored passwords.",
"type": "function",
"async": "callback",
"unsupported": true,
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when the browser's passwords have been cleared.",
"optional": true,
"parameters": []
}
]
},
{
"name": "removeWebSQL",
"description": "Clears websites' WebSQL data.",
"type": "function",
"async": "callback",
"unsupported": true,
"parameters": [
{
"$ref": "RemovalOptions",
"name": "options"
},
{
"name": "callback",
"type": "function",
"description": "Called when websites' WebSQL databases have been cleared.",
"optional": true,
"parameters": []
}
]
}
]
}
]

Просмотреть файл

@ -3,6 +3,5 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
geckoview.jar:
content/schemas/browsing_data.json
content/schemas/gecko_view_addons.json
content/schemas/tabs.json

Просмотреть файл

@ -4,8 +4,4 @@ support-files =
../../../../../../toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js
tags = webextensions
[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]

Просмотреть файл

@ -1,147 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>BrowsingData Cookies test</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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 {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const COOKIE = {
host: "example.com",
name: "test_cookie",
path: "/",
};
let since, oldCookie;
function addCookie(cookie) {
let expiry = Date.now() / 1000 + 10000;
Services.cookies.add(cookie.host, cookie.path, cookie.name, "test", false, false, false, expiry, {}, Ci.nsICookie.SAMESITE_NONE);
ok(Services.cookies.cookieExists(cookie.host, cookie.path, cookie.name, {}), `Cookie ${cookie.name} was created.`);
}
async function setUpCookies() {
// Add a cookie which will end up with an older creationTime.
oldCookie = Object.assign({}, COOKIE, {name: Date.now()});
addCookie(oldCookie);
await new Promise(resolve => setTimeout(resolve, 20));
since = Date.now();
await new Promise(resolve => setTimeout(resolve, 10));
// Add a cookie which will end up with a more recent creationTime.
addCookie(COOKIE);
}
add_task(async function testCookies() {
function background() {
browser.test.onMessage.addListener(async (msg, options) => {
if (msg == "removeCookies") {
await browser.browsingData.removeCookies(options);
} else {
await browser.browsingData.remove(options, {cookies: true});
}
browser.test.sendMessage("cookiesRemoved");
});
}
let extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["browsingData"],
},
});
async function testRemovalMethod(method) {
// Clear cookies with a recent since value.
await setUpCookies();
extension.sendMessage(method, {since});
await extension.awaitMessage("cookiesRemoved");
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.");
// Clear cookies with an old since value.
await setUpCookies();
addCookie(COOKIE);
extension.sendMessage(method, {since: since - 100000});
await extension.awaitMessage("cookiesRemoved");
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 cookies with no since value and valid originTypes.
await setUpCookies();
extension.sendMessage(
method,
{originTypes: {unprotectedWeb: true, protectedWeb: false}});
await extension.awaitMessage("cookiesRemoved");
ok(!Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), `Cookie ${COOKIE.name} was removed.`);
ok(!Services.cookies.cookieExists(oldCookie.host, oldCookie.path, oldCookie.name, {}), `Cookie ${oldCookie.name} was removed.`);
}
await extension.startup();
await testRemovalMethod("removeCookies");
await testRemovalMethod("remove");
await extension.unload();
});
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});
}
browser.test.sendMessage("cacheRemoved");
});
}
let extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
permissions: ["browsingData"],
},
});
// Returns a promise when 'cacheservice:empty-cache' event is fired
function topicObserved() {
return new Promise(resolve => {
let observe = (subject, topic, data) => {
Services.obs.removeObserver(observe, "cacheservice:empty-cache");
resolve(data);
};
Services.obs.addObserver(observe, "cacheservice:empty-cache");
});
}
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 = topicObserved();
extension.sendMessage(method);
await awaitNotification;
await extension.awaitMessage("cacheRemoved");
}
await extension.startup();
await testRemovalMethod("removeCache");
await testRemovalMethod("remove");
await extension.unload();
});
</script>
</body>
</html>

Просмотреть файл

@ -1,123 +0,0 @@
<!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/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";
var {Downloads} = ChromeUtils.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) => {
if (msg == "removeDownloads") {
await browser.browsingData.removeDownloads(options);
} else {
await browser.browsingData.remove(options, {downloads: true});
}
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 testRemovalMethod("remove");
await extension.unload();
});
</script>
</body>
</html>

Просмотреть файл

@ -1,149 +0,0 @@
<!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/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";
var {FormHistory} = ChromeUtils.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) => {
if (msg == "removeFormData") {
await browser.browsingData.removeFormData(options);
} else {
await browser.browsingData.remove(options, {formData: true});
}
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 testRemovalMethod("remove");
await extension.unload();
});
</script>
</body>
</html>

Просмотреть файл

@ -1,98 +0,0 @@
<!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/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";
var {SharedPreferences} = ChromeUtils.import("resource://gre/modules/SharedPreferences.jsm");
const PREF_DOMAIN = "android.not_a_preference.privacy.clear";
const PREF_KEY_PREFIX = "private.data.";
const SETTINGS_LIST = ["cache", "cookies", "history", "formData", "downloads"];
function checkPrefs(key, actualValue, prefs, prefSuffix) {
let prefValue = prefs.includes(`${PREF_KEY_PREFIX}${prefSuffix}`);
is(actualValue, prefValue, `${key} property of dataToRemove matches the expected pref.`);
}
function testSettingsPreferences(dataToRemove) {
let prefs = SharedPreferences.forProfile().getSetPref(PREF_DOMAIN);
for (let key of Object.keys(dataToRemove)) {
switch (key) {
case "formData":
checkPrefs(key, dataToRemove[key], prefs, "formdata");
break;
case "cookies":
checkPrefs(key, dataToRemove[key], prefs, "cookies_sessions");
break;
case "downloads":
checkPrefs(key, dataToRemove[key], prefs, "downloadFiles");
break;
default:
checkPrefs(key, dataToRemove[key], prefs, key);
}
}
}
add_task(async function testSettings() {
function background() {
browser.test.onMessage.addListener(async (msg) => {
if (msg == "retrieve-settings") {
let settings = await browser.browsingData.settings();
browser.test.sendMessage("settings", settings);
}
});
}
let extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
"permissions": ["browsingData"],
},
});
await extension.startup();
extension.sendMessage("retrieve-settings");
let {options, dataToRemove, dataRemovalPermitted} = await extension.awaitMessage("settings");
// Verify that we get the keys we expect.
is(SETTINGS_LIST.length, Object.keys(dataToRemove).length, `dataToRemove contains expected no of keys`);
is(SETTINGS_LIST.length, Object.keys(dataRemovalPermitted).length, `dataRemovalPermitted contains expected no of keys`);
for (let key of SETTINGS_LIST) {
is(true, dataRemovalPermitted[key],
`${key} property of dataRemovalPermitted matches the expected value.`);
}
// Verify values of dataToRemove keys are as expected.
testSettingsPreferences(dataToRemove);
// Verify object options returned as expected.
// For now, We do not provide option to delete history by time, so,
// since value is given 0, which means Everything.
is(options.since, 0, `options contains expected value.`);
// Explicitly set some prefs to true
const NEW_PREFS = ["private.data.cache", "private.data.cookies_sessions"];
SharedPreferences.forProfile().setSetPref(PREF_DOMAIN, NEW_PREFS);
extension.sendMessage("retrieve-settings");
let settings = await extension.awaitMessage("settings");
testSettingsPreferences(settings.dataToRemove);
await extension.unload();
});
</script>
</body>
</html>

Просмотреть файл

@ -1,503 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { Integration } = ChromeUtils.import(
"resource://gre/modules/Integration.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
Accounts: "resource://gre/modules/Accounts.jsm",
Downloads: "resource://gre/modules/Downloads.jsm",
EventDispatcher: "resource://gre/modules/Messaging.jsm",
FormHistory: "resource://gre/modules/FormHistory.jsm",
OfflineAppCacheHelper: "resource://gre/modules/offlineAppCache.jsm",
OS: "resource://gre/modules/osfile.jsm",
ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.jsm",
});
XPCOMUtils.defineLazyServiceGetters(this, {
quotaManagerService: [
"@mozilla.org/dom/quota-manager-service;1",
"nsIQuotaManagerService",
],
});
/* global DownloadIntegration */
Integration.downloads.defineModuleGetter(
this,
"DownloadIntegration",
"resource://gre/modules/DownloadIntegration.jsm"
);
var EXPORTED_SYMBOLS = ["Sanitizer"];
function Sanitizer() {}
Sanitizer.prototype = {
clearItem: function(aItemName, startTime, clearUnfinishedDownloads) {
// Only a subset of items support deletion with startTime.
// 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":
return this._clear("downloadFiles", {
startTime,
deleteFiles: false,
});
case "formdata":
return this._clear(aItemName, { startTime });
default:
return Promise.reject({
message: `Invalid argument: ${aItemName} does not support startTime argument.`,
});
}
} else if (
aItemName === "downloadFiles" &&
typeof clearUnfinishedDownloads != "undefined"
) {
return this._clear(aItemName, { clearUnfinishedDownloads });
} else {
return this._clear(aItemName);
}
},
_clear: function(aItemName, options) {
let item = this.items[aItemName];
let canClear = item.canClear;
if (typeof canClear == "function") {
let maybeDoClear = async () => {
let canClearResult = await new Promise(resolve => {
canClear(resolve);
});
if (canClearResult) {
return item.clear(options);
}
};
return maybeDoClear();
} else if (canClear) {
return item.clear(options);
}
},
// This code is mostly based on the Sanitizer code for desktop Firefox
// (browser/modules/Sanitzer.jsm), however over the course of time some
// general differences have evolved:
// - async shutdown (and seenException handling) isn't implemented in Fennec
// - currently there is only limited support for range-based clearing of data
// Any further specific differences caused by architectural differences between
// Fennec and desktop Firefox are documented below for each item.
items: {
// The difference is specifically the Sanitize:Cache message,
// so that the Android front-end can clear its caches as well,
// while everything else is unchanged.
cache: {
clear: function() {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
try {
Services.cache2.clear();
} catch (er) {}
let imageCache = Cc["@mozilla.org/image/tools;1"]
.getService(Ci.imgITools)
.getImgCacheForDocument(null);
try {
imageCache.clearCache(false); // true=chrome, false=content
} catch (er) {}
return EventDispatcher.instance
.sendRequestForResult({ type: "Sanitize:Cache" })
.catch(err => {
Cu.reportError(
`Java-side cache clearing failed with error: ${err}`
);
})
.then(() => {
TelemetryStopwatch.finish("FX_SANITIZE_CACHE", refObj);
});
},
get canClear() {
return true;
},
},
// Compared to desktop, we don't clear plugin data, as plugins
// aren't supported on Android.
cookies: {
clear: function() {
return new Promise(function(resolve, reject) {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
Services.cookies.removeAll();
TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
// Clear deviceIds. Done asynchronously (returns before complete).
try {
let mediaMgr = Cc["@mozilla.org/mediaManagerService;1"].getService(
Ci.nsIMediaManagerService
);
mediaMgr.sanitizeDeviceIds(0);
} catch (er) {}
resolve();
});
},
get canClear() {
return true;
},
},
// Same as desktop Firefox.
siteSettings: {
async clear() {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS", refObj);
// Clear site-specific permissions like "Allow this site to open popups"
Services.perms.removeAll();
// Clear site-specific settings like page-zoom level
Cc["@mozilla.org/content-pref/service;1"]
.getService(Ci.nsIContentPrefService2)
.removeAllDomains(null);
// Clear site security settings
var sss = Cc["@mozilla.org/ssservice;1"].getService(
Ci.nsISiteSecurityService
);
sss.clearAll();
// Clear push subscriptions
await new Promise((resolve, reject) => {
let push = Cc["@mozilla.org/push/Service;1"].getService(
Ci.nsIPushService
);
push.clearForDomain("*", status => {
if (Components.isSuccessCode(status)) {
resolve();
} else {
reject(new Error("Error clearing push subscriptions: " + status));
}
});
});
TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
},
get canClear() {
return true;
},
},
// Same as desktop Firefox.
offlineApps: {
async clear() {
// AppCache
// This doesn't wait for the cleanup to be complete.
OfflineAppCacheHelper.clear();
// LocalStorage
Services.obs.notifyObservers(null, "extension:purge-localStorage");
// ServiceWorkers
await ServiceWorkerCleanUp.removeAll();
// QuotaManager
let promises = [];
await new Promise(resolve => {
quotaManagerService.getUsage(request => {
if (request.resultCode != Cr.NS_OK) {
// We are probably shutting down. We don't want to propagate the
// error, rejecting the promise.
resolve();
return;
}
for (let item of request.result) {
let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
item.origin
);
let uri = principal.URI;
if (
uri.scheme == "http" ||
uri.scheme == "https" ||
uri.scheme == "file"
) {
promises.push(
new Promise(r => {
let req = quotaManagerService.clearStoragesForPrincipal(
principal
);
req.callback = () => {
r();
};
})
);
}
}
resolve();
});
});
return Promise.all(promises);
},
get canClear() {
return true;
},
},
// History on Android is implemented by the Java frontend and requires
// different handling. Everything else is the same as for desktop Firefox.
history: {
clear: function() {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_HISTORY", refObj);
return EventDispatcher.instance
.sendRequestForResult({ type: "Sanitize:ClearHistory" })
.catch(e => Cu.reportError("Java-side history clearing failed: " + e))
.then(function() {
TelemetryStopwatch.finish("FX_SANITIZE_HISTORY", refObj);
try {
Services.obs.notifyObservers(
null,
"browser:purge-session-history"
);
} catch (e) {}
try {
var predictor = Cc["@mozilla.org/network/predictor;1"].getService(
Ci.nsINetworkPredictor
);
predictor.reset();
} catch (e) {}
});
},
get canClear() {
// bug 347231: Always allow clearing history due to dependencies on
// the browser:purge-session-history notification. (like error console)
return true;
},
},
// Equivalent to openWindows on desktop, but specific to Fennec's implementation
// of tabbed browsing and the session store.
openTabs: {
clear: function() {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_OPENWINDOWS", refObj);
return EventDispatcher.instance
.sendRequestForResult({ type: "Sanitize:OpenTabs" })
.catch(e => Cu.reportError("Java-side tab clearing failed: " + e))
.then(function() {
try {
// clear "Recently Closed" tabs in Android App
Services.obs.notifyObservers(null, "browser:purge-session-tabs");
} catch (e) {}
TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
});
},
get canClear() {
return true;
},
},
// Specific to Fennec.
searchHistory: {
clear: function() {
return EventDispatcher.instance
.sendRequestForResult({
type: "Sanitize:ClearHistory",
clearSearchHistory: true,
})
.catch(e =>
Cu.reportError("Java-side search history clearing failed: " + e)
);
},
get canClear() {
return true;
},
},
// Browser search is handled by searchHistory above and the find bar doesn't
// require extra handling. FormHistory itself is cleared like on desktop.
formdata: {
clear: function({ startTime = 0 } = {}) {
return new Promise(function(resolve, reject) {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
// Conver time to microseconds
let time = startTime * 1000;
FormHistory.update(
{
op: "remove",
firstUsedStart: time,
},
{
handleCompletion() {
TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
resolve();
},
}
);
});
},
canClear: function(aCallback) {
let count = 0;
let countDone = {
handleResult: function(aResult) {
count = aResult;
},
handleError: function(aError) {
Cu.reportError(aError);
},
handleCompletion: function(aReason) {
aCallback(aReason == 0 && count > 0);
},
};
FormHistory.count({}, countDone);
},
},
// Adapted from desktop, but heavily modified - see comments below.
downloadFiles: {
async clear({
startTime = 0,
deleteFiles = true,
clearUnfinishedDownloads = false,
} = {}) {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
let list = await Downloads.getList(Downloads.ALL);
let downloads = await list.getAll();
var finalizePromises = [];
// Logic copied from DownloadList.removeFinished. Ideally, we would
// just use that method directly, but we want to be able to remove the
// downloaded files as well.
for (let download of downloads) {
let downloadFinished =
download.stopped && (!download.hasPartialData || download.error);
if (
(downloadFinished || clearUnfinishedDownloads) &&
download.startTime.getTime() >= startTime
) {
// Remove the download first, so that the views don't get the change
// notifications that may occur during finalization.
await list.remove(download);
// Ensure that the download is stopped and no partial data is kept.
// This works even if the download state has changed meanwhile. We
// don't need to wait for the procedure to be complete before
// processing the other downloads in the list.
finalizePromises.push(
download.finalize(true).then(() => null, Cu.reportError)
);
if (deleteFiles) {
// Delete the downloaded files themselves.
OS.File.remove(download.target.path).then(
() => null,
ex => {
if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
Cu.reportError(ex);
}
}
);
}
}
}
await Promise.all(finalizePromises);
await DownloadIntegration.forceSave();
TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
},
get canClear() {
return true;
},
},
// Specific to Fennec.
passwords: {
clear: function() {
return new Promise(function(resolve, reject) {
Services.logins.removeAllLogins();
resolve();
});
},
get canClear() {
let count = Services.logins.countLogins("", "", ""); // count all logins
return count > 0;
},
},
// Same as desktop Firefox.
sessions: {
clear: function() {
return new Promise(function(resolve, reject) {
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_SESSIONS", refObj);
// clear all auth tokens
var sdr = Cc["@mozilla.org/security/sdr;1"].getService(
Ci.nsISecretDecoderRing
);
sdr.logoutAndTeardown();
// clear FTP and plain HTTP auth sessions
Services.obs.notifyObservers(null, "net:clear-active-logins");
TelemetryStopwatch.finish("FX_SANITIZE_SESSIONS", refObj);
resolve();
});
},
get canClear() {
return true;
},
},
// Specific to Fennec.
syncedTabs: {
clear: function() {
return EventDispatcher.instance
.sendRequestForResult({ type: "Sanitize:ClearSyncedTabs" })
.catch(e =>
Cu.reportError("Java-side synced tabs clearing failed: " + e)
);
},
canClear: function(aCallback) {
Accounts.anySyncAccountsExist()
.then(aCallback)
.catch(function(err) {
Cu.reportError("Java-side synced tabs clearing failed: " + err);
aCallback(false);
});
},
},
},
};
var Sanitizer = new Sanitizer();

Просмотреть файл

@ -31,7 +31,6 @@ EXTRA_JS_MODULES += [
'MediaPlayerApp.jsm',
'Notifications.jsm',
'RuntimePermissions.jsm',
'Sanitizer.jsm',
'SharedPreferences.jsm',
'Snackbars.jsm',
'WebsiteMetadata.jsm'