summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/bouncetrackingprotection
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/antitracking/bouncetrackingprotection')
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp123
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h28
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp13
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h15
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp17
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h6
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp32
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h11
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl4
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js66
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js85
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml2
12 files changed, 339 insertions, 63 deletions
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
index e5d9ccfea9..2b0577d5c6 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
@@ -12,6 +12,7 @@
#include "ErrorList.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/Logging.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_privacy.h"
@@ -122,7 +123,7 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
aBounceTrackingState->Describe().get()));
// Assert: navigable’s bounce tracking record is not null.
- BounceTrackingRecord* record =
+ const Maybe<BounceTrackingRecord>& record =
aBounceTrackingState->GetBounceTrackingRecord();
NS_ENSURE_TRUE(record, NS_ERROR_FAILURE);
@@ -369,6 +370,11 @@ BounceTrackingProtection::TestRunPurgeBounceTrackers(
}
NS_IMETHODIMP
+BounceTrackingProtection::TestClearExpiredUserActivations() {
+ return ClearExpiredUserInteractions();
+}
+
+NS_IMETHODIMP
BounceTrackingProtection::TestAddBounceTrackerCandidate(
JS::Handle<JS::Value> aOriginAttributes, const nsACString& aHost,
const PRTime aBounceTime, JSContext* aCx) {
@@ -415,6 +421,22 @@ BounceTrackingProtection::TestAddUserActivation(
RefPtr<BounceTrackingProtection::PurgeBounceTrackersMozPromise>
BounceTrackingProtection::PurgeBounceTrackers() {
+ // Prevent multiple purge operations from running at the same time.
+ if (mPurgeInProgress) {
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
+ ("%s: Skip: Purge already in progress.", __FUNCTION__));
+ return PurgeBounceTrackersMozPromise::CreateAndReject(
+ nsresult::NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+ mPurgeInProgress = true;
+
+ // Obtain a cache of ContentBlockingAllowList permissions so we only need to
+ // fetch permissions once even when we do multiple base domain lookups.
+ ContentBlockingAllowListCache contentBlockingAllowListCache;
+
+ // Collect promises for all clearing operations to later await on.
+ nsTArray<RefPtr<ClearDataMozPromise>> clearPromises;
+
// Run the purging algorithm for all global state objects.
for (const auto& entry : mStorage->StateGlobalMapRef()) {
const OriginAttributes& originAttributes = entry.GetKey();
@@ -429,13 +451,17 @@ BounceTrackingProtection::PurgeBounceTrackers() {
oaSuffix.get()));
}
- PurgeBounceTrackersForStateGlobal(stateGlobal, originAttributes);
+ nsresult rv = PurgeBounceTrackersForStateGlobal(
+ stateGlobal, contentBlockingAllowListCache, clearPromises);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return PurgeBounceTrackersMozPromise::CreateAndReject(rv, __func__);
+ }
}
// Wait for all data clearing operations to complete. mClearPromises contains
// one promise per host / clear task.
return ClearDataMozPromise::AllSettled(GetCurrentSerialEventTarget(),
- mClearPromises)
+ clearPromises)
->Then(
GetCurrentSerialEventTarget(), __func__,
[&](ClearDataMozPromise::AllSettledPromiseType::ResolveOrRejectValue&&
@@ -450,7 +476,7 @@ BounceTrackingProtection::PurgeBounceTrackers() {
// If any clear call failed reject.
for (auto& result : aResults.ResolveValue()) {
if (result.IsReject()) {
- mClearPromises.Clear();
+ mPurgeInProgress = false;
return PurgeBounceTrackersMozPromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
}
@@ -458,7 +484,8 @@ BounceTrackingProtection::PurgeBounceTrackers() {
}
// No clearing errors, resolve.
- mClearPromises.Clear();
+
+ mPurgeInProgress = false;
return PurgeBounceTrackersMozPromise::CreateAndResolve(
std::move(purgedSiteHosts), __func__);
});
@@ -466,34 +493,17 @@ BounceTrackingProtection::PurgeBounceTrackers() {
nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
BounceTrackingStateGlobal* aStateGlobal,
- const OriginAttributes& aOriginAttributes) {
+ ContentBlockingAllowListCache& aContentBlockingAllowList,
+ nsTArray<RefPtr<ClearDataMozPromise>>& aClearPromises) {
MOZ_ASSERT(aStateGlobal);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
- ("%s: #mUserActivation: %d, #mBounceTrackers: %d", __FUNCTION__,
- aStateGlobal->UserActivationMapRef().Count(),
- aStateGlobal->BounceTrackersMapRef().Count()));
-
- // Purge already in progress.
- if (!mClearPromises.IsEmpty()) {
- MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
- ("%s: Skip: Purge already in progress.", __FUNCTION__));
- return NS_ERROR_NOT_AVAILABLE;
- }
+ ("%s: %s", __FUNCTION__, aStateGlobal->Describe().get()));
const PRTime now = PR_Now();
- // Convert the user activation lifetime into microseconds for calculation with
- // PRTime values. The pref is a 32-bit value. Cast into 64-bit before
- // multiplying so we get the correct result.
- int64_t activationLifetimeUsec =
- static_cast<int64_t>(
- StaticPrefs::
- privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec()) *
- PR_USEC_PER_SEC;
// 1. Remove hosts from the user activation map whose user activation flag has
// expired.
- nsresult rv =
- aStateGlobal->ClearUserActivationBefore(now - activationLifetimeUsec);
+ nsresult rv = ClearExpiredUserInteractions(aStateGlobal);
NS_ENSURE_SUCCESS(rv, rv);
// 2. Go over bounce tracker candidate map and purge state.
@@ -502,7 +512,6 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
do_GetService("@mozilla.org/clear-data-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
- mClearPromises.Clear();
nsTArray<nsCString> purgedSiteHosts;
// Collect hosts to remove from the bounce trackers map. We can not remove
@@ -545,6 +554,27 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
continue;
}
+ // Gecko specific: If the host is on the content blocking allow-list,
+ // continue.
+ bool isAllowListed = false;
+ rv = aContentBlockingAllowList.CheckForBaseDomain(
+ host, aStateGlobal->OriginAttributesRef(), isAllowListed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ if (isAllowListed) {
+ if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
+ nsAutoCString originAttributeSuffix;
+ aStateGlobal->OriginAttributesRef().CreateSuffix(originAttributeSuffix);
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
+ ("%s: Skip host on the content blocking allow-list: host: %s, "
+ "originAttributes: %s",
+ __FUNCTION__, PromiseFlatCString(host).get(),
+ originAttributeSuffix.get()));
+ }
+ continue;
+ }
+
// No exception above applies, clear state for the given host.
RefPtr<ClearDataMozPromise::Private> clearPromise =
@@ -562,7 +592,7 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
clearPromise->Reject(0, __func__);
}
- mClearPromises.AppendElement(clearPromise);
+ aClearPromises.AppendElement(clearPromise);
// Remove it from the bounce trackers map, it's about to be purged. If the
// clear call fails still remove it. We want to avoid an ever growing list
@@ -575,6 +605,43 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
return aStateGlobal->RemoveBounceTrackers(bounceTrackerCandidatesToRemove);
}
+nsresult BounceTrackingProtection::ClearExpiredUserInteractions(
+ BounceTrackingStateGlobal* aStateGlobal) {
+ if (!aStateGlobal && mStorage->StateGlobalMapRef().IsEmpty()) {
+ // Nothing to clear.
+ return NS_OK;
+ }
+
+ const PRTime now = PR_Now();
+
+ // Convert the user activation lifetime into microseconds for calculation with
+ // PRTime values. The pref is a 32-bit value. Cast into 64-bit before
+ // multiplying so we get the correct result.
+ int64_t activationLifetimeUsec =
+ static_cast<int64_t>(
+ StaticPrefs::
+ privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec()) *
+ PR_USEC_PER_SEC;
+
+ // Clear user activation for the given state global.
+ if (aStateGlobal) {
+ return aStateGlobal->ClearUserActivationBefore(now -
+ activationLifetimeUsec);
+ }
+
+ // aStateGlobal not passed, clear user activation for all state globals.
+ for (const auto& entry : mStorage->StateGlobalMapRef()) {
+ const RefPtr<BounceTrackingStateGlobal>& stateGlobal = entry.GetData();
+ MOZ_ASSERT(stateGlobal);
+
+ nsresult rv =
+ stateGlobal->ClearUserActivationBefore(now - activationLifetimeUsec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
// ClearDataCallback
NS_IMPL_ISUPPORTS(BounceTrackingProtection::ClearDataCallback,
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h
index 98c61504c0..e99cf895be 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h
@@ -17,6 +17,7 @@ namespace mozilla {
class BounceTrackingState;
class BounceTrackingStateGlobal;
class BounceTrackingProtectionStorage;
+class ContentBlockingAllowListCache;
class OriginAttributes;
extern LazyLogModule gBounceTrackingProtectionLog;
@@ -33,10 +34,17 @@ class BounceTrackingProtection final : public nsIBounceTrackingProtection {
// navigation start for bounce tracking, or if the client bounce detection
// timer expires after process response received for bounce tracking without
// observing a client redirect.
- nsresult RecordStatefulBounces(BounceTrackingState* aBounceTrackingState);
+ [[nodiscard]] nsresult RecordStatefulBounces(
+ BounceTrackingState* aBounceTrackingState);
// Stores a user activation flag with a timestamp for the given principal.
- nsresult RecordUserActivation(nsIPrincipal* aPrincipal);
+ [[nodiscard]] nsresult RecordUserActivation(nsIPrincipal* aPrincipal);
+
+ // Clears expired user interaction flags for the given state global. If
+ // aStateGlobal == nullptr, clears expired user interaction flags for all
+ // state globals.
+ [[nodiscard]] nsresult ClearExpiredUserInteractions(
+ BounceTrackingStateGlobal* aStateGlobal = nullptr);
private:
BounceTrackingProtection();
@@ -53,13 +61,19 @@ class BounceTrackingProtection final : public nsIBounceTrackingProtection {
MozPromise<nsTArray<nsCString>, nsresult, true>;
RefPtr<PurgeBounceTrackersMozPromise> PurgeBounceTrackers();
- nsresult PurgeBounceTrackersForStateGlobal(
- BounceTrackingStateGlobal* aStateGlobal,
- const OriginAttributes& aOriginAttributes);
-
// Pending clear operations are stored as ClearDataMozPromise, one per host.
using ClearDataMozPromise = MozPromise<nsCString, uint32_t, true>;
- nsTArray<RefPtr<ClearDataMozPromise>> mClearPromises;
+
+ // Clear state for classified bounce trackers for a specific state global.
+ // aClearPromises is populated with promises for each host that is cleared.
+ [[nodiscard]] nsresult PurgeBounceTrackersForStateGlobal(
+ BounceTrackingStateGlobal* aStateGlobal,
+ ContentBlockingAllowListCache& aContentBlockingAllowList,
+ nsTArray<RefPtr<ClearDataMozPromise>>& aClearPromises);
+
+ // Whether a purge operation is currently in progress. This avoids running
+ // multiple purge operations at the same time.
+ bool mPurgeInProgress = false;
// Wraps nsIClearDataCallback in MozPromise.
class ClearDataCallback final : public nsIClearDataCallback {
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp
index 14ee178ae2..d4b33f9edb 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp
@@ -12,13 +12,11 @@ namespace mozilla {
extern LazyLogModule gBounceTrackingProtectionLog;
-NS_IMPL_CYCLE_COLLECTION(BounceTrackingRecord);
-
void BounceTrackingRecord::SetInitialHost(const nsACString& aHost) {
mInitialHost = aHost;
}
-const nsACString& BounceTrackingRecord::GetInitialHost() {
+const nsACString& BounceTrackingRecord::GetInitialHost() const {
return mInitialHost;
}
@@ -26,7 +24,9 @@ void BounceTrackingRecord::SetFinalHost(const nsACString& aHost) {
mFinalHost = aHost;
}
-const nsACString& BounceTrackingRecord::GetFinalHost() { return mFinalHost; }
+const nsACString& BounceTrackingRecord::GetFinalHost() const {
+ return mFinalHost;
+}
void BounceTrackingRecord::AddBounceHost(const nsACString& aHost) {
mBounceHosts.Insert(aHost);
@@ -57,11 +57,12 @@ void BounceTrackingRecord::AddStorageAccessHost(const nsACString& aHost) {
mStorageAccessHosts.Insert(aHost);
}
-const nsTHashSet<nsCString>& BounceTrackingRecord::GetBounceHosts() {
+const nsTHashSet<nsCString>& BounceTrackingRecord::GetBounceHosts() const {
return mBounceHosts;
}
-const nsTHashSet<nsCString>& BounceTrackingRecord::GetStorageAccessHosts() {
+const nsTHashSet<nsCString>& BounceTrackingRecord::GetStorageAccessHosts()
+ const {
return mStorageAccessHosts;
}
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h
index d3e980d00b..73985a00a4 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h
@@ -7,9 +7,7 @@
#ifndef mozilla_BounceTrackingRecord_h
#define mozilla_BounceTrackingRecord_h
-#include "nsISupports.h"
#include "nsStringFwd.h"
-#include "nsCycleCollectionParticipant.h"
#include "nsTHashSet.h"
namespace mozilla {
@@ -22,31 +20,26 @@ class CanonicalBrowsingContext;
// navigation.
class BounceTrackingRecord final {
public:
- NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BounceTrackingRecord);
- NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(BounceTrackingRecord);
-
void SetInitialHost(const nsACString& aHost);
- const nsACString& GetInitialHost();
+ const nsACString& GetInitialHost() const;
void SetFinalHost(const nsACString& aHost);
- const nsACString& GetFinalHost();
+ const nsACString& GetFinalHost() const;
void AddBounceHost(const nsACString& aHost);
void AddStorageAccessHost(const nsACString& aHost);
- const nsTHashSet<nsCString>& GetBounceHosts();
+ const nsTHashSet<nsCString>& GetBounceHosts() const;
- const nsTHashSet<nsCString>& GetStorageAccessHosts();
+ const nsTHashSet<nsCString>& GetStorageAccessHosts() const;
// Create a string that describes this record. Used for logging.
nsCString Describe();
private:
- ~BounceTrackingRecord() = default;
-
// A site's host. The initiator site of the current extended navigation.
nsAutoCString mInitialHost;
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp
index c5abb8b8d7..b4af3daa07 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp
@@ -143,10 +143,11 @@ nsresult BounceTrackingState::Init(
}
void BounceTrackingState::ResetBounceTrackingRecord() {
- mBounceTrackingRecord = nullptr;
+ mBounceTrackingRecord = Nothing();
}
-BounceTrackingRecord* BounceTrackingState::GetBounceTrackingRecord() {
+const Maybe<BounceTrackingRecord>&
+BounceTrackingState::GetBounceTrackingRecord() {
return mBounceTrackingRecord;
}
@@ -452,7 +453,7 @@ nsresult BounceTrackingState::OnStartNavigation(
// tracking record to a new bounce tracking record with initial host set to
// initialHost.
if (!mBounceTrackingRecord) {
- mBounceTrackingRecord = new BounceTrackingRecord();
+ mBounceTrackingRecord = Some(BounceTrackingRecord());
mBounceTrackingRecord->SetInitialHost(siteHost);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
@@ -477,7 +478,7 @@ nsresult BounceTrackingState::OnStartNavigation(
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(!mBounceTrackingRecord);
- mBounceTrackingRecord = new BounceTrackingRecord();
+ mBounceTrackingRecord = Some(BounceTrackingRecord());
mBounceTrackingRecord->SetInitialHost(siteHost);
return NS_OK;
@@ -539,8 +540,12 @@ nsresult BounceTrackingState::OnResponseReceived(
("%s: Calling RecordStatefulBounces after timeout.", __FUNCTION__));
BounceTrackingState* bounceTrackingState = thisWeak;
- bounceTrackingState->mBounceTrackingProtection->RecordStatefulBounces(
- bounceTrackingState);
+ DebugOnly<nsresult> rv =
+ bounceTrackingState->mBounceTrackingProtection
+ ->RecordStatefulBounces(bounceTrackingState);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "Running RecordStatefulBounces after a timeout failed.");
bounceTrackingState->mClientBounceDetectionTimeout = nullptr;
},
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h
index 70deee5abe..17d324bda9 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h
@@ -7,6 +7,7 @@
#ifndef mozilla_BounceTrackingState_h
#define mozilla_BounceTrackingState_h
+#include "BounceTrackingRecord.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/OriginAttributes.h"
#include "nsIPrincipal.h"
@@ -22,7 +23,6 @@ class nsIPrincipal;
namespace mozilla {
class BounceTrackingProtection;
-class BounceTrackingRecord;
namespace dom {
class CanonicalBrowsingContext;
@@ -55,7 +55,7 @@ class BounceTrackingState : public nsIWebProgressListener,
static void ResetAllForOriginAttributesPattern(
const OriginAttributesPattern& aPattern);
- BounceTrackingRecord* GetBounceTrackingRecord();
+ const Maybe<BounceTrackingRecord>& GetBounceTrackingRecord();
void ResetBounceTrackingRecord();
@@ -113,7 +113,7 @@ class BounceTrackingState : public nsIWebProgressListener,
// Record to keep track of extended navigation data. Reset on extended
// navigation end.
- RefPtr<BounceTrackingRecord> mBounceTrackingRecord;
+ Maybe<BounceTrackingRecord> mBounceTrackingRecord;
// Timer to wait to wait for a client redirect after a navigation ends.
RefPtr<nsITimer> mClientBounceDetectionTimeout;
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp
index 3481753431..b94c887c90 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp
@@ -106,6 +106,11 @@ nsresult BounceTrackingStateGlobal::ClearByTimeRange(
NS_ENSURE_ARG_MIN(aFrom, 0);
NS_ENSURE_TRUE(!aTo || aTo.value() > aFrom, NS_ERROR_INVALID_ARG);
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
+ ("%s: Clearing user activations by time range from %" PRIu64
+ " to %" PRIu64 " %s",
+ __FUNCTION__, aFrom, aTo.valueOr(0), Describe().get()));
+
// Clear in memory user activation data.
if (aEntryType.isNothing() ||
aEntryType.value() ==
@@ -113,10 +118,10 @@ nsresult BounceTrackingStateGlobal::ClearByTimeRange(
for (auto iter = mUserActivation.Iter(); !iter.Done(); iter.Next()) {
if (iter.Data() >= aFrom &&
(aTo.isNothing() || iter.Data() <= aTo.value())) {
- iter.Remove();
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Remove user activation for %s", __FUNCTION__,
PromiseFlatCString(iter.Key()).get()));
+ iter.Remove();
}
}
}
@@ -128,10 +133,10 @@ nsresult BounceTrackingStateGlobal::ClearByTimeRange(
for (auto iter = mBounceTrackers.Iter(); !iter.Done(); iter.Next()) {
if (iter.Data() >= aFrom &&
(aTo.isNothing() || iter.Data() <= aTo.value())) {
- iter.Remove();
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Remove bouncer tracker for %s", __FUNCTION__,
PromiseFlatCString(iter.Key()).get()));
+ iter.Remove();
}
}
}
@@ -187,4 +192,27 @@ nsresult BounceTrackingStateGlobal::RemoveBounceTrackers(
return NS_OK;
}
+// static
+nsCString BounceTrackingStateGlobal::DescribeMap(
+ const nsTHashMap<nsCStringHashKey, PRTime>& aMap) {
+ nsAutoCString mapStr;
+
+ for (auto iter = aMap.ConstIter(); !iter.Done(); iter.Next()) {
+ mapStr.Append(nsPrintfCString("{ %s: %" PRIu64 " }, ",
+ PromiseFlatCString(iter.Key()).get(),
+ iter.Data()));
+ }
+
+ return std::move(mapStr);
+}
+
+nsCString BounceTrackingStateGlobal::Describe() {
+ nsAutoCString originAttributeSuffix;
+ mOriginAttributes.CreateSuffix(originAttributeSuffix);
+ return nsPrintfCString(
+ "{ mOriginAttributes: %s, mUserActivation: %s, mBounceTrackers: %s }",
+ originAttributeSuffix.get(), DescribeMap(mUserActivation).get(),
+ DescribeMap(mBounceTrackers).get());
+}
+
} // namespace mozilla
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h
index 6680ceae6f..c8ed72c11f 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h
@@ -37,6 +37,10 @@ class BounceTrackingStateGlobal final {
bool ShouldPersistToDisk() const { return !IsPrivateBrowsing(); }
+ const OriginAttributes& OriginAttributesRef() const {
+ return mOriginAttributes;
+ };
+
bool HasUserActivation(const nsACString& aSiteHost) const;
// Store a user interaction flag for the given host. This will remove the
@@ -79,6 +83,9 @@ class BounceTrackingStateGlobal final {
return mBounceTrackers;
}
+ // Create a string that describes this object. Used for logging.
+ nsCString Describe();
+
private:
~BounceTrackingStateGlobal() = default;
@@ -103,6 +110,10 @@ class BounceTrackingStateGlobal final {
// on the given site host performed an action that could indicate stateful
// bounce tracking took place.
nsTHashMap<nsCStringHashKey, PRTime> mBounceTrackers{};
+
+ // Helper to create a string representation of a siteHost -> timestamp map.
+ static nsCString DescribeMap(
+ const nsTHashMap<nsCStringHashKey, PRTime>& aMap);
};
} // namespace mozilla
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl b/toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl
index 9ade9cb0ea..1163492333 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl
+++ b/toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl
@@ -28,6 +28,10 @@ interface nsIBounceTrackingProtection : nsISupports {
[implicit_jscontext]
Promise testRunPurgeBounceTrackers();
+ // Clear expired user activation flags. Expiry is set via pref
+ // "privacy.bounceTrackingProtection.bounceTrackingActivationLifetimeSec".
+ void testClearExpiredUserActivations();
+
// Getters and setters for user activation and bounce tracker state.
// These are used for testing purposes only.
// State is keyed by OriginAttributes.
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
index a8e98b80f0..eedd374197 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
@@ -119,3 +119,69 @@ add_task(async function test_purging_skip_open_tab_extra_window() {
bounceTrackingProtection.clearAll();
});
+
+add_task(async function test_purging_skip_content_blocking_allow_list() {
+ initBounceTrackerState();
+
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ window.ContentBlockingAllowList.add(browser);
+ });
+
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.net"],
+ "Should only purge example.net. example.org is within the grace period, example.com is allow-listed."
+ );
+
+ info(
+ "Remove the allow-list entry for example.com and test that it gets purged now."
+ );
+
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ window.ContentBlockingAllowList.remove(browser);
+ });
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.com"],
+ "example.com should have been purged now that it is no longer allow-listed."
+ );
+
+ bounceTrackingProtection.clearAll();
+});
+
+add_task(
+ async function test_purging_skip_content_blocking_allow_list_subdomain() {
+ initBounceTrackerState();
+
+ await BrowserTestUtils.withNewTab(
+ "https://test1.example.com",
+ async browser => {
+ window.ContentBlockingAllowList.add(browser);
+ }
+ );
+
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.net"],
+ "Should only purge example.net. example.org is within the grace period, example.com is allow-listed via test1.example.com."
+ );
+
+ info(
+ "Remove the allow-list entry for test1.example.com and test that it gets purged now."
+ );
+
+ await BrowserTestUtils.withNewTab(
+ "https://test1.example.com",
+ async browser => {
+ window.ContentBlockingAllowList.remove(browser);
+ }
+ );
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.com"],
+ "example.com should have been purged now that test1.example.com it is no longer allow-listed."
+ );
+
+ bounceTrackingProtection.clearAll();
+ }
+);
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js
new file mode 100644
index 0000000000..28a1350b3e
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test that expired user activations are cleared by the the helper method
+ * testClearExpiredUserActivations.
+ */
+add_task(async function test() {
+ // Need a profile to data clearing calls.
+ do_get_profile();
+
+ let btp = Cc["@mozilla.org/bounce-tracking-protection;1"].getService(
+ Ci.nsIBounceTrackingProtection
+ );
+
+ // Reset global bounce tracking state.
+ btp.clearAll();
+
+ // Assert initial test state.
+ Assert.deepEqual(
+ btp.testGetBounceTrackerCandidateHosts({}),
+ [],
+ "No tracker candidates initially."
+ );
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({}),
+ [],
+ "No user activation hosts initially."
+ );
+
+ // Get the bounce tracking activation lifetime. The pref is in seconds, we
+ // need to convert it to microseconds, as the user activation timestamps are
+ // in microseconds (PRTime).
+ let bounceTrackingActivationLifetimeUSec =
+ 1000 *
+ 1000 *
+ Services.prefs.getIntPref(
+ "privacy.bounceTrackingProtection.bounceTrackingActivationLifetimeSec"
+ );
+
+ // Add some test data for user activation.
+ btp.testAddUserActivation({}, "not-expired1.com", Date.now() * 1000);
+ btp.testAddUserActivation(
+ {},
+ "not-expired2.com",
+ Date.now() * 1000 - bounceTrackingActivationLifetimeUSec / 2
+ );
+ btp.testAddUserActivation(
+ { privateBrowsingId: 1 },
+ "pbm-not-expired.com",
+ Date.now() * 1000
+ );
+ btp.testAddUserActivation(
+ {},
+ "expired1.com",
+ Date.now() * 1000 - bounceTrackingActivationLifetimeUSec * 2
+ );
+ btp.testAddUserActivation(
+ {},
+ "expired2.com",
+ Date.now() * 1000 - (bounceTrackingActivationLifetimeUSec + 1000 * 1000)
+ );
+ btp.testAddUserActivation({ privateBrowsingId: 1 }, "pbm-expired.com", 1);
+
+ // Clear expired user activations.
+ btp.testClearExpiredUserActivations();
+
+ // Assert that expired user activations have been cleared.
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({}).sort(),
+ ["not-expired1.com", "not-expired2.com"],
+ "Expired user activation flags have been cleared for normal browsing."
+ );
+
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({ privateBrowsingId: 1 }).sort(),
+ ["pbm-not-expired.com"],
+ "Expired user activation flags have been cleared for private browsing."
+ );
+
+ // Reset global bounce tracking state.
+ btp.clearAll();
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml
index 16e270b85c..c3aeee502f 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml
@@ -5,4 +5,6 @@ prefs = [
"privacy.bounceTrackingProtection.bounceTrackingPurgeTimerPeriodSec=0",
]
+["test_bouncetracking_clearExpiredUserActivation.js"]
+
["test_bouncetracking_purge.js"]