Bug 1355576 - Add ability to clear all localStorage with the browsingData API; r?bsilverberg, janv
MozReview-Commit-ID: 4UUqg62yIo9
--- a/browser/components/extensions/ext-browsingData.js
+++ b/browser/components/extensions/ext-browsingData.js
@@ -77,16 +77,20 @@ const clearDownloads = options => {
const clearFormData = options => {
return sanitizer.items.formdata.clear(makeRange(options));
};
const clearHistory = options => {
return sanitizer.items.history.clear(makeRange(options));
};
+const clearLocalStorage = async function(options) {
+ Services.obs.notifyObservers(null, "extension:purge-localStorage");
+};
+
const clearPasswords = async function(options) {
let loginManager = Services.logins;
let yieldCounter = 0;
if (options.since) {
// Iterate through the logins and delete any updated after our cutoff.
let logins = loginManager.getAllLogins();
for (let login of logins) {
@@ -146,16 +150,19 @@ const doRemoval = (options, dataToRemove
removalPromises.push(clearDownloads(options));
break;
case "formData":
removalPromises.push(clearFormData(options));
break;
case "history":
removalPromises.push(clearHistory(options));
break;
+ case "localStorage":
+ removalPromises.push(clearLocalStorage(options));
+ break;
case "passwords":
removalPromises.push(clearPasswords(options));
break;
case "pluginData":
removalPromises.push(clearPluginData(options));
break;
case "serviceWorkers":
removalPromises.push(clearServiceWorkers());
@@ -219,16 +226,19 @@ this.browsingData = class extends Extens
return doRemoval(options, {downloads: true});
},
removeFormData(options) {
return doRemoval(options, {formData: true});
},
removeHistory(options) {
return doRemoval(options, {history: true});
},
+ removeLocalStorage(options) {
+ return doRemoval(options, {localStorage: true});
+ },
removePasswords(options) {
return doRemoval(options, {passwords: true});
},
removePluginData(options) {
return doRemoval(options, {pluginData: true});
},
},
};
--- a/browser/components/extensions/schemas/browsing_data.json
+++ b/browser/components/extensions/schemas/browsing_data.json
@@ -330,17 +330,16 @@
}
]
},
{
"name": "removeLocalStorage",
"description": "Clears websites' local storage 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
@@ -39,16 +39,17 @@ support-files =
[browser_ext_browserAction_popup_preload.js]
skip-if = (os == 'win' && !debug) # bug 1352668
[browser_ext_browserAction_popup_resize.js]
[browser_ext_browserAction_simple.js]
[browser_ext_browserAction_telemetry.js]
[browser_ext_browserAction_theme_icons.js]
[browser_ext_browsingData_formData.js]
[browser_ext_browsingData_history.js]
+[browser_ext_browsingData_localStorage.js]
[browser_ext_browsingData_pluginData.js]
[browser_ext_browsingData_serviceWorkers.js]
[browser_ext_commands_execute_browser_action.js]
[browser_ext_commands_execute_page_action.js]
[browser_ext_commands_execute_sidebar_action.js]
[browser_ext_commands_getAll.js]
[browser_ext_commands_onCommand.js]
[browser_ext_contentscript_connect.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browsingData_localStorage.js
@@ -0,0 +1,93 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function testLocalStorage() {
+ async function background() {
+ function openTabs() {
+ let promise = new Promise(resolve => {
+ let tabURLs = [
+ "http://example.com/",
+ "http://example.net/",
+ ];
+
+ let tabs;
+ let waitingCount = tabURLs.length;
+
+ let listener = async msg => {
+ if (msg !== "content-script-ready" || --waitingCount) {
+ return;
+ }
+ browser.runtime.onMessage.removeListener(listener);
+ resolve(Promise.all(tabs));
+ };
+
+ browser.runtime.onMessage.addListener(listener);
+
+ tabs = tabURLs.map(url => {
+ return browser.tabs.create({url: url});
+ });
+ });
+
+ return promise;
+ }
+
+ function sendMessageToTabs(tabs, message) {
+ return Promise.all(
+ tabs.map(tab => { return browser.tabs.sendMessage(tab.id, message); }));
+ }
+
+ let tabs = await openTabs();
+
+ await sendMessageToTabs(tabs, "resetLocalStorage");
+ await sendMessageToTabs(tabs, "checkLocalStorageSet");
+ await browser.browsingData.removeLocalStorage({});
+ await sendMessageToTabs(tabs, "checkLocalStorageCleared");
+
+ await sendMessageToTabs(tabs, "resetLocalStorage");
+ await sendMessageToTabs(tabs, "checkLocalStorageSet");
+ await browser.browsingData.remove({}, {localStorage: true});
+ await sendMessageToTabs(tabs, "checkLocalStorageCleared");
+
+ browser.tabs.remove(tabs.map(tab => tab.id));
+
+ browser.test.notifyPass("done");
+ }
+
+ function contentScript() {
+ browser.runtime.onMessage.addListener(msg => {
+ if (msg === "resetLocalStorage") {
+ localStorage.clear();
+ localStorage.setItem("test", "test");
+ } else if (msg === "checkLocalStorageSet") {
+ browser.test.assertEq("test", localStorage.getItem("test"));
+ } else if (msg === "checkLocalStorageCleared") {
+ browser.test.assertEq(null, localStorage.getItem("test"));
+ }
+ });
+ browser.runtime.sendMessage("content-script-ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background,
+ manifest: {
+ "permissions": ["browsingData"],
+ "content_scripts": [{
+ "matches": [
+ "http://example.com/",
+ "http://example.net/",
+ ],
+ "js": ["content-script.js"],
+ "run_at": "document_start",
+ }],
+ },
+ files: {
+ "content-script.js": contentScript,
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("done");
+ await extension.unload();
+});
+
--- a/browser/components/extensions/test/xpcshell/test_ext_browsingData.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_browsingData.js
@@ -39,28 +39,28 @@ add_task(async function testInvalidArgum
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
await extension.awaitFinish("invalidArguments");
await extension.unload();
});
add_task(async function testUnimplementedDataType() {
function background() {
- browser.browsingData.remove({}, {localStorage: true});
+ browser.browsingData.remove({}, {indexedDB: true});
browser.test.sendMessage("finished");
}
let {messages} = await promiseConsoleOutput(async function() {
let extension = ExtensionTestUtils.loadExtension({
background: background,
manifest: {
permissions: ["browsingData"],
},
});
await extension.startup();
await extension.awaitMessage("finished");
await extension.unload();
});
- let warningObserved = messages.find(line => /Firefox does not support dataTypes: localStorage/.test(line));
+ let warningObserved = messages.find(line => /Firefox does not support dataTypes: indexedDB/.test(line));
ok(warningObserved, "Warning issued when calling remove with an unimplemented dataType.");
});
--- a/dom/storage/LocalStorageManager.cpp
+++ b/dom/storage/LocalStorageManager.cpp
@@ -381,17 +381,18 @@ LocalStorageManager::Observe(const char*
{
OriginAttributesPattern pattern;
if (!pattern.Init(aOriginAttributesPattern)) {
NS_ERROR("Cannot parse origin attributes pattern");
return NS_ERROR_FAILURE;
}
// Clear everything, caches + database
- if (!strcmp(aTopic, "cookie-cleared")) {
+ if (!strcmp(aTopic, "cookie-cleared") ||
+ !strcmp(aTopic, "extension:purge-localStorage-caches")) {
ClearCaches(LocalStorageCache::kUnloadComplete, pattern, EmptyCString());
return NS_OK;
}
// Clear from caches everything that has been stored
// while in session-only mode
if (!strcmp(aTopic, "session-only-cleared")) {
ClearCaches(LocalStorageCache::kUnloadSession, pattern, aOriginScope);
--- a/dom/storage/StorageObserver.cpp
+++ b/dom/storage/StorageObserver.cpp
@@ -61,16 +61,17 @@ StorageObserver::Init()
// Chrome clear operations.
obs->AddObserver(sSelf, kStartupTopic, true);
obs->AddObserver(sSelf, "cookie-changed", true);
obs->AddObserver(sSelf, "perm-changed", true);
obs->AddObserver(sSelf, "browser:purge-domain-data", true);
obs->AddObserver(sSelf, "last-pb-context-exited", true);
obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
+ obs->AddObserver(sSelf, "extension:purge-localStorage", true);
// Shutdown
obs->AddObserver(sSelf, "profile-after-change", true);
obs->AddObserver(sSelf, "profile-before-change", true);
obs->AddObserver(sSelf, "xpcom-shutdown", true);
// Observe low device storage notifications.
obs->AddObserver(sSelf, "disk-space-watcher", true);
@@ -247,16 +248,27 @@ StorageObserver::Observe(nsISupports* aS
NS_ENSURE_SUCCESS(rv, rv);
Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
originScope);
return NS_OK;
}
+ if (!strcmp(aTopic, "extension:purge-localStorage")) {
+ StorageDBBridge* db = LocalStorageCache::StartDatabase();
+ NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+
+ db->AsyncClearAll();
+
+ Notify("extension:purge-localStorage-caches");
+
+ return NS_OK;
+ }
+
// Clear everything (including so and pb data) from caches and database
// for the gived domain and subdomains.
if (!strcmp(aTopic, "browser:purge-domain-data")) {
// Convert the domain name to the ACE format
nsAutoCString aceDomain;
nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
if (converter) {
rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain);