summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp')
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp246
1 files changed, 220 insertions, 26 deletions
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
index 2b0577d5c6..7882cd6d79 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
@@ -7,6 +7,7 @@
#include "BounceTrackingProtectionStorage.h"
#include "BounceTrackingState.h"
#include "BounceTrackingRecord.h"
+#include "BounceTrackingMapEntry.h"
#include "BounceTrackingStateGlobal.h"
#include "ErrorList.h"
@@ -14,6 +15,7 @@
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/dom/Promise.h"
@@ -21,6 +23,7 @@
#include "nsHashPropertyBag.h"
#include "nsIClearDataService.h"
#include "nsIObserverService.h"
+#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsISupports.h"
#include "nsServiceManagerUtils.h"
@@ -39,6 +42,10 @@ LazyLogModule gBounceTrackingProtectionLog("BounceTrackingProtection");
static StaticRefPtr<BounceTrackingProtection> sBounceTrackingProtection;
+// Keeps track of whether the feature is enabled based on pref state.
+// Initialized on first call of GetSingleton.
+Maybe<bool> BounceTrackingProtection::sFeatureIsEnabled;
+
static constexpr uint32_t TRACKER_PURGE_FLAGS =
nsIClearDataService::CLEAR_ALL_CACHES | nsIClearDataService::CLEAR_COOKIES |
nsIClearDataService::CLEAR_DOM_STORAGES |
@@ -53,10 +60,33 @@ already_AddRefed<BounceTrackingProtection>
BounceTrackingProtection::GetSingleton() {
MOZ_ASSERT(XRE_IsParentProcess());
- if (!StaticPrefs::privacy_bounceTrackingProtection_enabled_AtStartup()) {
+ // First call to GetSingleton, check main feature pref and record telemetry.
+ if (sFeatureIsEnabled.isNothing()) {
+ if (StaticPrefs::privacy_bounceTrackingProtection_enabled_AtStartup()) {
+ sFeatureIsEnabled = Some(true);
+
+ glean::bounce_tracking_protection::enabled_at_startup.Set(true);
+ glean::bounce_tracking_protection::enabled_dry_run_mode_at_startup.Set(
+ StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode());
+ } else {
+ sFeatureIsEnabled = Some(false);
+
+ glean::bounce_tracking_protection::enabled_at_startup.Set(false);
+ glean::bounce_tracking_protection::enabled_dry_run_mode_at_startup.Set(
+ false);
+
+ // Feature is disabled.
+ return nullptr;
+ }
+ }
+ MOZ_ASSERT(sFeatureIsEnabled.isSome());
+
+ // Feature is disabled.
+ if (!sFeatureIsEnabled.value()) {
return nullptr;
}
+ // Feature is enabled, lazily create singleton instance.
if (!sBounceTrackingProtection) {
sBounceTrackingProtection = new BounceTrackingProtection();
@@ -67,7 +97,25 @@ BounceTrackingProtection::GetSingleton() {
}
BounceTrackingProtection::BounceTrackingProtection() {
- MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("constructor"));
+ MOZ_LOG(
+ gBounceTrackingProtectionLog, LogLevel::Info,
+ ("Init BounceTrackingProtection. Config: enableDryRunMode: %d, "
+ "bounceTrackingActivationLifetimeSec: %d, bounceTrackingGracePeriodSec: "
+ "%d, bounceTrackingPurgeTimerPeriodSec: %d, "
+ "clientBounceDetectionTimerPeriodMS: %d, requireStatefulBounces: %d, "
+ "HasMigratedUserActivationData: %d",
+ StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode(),
+ StaticPrefs::
+ privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec(),
+ StaticPrefs::
+ privacy_bounceTrackingProtection_bounceTrackingGracePeriodSec(),
+ StaticPrefs::
+ privacy_bounceTrackingProtection_bounceTrackingPurgeTimerPeriodSec(),
+ StaticPrefs::
+ privacy_bounceTrackingProtection_clientBounceDetectionTimerPeriodMS(),
+ StaticPrefs::privacy_bounceTrackingProtection_requireStatefulBounces(),
+ StaticPrefs::
+ privacy_bounceTrackingProtection_hasMigratedUserActivationData()));
mStorage = new BounceTrackingProtectionStorage();
@@ -78,6 +126,12 @@ BounceTrackingProtection::BounceTrackingProtection() {
return;
}
+ rv = MaybeMigrateUserInteractionPermissions();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Error,
+ ("user activation permission migration failed"));
+ }
+
// Schedule timer for tracker purging. The timer interval is determined by
// pref.
uint32_t purgeTimerPeriod = StaticPrefs::
@@ -134,6 +188,11 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
// For each host in navigable’s bounce tracking record's bounce set:
for (const nsACString& host : record->GetBounceHosts()) {
+ // Skip "null" entries, they are only used for logging purposes.
+ if (host.EqualsLiteral("null")) {
+ continue;
+ }
+
// If host equals navigable’s bounce tracking record's initial host,
// continue.
if (host == record->GetInitialHost()) {
@@ -187,9 +246,10 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
}
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
- ("%s: Added candidate to mBounceTrackers: %s, Time: %" PRIu64,
+ ("%s: Added bounce tracker candidate. siteHost: %s, "
+ "aBounceTrackingState: %s",
__FUNCTION__, PromiseFlatCString(host).get(),
- static_cast<uint64_t>(now)));
+ aBounceTrackingState->Describe().get()));
}
// Set navigable’s bounce tracking record to null.
@@ -220,30 +280,34 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
}
nsresult BounceTrackingProtection::RecordUserActivation(
- nsIPrincipal* aPrincipal) {
+ nsIPrincipal* aPrincipal, Maybe<PRTime> aActivationTime) {
MOZ_ASSERT(XRE_IsParentProcess());
-
NS_ENSURE_ARG_POINTER(aPrincipal);
- NS_ENSURE_TRUE(aPrincipal->GetIsContentPrincipal(), NS_ERROR_FAILURE);
+
+ if (!BounceTrackingState::ShouldTrackPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
nsAutoCString siteHost;
nsresult rv = aPrincipal->GetBaseDomain(siteHost);
NS_ENSURE_SUCCESS(rv, rv);
- MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: siteHost: %s", __FUNCTION__, siteHost.get()));
RefPtr<BounceTrackingStateGlobal> globalState =
mStorage->GetOrCreateStateGlobal(aPrincipal);
MOZ_ASSERT(globalState);
- return globalState->RecordUserActivation(siteHost, PR_Now());
+ // aActivationTime defaults to current time if no value is provided.
+ return globalState->RecordUserActivation(siteHost,
+ aActivationTime.valueOr(PR_Now()));
}
NS_IMETHODIMP
BounceTrackingProtection::TestGetBounceTrackerCandidateHosts(
JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
- nsTArray<nsCString>& aCandidates) {
+ nsTArray<RefPtr<nsIBounceTrackingMapEntry>>& aCandidates) {
MOZ_ASSERT(aCx);
OriginAttributes oa;
@@ -254,8 +318,11 @@ BounceTrackingProtection::TestGetBounceTrackerCandidateHosts(
BounceTrackingStateGlobal* globalState = mStorage->GetOrCreateStateGlobal(oa);
MOZ_ASSERT(globalState);
- for (const nsACString& host : globalState->BounceTrackersMapRef().Keys()) {
- aCandidates.AppendElement(host);
+ for (auto iter = globalState->BounceTrackersMapRef().ConstIter();
+ !iter.Done(); iter.Next()) {
+ RefPtr<nsIBounceTrackingMapEntry> candidate =
+ new BounceTrackingMapEntry(iter.Key(), iter.Data());
+ aCandidates.AppendElement(candidate);
}
return NS_OK;
@@ -264,7 +331,7 @@ BounceTrackingProtection::TestGetBounceTrackerCandidateHosts(
NS_IMETHODIMP
BounceTrackingProtection::TestGetUserActivationHosts(
JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
- nsTArray<nsCString>& aHosts) {
+ nsTArray<RefPtr<nsIBounceTrackingMapEntry>>& aHosts) {
MOZ_ASSERT(aCx);
OriginAttributes oa;
@@ -275,8 +342,11 @@ BounceTrackingProtection::TestGetUserActivationHosts(
BounceTrackingStateGlobal* globalState = mStorage->GetOrCreateStateGlobal(oa);
MOZ_ASSERT(globalState);
- for (const nsACString& host : globalState->UserActivationMapRef().Keys()) {
- aHosts.AppendElement(host);
+ for (auto iter = globalState->UserActivationMapRef().ConstIter();
+ !iter.Done(); iter.Next()) {
+ RefPtr<nsIBounceTrackingMapEntry> candidate =
+ new BounceTrackingMapEntry(iter.Key(), iter.Data());
+ aHosts.AppendElement(candidate);
}
return NS_OK;
@@ -419,6 +489,11 @@ BounceTrackingProtection::TestAddUserActivation(
return stateGlobal->RecordUserActivation(host, aActivationTime);
}
+NS_IMETHODIMP
+BounceTrackingProtection::TestMaybeMigrateUserInteractionPermissions() {
+ return MaybeMigrateUserInteractionPermissions();
+}
+
RefPtr<BounceTrackingProtection::PurgeBounceTrackersMozPromise>
BounceTrackingProtection::PurgeBounceTrackers() {
// Prevent multiple purge operations from running at the same time.
@@ -468,7 +543,7 @@ BounceTrackingProtection::PurgeBounceTrackers() {
aResults) {
MOZ_ASSERT(aResults.IsResolve(), "AllSettled never rejects");
- MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Done. Cleared %zu hosts.", __FUNCTION__,
aResults.ResolveValue().Length()));
@@ -572,6 +647,10 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
__FUNCTION__, PromiseFlatCString(host).get(),
originAttributeSuffix.get()));
}
+ // Remove allow-listed host so we don't need to check in again next purge
+ // run. If it gets classified again and the allow-list entry gets removed
+ // it will be purged in the next run.
+ bounceTrackerCandidatesToRemove.AppendElement(host);
continue;
}
@@ -581,15 +660,23 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
new ClearDataMozPromise::Private(__func__);
RefPtr<ClearDataCallback> cb = new ClearDataCallback(clearPromise, host);
- MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
- ("%s: Purge state for host: %s", __FUNCTION__,
- PromiseFlatCString(host).get()));
-
- // TODO: Bug 1842067: Clear by site + OA.
- rv = clearDataService->DeleteDataFromBaseDomain(host, false,
- TRACKER_PURGE_FLAGS, cb);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- clearPromise->Reject(0, __func__);
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
+ ("%s: Purging bounce tracker. siteHost: %s, bounceTime: %" PRIu64
+ " aStateGlobal: %s",
+ __FUNCTION__, PromiseFlatCString(host).get(), bounceTime,
+ aStateGlobal->Describe().get()));
+
+ if (StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode()) {
+ // In dry-run mode, we don't actually clear the data, but we still want to
+ // resolve the promise to indicate that the data would have been cleared.
+ clearPromise->Resolve(host, __func__);
+ } else {
+ // TODO: Bug 1842067: Clear by site + OA.
+ rv = clearDataService->DeleteDataFromBaseDomain(host, false,
+ TRACKER_PURGE_FLAGS, cb);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ clearPromise->Reject(0, __func__);
+ }
}
aClearPromises.AppendElement(clearPromise);
@@ -642,22 +729,129 @@ nsresult BounceTrackingProtection::ClearExpiredUserInteractions(
return NS_OK;
}
+nsresult BounceTrackingProtection::MaybeMigrateUserInteractionPermissions() {
+ // Only run the migration once.
+ if (StaticPrefs::
+ privacy_bounceTrackingProtection_hasMigratedUserActivationData()) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(
+ gBounceTrackingProtectionLog, LogLevel::Debug,
+ ("%s: Importing user activation data from permissions", __FUNCTION__));
+
+ // Get all user activation permissions that are within our user activation
+ // lifetime. We don't care about the rest since they are considered expired
+ // for BTP.
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPermissionManager> permManager =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(permManager, NS_ERROR_FAILURE);
+
+ // Construct the since time param. The permission manager expects epoch in
+ // miliseconds.
+ int64_t nowMS = PR_Now() / PR_USEC_PER_MSEC;
+ int64_t activationLifetimeMS =
+ static_cast<int64_t>(
+ StaticPrefs::
+ privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec()) *
+ PR_MSEC_PER_SEC;
+ int64_t since = nowMS - activationLifetimeMS;
+ MOZ_ASSERT(since > 0);
+
+ // Get all user activation permissions last modified between "since" and now.
+ nsTArray<RefPtr<nsIPermission>> userActivationPermissions;
+ rv = permManager->GetAllByTypeSince("storageAccessAPI"_ns, since,
+ userActivationPermissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
+ ("%s: Found %zu (non-expired) user activation permissions",
+ __FUNCTION__, userActivationPermissions.Length()));
+
+ for (const auto& perm : userActivationPermissions) {
+ nsCOMPtr<nsIPrincipal> permPrincipal;
+
+ rv = perm->GetPrincipal(getter_AddRefs(permPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ MOZ_ASSERT(permPrincipal);
+
+ // The time the permission was last modified is the time of last user
+ // activation.
+ int64_t modificationTimeMS;
+ rv = perm->GetModificationTime(&modificationTimeMS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(modificationTimeMS >= since,
+ "Unexpected permission modification time");
+
+ // We may end up with duplicates here since user activation permissions are
+ // tracked by origin, while BTP tracks user activation by site host.
+ // RecordUserActivation is responsible for only keeping the most recent user
+ // activation flag for a given site host and needs to make sure existing
+ // activation flags are not overwritten by older timestamps.
+ // RecordUserActivation expects epoch in microseconds.
+ rv = RecordUserActivation(permPrincipal,
+ Some(modificationTimeMS * PR_USEC_PER_MSEC));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ }
+
+ // Migration successful, set the pref to indicate that we have migrated.
+ return mozilla::Preferences::SetBool(
+ "privacy.bounceTrackingProtection.hasMigratedUserActivationData", true);
+}
+
// ClearDataCallback
NS_IMPL_ISUPPORTS(BounceTrackingProtection::ClearDataCallback,
nsIClearDataCallback);
+BounceTrackingProtection::ClearDataCallback::ClearDataCallback(
+ ClearDataMozPromise::Private* aPromise, const nsACString& aHost)
+ : mHost(aHost), mClearDurationTimer(0), mPromise(aPromise) {
+ MOZ_ASSERT(!aHost.IsEmpty(), "Host must not be empty");
+ if (!StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode()) {
+ // Only collect timing information when actually performing the deletion
+ mClearDurationTimer =
+ glean::bounce_tracking_protection::purge_duration.Start();
+ MOZ_ASSERT(mClearDurationTimer);
+ }
+};
+
+BounceTrackingProtection::ClearDataCallback::~ClearDataCallback() {
+ mPromise->Reject(0, __func__);
+ if (mClearDurationTimer) {
+ glean::bounce_tracking_protection::purge_duration.Cancel(
+ std::move(mClearDurationTimer));
+ }
+}
+
// nsIClearDataCallback implementation
NS_IMETHODIMP BounceTrackingProtection::ClearDataCallback::OnDataDeleted(
uint32_t aFailedFlags) {
if (aFailedFlags) {
mPromise->Reject(aFailedFlags, __func__);
} else {
- MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Cleared %s", __FUNCTION__, mHost.get()));
mPromise->Resolve(std::move(mHost), __func__);
}
+ RecordClearDurationTelemetry();
return NS_OK;
}
+void BounceTrackingProtection::ClearDataCallback::
+ RecordClearDurationTelemetry() {
+ if (mClearDurationTimer) {
+ glean::bounce_tracking_protection::purge_duration.StopAndAccumulate(
+ std::move(mClearDurationTimer));
+ mClearDurationTimer = 0;
+ }
+}
+
} // namespace mozilla