Bug 1894040 - Support for enabling or disabling BounceTrackingProtection during runtime. r=anti-tracking-reviewers,bvandersloot

This patch switches the main logic from a boolean pref to an integer with different feature modes.
See nsIBounceTrackingProtection for the new modes supported. Dry-run mode has been merged into the mode
pref.

A new state is introduced MODE_ENABLED_STANDBY in which BTP still collects user activation signals, but
does not classify or purge bounce trackers. This new mode should be used when it's expected that the
feature may be toggled on/off after rollout. Collecting user activation even in the "disabled" state
ensures that when the feature gets re-enabled the user activation map is already warmed up and sites
important to the user are not classified + purged.

The BounceTrackingProtection singleton listens for changes on the mode pref and inits / tears down accordingly.
However it never shuts down fully. To fully disable the feature a restart is required.

Differential Revision: https://phabricator.services.mozilla.com/D217848
This commit is contained in:
Paul Zuehlcke 2024-08-13 12:38:26 +00:00
Родитель 9fb7f63074
Коммит 8fcf41c736
26 изменённых файлов: 507 добавлений и 101 удалений

Просмотреть файл

@ -178,6 +178,10 @@ BrowsingContextWebProgress::GetBounceTrackingState() {
return do_AddRef(mBounceTrackingState);
}
void BrowsingContextWebProgress::DropBounceTrackingState() {
mBounceTrackingState = nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// nsIWebProgressListener

Просмотреть файл

@ -64,6 +64,10 @@ class BrowsingContextWebProgress final : public nsIWebProgress,
already_AddRefed<BounceTrackingState> GetBounceTrackingState();
// Drops our reference to BounceTrackingState. This is used when the feature
// gets disabled.
void DropBounceTrackingState();
private:
virtual ~BrowsingContextWebProgress();

Просмотреть файл

@ -14785,11 +14785,17 @@
value: ""
mirror: never
# Main pref to enable / disable the feature.
- name: privacy.bounceTrackingProtection.enabled
type: bool
value: true
mirror: once
# Controls Bounce Tracking Protection behavior.
# Set to 0 to fully disable. See nsIBounceTrackingProtection.idl for
# documentation.
- name: privacy.bounceTrackingProtection.mode
type: uint32_t
#ifdef NIGHTLY_BUILD
value: 1
#else
value: 3
#endif
mirror: always
# How long to wait for a client redirect after a navigation ends.
- name: privacy.bounceTrackingProtection.clientBounceDetectionTimerPeriodMS
@ -14823,14 +14829,6 @@
value: true
mirror: always
# Enables a mode where if bounce tracking protection is enabled it classifies
# but does not purge trackers. This mode is helpful for testing the feature
# without risking data loss. Telemetry is still collected normally.
- name: privacy.bounceTrackingProtection.enableDryRunMode
type: bool
value: @IS_NOT_NIGHTLY_BUILD@
mirror: always
# To be used in automated test environments to enable observer messages.
- name: privacy.bounceTrackingProtection.enableTestMode
type: bool

Просмотреть файл

@ -57,6 +57,8 @@ static bool sInitFailed = false;
// Initialized on first call of GetSingleton.
Maybe<bool> BounceTrackingProtection::sFeatureIsEnabled;
static const char kBTPModePref[] = "privacy.bounceTrackingProtection.mode";
static constexpr uint32_t TRACKER_PURGE_FLAGS =
nsIClearDataService::CLEAR_ALL_CACHES | nsIClearDataService::CLEAR_COOKIES |
nsIClearDataService::CLEAR_DOM_STORAGES |
@ -78,13 +80,8 @@ BounceTrackingProtection::GetSingleton() {
// 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 {
if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_DISABLED) {
sFeatureIsEnabled = Some(false);
glean::bounce_tracking_protection::enabled_at_startup.Set(false);
@ -94,6 +91,12 @@ BounceTrackingProtection::GetSingleton() {
// Feature is disabled.
return nullptr;
}
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_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN);
}
MOZ_ASSERT(sFeatureIsEnabled.isSome());
@ -124,14 +127,17 @@ BounceTrackingProtection::GetSingleton() {
}
nsresult BounceTrackingProtection::Init() {
MOZ_ASSERT(StaticPrefs::privacy_bounceTrackingProtection_mode() !=
nsIBounceTrackingProtection::MODE_DISABLED,
"Mode pref must have an enabled state for init to be called.");
MOZ_LOG(
gBounceTrackingProtectionLog, LogLevel::Info,
("Init BounceTrackingProtection. Config: enableDryRunMode: %d, "
("Init BounceTrackingProtection. Config: mode: %d, "
"bounceTrackingActivationLifetimeSec: %d, bounceTrackingGracePeriodSec: "
"%d, bounceTrackingPurgeTimerPeriodSec: %d, "
"clientBounceDetectionTimerPeriodMS: %d, requireStatefulBounces: %d, "
"HasMigratedUserActivationData: %d",
StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode(),
StaticPrefs::privacy_bounceTrackingProtection_mode(),
StaticPrefs::
privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec(),
StaticPrefs::
@ -155,6 +161,31 @@ nsresult BounceTrackingProtection::Init() {
("user activation permission migration failed"));
}
// Register feature pref listener which dynamically enables or disables the
// feature depending on feature pref state.
rv = Preferences::RegisterCallback(&BounceTrackingProtection::OnPrefChange,
kBTPModePref);
NS_ENSURE_SUCCESS(rv, rv);
// Run the remaining init logic.
return OnModeChange(true);
}
nsresult BounceTrackingProtection::UpdateBounceTrackingPurgeTimer(
bool aShouldEnable) {
// Cancel the existing timer.
// If disabling: we're done now.
// If enabling: schedule a new timer so interval changes (as controlled by the
// pref) are taken into account.
if (mBounceTrackingPurgeTimer) {
mBounceTrackingPurgeTimer->Cancel();
mBounceTrackingPurgeTimer = nullptr;
}
if (!aShouldEnable) {
return NS_OK;
}
// Schedule timer for tracker purging. The timer interval is determined by
// pref.
uint32_t purgeTimerPeriod = StaticPrefs::
@ -188,6 +219,81 @@ nsresult BounceTrackingProtection::Init() {
"mBounceTrackingPurgeTimer");
}
// static
void BounceTrackingProtection::OnPrefChange(const char* aPref, void* aData) {
MOZ_ASSERT(sBounceTrackingProtection);
MOZ_ASSERT(strcmp(kBTPModePref, aPref) == 0);
sBounceTrackingProtection->OnModeChange(false);
}
nsresult BounceTrackingProtection::OnModeChange(bool aIsStartup) {
// Get feature mode from pref and ensure its within bounds.
uint8_t modeInt = StaticPrefs::privacy_bounceTrackingProtection_mode();
NS_ENSURE_TRUE(modeInt <= nsIBounceTrackingProtection::MAX_MODE_VALUE,
NS_ERROR_FAILURE);
nsIBounceTrackingProtection::Modes mode =
static_cast<nsIBounceTrackingProtection::Modes>(modeInt);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: mode: %d.", __FUNCTION__, mode));
if (sInitFailed) {
return NS_ERROR_FAILURE;
}
nsresult result = NS_OK;
if (!aIsStartup) {
// Clear bounce tracker candidate map for any mode change so it's not leaked
// into other modes. For example if we switch from dry-run mode into fully
// enabled we want a clean slate to not purge trackers that we've classified
// in dry-run mode. User activation data must be kept to avoid false
// positives.
MOZ_ASSERT(mStorage);
result = mStorage->ClearByType(
BounceTrackingProtectionStorage::EntryType::BounceTracker);
}
// On disable
if (mode == nsIBounceTrackingProtection::MODE_DISABLED ||
mode == nsIBounceTrackingProtection::MODE_ENABLED_STANDBY) {
// No further cleanup needed if we're just starting up.
if (aIsStartup) {
MOZ_ASSERT(!mStorageObserver);
MOZ_ASSERT(!mBounceTrackingPurgeTimer);
return result;
}
// Destroy storage observer to stop receiving storage notifications.
mStorageObserver = nullptr;
// Stop regular purging.
nsresult rv = UpdateBounceTrackingPurgeTimer(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
result = rv;
// Even if this step fails try to do more cleanup.
}
// Clear all per-tab state.
BounceTrackingState::DestroyAll();
return result;
}
// On enable
MOZ_ASSERT(mode == nsIBounceTrackingProtection::MODE_ENABLED ||
mode == nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN);
// Create and init storage observer.
mStorageObserver = new BounceTrackingStorageObserver();
nsresult rv = mStorageObserver->Init();
NS_ENSURE_SUCCESS(rv, rv);
// Schedule regular purging.
rv = UpdateBounceTrackingPurgeTimer(true);
NS_ENSURE_SUCCESS(rv, rv);
return result;
}
nsresult BounceTrackingProtection::RecordStatefulBounces(
BounceTrackingState* aBounceTrackingState) {
NS_ENSURE_ARG_POINTER(aBounceTrackingState);
@ -611,6 +717,17 @@ BounceTrackingProtection::EnsureRemoteExceptionListService() {
RefPtr<BounceTrackingProtection::PurgeBounceTrackersMozPromise>
BounceTrackingProtection::PurgeBounceTrackers() {
// Only purge when the feature is actually enabled.
if (StaticPrefs::privacy_bounceTrackingProtection_mode() !=
nsIBounceTrackingProtection::MODE_ENABLED &&
StaticPrefs::privacy_bounceTrackingProtection_mode() !=
nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip: Purging disabled via mode pref.", __FUNCTION__));
return PurgeBounceTrackersMozPromise::CreateAndReject(
nsresult::NS_ERROR_NOT_AVAILABLE, __func__);
}
// Prevent multiple purge operations from running at the same time.
if (mPurgeInProgress) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
@ -744,6 +861,14 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: %s", __FUNCTION__, aStateGlobal->Describe().get()));
// Ensure we only purge when pref configuration allows it.
if (StaticPrefs::privacy_bounceTrackingProtection_mode() !=
nsIBounceTrackingProtection::MODE_ENABLED &&
StaticPrefs::privacy_bounceTrackingProtection_mode() !=
nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN) {
return NS_ERROR_NOT_AVAILABLE;
}
const PRTime now = PR_Now();
// 1. Remove hosts from the user activation map whose user activation flag has
@ -839,7 +964,8 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
__FUNCTION__, PromiseFlatCString(host).get(), bounceTime,
aStateGlobal->Describe().get()));
if (StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode()) {
if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN) {
// 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.
cb->OnDataDeleted(0);

Просмотреть файл

@ -4,6 +4,7 @@
#ifndef mozilla_BounceTrackingProtection_h__
#define mozilla_BounceTrackingProtection_h__
#include "BounceTrackingStorageObserver.h"
#include "mozilla/Logging.h"
#include "mozilla/MozPromise.h"
#include "nsIBounceTrackingProtection.h"
@ -79,6 +80,18 @@ class BounceTrackingProtection final : public nsIBounceTrackingProtection,
// Initializes the singleton instance of BounceTrackingProtection.
[[nodiscard]] nsresult Init();
// Listens for feature pref changes and enables / disables BTP.
static void OnPrefChange(const char* aPref, void* aData);
// Called by OnPrefChange when the mode pref changes.
// isStartup indicates whether this is the initial mode change after startup.
nsresult OnModeChange(bool aIsStartup);
// Schedules or cancels the periodic bounce tracker purging. If this method is
// called while purging is already scheduled it will cancel the existing timer
// and then start a new timer.
nsresult UpdateBounceTrackingPurgeTimer(bool aShouldEnable);
// Keeps track of whether the feature is enabled based on pref state.
// Initialized on first call of GetSingleton.
static Maybe<bool> sFeatureIsEnabled;
@ -86,6 +99,9 @@ class BounceTrackingProtection final : public nsIBounceTrackingProtection,
// Timer which periodically runs PurgeBounceTrackers.
nsCOMPtr<nsITimer> mBounceTrackingPurgeTimer;
// Used to notify BounceTrackingState of storage and cookie access.
RefPtr<BounceTrackingStorageObserver> mStorageObserver;
// Storage for user agent globals.
RefPtr<BounceTrackingProtectionStorage> mStorage;

Просмотреть файл

@ -19,6 +19,7 @@
#include "mozilla/Services.h"
#include "nsCOMPtr.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIBounceTrackingProtection.h"
#include "nsIObserverService.h"
#include "nsIPrincipal.h"
#include "nsIScriptSecurityManager.h"
@ -58,6 +59,20 @@ BounceTrackingProtectionStorage::GetOrCreateStateGlobal(
aOriginAttributes);
}
nsresult BounceTrackingProtectionStorage::ClearByType(
BounceTrackingProtectionStorage::EntryType aType) {
for (auto iter = mStateGlobal.Iter(); !iter.Done(); iter.Next()) {
BounceTrackingStateGlobal* stateGlobal = iter.Data();
MOZ_ASSERT(stateGlobal);
// Update in memory state. Skip storage so we can batch the writes later.
nsresult rv = stateGlobal->ClearByType(aType, true);
NS_ENSURE_SUCCESS(rv, rv);
}
// Clear on-disk state for all OriginAttributes by type.
return DeleteDBEntriesByType(nullptr, aType);
}
nsresult BounceTrackingProtectionStorage::ClearBySiteHost(
const nsACString& aSiteHost, OriginAttributes* aOriginAttributes) {
NS_ENSURE_TRUE(!aSiteHost.IsEmpty(), NS_ERROR_INVALID_ARG);
@ -262,6 +277,34 @@ nsresult BounceTrackingProtectionStorage::DeleteDBEntriesInTimeRange(
return NS_OK;
}
nsresult BounceTrackingProtectionStorage::DeleteDBEntriesByType(
OriginAttributes* aOriginAttributes,
BounceTrackingProtectionStorage::EntryType aEntryType) {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = WaitForInitialization();
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<BounceTrackingProtectionStorage> self = this;
Maybe<OriginAttributes> originAttributes;
if (aOriginAttributes) {
originAttributes.emplace(*aOriginAttributes);
}
IncrementPendingWrites();
mBackgroundThread->Dispatch(
NS_NewRunnableFunction(
"BounceTrackingProtectionStorage::DeleteDBEntriesByType",
[self, originAttributes, aEntryType]() {
nsresult rv = self->DeleteDataByType(self->mDatabaseConnection,
originAttributes, aEntryType);
self->DecrementPendingWrites();
NS_ENSURE_SUCCESS_VOID(rv);
}),
NS_DISPATCH_EVENT_MAY_BLOCK);
return NS_OK;
}
nsresult
BounceTrackingProtectionStorage::DeleteDBEntriesByOriginAttributesPattern(
const OriginAttributesPattern& aOriginAttributesPattern) {
@ -414,9 +457,9 @@ nsresult BounceTrackingProtectionStorage::Init() {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s", __FUNCTION__));
// Init shouldn't be called if the feature is disabled.
NS_ENSURE_TRUE(
StaticPrefs::privacy_bounceTrackingProtection_enabled_AtStartup(),
NS_ERROR_FAILURE);
NS_ENSURE_TRUE(StaticPrefs::privacy_bounceTrackingProtection_mode() !=
nsIBounceTrackingProtection::MODE_DISABLED,
NS_ERROR_FAILURE);
// Register a shutdown blocker so we can flush pending changes to disk before
// shutdown.
@ -784,6 +827,49 @@ nsresult BounceTrackingProtectionStorage::DeleteDataInTimeRange(
return deleteStmt->Execute();
}
// static
nsresult BounceTrackingProtectionStorage::DeleteDataByType(
mozIStorageConnection* aDatabaseConnection,
const Maybe<OriginAttributes>& aOriginAttributes,
BounceTrackingProtectionStorage::EntryType aEntryType) {
MOZ_ASSERT(!NS_IsMainThread(),
"Must not write to the table from the main thread.");
MOZ_ASSERT(aDatabaseConnection);
MOZ_ASSERT(aOriginAttributes.isNothing() ||
aOriginAttributes->mPrivateBrowsingId ==
nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID);
nsAutoCString deleteQuery(
"DELETE FROM sites "
"WHERE entryType = :entryType"_ns);
if (aOriginAttributes) {
deleteQuery.AppendLiteral(
" AND originAttributeSuffix = :originAttributeSuffix");
}
deleteQuery.AppendLiteral(";");
nsCOMPtr<mozIStorageStatement> deleteStmt;
nsresult rv = aDatabaseConnection->CreateStatement(
deleteQuery, getter_AddRefs(deleteStmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteStmt->BindInt32ByName("entryType"_ns,
static_cast<int32_t>(aEntryType));
NS_ENSURE_SUCCESS(rv, rv);
if (aOriginAttributes) {
nsAutoCString originAttributeSuffix;
aOriginAttributes->CreateSuffix(originAttributeSuffix);
rv = deleteStmt->BindUTF8StringByName("originAttributeSuffix"_ns,
originAttributeSuffix);
NS_ENSURE_SUCCESS(rv, rv);
}
return deleteStmt->Execute();
}
nsresult BounceTrackingProtectionStorage::DeleteDataByOriginAttributesPattern(
mozIStorageConnection* aDatabaseConnection,
const OriginAttributesPattern& aOriginAttributesPattern) {

Просмотреть файл

@ -64,6 +64,10 @@ class BounceTrackingProtectionStorage final : public nsIObserver,
// migration.
enum class EntryType : uint8_t { BounceTracker = 0, UserActivation = 1 };
// Clear all user activation or bounce tracker entries.
[[nodiscard]] nsresult ClearByType(
BounceTrackingProtectionStorage::EntryType aType);
// Clear all state for a given site host. If aOriginAttributes is passed, only
// entries for that OA will be deleted.
[[nodiscard]] nsresult ClearBySiteHost(const nsACString& aSiteHost,
@ -145,6 +149,13 @@ class BounceTrackingProtectionStorage final : public nsIObserver,
Maybe<PRTime> aTo,
Maybe<BounceTrackingProtectionStorage::EntryType> aEntryType = Nothing{});
// Delete all entries of a specific type.
// aOriginAttributes can be passed
[[nodiscard]] nsresult DeleteDataByType(
mozIStorageConnection* aDatabaseConnection,
const Maybe<OriginAttributes>& aOriginAttributes,
BounceTrackingProtectionStorage::EntryType aEntryType);
// Delete all entries matching the given OriginAttributesPattern. Worker
// thread only.
[[nodiscard]] static nsresult DeleteDataByOriginAttributesPattern(
@ -195,6 +206,12 @@ class BounceTrackingProtectionStorage final : public nsIObserver,
OriginAttributes* aOriginAttributes, PRTime aFrom,
Maybe<PRTime> aTo = Nothing{}, Maybe<EntryType> aEntryType = Nothing{});
// Delete all DB entries matching the given type.
// If aOriginAttributes is passed it acts as an additional filter.
[[nodiscard]] nsresult DeleteDBEntriesByType(
OriginAttributes* aOriginAttributes,
BounceTrackingProtectionStorage::EntryType aEntryType);
// Deletes all DB entries matching the given OriginAttributesPattern.
[[nodiscard]] nsresult DeleteDBEntriesByOriginAttributesPattern(
const OriginAttributesPattern& aOriginAttributesPattern);

Просмотреть файл

@ -39,17 +39,21 @@ namespace mozilla {
static StaticAutoPtr<nsTHashMap<uint64_t, WeakPtr<BounceTrackingState>>>
sBounceTrackingStates;
static StaticRefPtr<BounceTrackingStorageObserver> sStorageObserver;
NS_IMPL_ISUPPORTS(BounceTrackingState, nsIWebProgressListener,
nsISupportsWeakReference);
BounceTrackingState::BounceTrackingState() {
MOZ_ASSERT(StaticPrefs::privacy_bounceTrackingProtection_enabled_AtStartup());
MOZ_ASSERT(StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED ||
StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN);
mBounceTrackingProtection = BounceTrackingProtection::GetSingleton();
};
BounceTrackingState::~BounceTrackingState() {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose,
("BounceTrackingState destructor"));
if (sBounceTrackingStates) {
sBounceTrackingStates->Remove(mBrowserId);
}
@ -108,21 +112,55 @@ already_AddRefed<BounceTrackingState> BounceTrackingState::GetOrCreate(
}
sBounceTrackingStates->InsertOrUpdate(browserId, newBTS);
// And the storage observer.
if (!sStorageObserver) {
sStorageObserver = new BounceTrackingStorageObserver();
ClearOnShutdown(&sStorageObserver);
aRv = sStorageObserver->Init();
NS_ENSURE_SUCCESS(aRv, nullptr);
}
return newBTS.forget();
};
// static
void BounceTrackingState::ResetAll() { Reset(nullptr, nullptr); }
// static
void BounceTrackingState::DestroyAll() {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s", __FUNCTION__));
if (!sBounceTrackingStates) {
return;
}
// Fully reset all BounceTrackingStates, so even if some don't get destroyed
// straight away things like running timers are stopped.
BounceTrackingState::Reset(nullptr, nullptr);
// Destroy all BounceTrackingState objects.
for (auto iter = sBounceTrackingStates->Iter(); !iter.Done(); iter.Next()) {
WeakPtr<BounceTrackingState> bts = iter.Data();
// Need to remove the element from the map prior to calling Destroy()
// because the destructor also updates the map and we can't iterate and
// externally modify the map at the same time. This way the Remove() call of
// the destructor is a no-op.
iter.Remove();
if (!bts) {
continue;
}
// Destroy the BounceTrackingState by dropping references to it. This is
// best effort. If something still holds a reference it still stay alive
// longer.
// Tell the web progress to drop the BTS reference.
RefPtr<dom::BrowsingContext> browsingContext =
bts->CurrentBrowsingContext();
if (!browsingContext) {
continue;
}
dom::BrowsingContextWebProgress* webProgress =
browsingContext->Canonical()->GetWebProgress();
if (!webProgress) {
continue;
}
webProgress->DropBounceTrackingState();
}
// Clean up the map.
sBounceTrackingStates = nullptr;
}
// static
void BounceTrackingState::ResetAllForOriginAttributes(
const OriginAttributes& aOriginAttributes) {
@ -137,14 +175,19 @@ void BounceTrackingState::ResetAllForOriginAttributesPattern(
nsresult BounceTrackingState::Init(
dom::BrowsingContextWebProgress* aWebProgress) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("BounceTrackingState::%s", __FUNCTION__));
MOZ_ASSERT(!mIsInitialized,
"BounceTrackingState must not be initialized twice.");
mIsInitialized = true;
NS_ENSURE_ARG_POINTER(aWebProgress);
NS_ENSURE_TRUE(
StaticPrefs::privacy_bounceTrackingProtection_enabled_AtStartup(),
NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED ||
StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN,
NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(mBounceTrackingProtection, NS_ERROR_FAILURE);
// Store the browser ID so we can get the associated BC later without having
@ -160,11 +203,8 @@ nsresult BounceTrackingState::Init(
// Add a listener for window load. See BounceTrackingState::OnStateChange for
// the listener code.
nsresult rv = aWebProgress->AddProgressListener(
this, nsIWebProgress::NOTIFY_STATE_WINDOW);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
return aWebProgress->AddProgressListener(this,
nsIWebProgress::NOTIFY_STATE_WINDOW);
}
void BounceTrackingState::ResetBounceTrackingRecord() {
@ -226,8 +266,10 @@ bool BounceTrackingState::ShouldCreateBounceTrackingStateForWebProgress(
dom::BrowsingContextWebProgress* aWebProgress) {
NS_ENSURE_TRUE(aWebProgress, false);
// Feature is globally disabled.
if (!StaticPrefs::privacy_bounceTrackingProtection_enabled_AtStartup()) {
uint8_t mode = StaticPrefs::privacy_bounceTrackingProtection_mode();
// Classification / purging is disabled.
if (mode != nsIBounceTrackingProtection::MODE_ENABLED &&
mode != nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN) {
return false;
}

Просмотреть файл

@ -49,8 +49,15 @@ class BounceTrackingState : public nsIWebProgressListener,
// Reset state for all BounceTrackingState instances this includes resetting
// BounceTrackingRecords and cancelling any running timers.
static void ResetAll();
// Resets and destroys all BounceTrackingState objects. This is used when the
// feature gets disabled.
static void DestroyAll();
// Reset BounceTrackingState objects matching OriginAttributes.
static void ResetAllForOriginAttributes(
const OriginAttributes& aOriginAttributes);
// Same as above but for a pattern.
static void ResetAllForOriginAttributesPattern(
const OriginAttributesPattern& aPattern);

Просмотреть файл

@ -206,6 +206,26 @@ nsresult BounceTrackingStateGlobal::RemoveBounceTrackers(
return NS_OK;
}
nsresult BounceTrackingStateGlobal::ClearByType(
BounceTrackingProtectionStorage::EntryType aType, bool aSkipStorage) {
if (aType == BounceTrackingProtectionStorage::EntryType::BounceTracker) {
mBounceTrackers.Clear();
} else {
MOZ_ASSERT(aType ==
BounceTrackingProtectionStorage::EntryType::UserActivation);
mUserActivation.Clear();
}
if (aSkipStorage || !ShouldPersistToDisk()) {
return NS_OK;
}
NS_ENSURE_TRUE(mStorage, NS_ERROR_FAILURE);
return mStorage->DeleteDBEntriesByType(
&mOriginAttributes,
BounceTrackingProtectionStorage::EntryType::BounceTracker);
}
// static
nsCString BounceTrackingStateGlobal::DescribeMap(
const nsTHashMap<nsCStringHashKey, PRTime>& aMap) {

Просмотреть файл

@ -66,6 +66,10 @@ class BounceTrackingStateGlobal final {
[[nodiscard]] nsresult RemoveBounceTrackers(
const nsTArray<nsCString>& aSiteHosts);
// Clear user activation or bounce tracker map.
[[nodiscard]] nsresult ClearByType(
BounceTrackingProtectionStorage::EntryType aType, bool aSkipStorage);
[[nodiscard]] nsresult ClearSiteHost(const nsACString& aSiteHost,
bool aSkipStorage = false);

Просмотреть файл

@ -18,21 +18,25 @@
namespace mozilla {
NS_IMPL_ISUPPORTS(BounceTrackingStorageObserver, nsIObserver);
NS_IMPL_ISUPPORTS(BounceTrackingStorageObserver, nsIObserver,
nsISupportsWeakReference);
nsresult BounceTrackingStorageObserver::Init() {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s", __FUNCTION__));
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("BounceTrackingStorageObserver::%s", __FUNCTION__));
// Add observers to listen for cookie changes.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
NS_ENSURE_TRUE(observerService, NS_ERROR_FAILURE);
nsresult rv = observerService->AddObserver(this, "cookie-changed", false);
// Passing ownsWeak=true so we don't have to unregister the observer when
// BounceTrackingStorageObserver gets destroyed.
nsresult rv = observerService->AddObserver(this, "cookie-changed", true);
NS_ENSURE_SUCCESS(rv, rv);
return observerService->AddObserver(this, "private-cookie-changed", false);
return observerService->AddObserver(this, "private-cookie-changed", true);
}
// nsIObserver

Просмотреть файл

@ -6,6 +6,7 @@
#include "mozilla/Logging.h"
#include "nsIObserver.h"
#include "nsWeakReference.h"
namespace mozilla {
@ -15,19 +16,19 @@ class WindowContext;
extern LazyLogModule gBounceTrackingProtectionLog;
class BounceTrackingStorageObserver final : public nsIObserver {
class BounceTrackingStorageObserver : public nsIObserver,
public nsSupportsWeakReference {
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
public:
BounceTrackingStorageObserver() = default;
[[nodiscard]] nsresult Init();
[[nodiscard]] static nsresult OnInitialStorageAccess(
dom::WindowContext* aWindowContext);
[[nodiscard]] nsresult Init();
private:
~BounceTrackingStorageObserver() = default;
virtual ~BounceTrackingStorageObserver() = default;
};
} // namespace mozilla

Просмотреть файл

@ -4,6 +4,7 @@
#include "ClearDataCallback.h"
#include "mozilla/glean/GleanMetrics.h"
#include "nsIBounceTrackingProtection.h"
#include "nsIURIClassifier.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "nsNetCID.h"
@ -45,7 +46,8 @@ ClearDataCallback::ClearDataCallback(ClearDataMozPromise::Private* aPromise,
mClearDurationTimer(0) {
MOZ_ASSERT(!aHost.IsEmpty(), "Host must not be empty");
if (!StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode()) {
if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED) {
// Only collect timing information when actually performing the deletion
mClearDurationTimer =
glean::bounce_tracking_protection::purge_duration.Start();
@ -112,7 +114,8 @@ void ClearDataCallback::RecordClearDurationTelemetry() {
}
void ClearDataCallback::RecordPurgeCountTelemetry(bool aFailed) {
if (StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode()) {
if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN) {
MOZ_ASSERT(aFailed == 0, "Dry-run purge can't fail");
glean::bounce_tracking_protection::purge_count.Get("dry"_ns).Add(1);
} else if (aFailed) {
@ -180,8 +183,8 @@ void ClearDataCallback::RecordPurgeEventTelemetry(bool aSuccess) {
// Record a glean event for the clear action.
glean::bounce_tracking_protection::PurgeActionExtra extra = {
.bounceTime = Some(mBounceTime / PR_USEC_PER_SEC),
.isDryRun = Some(
StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode()),
.isDryRun = Some(StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN),
.siteHost = Some(mHost),
.success = Some(aSuccess),
};

Просмотреть файл

@ -7,6 +7,37 @@
[scriptable, uuid(4866F748-29DA-4C10-8EAA-ED2F7851E6B1)]
interface nsIBounceTrackingProtection : nsISupports {
/**
* Modes for Bounce Tracking Protection
*
* MODE_DISABLED - Feature fully disabled and not initialized at startup. No
* user activation signals are collected. Requires a restart to apply.
* MODE_ENABLED - Feature fully enabled. This includes: collection of user
* activation signals, classification of bounce trackers, periodic purging
* of bounce trackers.
* MODE_ENABLED_STANDBY - Tracker classification and purging is disabled.
* User activation signals are still collected and stored.
* MODE_ENABLED_DRY_RUN - Dry-run mode: The feature is fully enabled, but
* tracker purging is simulated. No site data is purged. Purge telemetry
* still gets collected. This mode is helpful for testing the feature
* without risking data loss.
*
* For toggling the feature in privacy settings UI switch between
* MODE_ENABLED and MODE_NO_PURGE. This is important so that user activation
* signals are still collected even if the feature is "turned off" for the
* user.
* Fully enabling / disabling the feature (MODE_DISABLED -> x or x ->
* MODE_DISABLED) requires a restart to apply.
*/
cenum Modes : 8 {
MODE_DISABLED = 0,
MODE_ENABLED = 1,
MODE_ENABLED_STANDBY = 2,
MODE_ENABLED_DRY_RUN = 3,
// Not a valid mode, only used for pref validation.
MAX_MODE_VALUE = 3,
};
// Reset the global bounce tracking state, including the maps for tracking
// bounce tracker candidates and user activation.
void clearAll();

Просмотреть файл

@ -1,10 +1,9 @@
[DEFAULT]
head = "head.js"
prefs = [
"privacy.bounceTrackingProtection.enabled=true",
"privacy.bounceTrackingProtection.mode=1",
"privacy.bounceTrackingProtection.enableTestMode=true",
"privacy.bounceTrackingProtection.bounceTrackingPurgeTimerPeriodSec=0",
"privacy.bounceTrackingProtection.enableDryRunMode=false",
# Do not upgrade ORIGIN_TRACKER_B, all other origins are already HTTPS
"dom.security.https_first=false",
]

Просмотреть файл

@ -86,7 +86,12 @@ add_setup(async function () {
add_task(async function test_purge_in_regular_mode() {
await SpecialPowers.pushPrefEnv({
set: [["privacy.bounceTrackingProtection.enableDryRunMode", false]],
set: [
[
"privacy.bounceTrackingProtection.mode",
Ci.nsIBounceTrackingProtection.MODE_ENABLED,
],
],
});
await runPurgeTest(true);
@ -94,7 +99,12 @@ add_task(async function test_purge_in_regular_mode() {
add_task(async function test_purge_in_dry_run_mode() {
await SpecialPowers.pushPrefEnv({
set: [["privacy.bounceTrackingProtection.enableDryRunMode", true]],
set: [
[
"privacy.bounceTrackingProtection.mode",
Ci.nsIBounceTrackingProtection.MODE_ENABLED_DRY_RUN,
],
],
});
await runPurgeTest(false);

Просмотреть файл

@ -23,7 +23,14 @@ function assertCounterNull() {
async function testPurgeCount(isDryRunMode) {
await SpecialPowers.pushPrefEnv({
set: [["privacy.bounceTrackingProtection.enableDryRunMode", isDryRunMode]],
set: [
[
"privacy.bounceTrackingProtection.mode",
isDryRunMode
? Ci.nsIBounceTrackingProtection.MODE_ENABLED_DRY_RUN
: Ci.nsIBounceTrackingProtection.MODE_ENABLED,
],
],
});
assertCounterNull();

Просмотреть файл

@ -7,7 +7,14 @@ let bounceTrackingProtection;
async function test_purge_duration(isDryRunMode) {
await SpecialPowers.pushPrefEnv({
set: [["privacy.bounceTrackingProtection.enableDryRunMode", isDryRunMode]],
set: [
[
"privacy.bounceTrackingProtection.mode",
isDryRunMode
? Ci.nsIBounceTrackingProtection.MODE_ENABLED_DRY_RUN
: Ci.nsIBounceTrackingProtection.MODE_ENABLED,
],
],
});
is(

Просмотреть файл

@ -22,7 +22,14 @@ add_setup(async function () {
async function runTest(useDryRunMode) {
await SpecialPowers.pushPrefEnv({
set: [["privacy.bounceTrackingProtection.enableDryRunMode", useDryRunMode]],
set: [
[
"privacy.bounceTrackingProtection.mode",
useDryRunMode
? Ci.nsIBounceTrackingProtection.MODE_ENABLED_DRY_RUN
: Ci.nsIBounceTrackingProtection.MODE_ENABLED,
],
],
});
is(

Просмотреть файл

@ -231,15 +231,21 @@ async function navigateLinkClick(
* run for the given browser.
*/
async function waitForRecordBounces(browser) {
return TestUtils.topicObserved(
let { browserId } = browser.browsingContext;
info(
`waitForRecordBounces: Waiting for record bounces for browser: ${browserId}.`
);
await TestUtils.topicObserved(
OBSERVER_MSG_RECORD_BOUNCES_FINISHED,
subject => {
// Ensure the message was dispatched for the browser we're interested in.
let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
let browserId = propBag.getProperty("browserId");
return browser.browsingContext.browserId == browserId;
return browserId == propBag.getProperty("browserId");
}
);
info(`waitForRecordBounces: Recorded bounces for browser ${browserId}.`);
}
/**

Просмотреть файл

@ -1,6 +1,6 @@
[DEFAULT]
prefs = [
"privacy.bounceTrackingProtection.enabled=true",
"privacy.bounceTrackingProtection.mode=1",
"privacy.bounceTrackingProtection.enableTestMode=true",
]

Просмотреть файл

@ -28,9 +28,11 @@ add_setup(function () {
* @param {bool} num - Number of hosts to purge
*/
async function testNumHostsPerPurgeRun(isDryRunMode, num) {
Services.prefs.setBoolPref(
"privacy.bounceTrackingProtection.enableDryRunMode",
Services.prefs.setIntPref(
"privacy.bounceTrackingProtection.mode",
isDryRunMode
? Ci.nsIBounceTrackingProtection.MODE_ENABLED_DRY_RUN
: Ci.nsIBounceTrackingProtection.MODE_ENABLED
);
Assert.equal(
@ -69,9 +71,7 @@ async function testNumHostsPerPurgeRun(isDryRunMode, num) {
}
// Cleanup
Services.prefs.clearUserPref(
"privacy.bounceTrackingProtection.enableDryRunMode"
);
Services.prefs.clearUserPref("privacy.bounceTrackingProtection.mode");
Services.fog.testResetFOG();
btp.clearAll();
}

Просмотреть файл

@ -1,9 +1,8 @@
[DEFAULT]
prefs = [
"privacy.bounceTrackingProtection.enabled=true",
"privacy.bounceTrackingProtection.mode=1",
"privacy.bounceTrackingProtection.enableTestMode=true",
"privacy.bounceTrackingProtection.bounceTrackingPurgeTimerPeriodSec=0",
"privacy.bounceTrackingProtection.enableDryRunMode=false",
]
["test_bouncetracking_clearExpiredUserActivation.js"]

Просмотреть файл

@ -40,9 +40,9 @@ XPCOMUtils.defineLazyServiceGetter(
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"isBounceTrackingProtectionEnabled",
"privacy.bounceTrackingProtection.enabled",
false
"bounceTrackingProtectionMode",
"privacy.bounceTrackingProtection.mode",
Ci.nsIBounceTrackingProtection.MODE_DISABLED
);
/**
@ -1798,14 +1798,20 @@ const IdentityCredentialStorageCleaner = {
const BounceTrackingProtectionStateCleaner = {
async deleteAll() {
if (!lazy.isBounceTrackingProtectionEnabled) {
if (
lazy.bounceTrackingProtectionMode ==
Ci.nsIBounceTrackingProtection.MODE_DISABLED
) {
return;
}
await lazy.bounceTrackingProtection.clearAll();
},
async deleteByPrincipal(aPrincipal) {
if (!lazy.isBounceTrackingProtectionEnabled) {
if (
lazy.bounceTrackingProtectionMode ==
Ci.nsIBounceTrackingProtection.MODE_DISABLED
) {
return;
}
let { baseDomain, originAttributes } = aPrincipal;
@ -1816,21 +1822,30 @@ const BounceTrackingProtectionStateCleaner = {
},
async deleteByBaseDomain(aBaseDomain) {
if (!lazy.isBounceTrackingProtectionEnabled) {
if (
lazy.bounceTrackingProtectionMode ==
Ci.nsIBounceTrackingProtection.MODE_DISABLED
) {
return;
}
await lazy.bounceTrackingProtection.clearBySiteHost(aBaseDomain);
},
async deleteByRange(aFrom, aTo) {
if (!lazy.isBounceTrackingProtectionEnabled) {
if (
lazy.bounceTrackingProtectionMode ==
Ci.nsIBounceTrackingProtection.MODE_DISABLED
) {
return;
}
await lazy.bounceTrackingProtection.clearByTimeRange(aFrom, aTo);
},
async deleteByHost(aHost) {
if (!lazy.isBounceTrackingProtectionEnabled) {
if (
lazy.bounceTrackingProtectionMode ==
Ci.nsIBounceTrackingProtection.MODE_DISABLED
) {
return;
}
let baseDomain = getBaseDomainWithFallback(aHost);
@ -1838,7 +1853,10 @@ const BounceTrackingProtectionStateCleaner = {
},
async deleteByOriginAttributes(aOriginAttributesPatternString) {
if (!lazy.isBounceTrackingProtectionEnabled) {
if (
lazy.bounceTrackingProtectionMode ==
Ci.nsIBounceTrackingProtection.MODE_DISABLED
) {
return;
}
await lazy.bounceTrackingProtection.clearByOriginAttributesPattern(

Просмотреть файл

@ -3423,21 +3423,11 @@ bounceTrackingProtection:
hasExposure: false
variables:
enabled:
type: boolean
type: int
setPref:
branch: default
pref: privacy.bounceTrackingProtection.enabled
description: Main flag to enable / disable the feature.
enableDryRunMode:
type: boolean
setPref:
branch: default
pref: privacy.bounceTrackingProtection.enableDryRunMode
description: >-
Enables a mode where if bounce tracking protection is enabled it
classifies but does not purge trackers. This mode is helpful for testing
the feature without risking data loss. Telemetry is still collected
normally.
pref: privacy.bounceTrackingProtection.mode
description: Mode to run the feature in. See nsIBounceTrackingProtection.idl for documentation.
remoteTabManagement:
description: >