Bug 1713139 - Clear partitioned storage for session storage and legacy localStorage implementation. r=dom-storage-reviewers,johannh,asuth

Differential Revision: https://phabricator.services.mozilla.com/D116607
This commit is contained in:
Paul Zuehlcke 2021-07-06 21:10:58 +00:00
Родитель 38e07b9a08
Коммит da24464574
7 изменённых файлов: 221 добавлений и 15 удалений

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

@ -382,8 +382,9 @@ nsresult LocalStorageManager::Observe(const char* aTopic,
return NS_OK;
}
// Clear localStorage data beloging to an origin pattern
if (!strcmp(aTopic, "origin-attr-pattern-cleared")) {
// Clear localStorage data belonging to an origin pattern
if (!strcmp(aTopic, "clear-origin-attributes-data") ||
!strcmp(aTopic, "dom-storage:clear-origin-attributes-data")) {
ClearCaches(LocalStorageCache::kUnloadComplete, pattern, ""_ns);
return NS_OK;
}

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

@ -599,6 +599,13 @@ nsresult SessionStorageManager::Observe(
return NS_OK;
}
// Clear entries which match an OriginAttributesPattern.
if (!strcmp(aTopic, "dom-storage:clear-origin-attributes-data") ||
!strcmp(aTopic, "session-storage:clear-origin-attributes-data")) {
ClearStorages(pattern, aOriginScope);
return NS_OK;
}
if (!strcmp(aTopic, "profile-change")) {
// For case caches are still referenced - clear them completely
ClearStorages(pattern, ""_ns);

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

@ -64,6 +64,7 @@ nsresult StorageObserver::Init() {
obs->AddObserver(sSelf, "perm-changed", true);
obs->AddObserver(sSelf, "last-pb-context-exited", true);
obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
obs->AddObserver(sSelf, "dom-storage:clear-origin-attributes-data", true);
obs->AddObserver(sSelf, "extension:purge-localStorage", true);
obs->AddObserver(sSelf, "browser:purge-sessionStorage", true);
@ -392,19 +393,22 @@ StorageObserver::Observe(nsISupports* aSubject, const char* aTopic,
}
// Clear data of the origins whose prefixes will match the suffix.
if (!strcmp(aTopic, "clear-origin-attributes-data")) {
if (!strcmp(aTopic, "clear-origin-attributes-data") ||
!strcmp(aTopic, "dom-storage:clear-origin-attributes-data")) {
MOZ_ASSERT(XRE_IsParentProcess());
if (NextGenLocalStorageEnabled()) {
return NS_OK;
}
OriginAttributesPattern pattern;
if (!pattern.Init(nsDependentString(aData))) {
NS_ERROR("Cannot parse origin attributes pattern");
return NS_ERROR_FAILURE;
}
if (NextGenLocalStorageEnabled()) {
Notify("session-storage:clear-origin-attributes-data",
nsDependentString(aData));
return NS_OK;
}
for (const uint32_t id : {0, 1}) {
StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
if (NS_WARN_IF(!storageChild)) {
@ -414,7 +418,7 @@ StorageObserver::Observe(nsISupports* aSubject, const char* aTopic,
storageChild->SendClearMatchingOriginAttributes(pattern);
}
Notify("origin-attr-pattern-cleared", nsDependentString(aData));
Notify(aTopic, nsDependentString(aData));
return NS_OK;
}

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

@ -582,8 +582,6 @@ const QuotaCleaner = {
// localStorage: The legacy LocalStorage implementation that will
// eventually be removed depends on this observer notification to clear by
// host. Some other subsystems like Reporting headers depend on this too.
// TODO: Update observers to support clearing by base domain including
// partitioned storage. Bug 1713139.
Services.obs.notifyObservers(
null,
"extension:purge-localStorage",
@ -597,6 +595,18 @@ const QuotaCleaner = {
aBaseDomain
);
// Clear third-party storage partitioned under aBaseDomain.
// This notification is forwarded via the StorageObserver and consumed only
// by the SessionStorageManager and (legacy) LocalStorageManager.
// There is a similar (legacy) notification "clear-origin-attributes-data"
// which additionally clears data across various other storages unrelated to
// the QuotaCleaner.
Services.obs.notifyObservers(
null,
"dom-storage:clear-origin-attributes-data",
JSON.stringify({ partitionKeyPattern: { baseDomain: aBaseDomain } })
);
// ServiceWorkers must be removed before cleaning QuotaManager. We store
// potential errors so we can re-throw later, once all operations have
// completed.

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

@ -7,5 +7,6 @@ support-files =
file_image_cache.html
file_image_cache.jpg
[browser_serviceworkers.js]
[browser_sessionStorage.js]
[browser_quota.js]
support-files = worker.js

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

@ -0,0 +1,188 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const BASE_DOMAIN_A = "example.com";
const ORIGIN_A = `https://${BASE_DOMAIN_A}`;
const ORIGIN_A_HTTP = `http://${BASE_DOMAIN_A}`;
const ORIGIN_A_SUB = `https://test1.${BASE_DOMAIN_A}`;
const BASE_DOMAIN_B = "example.org";
const ORIGIN_B = `https://${BASE_DOMAIN_B}`;
const ORIGIN_B_HTTP = `http://${BASE_DOMAIN_B}`;
const ORIGIN_B_SUB = `https://test1.${BASE_DOMAIN_B}`;
const TEST_ROOT_DIR = getRootDirectory(gTestPath);
// Session storage is only valid for the lifetime of a tab, so we need to keep
// tabs open for the duration of a test.
let originToTabs = {};
function getTestURLForOrigin(origin) {
return TEST_ROOT_DIR.replace("chrome://mochitests/content", origin);
}
function getTestEntryName(origin, partitioned) {
return `${origin}${partitioned ? "_partitioned" : ""}`;
}
async function testHasEntry(origin, isSet, testPartitioned = false) {
let tabs = originToTabs[origin];
for (let tab of tabs) {
// For the partition test we inspect the sessionStorage of the iframe.
let browsingContext = testPartitioned
? tab.linkedBrowser.browsingContext.children[0]
: tab.linkedBrowser.browsingContext;
let actualSet = await SpecialPowers.spawn(
browsingContext,
[getTestEntryName(origin, testPartitioned)],
key => {
return !!content.sessionStorage.getItem(key);
}
);
let msg = `${origin}${isSet ? " " : " not "}set`;
if (testPartitioned) {
msg = "Partitioned under " + msg;
}
is(actualSet, isSet, msg);
}
}
/**
* Creates tabs and sets sessionStorage entries in first party and third party
* context.
* @returns {Promise} - Promise which resolves once all tabs are initialized,
* {@link originToTabs} is populated and (sub-)resources have loaded.
*/
function addTestTabs() {
let promises = [
[ORIGIN_A, ORIGIN_B],
[ORIGIN_A_SUB, ORIGIN_B_SUB],
[ORIGIN_A_HTTP, ORIGIN_B_HTTP],
[ORIGIN_B, ORIGIN_A],
[ORIGIN_B_SUB, ORIGIN_A_SUB],
[ORIGIN_B_HTTP, ORIGIN_A_HTTP],
].map(async ([firstParty, thirdParty]) => {
info("Creating new tab for " + firstParty);
let tab = BrowserTestUtils.addTab(gBrowser, firstParty);
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
info("Creating iframe for " + thirdParty);
await SpecialPowers.spawn(
tab.linkedBrowser,
[getTestEntryName(firstParty, false), thirdParty],
async (storageKey, url) => {
// Set unpartitioned sessionStorage for firstParty.
content.sessionStorage.setItem(storageKey, "foo");
let iframe = content.document.createElement("iframe");
iframe.src = url;
let loadPromise = ContentTaskUtils.waitForEvent(iframe, "load", false);
content.document.body.appendChild(iframe);
await loadPromise;
}
);
await SpecialPowers.spawn(
tab.linkedBrowser.browsingContext.children[0],
[getTestEntryName(thirdParty, true)],
async storageKey => {
// Set sessionStorage in partitioned third-party iframe.
content.sessionStorage.setItem(storageKey, "foo");
}
);
let tabs = originToTabs[firstParty];
if (!tabs) {
tabs = [];
originToTabs[firstParty] = tabs;
}
tabs.push(tab);
});
return Promise.all(promises);
}
function cleanup() {
Object.values(originToTabs)
.flat()
.forEach(BrowserTestUtils.removeTab);
originToTabs = {};
Services.obs.notifyObservers(null, "browser:purge-sessionStorage");
}
add_task(function setup() {
cleanup();
});
add_task(async function test_deleteByBaseDomain() {
await addTestTabs();
// Clear data for base domain of A.
info("Clearing sessionStorage for base domain " + BASE_DOMAIN_A);
await new Promise(resolve => {
Services.clearData.deleteDataFromBaseDomain(
BASE_DOMAIN_A,
false,
Ci.nsIClearDataService.CLEAR_DOM_QUOTA,
resolve
);
});
// All entries for A should have been cleared.
await testHasEntry(ORIGIN_A, false);
await testHasEntry(ORIGIN_A_SUB, false);
await testHasEntry(ORIGIN_A_HTTP, false);
// Entries for B should still exist.
await testHasEntry(ORIGIN_B, true);
await testHasEntry(ORIGIN_B_SUB, true);
await testHasEntry(ORIGIN_B_HTTP, true);
// All partitioned entries for B under A should have been cleared.
await testHasEntry(ORIGIN_A, false, true);
await testHasEntry(ORIGIN_A_SUB, false, true);
await testHasEntry(ORIGIN_A_HTTP, false, true);
// All partitioned entries of A under B should have been cleared.
await testHasEntry(ORIGIN_B, false, true);
await testHasEntry(ORIGIN_B_SUB, false, true);
await testHasEntry(ORIGIN_B_HTTP, false, true);
cleanup();
});
add_task(async function test_deleteAll() {
await addTestTabs();
// Clear all sessionStorage.
info("Clearing sessionStorage for base domain " + BASE_DOMAIN_A);
await new Promise(resolve => {
Services.clearData.deleteData(
Ci.nsIClearDataService.CLEAR_DOM_QUOTA,
resolve
);
});
// All entries should have been cleared.
await testHasEntry(ORIGIN_A, false);
await testHasEntry(ORIGIN_A_SUB, false);
await testHasEntry(ORIGIN_A_HTTP, false);
await testHasEntry(ORIGIN_B, false);
await testHasEntry(ORIGIN_B_SUB, false);
await testHasEntry(ORIGIN_B_HTTP, false);
// All partitioned entries should have been cleared.
await testHasEntry(ORIGIN_A, false, true);
await testHasEntry(ORIGIN_A_SUB, false, true);
await testHasEntry(ORIGIN_A_HTTP, false, true);
await testHasEntry(ORIGIN_B, false, true);
await testHasEntry(ORIGIN_B_SUB, false, true);
await testHasEntry(ORIGIN_B_HTTP, false, true);
cleanup();
});

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

@ -279,11 +279,6 @@ async function runTestHost(storageType) {
add_task(function setup() {
// Allow setting local storage in xpcshell tests.
Services.prefs.setBoolPref("dom.storage.client_validation", false);
// Run with next-gen storage implementation.
// TODO: Remove this pref override once the legacy storage implementation
// fully supports deleteDataFromBaseDomain. See Bug 1713139.
Services.prefs.setBoolPref("dom.storage.next_gen", true);
});
/**