1
0
Fork 0
firefox/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

1440 lines
51 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BounceTrackingProtection.h"
#include "BounceTrackingAllowList.h"
#include "BounceTrackingProtectionStorage.h"
#include "BounceTrackingState.h"
#include "BounceTrackingRecord.h"
#include "BounceTrackingMapEntry.h"
#include "ClearDataCallback.h"
#include "PromiseNativeWrapper.h"
#include "BounceTrackingStateGlobal.h"
#include "ErrorList.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Logging.h"
#include "mozilla/Maybe.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/dom/Promise.h"
#include "nsDebug.h"
#include "nsHashPropertyBag.h"
#include "nsIClearDataService.h"
#include "nsIObserverService.h"
#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsISupports.h"
#include "nsISupportsPrimitives.h"
#include "nsServiceManagerUtils.h"
#include "nscore.h"
#include "prtime.h"
#include "mozilla/dom/BrowsingContext.h"
#include "xpcpublic.h"
#include "mozilla/glean/AntitrackingBouncetrackingprotectionMetrics.h"
#include "mozilla/ContentBlockingLog.h"
#include "mozilla/glean/GleanPings.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "nsIConsoleService.h"
#include "mozilla/intl/Localization.h"
#include "mozilla/browser/NimbusFeatures.h"
#define TEST_OBSERVER_MSG_RECORD_BOUNCES_FINISHED "test-record-bounces-finished"
namespace mozilla {
NS_IMPL_ISUPPORTS(BounceTrackingProtection, nsIObserver,
nsISupportsWeakReference, nsIBounceTrackingProtection);
LazyLogModule gBounceTrackingProtectionLog("BounceTrackingProtection");
static StaticRefPtr<BounceTrackingProtection> sBounceTrackingProtection;
static bool sInitFailed = false;
Maybe<uint32_t> BounceTrackingProtection::sLastRecordedModeTelemetry;
static const char kBTPModePref[] = "privacy.bounceTrackingProtection.mode";
// static
already_AddRefed<BounceTrackingProtection>
BounceTrackingProtection::GetSingleton() {
MOZ_ASSERT(XRE_IsParentProcess());
// Init previously failed, don't try again.
if (sInitFailed) {
return nullptr;
}
RecordModePrefTelemetry();
// Feature is disabled.
if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_DISABLED) {
return nullptr;
}
// Feature is enabled, lazily create singleton instance.
if (!sBounceTrackingProtection) {
sBounceTrackingProtection = new BounceTrackingProtection();
RunOnShutdown([] {
if (sBounceTrackingProtection &&
sBounceTrackingProtection->mRemoteExceptionList) {
Unused << sBounceTrackingProtection->mRemoteExceptionList->Shutdown();
}
sBounceTrackingProtection = nullptr;
});
nsresult rv = sBounceTrackingProtection->Init();
if (NS_WARN_IF(NS_FAILED(rv))) {
sInitFailed = true;
return nullptr;
}
}
return do_AddRef(sBounceTrackingProtection);
}
// static
void BounceTrackingProtection::RecordModePrefTelemetry() {
uint32_t featureMode = StaticPrefs::privacy_bounceTrackingProtection_mode();
// Record mode telemetry, but only if the mode changed.
if (sLastRecordedModeTelemetry.isNothing() ||
sLastRecordedModeTelemetry.value() != featureMode) {
glean::bounce_tracking_protection::mode.Set(featureMode);
sLastRecordedModeTelemetry = Some(featureMode);
}
}
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: mode: %d, "
"bounceTrackingActivationLifetimeSec: %d, bounceTrackingGracePeriodSec: "
"%d, bounceTrackingPurgeTimerPeriodSec: %d, "
"clientBounceDetectionTimerPeriodMS: %d, requireStatefulBounces: %d, "
"HasMigratedUserActivationData: %d",
StaticPrefs::privacy_bounceTrackingProtection_mode(),
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();
nsresult rv = mStorage->Init();
NS_ENSURE_SUCCESS(rv, rv);
rv = MaybeMigrateUserInteractionPermissions();
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Error,
("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::
privacy_bounceTrackingProtection_bounceTrackingPurgeTimerPeriodSec();
// The pref can be set to 0 to disable interval purging.
if (purgeTimerPeriod == 0) {
return NS_OK;
}
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("Scheduling mBounceTrackingPurgeTimer. Interval: %d seconds.",
purgeTimerPeriod));
return NS_NewTimerWithCallback(
getter_AddRefs(mBounceTrackingPurgeTimer),
[](auto) {
if (!sBounceTrackingProtection) {
return;
}
sBounceTrackingProtection->PurgeBounceTrackers()->Then(
GetMainThreadSerialEventTarget(), __func__,
[] {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: PurgeBounceTrackers finished after timer call.",
__FUNCTION__));
},
[] { NS_WARNING("RunPurgeBounceTrackers failed"); });
},
purgeTimerPeriod * PR_MSEC_PER_SEC, nsITimer::TYPE_REPEATING_SLACK,
"mBounceTrackingPurgeTimer");
}
// static
void BounceTrackingProtection::OnPrefChange(const char* aPref, void* aData) {
MOZ_ASSERT(sBounceTrackingProtection);
MOZ_ASSERT(strcmp(kBTPModePref, aPref) == 0);
RecordModePrefTelemetry();
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);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: aBounceTrackingState: %s", __FUNCTION__,
aBounceTrackingState->Describe().get()));
// Assert: navigables bounce tracking record is not null.
const Maybe<BounceTrackingRecord>& record =
aBounceTrackingState->GetBounceTrackingRecord();
NS_ENSURE_TRUE(record, NS_ERROR_FAILURE);
// Get the bounce tracker map and the user activation map.
RefPtr<BounceTrackingStateGlobal> globalState =
mStorage->GetOrCreateStateGlobal(aBounceTrackingState);
MOZ_ASSERT(globalState);
nsTArray<nsCString> classifiedHosts;
// For each host in navigables 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 navigables bounce tracking record's initial host,
// continue.
if (host == record->GetInitialHost()) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip host == initialHost: %s", __FUNCTION__,
PromiseFlatCString(host).get()));
continue;
}
// If host equals navigables bounce tracking record's final host, continue.
if (host == record->GetFinalHost()) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip host == finalHost: %s", __FUNCTION__,
PromiseFlatCString(host).get()));
continue;
}
// If user activation map contains host, continue.
if (globalState->HasUserActivation(host)) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip host with recent user activation: %s", __FUNCTION__,
PromiseFlatCString(host).get()));
continue;
}
// If stateful bounce tracking map contains host, continue.
if (globalState->HasBounceTracker(host)) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip already existing host: %s", __FUNCTION__,
PromiseFlatCString(host).get()));
continue;
}
// If navigables bounce tracking record's storage access set does not
// contain host, continue.
if (StaticPrefs::
privacy_bounceTrackingProtection_requireStatefulBounces() &&
!record->GetStorageAccessHosts().Contains(host)) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip host without storage access: %s", __FUNCTION__,
PromiseFlatCString(host).get()));
continue;
}
// Set stateful bounce tracking map[host] to topDocuments relevant settings
// object's current wall time.
PRTime now = PR_Now();
MOZ_ASSERT(!globalState->HasBounceTracker(host));
nsresult rv = globalState->RecordBounceTracker(host, now);
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
classifiedHosts.AppendElement(host);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
("%s: Added bounce tracker candidate. siteHost: %s, "
"aBounceTrackingState: %s",
__FUNCTION__, PromiseFlatCString(host).get(),
aBounceTrackingState->Describe().get()));
}
// Set navigables bounce tracking record to null.
aBounceTrackingState->ResetBounceTrackingRecord();
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Done, reset aBounceTrackingState: %s", __FUNCTION__,
aBounceTrackingState->Describe().get()));
// Log a message to the web console for each classified host.
nsresult rv = LogBounceTrackersClassifiedToWebConsole(aBounceTrackingState,
classifiedHosts);
NS_ENSURE_SUCCESS(rv, rv);
// If running in test automation, dispatch an observer message indicating
// we're finished recording bounces.
if (StaticPrefs::privacy_bounceTrackingProtection_enableTestMode()) {
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
NS_ENSURE_TRUE(obsSvc, NS_ERROR_FAILURE);
RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
nsresult rv = props->SetPropertyAsUint64(
u"browserId"_ns, aBounceTrackingState->GetBrowserId());
NS_ENSURE_SUCCESS(rv, rv);
rv = obsSvc->NotifyObservers(
ToSupports(props), TEST_OBSERVER_MSG_RECORD_BOUNCES_FINISHED, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult BounceTrackingProtection::RecordUserActivation(
nsIPrincipal* aPrincipal, Maybe<PRTime> aActivationTime,
dom::CanonicalBrowsingContext* aTopBrowsingContext) {
MOZ_ASSERT(XRE_IsParentProcess());
NS_ENSURE_ARG_POINTER(aPrincipal);
NS_ENSURE_TRUE(!aTopBrowsingContext || aTopBrowsingContext->IsTop(),
NS_ERROR_INVALID_ARG);
RefPtr<BounceTrackingProtection> btp = GetSingleton();
// May be nullptr if feature is disabled.
if (!btp) {
return NS_OK;
}
if (!BounceTrackingState::ShouldTrackPrincipal(aPrincipal)) {
return NS_OK;
}
nsAutoCString siteHost;
nsresult rv = aPrincipal->GetBaseDomain(siteHost);
NS_ENSURE_SUCCESS(rv, rv);
if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
nsAutoCString oaStr;
aPrincipal->OriginAttributesRef().CreateSuffix(oaStr);
MOZ_LOG_FMT(gBounceTrackingProtectionLog, LogLevel::Debug,
"{}: originAttributes: {}, siteHost: {}", __FUNCTION__, oaStr,
siteHost.get());
}
RefPtr<BounceTrackingStateGlobal> globalState =
btp->mStorage->GetOrCreateStateGlobal(aPrincipal);
MOZ_ASSERT(globalState);
// aActivationTime defaults to current time if no value is provided.
rv = globalState->RecordUserActivation(siteHost,
aActivationTime.valueOr(PR_Now()));
NS_ENSURE_SUCCESS(rv, rv);
if (aTopBrowsingContext) {
MOZ_ASSERT(aTopBrowsingContext->IsTop());
dom::BrowsingContextWebProgress* webProgress =
aTopBrowsingContext->GetWebProgress();
NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE);
RefPtr<BounceTrackingState> bounceTrackingState =
webProgress->GetBounceTrackingState();
// We may not always get a BounceTrackingState, e.g the feature is disabled,
// if the user has opted into all cookies, no cookies, or some other case.
// If we don't have it, just return OK here.
NS_ENSURE_TRUE(bounceTrackingState, NS_OK);
return bounceTrackingState->OnUserActivation(siteHost);
}
return NS_OK;
}
nsresult BounceTrackingProtection::RecordUserActivation(
dom::WindowContext* aWindowContext) {
NS_ENSURE_ARG_POINTER(aWindowContext);
if (XRE_IsContentProcess()) {
dom::WindowGlobalChild* wgc = aWindowContext->GetWindowGlobalChild();
NS_ENSURE_TRUE(wgc, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(wgc->SendRecordUserActivationForBTP(), NS_ERROR_FAILURE);
return NS_OK;
}
MOZ_ASSERT(XRE_IsParentProcess());
dom::WindowGlobalParent* wgp = aWindowContext->Canonical();
MOZ_ASSERT(wgp);
NS_ENSURE_TRUE(wgp->RecvRecordUserActivationForBTP(), NS_ERROR_FAILURE);
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: aTopic: %s", __FUNCTION__, aTopic));
if (!strcmp(aTopic, "idle-daily")) {
#ifndef MOZ_WIDGET_ANDROID // OHTTP is not supported on Android.
// Submit custom telemetry ping.
glean_pings::BounceTrackingProtection.Submit();
#endif // #ifndef MOZ_WIDGET_ANDROID
}
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::TestGetBounceTrackerCandidateHosts(
JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
nsTArray<RefPtr<nsIBounceTrackingMapEntry>>& aCandidates) {
MOZ_ASSERT(aCx);
OriginAttributes oa;
if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<BounceTrackingStateGlobal> globalState = mStorage->GetStateGlobal(oa);
if (!globalState) {
return NS_OK;
}
for (auto iter = globalState->BounceTrackersMapRef().ConstIter();
!iter.Done(); iter.Next()) {
RefPtr<nsIBounceTrackingMapEntry> candidate = new BounceTrackingMapEntry(
globalState->OriginAttributesRef(), iter.Key(), iter.Data());
aCandidates.AppendElement(candidate);
}
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::TestGetUserActivationHosts(
JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
nsTArray<RefPtr<nsIBounceTrackingMapEntry>>& aHosts) {
MOZ_ASSERT(aCx);
OriginAttributes oa;
if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<BounceTrackingStateGlobal> globalState = mStorage->GetStateGlobal(oa);
if (!globalState) {
return NS_OK;
}
for (auto iter = globalState->UserActivationMapRef().ConstIter();
!iter.Done(); iter.Next()) {
RefPtr<nsIBounceTrackingMapEntry> candidate = new BounceTrackingMapEntry(
globalState->OriginAttributesRef(), iter.Key(), iter.Data());
aHosts.AppendElement(candidate);
}
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::TestGetRecentlyPurgedTrackers(
JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
nsTArray<RefPtr<nsIBounceTrackingPurgeEntry>>& aPurgedTrackers) {
MOZ_ASSERT(aCx);
OriginAttributes oa;
if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<BounceTrackingStateGlobal> globalState = mStorage->GetStateGlobal(oa);
if (!globalState) {
return NS_OK;
}
nsTArray<RefPtr<BounceTrackingPurgeEntry>> purgeEntriesSorted;
for (auto iter = globalState->RecentPurgesMapRef().ConstIter(); !iter.Done();
iter.Next()) {
for (const auto& entry : iter.Data()) {
purgeEntriesSorted.InsertElementSorted(entry, PurgeEntryTimeComparator{});
}
}
aPurgedTrackers.AppendElements(purgeEntriesSorted);
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::ClearAll() {
BounceTrackingState::ResetAll();
return mStorage->Clear();
}
NS_IMETHODIMP
BounceTrackingProtection::ClearBySiteHostAndOriginAttributes(
const nsACString& aSiteHost, JS::Handle<JS::Value> aOriginAttributes,
JSContext* aCx) {
NS_ENSURE_ARG_POINTER(aCx);
OriginAttributes originAttributes;
if (!aOriginAttributes.isObject() ||
!originAttributes.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
// Reset per tab state for tabs matching the given OriginAttributes.
BounceTrackingState::ResetAllForOriginAttributes(originAttributes);
return mStorage->ClearBySiteHost(aSiteHost, &originAttributes);
}
NS_IMETHODIMP
BounceTrackingProtection::ClearBySiteHostAndOriginAttributesPattern(
const nsACString& aSiteHost, JS::Handle<JS::Value> aOriginAttributesPattern,
JSContext* aCx) {
NS_ENSURE_ARG_POINTER(aCx);
OriginAttributesPattern pattern;
if (!aOriginAttributesPattern.isObject() ||
!pattern.Init(aCx, aOriginAttributesPattern)) {
return NS_ERROR_INVALID_ARG;
}
// Clear per-tab state.
BounceTrackingState::ResetAllForOriginAttributesPattern(pattern);
// Clear global state including on-disk state.
return mStorage->ClearByOriginAttributesPattern(pattern,
Some(nsCString(aSiteHost)));
}
NS_IMETHODIMP
BounceTrackingProtection::ClearByTimeRange(PRTime aFrom, PRTime aTo) {
NS_ENSURE_TRUE(aFrom >= 0, NS_ERROR_INVALID_ARG);
NS_ENSURE_TRUE(aFrom < aTo, NS_ERROR_INVALID_ARG);
// Clear all BounceTrackingState, we don't keep track of time ranges.
BounceTrackingState::ResetAll();
return mStorage->ClearByTimeRange(aFrom, aTo);
}
NS_IMETHODIMP
BounceTrackingProtection::ClearByOriginAttributesPattern(
const nsAString& aPattern) {
OriginAttributesPattern pattern;
if (!pattern.Init(aPattern)) {
return NS_ERROR_INVALID_ARG;
}
// Reset all per-tab state matching the given OriginAttributesPattern.
BounceTrackingState::ResetAllForOriginAttributesPattern(pattern);
return mStorage->ClearByOriginAttributesPattern(pattern);
}
NS_IMETHODIMP
BounceTrackingProtection::AddSiteHostExceptions(
const nsTArray<nsCString>& aSiteHosts) {
for (const auto& host : aSiteHosts) {
mRemoteSiteHostExceptions.Insert(host);
}
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::RemoveSiteHostExceptions(
const nsTArray<nsCString>& aSiteHosts) {
for (const auto& host : aSiteHosts) {
mRemoteSiteHostExceptions.Remove(host);
}
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::HasRecentlyPurgedSite(const nsACString& aSiteHost,
bool* aResult) {
NS_ENSURE_ARG_POINTER(aResult);
*aResult = false;
NS_ENSURE_TRUE(!aSiteHost.IsEmpty(), NS_ERROR_INVALID_ARG);
// Look for a purge log entry matching the site and return greedily if we find
// one. We look through all state globals because we want to search across all
// OriginAttributes.
for (const auto& entry : mStorage->StateGlobalMapRef()) {
RefPtr<BounceTrackingStateGlobal> stateGlobal = entry.GetData();
MOZ_ASSERT(stateGlobal);
if (stateGlobal->RecentPurgesMapRef().Contains(aSiteHost)) {
*aResult = true;
return NS_OK;
}
}
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::TestGetSiteHostExceptions(
nsTArray<nsCString>& aSiteHostExceptions) {
aSiteHostExceptions.Clear();
for (const auto& host : mRemoteSiteHostExceptions) {
aSiteHostExceptions.AppendElement(host);
}
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::TestRunPurgeBounceTrackers(
JSContext* aCx, mozilla::dom::Promise** aPromise) {
NS_ENSURE_ARG_POINTER(aCx);
NS_ENSURE_ARG_POINTER(aPromise);
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
if (!globalObject) {
return NS_ERROR_UNEXPECTED;
}
ErrorResult result;
RefPtr<dom::Promise> promise = dom::Promise::Create(globalObject, result);
if (result.Failed()) {
return result.StealNSResult();
}
// PurgeBounceTrackers returns a MozPromise, wrap it in a dom::Promise
// required for XPCOM.
PurgeBounceTrackers()->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise](const PurgeBounceTrackersMozPromise::ResolveValueType&
purgedEntries) {
// Convert array of BounceTrackingMapEntry to array of site host
// strings for the JS caller. We can't pass an XPCOM type as the
// resolver value of a JS Promise.
nsTArray<nsCString> purgedSitesHosts;
for (const auto& entry : purgedEntries) {
purgedSitesHosts.AppendElement(entry->SiteHostRef());
}
promise->MaybeResolve(purgedSitesHosts);
},
[promise](const PurgeBounceTrackersMozPromise::RejectValueType& error) {
promise->MaybeReject(error);
});
promise.forget(aPromise);
return NS_OK;
}
NS_IMETHODIMP
BounceTrackingProtection::TestClearExpiredUserActivations() {
return ClearExpiredUserInteractions();
}
NS_IMETHODIMP
BounceTrackingProtection::TestAddBounceTrackerCandidate(
JS::Handle<JS::Value> aOriginAttributes, const nsACString& aHost,
const PRTime aBounceTime, JSContext* aCx) {
MOZ_ASSERT(aCx);
OriginAttributes oa;
if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<BounceTrackingStateGlobal> stateGlobal =
mStorage->GetOrCreateStateGlobal(oa);
// Ensure aHost is lowercase to match nsIURI and nsIPrincipal.
nsAutoCString host(aHost);
ToLowerCase(host);
// Can not have a host in both maps.
nsresult rv = stateGlobal->TestRemoveUserActivation(host);
NS_ENSURE_SUCCESS(rv, rv);
return stateGlobal->RecordBounceTracker(host, aBounceTime);
}
NS_IMETHODIMP
BounceTrackingProtection::TestAddUserActivation(
JS::Handle<JS::Value> aOriginAttributes, const nsACString& aHost,
const PRTime aActivationTime, JSContext* aCx) {
MOZ_ASSERT(aCx);
OriginAttributes oa;
if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<BounceTrackingStateGlobal> stateGlobal =
mStorage->GetOrCreateStateGlobal(oa);
MOZ_ASSERT(stateGlobal);
// Ensure aHost is lowercase to match nsIURI and nsIPrincipal.
nsAutoCString host(aHost);
ToLowerCase(host);
return stateGlobal->RecordUserActivation(host, aActivationTime);
}
NS_IMETHODIMP
BounceTrackingProtection::TestMaybeMigrateUserInteractionPermissions() {
return MaybeMigrateUserInteractionPermissions();
}
// static
nsresult BounceTrackingProtection::LogBounceTrackersClassifiedToWebConsole(
BounceTrackingState* aBounceTrackingState,
const nsTArray<nsCString>& aSiteHosts) {
NS_ENSURE_ARG(aBounceTrackingState);
// Nothing to log.
if (aSiteHosts.IsEmpty()) {
return NS_OK;
}
RefPtr<dom::BrowsingContext> browsingContext =
aBounceTrackingState->CurrentBrowsingContext();
if (!browsingContext) {
return NS_OK;
}
// Get the localized copy from antiTracking.ftl and insert the variables.
nsTArray<nsCString> resourceIDs = {"toolkit/global/antiTracking.ftl"_ns};
RefPtr<intl::Localization> l10n =
intl::Localization::Create(resourceIDs, true);
for (const nsACString& siteHost : aSiteHosts) {
auto l10nArgs = dom::Optional<intl::L10nArgs>();
l10nArgs.Construct();
auto siteHostArg = l10nArgs.Value().Entries().AppendElement();
siteHostArg->mKey = "siteHost";
siteHostArg->mValue.SetValue().SetAsUTF8String().Assign(siteHost);
auto gracePeriodArg = l10nArgs.Value().Entries().AppendElement();
gracePeriodArg->mKey = "gracePeriodSeconds";
gracePeriodArg->mValue.SetValue().SetAsDouble() = StaticPrefs::
privacy_bounceTrackingProtection_bounceTrackingGracePeriodSec();
// Construct the localized string.
nsAutoCString message;
ErrorResult errorResult;
l10n->FormatValueSync("btp-warning-tracker-classified"_ns, l10nArgs,
message, errorResult);
if (NS_WARN_IF(errorResult.Failed())) {
return errorResult.StealNSResult();
}
// Log to the console via nsIScriptError object.
nsresult rv = NS_OK;
nsCOMPtr<nsIScriptError> error =
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = error->InitWithWindowID(
NS_ConvertUTF8toUTF16(message), ""_ns, 0, 0,
nsIScriptError::warningFlag, "bounceTrackingProtection",
browsingContext->GetCurrentInnerWindowId(), true);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIConsoleService> consoleService =
do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// The actual log call.
rv = consoleService->LogMessage(error);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
RefPtr<GenericNonExclusivePromise>
BounceTrackingProtection::EnsureRemoteExceptionListService() {
// mRemoteExceptionList already initialized or currently initializing.
if (mRemoteExceptionListInitPromise) {
return mRemoteExceptionListInitPromise;
}
// Create the service instance.
nsresult rv;
mRemoteExceptionList =
do_GetService(NS_NSIBTPEXCEPTIONLISTSERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
mRemoteExceptionListInitPromise =
GenericNonExclusivePromise::CreateAndReject(rv, __func__);
return mRemoteExceptionListInitPromise;
}
// Call the init method and get the Promise. It resolves once the allow-list
// entries have been imported.
RefPtr<dom::Promise> jsPromise;
rv = mRemoteExceptionList->Init(this, getter_AddRefs(jsPromise));
if (NS_WARN_IF(NS_FAILED(rv))) {
mRemoteExceptionListInitPromise =
GenericNonExclusivePromise::CreateAndReject(rv, __func__);
return mRemoteExceptionListInitPromise;
}
MOZ_ASSERT(jsPromise);
// Convert to MozPromise so it can be handled from C++ side. Also store the
// promise so that subsequent calls to this method can wait for init too.
mRemoteExceptionListInitPromise =
PromiseNativeWrapper::ConvertJSPromiseToMozPromise(jsPromise);
return mRemoteExceptionListInitPromise;
}
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,
("%s: Skip: Purge already in progress.", __FUNCTION__));
return PurgeBounceTrackersMozPromise::CreateAndReject(
nsresult::NS_ERROR_NOT_AVAILABLE, __func__);
}
mPurgeInProgress = true;
RefPtr<PurgeBounceTrackersMozPromise::Private> resultPromise =
new PurgeBounceTrackersMozPromise::Private(__func__);
RefPtr<BounceTrackingProtection> self = this;
// Wait for the remote exception list service to be ready before purging.
EnsureRemoteExceptionListService()->Then(
GetCurrentSerialEventTarget(), __func__,
[self, resultPromise](
const GenericNonExclusivePromise::ResolveOrRejectValue& aResult) {
if (aResult.IsReject()) {
nsresult rv = aResult.RejectValue();
resultPromise->Reject(rv, __func__);
return;
}
// Remote exception list is ready.
// Obtain a cache of allow-list permissions so we only need to
// fetch permissions once even when we do multiple base domain lookups.
BounceTrackingAllowList bounceTrackingAllowList;
// 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 : self->mStorage->StateGlobalMapRef()) {
const OriginAttributes& originAttributes = entry.GetKey();
BounceTrackingStateGlobal* stateGlobal = entry.GetData();
MOZ_ASSERT(stateGlobal);
if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
nsAutoCString oaSuffix;
originAttributes.CreateSuffix(oaSuffix);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Running purge algorithm for OA: '%s'", __FUNCTION__,
oaSuffix.get()));
}
nsresult rv = self->PurgeBounceTrackersForStateGlobal(
stateGlobal, bounceTrackingAllowList, clearPromises);
if (NS_WARN_IF(NS_FAILED(rv))) {
resultPromise->Reject(rv, __func__);
return;
}
}
// Wait for all data clearing operations to complete. mClearPromises
// contains one promise per host / clear task.
ClearDataMozPromise::AllSettled(GetCurrentSerialEventTarget(),
clearPromises)
->Then(
GetCurrentSerialEventTarget(), __func__,
[resultPromise,
self](ClearDataMozPromise::AllSettledPromiseType::
ResolveOrRejectValue&& aResults) {
MOZ_ASSERT(aResults.IsResolve(), "AllSettled never rejects");
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Done. Cleared %zu hosts.", __FUNCTION__,
aResults.ResolveValue().Length()));
if (!aResults.ResolveValue().IsEmpty()) {
glean::bounce_tracking_protection::num_hosts_per_purge_run
.AccumulateSingleSample(
aResults.ResolveValue().Length());
}
// Check if any clear call failed.
bool anyFailed = false;
nsTArray<RefPtr<BounceTrackingPurgeEntry>> purgedSites;
// If any clear call failed reject.
for (auto& result : aResults.ResolveValue()) {
if (result.IsReject()) {
anyFailed = true;
} else {
purgedSites.AppendElement(result.ResolveValue());
}
}
// Record exposure of the feature for Nimbus
// experimentation.
// The error result returned by this method isn't very
// useful, so we ignore it. Thee method will also return
// errors if the client is not enrolled in an experiment
// involving BTP which we don't consider a failure state.
//
// We record exposure for MODE_ENABLED_DRY_RUN in addition to
// MODE_ENABLED so we know in Nimbus when a client would have
// been exposed to BTP had it been enabled. This enables us to
// compare the control and treatment branches with exposure.
Unused << NimbusFeatures::RecordExposureEvent(
"bounceTrackingProtection"_ns, false);
if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED) {
// Log successful purges.
for (const auto& entry : purgedSites) {
RefPtr<BounceTrackingStateGlobal> stateGlobal =
self->mStorage->GetOrCreateStateGlobal(
entry->OriginAttributesRef());
MOZ_ASSERT(stateGlobal);
DebugOnly<nsresult> rv =
stateGlobal->RecordPurgedTracker(entry);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"Failed to record purged tracker in log.");
}
if (purgedSites.Length() > 0) {
// Record successful purges via nsITrackingDBService for
// tracker stats.
ReportPurgedTrackersToAntiTrackingDB(purgedSites);
}
}
self->mPurgeInProgress = false;
// If any clear call failed reject the promise.
if (anyFailed) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
resultPromise->Resolve(std::move(purgedSites), __func__);
});
});
return resultPromise.forget();
}
// static
void BounceTrackingProtection::ReportPurgedTrackersToAntiTrackingDB(
const nsTArray<RefPtr<BounceTrackingPurgeEntry>>& aPurgedSiteHosts) {
MOZ_ASSERT(!aPurgedSiteHosts.IsEmpty());
MOZ_ASSERT(StaticPrefs::privacy_bounceTrackingProtection_mode() ==
nsIBounceTrackingProtection::MODE_ENABLED);
ContentBlockingLog log;
for (const RefPtr<BounceTrackingPurgeEntry>& entry : aPurgedSiteHosts) {
nsAutoCString origin("https://");
origin.Append(entry->SiteHostRef());
log.RecordLogParent(
origin, nsIWebProgressListener::STATE_PURGED_BOUNCETRACKER, true);
}
log.ReportLog();
}
nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
BounceTrackingStateGlobal* aStateGlobal,
BounceTrackingAllowList& aBounceTrackingAllowList,
nsTArray<RefPtr<ClearDataMozPromise>>& aClearPromises) {
MOZ_ASSERT(aStateGlobal);
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
// expired.
nsresult rv = ClearExpiredUserInteractions(aStateGlobal);
NS_ENSURE_SUCCESS(rv, rv);
// 2. Go over bounce tracker candidate map and purge state.
// Collect hosts to remove from the bounce trackers map. We can not remove
// them while iterating over the map.
nsTArray<nsCString> bounceTrackerCandidatesToRemove;
for (auto hostIter = aStateGlobal->BounceTrackersMapRef().ConstIter();
!hostIter.Done(); hostIter.Next()) {
const nsACString& host = hostIter.Key();
const PRTime& bounceTime = hostIter.Data();
// If bounceTime + bounce tracking grace period is after now, then continue.
// The host is still within the grace period and must not be purged.
if (bounceTime +
StaticPrefs::
privacy_bounceTrackingProtection_bounceTrackingGracePeriodSec() *
PR_USEC_PER_SEC >
now) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip host within bounce tracking grace period %s",
__FUNCTION__, PromiseFlatCString(host).get()));
continue;
}
// If there is a top-level traversable whose active document's origin's
// site's host equals host, then continue.
// TODO: Bug 1842047: Implement a more accurate check that calls into the
// browser implementations to determine whether the site is currently open
// on the top level.
bool hostIsActive;
rv = BounceTrackingState::HasBounceTrackingStateForSite(
host, aStateGlobal->OriginAttributesRef(), hostIsActive);
if (NS_WARN_IF(NS_FAILED(rv))) {
hostIsActive = false;
}
if (hostIsActive) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip host which is active %s", __FUNCTION__,
PromiseFlatCString(host).get()));
continue;
}
// Gecko specific: If the host is on the content blocking allow-list or
// allow-listed via RemoteSettings continue.
bool isAllowListed = mRemoteSiteHostExceptions.Contains(host);
// If remote settings doesn't allowlist also check the content blocking
// allow-list.
if (!isAllowListed) {
rv = aBounceTrackingAllowList.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 allow-listed: host: %s, "
"originAttributes: %s",
__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;
}
// No exception above applies, clear state for the given host.
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
("%s: Purging bounce tracker. siteHost: %s, bounceTime: %" PRIu64
" aStateGlobal: %s",
__FUNCTION__, PromiseFlatCString(host).get(), bounceTime,
aStateGlobal->Describe().get()));
// 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
// of hosts in case of repeated failures.
bounceTrackerCandidatesToRemove.AppendElement(host);
RefPtr<ClearDataMozPromise> clearDataPromise;
rv = PurgeStateForHostAndOriginAttributes(
host, bounceTime, aStateGlobal->OriginAttributesRef(),
getter_AddRefs(clearDataPromise));
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
MOZ_ASSERT(clearDataPromise);
aClearPromises.AppendElement(clearDataPromise);
}
// Remove hosts from the bounce trackers map which we executed purge calls
// for.
return aStateGlobal->RemoveBounceTrackers(bounceTrackerCandidatesToRemove);
}
nsresult BounceTrackingProtection::PurgeStateForHostAndOriginAttributes(
const nsACString& aHost, PRTime bounceTime,
const OriginAttributes& aOriginAttributes,
ClearDataMozPromise** aClearPromise) {
MOZ_ASSERT(!aHost.IsEmpty());
MOZ_ASSERT(aClearPromise);
RefPtr<ClearDataMozPromise::Private> clearPromise =
new ClearDataMozPromise::Private(__func__);
RefPtr<ClearDataCallback> cb =
new ClearDataCallback(clearPromise, aOriginAttributes, aHost, bounceTime);
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);
clearPromise.forget(aClearPromise);
return NS_OK;
}
nsresult rv = NS_OK;
nsCOMPtr<nsIClearDataService> clearDataService =
do_GetService("@mozilla.org/clear-data-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
// nsIClearDataService expects a schemeless site which for IPV6 addresses
// includes brackets. Add them if needed.
nsAutoCString hostToPurge(aHost);
nsContentUtils::MaybeFixIPv6Host(hostToPurge);
// When clearing data for a specific site host we need to ensure that we
// only clear for matching OriginAttributes. For example if the current
// state global is private browsing only we must not clear normal browsing
// data. To restrict clearing we pass in a (stringified)
// OriginAttributesPattern which matches the state global's
// OriginAttributes.
nsAutoString oaPatternString;
OriginAttributesPattern pattern;
// partitionKey and firstPartyDomain are omitted making them wildcards. We
// want to clear across partitions since BTP should clear all data for a
// given top level.
pattern.mUserContextId.Construct(aOriginAttributes.mUserContextId);
pattern.mPrivateBrowsingId.Construct(aOriginAttributes.mPrivateBrowsingId);
pattern.mGeckoViewSessionContextId.Construct(
aOriginAttributes.mGeckoViewSessionContextId);
NS_ENSURE_TRUE(pattern.ToJSON(oaPatternString), NS_ERROR_FAILURE);
rv = clearDataService->DeleteDataFromSiteAndOriginAttributesPatternString(
hostToPurge, oaPatternString, false,
// Exempt purging our own state for the given tracker since we already
// update it ourselves. Additionally a nested call to the
// BounceTrackingProtectionCleaner while iterating over the candidate set
// may lead to crashes.
nsIClearDataService::CLEAR_STATE_FOR_TRACKER_PURGING &
~nsIClearDataService::CLEAR_BOUNCE_TRACKING_PROTECTION_STATE,
cb);
NS_ENSURE_SUCCESS(rv, rv);
clearPromise.forget(aClearPromise);
return NS_OK;
}
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;
}
void BounceTrackingProtection::MaybeLogPurgedWarningForSite(
nsIPrincipal* aPrincipal, BounceTrackingState* aBounceTrackingState) {
NS_ENSURE_TRUE_VOID(aPrincipal);
NS_ENSURE_TRUE_VOID(aBounceTrackingState);
// Ensure that the BTS is connected to a BrowsingContext. We will later use
// this BC to get the DevTools console to log to.
RefPtr<dom::BrowsingContext> browsingContext =
aBounceTrackingState->CurrentBrowsingContext();
if (!browsingContext) {
return;
}
// Check if the site is in the recently purged log. If it is we should log a
// console warning.
RefPtr<BounceTrackingStateGlobal> stateGlobal =
mStorage->GetStateGlobal(aPrincipal);
if (!stateGlobal) {
// No state global which means we don't have any purge data for the given
// OriginAttributes on record.
return;
}
nsAutoCString siteHost;
nsresult rv = aPrincipal->GetBaseDomain(siteHost);
NS_ENSURE_SUCCESS_VOID(rv);
if (!stateGlobal->RecentPurgesMapRef().Contains(siteHost)) {
// No recent purge found for the given site.
return;
}
// Recently purged the site. Log a warning.
// Get the localized copy from antiTracking.ftl and insert the variables.
nsTArray<nsCString> resourceIDs = {"toolkit/global/antiTracking.ftl"_ns};
RefPtr<intl::Localization> l10n =
intl::Localization::Create(resourceIDs, true);
auto l10nArgs = dom::Optional<intl::L10nArgs>();
l10nArgs.Construct();
auto siteHostArg = l10nArgs.Value().Entries().AppendElement();
siteHostArg->mKey = "siteHost";
siteHostArg->mValue.SetValue().SetAsUTF8String().Assign(siteHost);
// Construct the localized string.
nsAutoCString message;
ErrorResult errorResult;
l10n->FormatValueSync("btp-warning-tracker-purged"_ns, l10nArgs, message,
errorResult);
if (NS_WARN_IF(errorResult.Failed())) {
return;
}
rv = nsContentUtils::ReportToConsoleByWindowID(
NS_ConvertUTF8toUTF16(message), nsIScriptError::warningFlag,
"bounceTrackingProtection"_ns,
browsingContext->GetCurrentInnerWindowId());
NS_ENSURE_SUCCESS_VOID(rv);
}
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);
}
} // namespace mozilla