tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 3428dbfba2c87c0b8d486685c1294741e88f77d0
parent c3f7c6c25567350ffc5612d448ef62b734fb01e2
Author: jim <zijin@ualberta.ca>
Date:   Mon, 13 Oct 2025 13:01:01 +0000

Bug 1886894 - add extension:purge-sessionStorage event to do exact domain match when domain is provided. r=robwu,dom-storage-reviewers,edenchuang

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

Diffstat:
Mdom/storage/LocalStorageManager.cpp | 3++-
Mdom/storage/PBackgroundSessionStorageManager.ipdl | 2+-
Mdom/storage/SessionStorageManager.cpp | 45++++++++++++++++++++++++++++++++++++++-------
Mdom/storage/SessionStorageManager.h | 25+++++++++++++++++++------
Mdom/storage/StorageIPC.cpp | 6++++--
Mdom/storage/StorageIPC.h | 4++--
Mdom/storage/StorageObserver.cpp | 4+++-
Mtoolkit/components/extensions/parent/ext-browsingData.js | 4++--
Mtoolkit/components/extensions/test/mochitest/test_ext_browsingData_sessionStorage.html | 16+++++++++-------
9 files changed, 80 insertions(+), 29 deletions(-)

diff --git a/dom/storage/LocalStorageManager.cpp b/dom/storage/LocalStorageManager.cpp @@ -376,7 +376,8 @@ nsresult LocalStorageManager::Observe(const char* aTopic, return NS_OK; } - if (!strcmp(aTopic, "browser:purge-sessionStorage")) { + if (!strcmp(aTopic, "browser:purge-sessionStorage") || + !strcmp(aTopic, "extension:purge-sessionStorage")) { // This is only meant for SessionStorageManager. return NS_OK; } diff --git a/dom/storage/PBackgroundSessionStorageManager.ipdl b/dom/storage/PBackgroundSessionStorageManager.ipdl @@ -23,7 +23,7 @@ sync protocol PBackgroundSessionStorageManager parent: async PBackgroundSessionStorageCache(PrincipalInfo aPrincipalInfo, nsCString aOriginKey); - async ClearStorages(OriginAttributesPattern aPattern, nsCString aOriginScope); + async ClearStorages(OriginAttributesPattern aPattern, nsCString aOriginScope, uint32_t aMode); async DeleteMe(); diff --git a/dom/storage/SessionStorageManager.cpp b/dom/storage/SessionStorageManager.cpp @@ -34,6 +34,21 @@ namespace mozilla::dom { using namespace StorageUtils; +static bool ExactDomainMatch(const nsACString& aOriginKey, + const nsACString& aOriginScope) { + // aOriginScope is reversed domain with trailing dot. + // e.g: example.com => moc.elpmaxe. (see StorageUtils.cpp) + // aOriginKey is reversed domain with trailing dot, plus ":", + // scheme, and optional port. e.g: moc.elpmaxe.:http:80 (see + // https://searchfox.org/firefox-main/rev/987f566373ea82403c5c1235b219bd9e7d56a4aa/caps/BasePrincipal.cpp#1539) + // To check if it is an exact match, we need to ensure that aOriginKey starts + // with aOriginScope and the first character immediately following the + // matching part is ":". i.e: Domain part of aOriginKey is identical to + // aOriginScope. + return StringBeginsWith(aOriginKey, aOriginScope) && + aOriginKey.CharAt(aOriginScope.Length()) == ':'; +} + // Parent process, background thread hashmap that stores top context id and // manager pair. static StaticAutoPtr< @@ -151,7 +166,10 @@ bool RecvClearStoragesForOrigin(const nsACString& aOriginAttrs, } void SessionStorageManagerBase::ClearStoragesInternal( - const OriginAttributesPattern& aPattern, const nsACString& aOriginScope) { + const OriginAttributesPattern& aPattern, const nsACString& aOriginScope, + DomainMatchingMode aMode) { + const bool isExactMatch = aMode == DomainMatchingMode::EXACT_MATCH; + for (const auto& oaEntry : mOATable) { OriginAttributes oa; DebugOnly<bool> ok = oa.PopulateFromSuffix(oaEntry.GetKey()); @@ -164,7 +182,10 @@ void SessionStorageManagerBase::ClearStoragesInternal( OriginKeyHashTable* table = oaEntry.GetWeak(); for (const auto& originKeyEntry : *table) { if (aOriginScope.IsEmpty() || - StringBeginsWith(originKeyEntry.GetKey(), aOriginScope)) { + (!isExactMatch && + StringBeginsWith(originKeyEntry.GetKey(), aOriginScope)) || + (isExactMatch && + ExactDomainMatch(originKeyEntry.GetKey(), aOriginScope))) { const auto cache = originKeyEntry.GetData()->mCache; cache->Clear(false); cache->ResetWriteInfos(); @@ -626,17 +647,19 @@ SessionStorageManager::CheckStorage(nsIPrincipal* aPrincipal, Storage* aStorage, } void SessionStorageManager::ClearStorages( - const OriginAttributesPattern& aPattern, const nsACString& aOriginScope) { + const OriginAttributesPattern& aPattern, const nsACString& aOriginScope, + DomainMatchingMode aMode) { if (CanLoadData()) { nsresult rv = EnsureManager(); if (NS_WARN_IF(NS_FAILED(rv))) { return; } - mActor->SendClearStorages(aPattern, nsCString(aOriginScope)); + mActor->SendClearStorages(aPattern, nsCString(aOriginScope), + static_cast<uint32_t>(aMode)); } - ClearStoragesInternal(aPattern, aOriginScope); + ClearStoragesInternal(aPattern, aOriginScope, aMode); } nsresult SessionStorageManager::Observe( @@ -668,6 +691,13 @@ nsresult SessionStorageManager::Observe( return NS_OK; } + // Clear everything (including so and pb data) from caches and database + // for the given domain. + if (!strcmp(aTopic, "extension:purge-sessionStorage")) { + ClearStorages(pattern, aOriginScope, DomainMatchingMode::EXACT_MATCH); + 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")) { @@ -893,10 +923,11 @@ void BackgroundSessionStorageManager::UpdateData( } void BackgroundSessionStorageManager::ClearStorages( - const OriginAttributesPattern& aPattern, const nsACString& aOriginScope) { + const OriginAttributesPattern& aPattern, const nsACString& aOriginScope, + DomainMatchingMode aMode) { MOZ_ASSERT(XRE_IsParentProcess()); ::mozilla::ipc::AssertIsOnBackgroundThread(); - ClearStoragesInternal(aPattern, aOriginScope); + ClearStoragesInternal(aPattern, aOriginScope, aMode); } void BackgroundSessionStorageManager::ClearStoragesForOrigin( diff --git a/dom/storage/SessionStorageManager.h b/dom/storage/SessionStorageManager.h @@ -43,6 +43,16 @@ bool RecvLoadSessionStorageData( bool RecvClearStoragesForOrigin(const nsACString& aOriginAttrs, const nsACString& aOriginKey); +// DomainMatchingMode is used to allow ClearStorages to mimic one of +// the LSNG behaviours - exact domain match. +// When ClearStorages is invoked with EXACT_MATCH, it clears only the +// data for a given domain, and would not affect any subdomains. +// Currently EXACT_MATCH is only passed when browser:purge-sessionStorage event +// is triggered. By default, ClearStorages is called with PREFIX_MATCH, which +// clears data for a given domain and all the subdomains. This ensures that the +// behaviour of other events that call ClearStorages remain unchanged. +enum class DomainMatchingMode { PREFIX_MATCH, EXACT_MATCH }; + class BrowsingContext; class ContentParent; class SSSetItemInfo; @@ -90,8 +100,9 @@ class SessionStorageManagerBase { FlippedOnce<false> mLoaded; }; - void ClearStoragesInternal(const OriginAttributesPattern& aPattern, - const nsACString& aOriginScope); + void ClearStoragesInternal( + const OriginAttributesPattern& aPattern, const nsACString& aOriginScope, + DomainMatchingMode aMode = DomainMatchingMode::PREFIX_MATCH); void ClearStoragesForOriginInternal(const nsACString& aOriginAttrs, const nsACString& aOriginKey); @@ -153,8 +164,9 @@ class SessionStorageManager final : public SessionStorageManagerBase, SessionStorageCache* aCloneFrom, RefPtr<SessionStorageCache>* aRetVal); - void ClearStorages(const OriginAttributesPattern& aPattern, - const nsACString& aOriginScope); + void ClearStorages( + const OriginAttributesPattern& aPattern, const nsACString& aOriginScope, + DomainMatchingMode aMode = DomainMatchingMode::PREFIX_MATCH); SessionStorageCacheChild* EnsureCache(nsIPrincipal& aPrincipal, const nsACString& aOriginKey, @@ -213,8 +225,9 @@ class BackgroundSessionStorageManager final : public SessionStorageManagerBase { void UpdateData(const nsACString& aOriginAttrs, const nsACString& aOriginKey, const nsTArray<SSSetItemInfo>& aData); - void ClearStorages(const OriginAttributesPattern& aPattern, - const nsACString& aOriginScope); + void ClearStorages( + const OriginAttributesPattern& aPattern, const nsACString& aOriginScope, + DomainMatchingMode aMode = DomainMatchingMode::PREFIX_MATCH); void ClearStoragesForOrigin(const nsACString& aOriginAttrs, const nsACString& aOriginKey); diff --git a/dom/storage/StorageIPC.cpp b/dom/storage/StorageIPC.cpp @@ -1452,9 +1452,11 @@ BackgroundSessionStorageManager* SessionStorageManagerParent::GetManager() } mozilla::ipc::IPCResult SessionStorageManagerParent::RecvClearStorages( - const OriginAttributesPattern& aPattern, const nsACString& aOriginScope) { + const OriginAttributesPattern& aPattern, const nsACString& aOriginScope, + const uint32_t& aMode) { ::mozilla::ipc::AssertIsOnBackgroundThread(); - mBackgroundManager->ClearStorages(aPattern, aOriginScope); + mBackgroundManager->ClearStorages(aPattern, aOriginScope, + static_cast<DomainMatchingMode>(aMode)); return IPC_OK(); } diff --git a/dom/storage/StorageIPC.h b/dom/storage/StorageIPC.h @@ -561,8 +561,8 @@ class SessionStorageManagerParent final BackgroundSessionStorageManager* GetManager() const; mozilla::ipc::IPCResult RecvClearStorages( - const OriginAttributesPattern& aPattern, - const nsACString& aOriginScope) override; + const OriginAttributesPattern& aPattern, const nsACString& aOriginScope, + const uint32_t& aMode) override; private: ~SessionStorageManagerParent(); diff --git a/dom/storage/StorageObserver.cpp b/dom/storage/StorageObserver.cpp @@ -71,6 +71,7 @@ nsresult StorageObserver::Init() { obs->AddObserver(sSelf, "dom-storage:clear-origin-attributes-data", true); obs->AddObserver(sSelf, "extension:purge-localStorage", true); obs->AddObserver(sSelf, "browser:purge-sessionStorage", true); + obs->AddObserver(sSelf, "extension:purge-sessionStorage", true); // Shutdown obs->AddObserver(sSelf, "profile-after-change", true); @@ -358,7 +359,8 @@ StorageObserver::Observe(nsISupports* aSubject, const char* aTopic, return NS_OK; } - if (!strcmp(aTopic, "browser:purge-sessionStorage")) { + if (!strcmp(aTopic, "browser:purge-sessionStorage") || + !strcmp(aTopic, "extension:purge-sessionStorage")) { // The caller passed an nsIClearBySiteEntry object which consists of both // site and pattern. // If both are passed, aSubject takes precedence over aData. diff --git a/toolkit/components/extensions/parent/ext-browsingData.js b/toolkit/components/extensions/parent/ext-browsingData.js @@ -223,9 +223,9 @@ const clearLocalStorage = async function (options) { ) : ""; - Services.obs.notifyObservers(entry, "browser:purge-sessionStorage"); + Services.obs.notifyObservers(entry, "extension:purge-sessionStorage"); } else { - Services.obs.notifyObservers(null, "browser:purge-sessionStorage"); + Services.obs.notifyObservers(null, "extension:purge-sessionStorage"); } }; diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_sessionStorage.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_sessionStorage.html @@ -65,8 +65,7 @@ add_task(async function testLocalStorage() { }); await browser.tabs.sendMessage(tabs["https://example.com/"].id, "checkSessionStorageCleared"); await browser.tabs.sendMessage(tabs["https://example.net/"].id, "checkSessionStorageSet"); - ////TODO: Legacy sessionStorage implementation would not recognize the difference between example.com and test1.example.com, temporarily use example.org instead. - await browser.tabs.sendMessage(tabs["https://test1.example.org/"].id, "checkSessionStorageSet"); + await browser.tabs.sendMessage(tabs["https://test1.example.com/"].id, "checkSessionStorageSet"); await sendMessageToTabs(tabs, "resetSessionStorage"); @@ -93,7 +92,7 @@ add_task(async function testLocalStorage() { // TODO: containers support is lacking on GeckoView (Bug 1643740) if (!navigator.userAgent.includes("Android")) { - await browser.tabs.sendMessage(tabs["https://test1.example.org/"].id, "checkSessionStorageCleared"); + await browser.tabs.sendMessage(tabs["https://test1.example.com/"].id, "checkSessionStorageCleared"); } await sendMessageToTabs(tabs, "resetSessionStorage"); @@ -102,13 +101,16 @@ add_task(async function testLocalStorage() { // Happy path: Passing both hostname and cookieStoreId would clear the sessionStorage. await browser.browsingData.removeLocalStorage({ cookieStoreId: "firefox-container-1", - hostnames: ["example.org"], + hostnames: ["test1.example.com"], }); await browser.tabs.sendMessage(tabs["https://example.com/"].id, "checkSessionStorageSet"); await browser.tabs.sendMessage(tabs["https://example.net/"].id, "checkSessionStorageSet"); - await browser.tabs.sendMessage(tabs["https://test1.example.org/"].id, "checkSessionStorageCleared"); + // TODO: containers support is lacking on GeckoView (Bug 1643740) + if (!navigator.userAgent.includes("Android")) { + await browser.tabs.sendMessage(tabs["https://test1.example.com/"].id, "checkSessionStorageCleared"); + } await sendMessageToTabs(tabs, "resetSessionStorage"); await sendMessageToTabs(tabs, "checkSessionStorageSet"); // Hostname doesn't match, so nothing cleared. @@ -177,7 +179,7 @@ add_task(async function testLocalStorage() { { url: "https://example.com" }, { url: "https://example.net" }, { - url: "https://test1.example.org", + url: "https://test1.example.com", cookieStoreId: 'firefox-container-1', }, ]; @@ -234,7 +236,7 @@ add_task(async function testLocalStorage() { matches: [ "https://example.com/", "https://example.net/", - "https://test1.example.org/", + "https://test1.example.com/", ], js: ["content-script.js"], run_at: "document_end",