Bug 1388428 - Extend browsingData to restrict removing localStorage to a given list of hostnames; r?bsilverberg,janv
MozReview-Commit-ID: ELgjLHsVLkv
--- a/browser/components/extensions/ext-browsingData.js
+++ b/browser/components/extensions/ext-browsingData.js
@@ -102,17 +102,27 @@ const clearIndexedDB = async function(op
resolve();
});
});
return Promise.all(promises);
};
const clearLocalStorage = async function(options) {
- Services.obs.notifyObservers(null, "extension:purge-localStorage");
+ if (options.since) {
+ return Promise.reject(
+ {message: "Firefox does not support clearing localStorage with 'since'."});
+ }
+ if (options.hostnames) {
+ for (let hostname of options.hostnames) {
+ Services.obs.notifyObservers(null, "extension:purge-localStorage", hostname);
+ }
+ } else {
+ 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.
--- a/browser/components/extensions/schemas/browsing_data.json
+++ b/browser/components/extensions/schemas/browsing_data.json
@@ -31,17 +31,17 @@
"$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)."
+ "description": "Only remove data associated with these hostnames (only applies to cookies and localStorage)."
},
"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",
--- a/browser/components/extensions/test/browser/browser_ext_browsingData_localStorage.js
+++ b/browser/components/extensions/test/browser/browser_ext_browsingData_localStorage.js
@@ -34,16 +34,27 @@ add_task(async function testLocalStorage
function sendMessageToTabs(tabs, message) {
return Promise.all(
tabs.map(tab => { return browser.tabs.sendMessage(tab.id, message); }));
}
let tabs = await openTabs();
+ browser.test.assertRejects(
+ browser.browsingData.removeLocalStorage({since: Date.now()}),
+ "Firefox does not support clearing localStorage with 'since'.",
+ "Expected error received when using unimplemented parameter 'since'."
+ );
+
+ await sendMessageToTabs(tabs, "resetLocalStorage");
+ await browser.browsingData.removeLocalStorage({hostnames: ["example.com"]});
+ await browser.tabs.sendMessage(tabs[0].id, "checkLocalStorageCleared");
+ await browser.tabs.sendMessage(tabs[1].id, "checkLocalStorageSet");
+
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});
--- a/dom/storage/LocalStorageManager.cpp
+++ b/dom/storage/LocalStorageManager.cpp
@@ -370,22 +370,27 @@ 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") ||
- !strcmp(aTopic, "extension:purge-localStorage-caches")) {
+ if (!strcmp(aTopic, "cookie-cleared")) {
ClearCaches(LocalStorageCache::kUnloadComplete, pattern, EmptyCString());
return NS_OK;
}
+ // Clear everything, caches + database
+ if (!strcmp(aTopic, "extension:purge-localStorage-caches")) {
+ ClearCaches(LocalStorageCache::kUnloadComplete, pattern, aOriginScope);
+ 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);
return NS_OK;
}
// Clear everything (including so and pb data) from caches and database
--- a/dom/storage/StorageObserver.cpp
+++ b/dom/storage/StorageObserver.cpp
@@ -148,16 +148,60 @@ StorageObserver::Notify(const char* aTop
}
void
StorageObserver::NoteBackgroundThread(nsIEventTarget* aBackgroundThread)
{
mBackgroundThread = aBackgroundThread;
}
+nsresult
+StorageObserver::ClearMatchingOrigin(const char16_t* aData,
+ nsACString& aOriginScope)
+{
+ nsresult rv;
+
+ NS_ConvertUTF16toUTF8 domain(aData);
+
+ nsAutoCString convertedDomain;
+ nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (converter) {
+ // Convert the domain name to the ACE format
+ rv = converter->ConvertUTF8toACE(domain, convertedDomain);
+ } else {
+ // In case the IDN service is not available, this is the best we can come
+ // up with!
+ rv = NS_EscapeURL(domain,
+ esc_OnlyNonASCII | esc_AlwaysCopy,
+ convertedDomain,
+ fallible);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString originScope;
+ rv = CreateReversedDomain(convertedDomain, originScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (XRE_IsParentProcess()) {
+ StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+ if (NS_WARN_IF(!storageChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ storageChild->SendClearMatchingOrigin(originScope);
+ }
+
+ aOriginScope = originScope;
+ return NS_OK;
+}
+
NS_IMETHODIMP
StorageObserver::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
nsresult rv;
// Start the thread that opens the database.
@@ -271,62 +315,51 @@ StorageObserver::Observe(nsISupports* aS
Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
originScope);
return NS_OK;
}
if (!strcmp(aTopic, "extension:purge-localStorage")) {
- StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
- if (NS_WARN_IF(!storageChild)) {
- return NS_ERROR_FAILURE;
- }
+ const char topic[] = "extension:purge-localStorage-caches";
+
+ if (aData) {
+ nsCString originScope;
+ rv = ClearMatchingOrigin(aData, originScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
- storageChild->AsyncClearAll();
+ Notify(topic, EmptyString(), originScope);
+ } else {
+ StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
+ if (NS_WARN_IF(!storageChild)) {
+ return NS_ERROR_FAILURE;
+ }
- if (XRE_IsParentProcess()) {
- storageChild->SendClearAll();
+ storageChild->AsyncClearAll();
+
+ if (XRE_IsParentProcess()) {
+ storageChild->SendClearAll();
+ }
+
+ Notify(topic);
}
- 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.
+ // for the given 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);
- NS_ENSURE_SUCCESS(rv, rv);
- } else {
- // In case the IDN service is not available, this is the best we can come
- // up with!
- rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(aData),
- esc_OnlyNonASCII | esc_AlwaysCopy,
- aceDomain,
- fallible);
- NS_ENSURE_SUCCESS(rv, rv);
- }
-
- nsAutoCString originScope;
- rv = CreateReversedDomain(aceDomain, originScope);
- NS_ENSURE_SUCCESS(rv, rv);
-
- if (XRE_IsParentProcess()) {
- StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
- if (NS_WARN_IF(!storageChild)) {
- return NS_ERROR_FAILURE;
- }
-
- storageChild->SendClearMatchingOrigin(originScope);
+ nsCString originScope;
+ rv = ClearMatchingOrigin(aData, originScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
}
Notify("domain-data-cleared", EmptyString(), originScope);
return NS_OK;
}
// Clear all private-browsing caches
--- a/dom/storage/StorageObserver.h
+++ b/dom/storage/StorageObserver.h
@@ -52,16 +52,20 @@ public:
const nsACString& aOriginScope = EmptyCString());
void
NoteBackgroundThread(nsIEventTarget* aBackgroundThread);
private:
virtual ~StorageObserver() {}
+ nsresult
+ ClearMatchingOrigin(const char16_t* aData,
+ nsACString& aOriginScope);
+
static void TestingPrefChanged(const char* aPrefName, void* aClosure);
static StorageObserver* sSelf;
nsCOMPtr<nsIEventTarget> mBackgroundThread;
// Weak references
nsTObserverArray<StorageObserverSink*> mSinks;