diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp index c32c52ea4097..691bec47a91b 100644 --- a/dom/base/ChromeUtils.cpp +++ b/dom/base/ChromeUtils.cpp @@ -753,11 +753,20 @@ void ChromeUtils::ClearRecentJSDevError(GlobalObject&) { } #endif // NIGHTLY_BUILD -void ChromeUtils::ClearStyleSheetCache(GlobalObject&, - nsIPrincipal* aForPrincipal) { +void ChromeUtils::ClearStyleSheetCacheByPrincipal(GlobalObject&, + nsIPrincipal* aForPrincipal) { SharedStyleSheetCache::Clear(aForPrincipal); } +void ChromeUtils::ClearStyleSheetCacheByBaseDomain( + GlobalObject&, const nsACString& aBaseDomain) { + SharedStyleSheetCache::Clear(nullptr, &aBaseDomain); +} + +void ChromeUtils::ClearStyleSheetCache(GlobalObject&) { + SharedStyleSheetCache::Clear(); +} + #define PROCTYPE_TO_WEBIDL_CASE(_procType, _webidl) \ case mozilla::ProcType::_procType: \ return WebIDLProcType::_webidl diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h index 3983481136a6..e496e824292b 100644 --- a/dom/base/ChromeUtils.h +++ b/dom/base/ChromeUtils.h @@ -151,7 +151,13 @@ class ChromeUtils { static void ClearRecentJSDevError(GlobalObject& aGlobal); - static void ClearStyleSheetCache(GlobalObject&, nsIPrincipal* aForPrincipal); + static void ClearStyleSheetCacheByPrincipal(GlobalObject&, + nsIPrincipal* aForPrincipal); + + static void ClearStyleSheetCacheByBaseDomain(GlobalObject& aGlobal, + const nsACString& aBaseDomain); + + static void ClearStyleSheetCache(GlobalObject& aGlobal); static already_AddRefed RequestPerformanceMetrics( GlobalObject& aGlobal, ErrorResult& aRv); diff --git a/dom/chrome-webidl/ChromeUtils.webidl b/dom/chrome-webidl/ChromeUtils.webidl index 355284891cb7..b250ddbdf357 100644 --- a/dom/chrome-webidl/ChromeUtils.webidl +++ b/dom/chrome-webidl/ChromeUtils.webidl @@ -193,9 +193,20 @@ namespace ChromeUtils { #endif // NIGHTLY_BUILD /** - * Clears the stylesheet cache. + * Clears the stylesheet cache by baseDomain. This includes associated + * state-partitioned cache. */ - void clearStyleSheetCache(optional Principal? principal = null); + void clearStyleSheetCacheByBaseDomain(UTF8String baseDomain); + + /** + * Clears the stylesheet cache by principal. + */ + void clearStyleSheetCacheByPrincipal(Principal principal); + + /** + * Clears the entire stylesheet cache. + */ + void clearStyleSheetCache(); /** * If the profiler is currently running and recording the current thread, diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 2ea7097ad6b3..5ba93a4bfaa6 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -2009,11 +2009,13 @@ mozilla::ipc::IPCResult ContentChild::RecvRegisterChromeItem( return IPC_OK(); } - mozilla::ipc::IPCResult ContentChild::RecvClearStyleSheetCache( - const Maybe>& aForPrincipal) { - nsIPrincipal* prin = aForPrincipal ? aForPrincipal.value().get() : nullptr; - SharedStyleSheetCache::Clear(prin); + const Maybe>& aForPrincipal, + const Maybe& aBaseDomain) { + nsIPrincipal* principal = + aForPrincipal ? aForPrincipal.value().get() : nullptr; + const nsCString* baseDomain = aBaseDomain ? aBaseDomain.ptr() : nullptr; + SharedStyleSheetCache::Clear(principal, baseDomain); return IPC_OK(); } diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index ebbafd59372a..d6a01a25f6b2 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -277,7 +277,8 @@ class ContentChild final : public PContentChild, const ChromeRegistryItem& item); mozilla::ipc::IPCResult RecvClearStyleSheetCache( - const Maybe>& aForPrincipal); + const Maybe>& aForPrincipal, + const Maybe& aBaseDomain); mozilla::ipc::IPCResult RecvClearImageCache(const bool& privateLoader, const bool& chrome); diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 2025a57e07ef..efe351c491a4 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -584,7 +584,8 @@ child: async ClearImageCache(bool privateLoader, bool chrome); - async ClearStyleSheetCache(nsIPrincipal? aForPrincipal); + async ClearStyleSheetCache(nsIPrincipal? aForPrincipal, + nsCString? aBaseDomain); async SetOffline(bool offline); async SetConnectivity(bool connectivity); diff --git a/layout/style/Loader.h b/layout/style/Loader.h index d171c1a54850..82a44aa149d0 100644 --- a/layout/style/Loader.h +++ b/layout/style/Loader.h @@ -130,6 +130,8 @@ class SheetLoadDataHashKey : public PLDHashEntryHdr { nsIPrincipal* LoaderPrincipal() const { return mLoaderPrincipal; } + nsIPrincipal* PartitionPrincipal() const { return mPartitionPrincipal; } + css::SheetParsingMode ParsingMode() const { return mParsingMode; } enum { ALLOW_MEMMOVE = true }; diff --git a/layout/style/SharedStyleSheetCache.cpp b/layout/style/SharedStyleSheetCache.cpp index c754e1c96bd0..5c4a892acaf2 100644 --- a/layout/style/SharedStyleSheetCache.cpp +++ b/layout/style/SharedStyleSheetCache.cpp @@ -7,6 +7,7 @@ #include "SharedStyleSheetCache.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/StoragePrincipalHelper.h" #include "mozilla/StyleSheet.h" #include "mozilla/css/SheetLoadData.h" #include "mozilla/dom/ContentParent.h" @@ -28,13 +29,16 @@ using IsAlternate = css::Loader::IsAlternate; SharedStyleSheetCache* SharedStyleSheetCache::sInstance; -void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal) { +void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal, + const nsACString* aBaseDomain) { using ContentParent = dom::ContentParent; if (XRE_IsParentProcess()) { auto forPrincipal = aForPrincipal ? Some(RefPtr(aForPrincipal)) : Nothing(); + auto baseDomain = aBaseDomain ? Some(nsCString(*aBaseDomain)) : Nothing(); + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { - Unused << cp->SendClearStyleSheetCache(forPrincipal); + Unused << cp->SendClearStyleSheetCache(forPrincipal, baseDomain); } } @@ -42,14 +46,43 @@ void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal) { return; } - if (!aForPrincipal) { + // No filter, clear all. + if (!aForPrincipal && !aBaseDomain) { sInstance->mCompleteSheets.Clear(); return; } for (auto iter = sInstance->mCompleteSheets.Iter(); !iter.Done(); iter.Next()) { - if (iter.Key().Principal()->Equals(aForPrincipal)) { + const bool shouldRemove = [&] { + if (aForPrincipal && iter.Key().Principal()->Equals(aForPrincipal)) { + return true; + } + if (!aBaseDomain) { + return false; + } + // Clear by baseDomain. + nsIPrincipal* partitionPrincipal = iter.Key().PartitionPrincipal(); + + // Clear entries with matching base domain. This includes entries + // which are partitioned under other top level sites (= have a + // partitionKey set). + nsAutoCString principalBaseDomain; + nsresult rv = partitionPrincipal->GetBaseDomain(principalBaseDomain); + if (NS_SUCCEEDED(rv) && principalBaseDomain.Equals(*aBaseDomain)) { + return true; + } + + // Clear entries partitioned under aBaseDomain. + nsAutoString partitionKeyBaseDomain; + bool ok = StoragePrincipalHelper::GetBaseDomainFromPartitionKey( + partitionPrincipal->OriginAttributesRef().mPartitionKey, + partitionKeyBaseDomain); + return ok && + NS_ConvertUTF16toUTF8(partitionKeyBaseDomain).Equals(*aBaseDomain); + }(); + + if (shouldRemove) { iter.Remove(); } } diff --git a/layout/style/SharedStyleSheetCache.h b/layout/style/SharedStyleSheetCache.h index 00a0ea429371..c9a8130cbca4 100644 --- a/layout/style/SharedStyleSheetCache.h +++ b/layout/style/SharedStyleSheetCache.h @@ -99,7 +99,8 @@ class SharedStyleSheetCache final : public nsIMemoryReporter { // be called when the document goes away, or when its principal changes. void UnregisterLoader(css::Loader&); - static void Clear(nsIPrincipal* aForPrincipal = nullptr); + static void Clear(nsIPrincipal* aForPrincipal = nullptr, + const nsACString* aBaseDomain = nullptr); private: static already_AddRefed Create(); diff --git a/toolkit/components/cleardata/ClearDataService.jsm b/toolkit/components/cleardata/ClearDataService.jsm index fb5748136c3d..4fae2569d63d 100644 --- a/toolkit/components/cleardata/ClearDataService.jsm +++ b/toolkit/components/cleardata/ClearDataService.jsm @@ -251,17 +251,16 @@ const CSSCacheCleaner = { aOriginAttributes ); - ChromeUtils.clearStyleSheetCache(httpPrincipal); - ChromeUtils.clearStyleSheetCache(httpsPrincipal); + ChromeUtils.clearStyleSheetCacheByPrincipal(httpPrincipal); + ChromeUtils.clearStyleSheetCacheByPrincipal(httpsPrincipal); }, async deleteByPrincipal(aPrincipal) { - ChromeUtils.clearStyleSheetCache(aPrincipal); + ChromeUtils.clearStyleSheetCacheByPrincipal(aPrincipal); }, - deleteByBaseDomain(aBaseDomain) { - // TODO: Bug 1705032 - return this.deleteByHost(aBaseDomain, {}); + async deleteByBaseDomain(aBaseDomain) { + ChromeUtils.clearStyleSheetCacheByBaseDomain(aBaseDomain); }, async deleteAll() { diff --git a/toolkit/components/cleardata/tests/browser/browser.ini b/toolkit/components/cleardata/tests/browser/browser.ini index 92373c3ec7f6..9656e61d9133 100644 --- a/toolkit/components/cleardata/tests/browser/browser.ini +++ b/toolkit/components/cleardata/tests/browser/browser.ini @@ -1,3 +1,7 @@ +[browser_css_cache.js] +support-files = + file_css_cache.css + file_css_cache.html [browser_serviceworkers.js] [browser_quota.js] support-files = worker.js diff --git a/toolkit/components/cleardata/tests/browser/browser_css_cache.js b/toolkit/components/cleardata/tests/browser/browser_css_cache.js new file mode 100644 index 000000000000..47088e50114c --- /dev/null +++ b/toolkit/components/cleardata/tests/browser/browser_css_cache.js @@ -0,0 +1,129 @@ +/* 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); + +// Stylesheets are cached per process, so we need to keep tabs open for the +// duration of a test. +let tabs = {}; + +function getTestURLForOrigin(origin) { + return ( + TEST_ROOT_DIR.replace("chrome://mochitests/content", origin) + + "file_css_cache.html" + ); +} + +async function testCached(origin, isCached) { + let url = getTestURLForOrigin(origin); + + let numParsed; + + let tab = tabs[origin]; + let loadedPromise; + if (!tab) { + info("Creating new tab for " + url); + tab = BrowserTestUtils.addTab(gBrowser, url); + loadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + tabs[origin] = tab; + } else { + loadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + tab.linkedBrowser.reload(); + } + await loadedPromise; + + numParsed = await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + return SpecialPowers.getDOMWindowUtils(content).parsedStyleSheets; + }); + + // Stylesheets is cached if numParsed is 0. + is(!numParsed, isCached, `${origin} is${isCached ? " " : " not "}cached`); +} + +async function addTestTabs() { + await testCached(ORIGIN_A, false); + await testCached(ORIGIN_A_SUB, false); + await testCached(ORIGIN_A_HTTP, false); + await testCached(ORIGIN_B, false); + await testCached(ORIGIN_B_SUB, false); + await testCached(ORIGIN_B_HTTP, false); + // Test that the cache has been populated. + await testCached(ORIGIN_A, true); + await testCached(ORIGIN_A_SUB, true); + await testCached(ORIGIN_A_HTTP, true); + await testCached(ORIGIN_B, true); + await testCached(ORIGIN_B_SUB, true); + await testCached(ORIGIN_B_HTTP, true); +} + +async function cleanupTestTabs() { + Object.values(tabs).forEach(BrowserTestUtils.removeTab); + tabs = {}; +} + +add_task(async function test_deleteByPrincipal() { + await addTestTabs(); + + // Clear data for content principal of A + info("Clearing cache for principal " + ORIGIN_A); + await new Promise(resolve => { + Services.clearData.deleteDataFromPrincipal( + Services.scriptSecurityManager.createContentPrincipalFromOrigin(ORIGIN_A), + false, + Ci.nsIClearDataService.CLEAR_CSS_CACHE, + resolve + ); + }); + + // Only the cache entry for ORIGIN_A should have been cleared. + await testCached(ORIGIN_A, false); + await testCached(ORIGIN_A_SUB, true); + await testCached(ORIGIN_A_HTTP, true); + await testCached(ORIGIN_B, true); + await testCached(ORIGIN_B_SUB, true); + await testCached(ORIGIN_B_HTTP, true); + + // Cleanup + cleanupTestTabs(); + ChromeUtils.clearStyleSheetCache(); +}); + +add_task(async function test_deleteByBaseDomain() { + await addTestTabs(); + + // Clear data for base domain of A. + info("Clearing cache for base domain " + BASE_DOMAIN_A); + await new Promise(resolve => { + Services.clearData.deleteDataFromBaseDomain( + BASE_DOMAIN_A, + false, + Ci.nsIClearDataService.CLEAR_CSS_CACHE, + resolve + ); + }); + + // All entries for A should have been cleared. + await testCached(ORIGIN_A, false); + await testCached(ORIGIN_A_SUB, false); + await testCached(ORIGIN_A_HTTP, false); + // Entries for B should still exist. + await testCached(ORIGIN_B, true); + await testCached(ORIGIN_B_SUB, true); + await testCached(ORIGIN_B_HTTP, true); + + // Cleanup + cleanupTestTabs(); + ChromeUtils.clearStyleSheetCache(); +}); diff --git a/toolkit/components/cleardata/tests/browser/file_css_cache.css b/toolkit/components/cleardata/tests/browser/file_css_cache.css new file mode 100644 index 000000000000..2ceb1b7e0bb4 --- /dev/null +++ b/toolkit/components/cleardata/tests/browser/file_css_cache.css @@ -0,0 +1,3 @@ +:root { + background-color: lime; +} diff --git a/toolkit/components/cleardata/tests/browser/file_css_cache.html b/toolkit/components/cleardata/tests/browser/file_css_cache.html new file mode 100644 index 000000000000..b382bc188781 --- /dev/null +++ b/toolkit/components/cleardata/tests/browser/file_css_cache.html @@ -0,0 +1,6 @@ + + + + + +