Bug 1915419 - CSSCacheCleaner, JSCacheCleaner support for OriginAttributesPattern. r=emilio,anti-tracking-reviewers,timhuang

Differential Revision: https://phabricator.services.mozilla.com/D220713
This commit is contained in:
Paul Zuehlcke
2024-09-16 13:25:32 +00:00
parent 8dc3f9fdd6
commit 2ac59307af
14 changed files with 198 additions and 82 deletions

View File

@@ -1361,12 +1361,14 @@ void ChromeUtils::ClearRecentJSDevError(GlobalObject&) {
void ChromeUtils::ClearStyleSheetCacheByPrincipal(GlobalObject&,
nsIPrincipal* aForPrincipal) {
SharedStyleSheetCache::Clear(aForPrincipal);
SharedStyleSheetCache::Clear(Some(aForPrincipal));
}
void ChromeUtils::ClearStyleSheetCacheByBaseDomain(
GlobalObject&, const nsACString& aBaseDomain) {
SharedStyleSheetCache::Clear(nullptr, &aBaseDomain);
void ChromeUtils::ClearStyleSheetCacheBySite(
GlobalObject&, const nsACString& aSchemelessSite,
const dom::OriginAttributesPatternDictionary& aPattern) {
SharedStyleSheetCache::Clear(Nothing(), Some(nsCString(aSchemelessSite)),
Some(OriginAttributesPattern(aPattern)));
}
void ChromeUtils::ClearStyleSheetCache(GlobalObject&) {
@@ -1375,12 +1377,14 @@ void ChromeUtils::ClearStyleSheetCache(GlobalObject&) {
void ChromeUtils::ClearScriptCacheByPrincipal(GlobalObject&,
nsIPrincipal* aForPrincipal) {
SharedScriptCache::Clear(aForPrincipal);
SharedScriptCache::Clear(Some(aForPrincipal));
}
void ChromeUtils::ClearScriptCacheByBaseDomain(GlobalObject&,
const nsACString& aBaseDomain) {
SharedScriptCache::Clear(nullptr, &aBaseDomain);
void ChromeUtils::ClearScriptCacheBySite(
GlobalObject&, const nsACString& aSchemelessSite,
const dom::OriginAttributesPatternDictionary& aPattern) {
SharedScriptCache::Clear(Nothing(), Some(nsCString(aSchemelessSite)),
Some(aPattern));
}
void ChromeUtils::ClearScriptCache(GlobalObject&) {

View File

@@ -185,16 +185,18 @@ class ChromeUtils {
static void ClearStyleSheetCacheByPrincipal(GlobalObject&,
nsIPrincipal* aForPrincipal);
static void ClearStyleSheetCacheByBaseDomain(GlobalObject& aGlobal,
const nsACString& aBaseDomain);
static void ClearStyleSheetCacheBySite(
GlobalObject&, const nsACString& aSchemelessSite,
const dom::OriginAttributesPatternDictionary& aPattern);
static void ClearStyleSheetCache(GlobalObject& aGlobal);
static void ClearScriptCacheByPrincipal(GlobalObject&,
nsIPrincipal* aForPrincipal);
static void ClearScriptCacheByBaseDomain(GlobalObject& aGlobal,
const nsACString& aBaseDomain);
static void ClearScriptCacheBySite(
GlobalObject& aGlobal, const nsACString& aSchemelessSite,
const dom::OriginAttributesPatternDictionary& aPattern);
static void ClearScriptCache(GlobalObject& aGlobal);

View File

@@ -228,10 +228,10 @@ namespace ChromeUtils {
#endif // NIGHTLY_BUILD
/**
* Clears the stylesheet cache by baseDomain. This includes associated
* Clears the stylesheet cache by site. This includes associated
* state-partitioned cache.
*/
undefined clearStyleSheetCacheByBaseDomain(UTF8String baseDomain);
undefined clearStyleSheetCacheBySite(UTF8String schemelessSite, optional OriginAttributesPatternDictionary pattern = {});
/**
* Clears the stylesheet cache by principal.
@@ -244,10 +244,10 @@ namespace ChromeUtils {
undefined clearStyleSheetCache();
/**
* Clears the JavaScript cache by baseDomain. This includes associated
* Clears the JavaScript cache by schemeless site. This includes associated
* state-partitioned cache.
*/
undefined clearScriptCacheByBaseDomain(UTF8String baseDomain);
undefined clearScriptCacheBySite(UTF8String schemelessSite, optional OriginAttributesPatternDictionary pattern = {});
/**
* Clears the JavaScript cache by principal.

View File

@@ -2090,22 +2090,18 @@ mozilla::ipc::IPCResult ContentChild::RecvRegisterChromeItem(
return IPC_OK();
}
mozilla::ipc::IPCResult ContentChild::RecvClearStyleSheetCache(
const Maybe<RefPtr<nsIPrincipal>>& aForPrincipal,
const Maybe<nsCString>& aBaseDomain) {
nsIPrincipal* principal =
aForPrincipal ? aForPrincipal.value().get() : nullptr;
const nsCString* baseDomain = aBaseDomain ? aBaseDomain.ptr() : nullptr;
SharedStyleSheetCache::Clear(principal, baseDomain);
const Maybe<RefPtr<nsIPrincipal>>& aPrincipal,
const Maybe<nsCString>& aSchemelessSite,
const Maybe<OriginAttributesPattern>& aPattern) {
SharedStyleSheetCache::Clear(aPrincipal, aSchemelessSite, aPattern);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentChild::RecvClearScriptCache(
const Maybe<RefPtr<nsIPrincipal>>& aForPrincipal,
const Maybe<nsCString>& aBaseDomain) {
nsIPrincipal* principal =
aForPrincipal ? aForPrincipal.value().get() : nullptr;
const nsCString* baseDomain = aBaseDomain ? aBaseDomain.ptr() : nullptr;
SharedScriptCache::Clear(principal, baseDomain);
const Maybe<RefPtr<nsIPrincipal>>& aPrincipal,
const Maybe<nsCString>& aSchemelessSite,
const Maybe<OriginAttributesPattern>& aPattern) {
SharedScriptCache::Clear(aPrincipal, aSchemelessSite, aPattern);
return IPC_OK();
}

View File

@@ -246,12 +246,14 @@ class ContentChild final : public PContentChild,
const ChromeRegistryItem& item);
mozilla::ipc::IPCResult RecvClearStyleSheetCache(
const Maybe<RefPtr<nsIPrincipal>>& aForPrincipal,
const Maybe<nsCString>& aBaseDomain);
const Maybe<RefPtr<nsIPrincipal>>& aPrincipal,
const Maybe<nsCString>& aSchemelessSite,
const Maybe<OriginAttributesPattern>& aPattern);
mozilla::ipc::IPCResult RecvClearScriptCache(
const Maybe<RefPtr<nsIPrincipal>>& aForPrincipal,
const Maybe<nsCString>& aBaseDomain);
const Maybe<RefPtr<nsIPrincipal>>& aPrincipal,
const Maybe<nsCString>& aSchemelessSite,
const Maybe<OriginAttributesPattern>& aPattern);
mozilla::ipc::IPCResult RecvClearImageCacheFromPrincipal(
nsIPrincipal* aPrincipal);

View File

@@ -109,6 +109,7 @@ using mozilla::widget::ThemeChangeKind from "mozilla/widget/WidgetMessageUtils.h
using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h";
[MoveOnly=data] using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h";
using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
using mozilla::OriginAttributesPattern from "mozilla/dom/quota/SerializationHelpers.h";
using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h";
using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h";
@@ -667,11 +668,13 @@ child:
async ClearImageCache(bool privateLoader, bool chrome);
async ClearStyleSheetCache(nullable nsIPrincipal? aForPrincipal,
nsCString? aBaseDomain);
async ClearStyleSheetCache(nullable nsIPrincipal? aPrincipal,
nsCString? aSchemelessSite,
OriginAttributesPattern? aPattern);
async ClearScriptCache(nullable nsIPrincipal? aForPrincipal,
nsCString? aBaseDomain);
async ClearScriptCache(nullable nsIPrincipal? aPrincipal,
nsCString? aSchemelessSite,
OriginAttributesPattern? aPattern);
async SetOffline(bool offline);
async SetConnectivity(bool connectivity);

View File

@@ -84,13 +84,36 @@ struct ParamTraits<mozilla::OriginAttributesPattern> {
WriteParam(aWriter, aParam.mPrivateBrowsingId);
WriteParam(aWriter, aParam.mUserContextId);
WriteParam(aWriter, aParam.mGeckoViewSessionContextId);
WriteParam(aWriter, aParam.mPartitionKey);
WriteParam(aWriter, aParam.mPartitionKeyPattern);
}
static bool Read(MessageReader* aReader, paramType* aResult) {
return ReadParam(aReader, &aResult->mFirstPartyDomain) &&
ReadParam(aReader, &aResult->mPrivateBrowsingId) &&
ReadParam(aReader, &aResult->mUserContextId) &&
ReadParam(aReader, &aResult->mGeckoViewSessionContextId);
ReadParam(aReader, &aResult->mGeckoViewSessionContextId) &&
ReadParam(aReader, &aResult->mPartitionKey) &&
ReadParam(aReader, &aResult->mPartitionKeyPattern);
}
};
template <>
struct ParamTraits<mozilla::dom::PartitionKeyPatternDictionary> {
typedef mozilla::dom::PartitionKeyPatternDictionary paramType;
static void Write(MessageWriter* aWriter, const paramType& aParam) {
WriteParam(aWriter, aParam.mScheme);
WriteParam(aWriter, aParam.mBaseDomain);
WriteParam(aWriter, aParam.mPort);
WriteParam(aWriter, aParam.mForeignByAncestorContext);
}
static bool Read(MessageReader* aReader, paramType* aResult) {
return ReadParam(aReader, &aResult->mScheme) &&
ReadParam(aReader, &aResult->mBaseDomain) &&
ReadParam(aReader, &aResult->mPort) &&
ReadParam(aReader, &aResult->mForeignByAncestorContext);
}
};

View File

@@ -138,21 +138,19 @@ SharedScriptCache::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
void SharedScriptCache::Clear(nsIPrincipal* aForPrincipal,
const nsACString* aBaseDomain) {
void SharedScriptCache::Clear(const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
const Maybe<nsCString>& aSchemelessSite,
const Maybe<OriginAttributesPattern>& aPattern) {
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->SendClearScriptCache(forPrincipal, baseDomain);
Unused << cp->SendClearScriptCache(aPrincipal, aSchemelessSite, aPattern);
}
}
if (sSingleton) {
sSingleton->ClearInProcess(aForPrincipal, aBaseDomain);
sSingleton->ClearInProcess(aPrincipal, aSchemelessSite, aPattern);
}
}

View File

@@ -189,8 +189,9 @@ class SharedScriptCache final
// a sheet cache (loaders that are not owned by a document).
static void LoadCompleted(SharedScriptCache*, ScriptLoadData&);
using Base::LoadCompleted;
static void Clear(nsIPrincipal* aForPrincipal = nullptr,
const nsACString* aBaseDomain = nullptr);
static void Clear(const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal = Nothing(),
const Maybe<nsCString>& aSchemelessSite = Nothing(),
const Maybe<OriginAttributesPattern>& aPattern = Nothing());
protected:
~SharedScriptCache();

View File

@@ -199,21 +199,21 @@ SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
return NS_OK;
}
void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal,
const nsACString* aBaseDomain) {
void SharedStyleSheetCache::Clear(
const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
const Maybe<nsCString>& aSchemelessSite,
const Maybe<OriginAttributesPattern>& aPattern) {
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, baseDomain);
Unused << cp->SendClearStyleSheetCache(aPrincipal, aSchemelessSite,
aPattern);
}
}
if (sSingleton) {
sSingleton->ClearInProcess(aForPrincipal, aBaseDomain);
sSingleton->ClearInProcess(aPrincipal, aSchemelessSite, aPattern);
}
}

View File

@@ -66,8 +66,9 @@ class SharedStyleSheetCache final
using Base::LoadCompleted;
static void LoadCompletedInternal(SharedStyleSheetCache*, css::SheetLoadData&,
nsTArray<RefPtr<css::SheetLoadData>>&);
static void Clear(nsIPrincipal* aForPrincipal = nullptr,
const nsACString* aBaseDomain = nullptr);
static void Clear(const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal = Nothing(),
const Maybe<nsCString>& aSchemelessSite = Nothing(),
const Maybe<OriginAttributesPattern>& aPattern = Nothing());
protected:
void InsertIfNeeded(css::SheetLoadData&);

View File

@@ -156,8 +156,9 @@ class SharedSubResourceCache {
// to be called when the document goes away, or when its principal changes.
void UnregisterLoader(Loader&);
void ClearInProcess(nsIPrincipal* aForPrincipal = nullptr,
const nsACString* aBaseDomain = nullptr);
void ClearInProcess(const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
const Maybe<nsCString>& aSchemelessSite,
const Maybe<OriginAttributesPattern>& aPattern);
protected:
void CancelPendingLoadsForLoader(Loader&);
@@ -193,36 +194,52 @@ class SharedSubResourceCache {
template <typename Traits, typename Derived>
void SharedSubResourceCache<Traits, Derived>::ClearInProcess(
nsIPrincipal* aForPrincipal, const nsACString* aBaseDomain) {
if (!aForPrincipal && !aBaseDomain) {
const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
const Maybe<nsCString>& aSchemelessSite,
const Maybe<OriginAttributesPattern>& aPattern) {
MOZ_ASSERT(aSchemelessSite.isSome() == aPattern.isSome(),
"Must pass both site and OA pattern.");
if (!aPrincipal && !aSchemelessSite) {
mComplete.Clear();
return;
}
for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
const bool shouldRemove = [&] {
if (aForPrincipal && iter.Key().Principal()->Equals(aForPrincipal)) {
if (aPrincipal && iter.Key().Principal()->Equals(aPrincipal.ref())) {
return true;
}
if (!aBaseDomain) {
if (!aSchemelessSite) {
return false;
}
// Clear by baseDomain.
// Clear by site.
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).
// Clear entries with site. 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)) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
if (principalBaseDomain.Equals(aSchemelessSite.ref()) &&
aPattern.ref().Matches(partitionPrincipal->OriginAttributesRef())) {
return true;
}
// Clear entries partitioned under aBaseDomain.
return StoragePrincipalHelper::PartitionKeyHasBaseDomain(
partitionPrincipal->OriginAttributesRef().mPartitionKey,
*aBaseDomain);
// Clear entries partitioned under aSchemelessSite. We need to add the
// partition key filter to aPattern so that we include any OA filtering
// specified by the caller. For example the caller may pass aPattern = {
// privateBrowsingId: 1 } which means we may only clear partitioned
// private browsing data.
OriginAttributesPattern patternWithPartitionKey(aPattern.ref());
patternWithPartitionKey.mPartitionKeyPattern.Construct();
patternWithPartitionKey.mPartitionKeyPattern.Value()
.mBaseDomain.Construct(NS_ConvertUTF8toUTF16(aSchemelessSite.ref()));
return patternWithPartitionKey.Matches(
partitionPrincipal->OriginAttributesRef());
}();
if (shouldRemove) {

View File

@@ -528,9 +528,11 @@ const CSSCacheCleaner = {
ChromeUtils.clearStyleSheetCacheByPrincipal(aPrincipal);
},
async deleteBySite(aSchemelessSite, _aOriginAttributesPattern) {
// TODO: aOriginAttributesPattern
ChromeUtils.clearStyleSheetCacheByBaseDomain(aSchemelessSite);
async deleteBySite(aSchemelessSite, aOriginAttributesPattern) {
ChromeUtils.clearStyleSheetCacheBySite(
aSchemelessSite,
aOriginAttributesPattern
);
},
async deleteAll() {
@@ -560,9 +562,11 @@ const JSCacheCleaner = {
ChromeUtils.clearScriptCacheByPrincipal(aPrincipal);
},
async deleteBySite(aSchemelessSite, _aOriginAttributesPattern) {
// TODO: aOriginAttributesPattern
ChromeUtils.clearScriptCacheByBaseDomain(aSchemelessSite);
async deleteBySite(aSchemelessSite, aOriginAttributesPattern) {
ChromeUtils.clearScriptCacheBySite(
aSchemelessSite,
aOriginAttributesPattern
);
},
async deleteAll() {

View File

@@ -8,6 +8,17 @@ 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 CONTAINER_PRINCIPAL_A =
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(ORIGIN_A),
{ userContextId: 2 }
);
const CONTAINER_PRINCIPAL_A_SUB =
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(ORIGIN_A_SUB),
{ userContextId: 2 }
);
const BASE_DOMAIN_B = "example.org";
const ORIGIN_B = `https://${BASE_DOMAIN_B}`;
const ORIGIN_B_HTTP = `http://${BASE_DOMAIN_B}`;
@@ -27,17 +38,23 @@ function getTestURLForOrigin(origin) {
}
async function testCached(origin, isCached) {
let url = getTestURLForOrigin(origin);
let principal =
Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
let url = getTestURLForOrigin(principal.originNoSuffix);
let numParsed;
let tab = tabs[origin];
let tab = tabs[principal.origin];
let loadedPromise;
if (!tab) {
info("Creating new tab for " + url);
tab = BrowserTestUtils.addTab(gBrowser, url);
tab = gBrowser.addTab(url, {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
userContextId: principal.originAttributes.userContextId,
});
gBrowser.selectedTab = tab;
loadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
tabs[origin] = tab;
tabs[principal.origin] = tab;
} else {
loadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
tab.linkedBrowser.reload();
@@ -59,6 +76,11 @@ async function addTestTabs() {
await testCached(ORIGIN_B, false);
await testCached(ORIGIN_B_SUB, false);
await testCached(ORIGIN_B_HTTP, false);
// Add more entries for ORIGIN_A but set a different user context.
await testCached(CONTAINER_PRINCIPAL_A.origin, false);
// Add another entry for ORIGIN_A but set a different user context.
await testCached(CONTAINER_PRINCIPAL_A_SUB.origin, false);
// Test that the cache has been populated.
await testCached(ORIGIN_A, true);
await testCached(ORIGIN_A_SUB, true);
@@ -66,6 +88,8 @@ async function addTestTabs() {
await testCached(ORIGIN_B, true);
await testCached(ORIGIN_B_SUB, true);
await testCached(ORIGIN_B_HTTP, true);
await testCached(CONTAINER_PRINCIPAL_A.origin, true);
await testCached(CONTAINER_PRINCIPAL_A_SUB.origin, true);
}
async function cleanupTestTabs() {
@@ -95,17 +119,20 @@ add_task(async function test_deleteByPrincipal() {
await testCached(ORIGIN_B, true);
await testCached(ORIGIN_B_SUB, true);
await testCached(ORIGIN_B_HTTP, true);
// User context 2 not cleared because we clear by exact principal.
await testCached(CONTAINER_PRINCIPAL_A.origin, true);
await testCached(CONTAINER_PRINCIPAL_A_SUB.origin, true);
// Cleanup
cleanupTestTabs();
ChromeUtils.clearStyleSheetCache();
});
add_task(async function test_deleteByBaseDomain() {
add_task(async function test_deleteBySite() {
await addTestTabs();
// Clear data for base domain of A.
info("Clearing cache for base domain " + BASE_DOMAIN_A);
info("Clearing cache for (schemeless) site " + BASE_DOMAIN_A);
await new Promise(resolve => {
Services.clearData.deleteDataFromSite(
BASE_DOMAIN_A,
@@ -120,6 +147,10 @@ add_task(async function test_deleteByBaseDomain() {
await testCached(ORIGIN_A, false);
await testCached(ORIGIN_A_SUB, false);
await testCached(ORIGIN_A_HTTP, false);
// User context 2 also cleared because we passed the wildcard
// OriginAttributesPattern {} above.
await testCached(CONTAINER_PRINCIPAL_A.origin, false);
await testCached(CONTAINER_PRINCIPAL_A_SUB.origin, false);
// Entries for B should still exist.
await testCached(ORIGIN_B, true);
await testCached(ORIGIN_B_SUB, true);
@@ -129,3 +160,37 @@ add_task(async function test_deleteByBaseDomain() {
cleanupTestTabs();
ChromeUtils.clearStyleSheetCache();
});
add_task(async function test_deleteBySite_oa_pattern() {
await addTestTabs();
// Clear data for site A.
info("Clearing cache for (schemeless) site+pattern " + BASE_DOMAIN_A);
await new Promise(resolve => {
Services.clearData.deleteDataFromSite(
BASE_DOMAIN_A,
{ userContextId: CONTAINER_PRINCIPAL_A.originAttributes.userContextId },
false,
Ci.nsIClearDataService.CLEAR_CSS_CACHE,
resolve
);
});
// Normal entries should not have been cleared.
await testCached(ORIGIN_A, true);
await testCached(ORIGIN_A_SUB, true);
await testCached(ORIGIN_A_HTTP, true);
// Container entries should have been cleared because we have targeted them with the pattern.
await testCached(CONTAINER_PRINCIPAL_A.origin, false);
await testCached(CONTAINER_PRINCIPAL_A_SUB.origin, false);
// Entries for unrelated site 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();
});