зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
38e07b9a08
Коммит
da24464574
|
@ -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);
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче