Bug 1943650 - Command-line --help output misformatted after --dbus-service. r=emilio
[gecko.git] / toolkit / components / antitracking / bouncetrackingprotection / BounceTrackingProtection.cpp
blobf15e8667edf6fca9ef20a4ebef8de67cb252b858
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "BounceTrackingProtection.h"
7 #include "BounceTrackingAllowList.h"
8 #include "BounceTrackingProtectionStorage.h"
9 #include "BounceTrackingState.h"
10 #include "BounceTrackingRecord.h"
11 #include "BounceTrackingMapEntry.h"
12 #include "ClearDataCallback.h"
13 #include "PromiseNativeWrapper.h"
15 #include "BounceTrackingStateGlobal.h"
16 #include "ErrorList.h"
17 #include "mozilla/AlreadyAddRefed.h"
18 #include "mozilla/ClearOnShutdown.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/Maybe.h"
21 #include "mozilla/Services.h"
22 #include "mozilla/StaticPrefs_privacy.h"
23 #include "mozilla/dom/Promise.h"
24 #include "nsDebug.h"
25 #include "nsHashPropertyBag.h"
26 #include "nsIClearDataService.h"
27 #include "nsIObserverService.h"
28 #include "nsIPermissionManager.h"
29 #include "nsIPrincipal.h"
30 #include "nsISupports.h"
31 #include "nsISupportsPrimitives.h"
32 #include "nsServiceManagerUtils.h"
33 #include "nscore.h"
34 #include "prtime.h"
35 #include "mozilla/dom/BrowsingContext.h"
36 #include "xpcpublic.h"
37 #include "mozilla/glean/AntitrackingBouncetrackingprotectionMetrics.h"
38 #include "mozilla/ContentBlockingLog.h"
39 #include "mozilla/glean/GleanPings.h"
40 #include "mozilla/dom/WindowContext.h"
41 #include "mozilla/dom/WindowGlobalChild.h"
42 #include "mozilla/dom/WindowGlobalParent.h"
43 #include "nsIConsoleService.h"
44 #include "mozilla/intl/Localization.h"
45 #include "mozilla/browser/NimbusFeatures.h"
47 #define TEST_OBSERVER_MSG_RECORD_BOUNCES_FINISHED "test-record-bounces-finished"
49 namespace mozilla {
51 NS_IMPL_ISUPPORTS(BounceTrackingProtection, nsIObserver,
52 nsISupportsWeakReference, nsIBounceTrackingProtection);
54 LazyLogModule gBounceTrackingProtectionLog("BounceTrackingProtection");
56 static StaticRefPtr<BounceTrackingProtection> sBounceTrackingProtection;
57 static bool sInitFailed = false;
59 Maybe<uint32_t> BounceTrackingProtection::sLastRecordedModeTelemetry;
61 static const char kBTPModePref[] = "privacy.bounceTrackingProtection.mode";
63 // static
64 already_AddRefed<BounceTrackingProtection>
65 BounceTrackingProtection::GetSingleton() {
66 MOZ_ASSERT(XRE_IsParentProcess());
68 // Init previously failed, don't try again.
69 if (sInitFailed) {
70 return nullptr;
73 RecordModePrefTelemetry();
75 // Feature is disabled.
76 if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
77 nsIBounceTrackingProtection::MODE_DISABLED) {
78 return nullptr;
81 // Feature is enabled, lazily create singleton instance.
82 if (!sBounceTrackingProtection) {
83 sBounceTrackingProtection = new BounceTrackingProtection();
84 RunOnShutdown([] {
85 if (sBounceTrackingProtection &&
86 sBounceTrackingProtection->mRemoteExceptionList) {
87 Unused << sBounceTrackingProtection->mRemoteExceptionList->Shutdown();
89 sBounceTrackingProtection = nullptr;
90 });
92 nsresult rv = sBounceTrackingProtection->Init();
93 if (NS_WARN_IF(NS_FAILED(rv))) {
94 sInitFailed = true;
95 return nullptr;
99 return do_AddRef(sBounceTrackingProtection);
102 // static
103 void BounceTrackingProtection::RecordModePrefTelemetry() {
104 uint32_t featureMode = StaticPrefs::privacy_bounceTrackingProtection_mode();
106 // Record mode telemetry, but only if the mode changed.
107 if (sLastRecordedModeTelemetry.isNothing() ||
108 sLastRecordedModeTelemetry.value() != featureMode) {
109 glean::bounce_tracking_protection::mode.Set(featureMode);
110 sLastRecordedModeTelemetry = Some(featureMode);
114 nsresult BounceTrackingProtection::Init() {
115 MOZ_ASSERT(StaticPrefs::privacy_bounceTrackingProtection_mode() !=
116 nsIBounceTrackingProtection::MODE_DISABLED,
117 "Mode pref must have an enabled state for init to be called.");
118 MOZ_LOG(
119 gBounceTrackingProtectionLog, LogLevel::Info,
120 ("Init BounceTrackingProtection. Config: mode: %d, "
121 "bounceTrackingActivationLifetimeSec: %d, bounceTrackingGracePeriodSec: "
122 "%d, bounceTrackingPurgeTimerPeriodSec: %d, "
123 "clientBounceDetectionTimerPeriodMS: %d, requireStatefulBounces: %d, "
124 "HasMigratedUserActivationData: %d",
125 StaticPrefs::privacy_bounceTrackingProtection_mode(),
126 StaticPrefs::
127 privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec(),
128 StaticPrefs::
129 privacy_bounceTrackingProtection_bounceTrackingGracePeriodSec(),
130 StaticPrefs::
131 privacy_bounceTrackingProtection_bounceTrackingPurgeTimerPeriodSec(),
132 StaticPrefs::
133 privacy_bounceTrackingProtection_clientBounceDetectionTimerPeriodMS(),
134 StaticPrefs::privacy_bounceTrackingProtection_requireStatefulBounces(),
135 StaticPrefs::
136 privacy_bounceTrackingProtection_hasMigratedUserActivationData()));
138 mStorage = new BounceTrackingProtectionStorage();
140 nsresult rv = mStorage->Init();
141 NS_ENSURE_SUCCESS(rv, rv);
143 rv = MaybeMigrateUserInteractionPermissions();
144 if (NS_WARN_IF(NS_FAILED(rv))) {
145 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Error,
146 ("user activation permission migration failed"));
149 // Register feature pref listener which dynamically enables or disables the
150 // feature depending on feature pref state.
151 rv = Preferences::RegisterCallback(&BounceTrackingProtection::OnPrefChange,
152 kBTPModePref);
153 NS_ENSURE_SUCCESS(rv, rv);
155 // Run the remaining init logic.
156 return OnModeChange(true);
159 nsresult BounceTrackingProtection::UpdateBounceTrackingPurgeTimer(
160 bool aShouldEnable) {
161 // Cancel the existing timer.
162 // If disabling: we're done now.
163 // If enabling: schedule a new timer so interval changes (as controlled by the
164 // pref) are taken into account.
165 if (mBounceTrackingPurgeTimer) {
166 mBounceTrackingPurgeTimer->Cancel();
167 mBounceTrackingPurgeTimer = nullptr;
170 if (!aShouldEnable) {
171 return NS_OK;
174 // Schedule timer for tracker purging. The timer interval is determined by
175 // pref.
176 uint32_t purgeTimerPeriod = StaticPrefs::
177 privacy_bounceTrackingProtection_bounceTrackingPurgeTimerPeriodSec();
179 // The pref can be set to 0 to disable interval purging.
180 if (purgeTimerPeriod == 0) {
181 return NS_OK;
184 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
185 ("Scheduling mBounceTrackingPurgeTimer. Interval: %d seconds.",
186 purgeTimerPeriod));
188 return NS_NewTimerWithCallback(
189 getter_AddRefs(mBounceTrackingPurgeTimer),
190 [](auto) {
191 if (!sBounceTrackingProtection) {
192 return;
194 sBounceTrackingProtection->PurgeBounceTrackers()->Then(
195 GetMainThreadSerialEventTarget(), __func__,
196 [] {
197 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
198 ("%s: PurgeBounceTrackers finished after timer call.",
199 __FUNCTION__));
201 [] { NS_WARNING("RunPurgeBounceTrackers failed"); });
203 purgeTimerPeriod * PR_MSEC_PER_SEC, nsITimer::TYPE_REPEATING_SLACK,
204 "mBounceTrackingPurgeTimer");
207 // static
208 void BounceTrackingProtection::OnPrefChange(const char* aPref, void* aData) {
209 MOZ_ASSERT(sBounceTrackingProtection);
210 MOZ_ASSERT(strcmp(kBTPModePref, aPref) == 0);
212 RecordModePrefTelemetry();
214 sBounceTrackingProtection->OnModeChange(false);
217 nsresult BounceTrackingProtection::OnModeChange(bool aIsStartup) {
218 // Get feature mode from pref and ensure its within bounds.
219 uint8_t modeInt = StaticPrefs::privacy_bounceTrackingProtection_mode();
220 NS_ENSURE_TRUE(modeInt <= nsIBounceTrackingProtection::MAX_MODE_VALUE,
221 NS_ERROR_FAILURE);
222 nsIBounceTrackingProtection::Modes mode =
223 static_cast<nsIBounceTrackingProtection::Modes>(modeInt);
225 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
226 ("%s: mode: %d.", __FUNCTION__, mode));
227 if (sInitFailed) {
228 return NS_ERROR_FAILURE;
231 nsresult result = NS_OK;
233 if (!aIsStartup) {
234 // Clear bounce tracker candidate map for any mode change so it's not leaked
235 // into other modes. For example if we switch from dry-run mode into fully
236 // enabled we want a clean slate to not purge trackers that we've classified
237 // in dry-run mode. User activation data must be kept to avoid false
238 // positives.
239 MOZ_ASSERT(mStorage);
240 result = mStorage->ClearByType(
241 BounceTrackingProtectionStorage::EntryType::BounceTracker);
244 // On disable
245 if (mode == nsIBounceTrackingProtection::MODE_DISABLED ||
246 mode == nsIBounceTrackingProtection::MODE_ENABLED_STANDBY) {
247 // No further cleanup needed if we're just starting up.
248 if (aIsStartup) {
249 MOZ_ASSERT(!mStorageObserver);
250 MOZ_ASSERT(!mBounceTrackingPurgeTimer);
251 return result;
254 // Destroy storage observer to stop receiving storage notifications.
255 mStorageObserver = nullptr;
257 // Stop regular purging.
258 nsresult rv = UpdateBounceTrackingPurgeTimer(false);
259 if (NS_WARN_IF(NS_FAILED(rv))) {
260 result = rv;
261 // Even if this step fails try to do more cleanup.
264 // Clear all per-tab state.
265 BounceTrackingState::DestroyAll();
266 return result;
269 // On enable
270 MOZ_ASSERT(mode == nsIBounceTrackingProtection::MODE_ENABLED ||
271 mode == nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN);
273 // Create and init storage observer.
274 mStorageObserver = new BounceTrackingStorageObserver();
275 nsresult rv = mStorageObserver->Init();
276 NS_ENSURE_SUCCESS(rv, rv);
278 // Schedule regular purging.
279 rv = UpdateBounceTrackingPurgeTimer(true);
280 NS_ENSURE_SUCCESS(rv, rv);
282 return result;
285 nsresult BounceTrackingProtection::RecordStatefulBounces(
286 BounceTrackingState* aBounceTrackingState) {
287 NS_ENSURE_ARG_POINTER(aBounceTrackingState);
289 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
290 ("%s: aBounceTrackingState: %s", __FUNCTION__,
291 aBounceTrackingState->Describe().get()));
293 // Assert: navigable’s bounce tracking record is not null.
294 const Maybe<BounceTrackingRecord>& record =
295 aBounceTrackingState->GetBounceTrackingRecord();
296 NS_ENSURE_TRUE(record, NS_ERROR_FAILURE);
298 // Get the bounce tracker map and the user activation map.
299 RefPtr<BounceTrackingStateGlobal> globalState =
300 mStorage->GetOrCreateStateGlobal(aBounceTrackingState);
301 MOZ_ASSERT(globalState);
303 nsTArray<nsCString> classifiedHosts;
305 // For each host in navigable’s bounce tracking record's bounce set:
306 for (const nsACString& host : record->GetBounceHosts()) {
307 // Skip "null" entries, they are only used for logging purposes.
308 if (host.EqualsLiteral("null")) {
309 continue;
312 // If host equals navigable’s bounce tracking record's initial host,
313 // continue.
314 if (host == record->GetInitialHost()) {
315 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
316 ("%s: Skip host == initialHost: %s", __FUNCTION__,
317 PromiseFlatCString(host).get()));
318 continue;
320 // If host equals navigable’s bounce tracking record's final host, continue.
321 if (host == record->GetFinalHost()) {
322 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
323 ("%s: Skip host == finalHost: %s", __FUNCTION__,
324 PromiseFlatCString(host).get()));
325 continue;
328 // If user activation map contains host, continue.
329 if (globalState->HasUserActivation(host)) {
330 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
331 ("%s: Skip host with recent user activation: %s", __FUNCTION__,
332 PromiseFlatCString(host).get()));
333 continue;
336 // If stateful bounce tracking map contains host, continue.
337 if (globalState->HasBounceTracker(host)) {
338 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
339 ("%s: Skip already existing host: %s", __FUNCTION__,
340 PromiseFlatCString(host).get()));
341 continue;
344 // If navigable’s bounce tracking record's storage access set does not
345 // contain host, continue.
346 if (StaticPrefs::
347 privacy_bounceTrackingProtection_requireStatefulBounces() &&
348 !record->GetStorageAccessHosts().Contains(host)) {
349 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
350 ("%s: Skip host without storage access: %s", __FUNCTION__,
351 PromiseFlatCString(host).get()));
352 continue;
355 // Set stateful bounce tracking map[host] to topDocument’s relevant settings
356 // object's current wall time.
357 PRTime now = PR_Now();
358 MOZ_ASSERT(!globalState->HasBounceTracker(host));
359 nsresult rv = globalState->RecordBounceTracker(host, now);
360 if (NS_WARN_IF(NS_FAILED(rv))) {
361 continue;
364 classifiedHosts.AppendElement(host);
366 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
367 ("%s: Added bounce tracker candidate. siteHost: %s, "
368 "aBounceTrackingState: %s",
369 __FUNCTION__, PromiseFlatCString(host).get(),
370 aBounceTrackingState->Describe().get()));
373 // Set navigable’s bounce tracking record to null.
374 aBounceTrackingState->ResetBounceTrackingRecord();
375 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
376 ("%s: Done, reset aBounceTrackingState: %s", __FUNCTION__,
377 aBounceTrackingState->Describe().get()));
379 // Log a message to the web console for each classified host.
380 nsresult rv = LogBounceTrackersClassifiedToWebConsole(aBounceTrackingState,
381 classifiedHosts);
382 NS_ENSURE_SUCCESS(rv, rv);
384 // If running in test automation, dispatch an observer message indicating
385 // we're finished recording bounces.
386 if (StaticPrefs::privacy_bounceTrackingProtection_enableTestMode()) {
387 nsCOMPtr<nsIObserverService> obsSvc =
388 mozilla::services::GetObserverService();
389 NS_ENSURE_TRUE(obsSvc, NS_ERROR_FAILURE);
391 RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
393 nsresult rv = props->SetPropertyAsUint64(
394 u"browserId"_ns, aBounceTrackingState->GetBrowserId());
395 NS_ENSURE_SUCCESS(rv, rv);
397 rv = obsSvc->NotifyObservers(
398 ToSupports(props), TEST_OBSERVER_MSG_RECORD_BOUNCES_FINISHED, nullptr);
399 NS_ENSURE_SUCCESS(rv, rv);
402 return NS_OK;
405 nsresult BounceTrackingProtection::RecordUserActivation(
406 nsIPrincipal* aPrincipal, Maybe<PRTime> aActivationTime,
407 dom::CanonicalBrowsingContext* aTopBrowsingContext) {
408 MOZ_ASSERT(XRE_IsParentProcess());
409 NS_ENSURE_ARG_POINTER(aPrincipal);
410 NS_ENSURE_TRUE(!aTopBrowsingContext || aTopBrowsingContext->IsTop(),
411 NS_ERROR_INVALID_ARG);
413 RefPtr<BounceTrackingProtection> btp = GetSingleton();
414 // May be nullptr if feature is disabled.
415 if (!btp) {
416 return NS_OK;
419 if (!BounceTrackingState::ShouldTrackPrincipal(aPrincipal)) {
420 return NS_OK;
423 nsAutoCString siteHost;
424 nsresult rv = aPrincipal->GetBaseDomain(siteHost);
425 NS_ENSURE_SUCCESS(rv, rv);
427 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
428 ("%s: siteHost: %s", __FUNCTION__, siteHost.get()));
430 RefPtr<BounceTrackingStateGlobal> globalState =
431 btp->mStorage->GetOrCreateStateGlobal(aPrincipal);
432 MOZ_ASSERT(globalState);
434 // aActivationTime defaults to current time if no value is provided.
435 rv = globalState->RecordUserActivation(siteHost,
436 aActivationTime.valueOr(PR_Now()));
437 NS_ENSURE_SUCCESS(rv, rv);
439 if (aTopBrowsingContext) {
440 MOZ_ASSERT(aTopBrowsingContext->IsTop());
441 dom::BrowsingContextWebProgress* webProgress =
442 aTopBrowsingContext->GetWebProgress();
443 NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE);
445 RefPtr<BounceTrackingState> bounceTrackingState =
446 webProgress->GetBounceTrackingState();
447 // We may not always get a BounceTrackingState, e.g the feature is disabled,
448 // if the user has opted into all cookies, no cookies, or some other case.
449 // If we don't have it, just return OK here.
450 NS_ENSURE_TRUE(bounceTrackingState, NS_OK);
452 return bounceTrackingState->OnUserActivation(siteHost);
454 return NS_OK;
457 nsresult BounceTrackingProtection::RecordUserActivation(
458 dom::WindowContext* aWindowContext) {
459 NS_ENSURE_ARG_POINTER(aWindowContext);
461 if (XRE_IsContentProcess()) {
462 dom::WindowGlobalChild* wgc = aWindowContext->GetWindowGlobalChild();
463 NS_ENSURE_TRUE(wgc, NS_ERROR_FAILURE);
464 NS_ENSURE_TRUE(wgc->SendRecordUserActivationForBTP(), NS_ERROR_FAILURE);
465 return NS_OK;
467 MOZ_ASSERT(XRE_IsParentProcess());
469 dom::WindowGlobalParent* wgp = aWindowContext->Canonical();
470 MOZ_ASSERT(wgp);
472 NS_ENSURE_TRUE(wgp->RecvRecordUserActivationForBTP(), NS_ERROR_FAILURE);
474 return NS_OK;
477 NS_IMETHODIMP
478 BounceTrackingProtection::Observe(nsISupports* aSubject, const char* aTopic,
479 const char16_t* aData) {
480 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
481 ("%s: aTopic: %s", __FUNCTION__, aTopic));
483 if (!strcmp(aTopic, "idle-daily")) {
484 #ifndef MOZ_WIDGET_ANDROID // OHTTP is not supported on Android.
485 // Submit custom telemetry ping.
486 glean_pings::BounceTrackingProtection.Submit();
487 #endif // #ifndef MOZ_WIDGET_ANDROID
489 return NS_OK;
492 NS_IMETHODIMP
493 BounceTrackingProtection::TestGetBounceTrackerCandidateHosts(
494 JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
495 nsTArray<RefPtr<nsIBounceTrackingMapEntry>>& aCandidates) {
496 MOZ_ASSERT(aCx);
498 OriginAttributes oa;
499 if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
500 return NS_ERROR_INVALID_ARG;
503 RefPtr<BounceTrackingStateGlobal> globalState = mStorage->GetStateGlobal(oa);
504 if (!globalState) {
505 return NS_OK;
508 for (auto iter = globalState->BounceTrackersMapRef().ConstIter();
509 !iter.Done(); iter.Next()) {
510 RefPtr<nsIBounceTrackingMapEntry> candidate = new BounceTrackingMapEntry(
511 globalState->OriginAttributesRef(), iter.Key(), iter.Data());
512 aCandidates.AppendElement(candidate);
515 return NS_OK;
518 NS_IMETHODIMP
519 BounceTrackingProtection::TestGetUserActivationHosts(
520 JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
521 nsTArray<RefPtr<nsIBounceTrackingMapEntry>>& aHosts) {
522 MOZ_ASSERT(aCx);
524 OriginAttributes oa;
525 if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
526 return NS_ERROR_INVALID_ARG;
529 RefPtr<BounceTrackingStateGlobal> globalState = mStorage->GetStateGlobal(oa);
530 if (!globalState) {
531 return NS_OK;
534 for (auto iter = globalState->UserActivationMapRef().ConstIter();
535 !iter.Done(); iter.Next()) {
536 RefPtr<nsIBounceTrackingMapEntry> candidate = new BounceTrackingMapEntry(
537 globalState->OriginAttributesRef(), iter.Key(), iter.Data());
538 aHosts.AppendElement(candidate);
541 return NS_OK;
544 NS_IMETHODIMP
545 BounceTrackingProtection::TestGetRecentlyPurgedTrackers(
546 JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
547 nsTArray<RefPtr<nsIBounceTrackingPurgeEntry>>& aPurgedTrackers) {
548 MOZ_ASSERT(aCx);
550 OriginAttributes oa;
551 if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
552 return NS_ERROR_INVALID_ARG;
555 RefPtr<BounceTrackingStateGlobal> globalState = mStorage->GetStateGlobal(oa);
556 if (!globalState) {
557 return NS_OK;
560 nsTArray<RefPtr<BounceTrackingPurgeEntry>> purgeEntriesSorted;
561 for (auto iter = globalState->RecentPurgesMapRef().ConstIter(); !iter.Done();
562 iter.Next()) {
563 for (const auto& entry : iter.Data()) {
564 purgeEntriesSorted.InsertElementSorted(entry, PurgeEntryTimeComparator{});
568 aPurgedTrackers.AppendElements(purgeEntriesSorted);
570 return NS_OK;
573 NS_IMETHODIMP
574 BounceTrackingProtection::ClearAll() {
575 BounceTrackingState::ResetAll();
576 return mStorage->Clear();
579 NS_IMETHODIMP
580 BounceTrackingProtection::ClearBySiteHostAndOriginAttributes(
581 const nsACString& aSiteHost, JS::Handle<JS::Value> aOriginAttributes,
582 JSContext* aCx) {
583 NS_ENSURE_ARG_POINTER(aCx);
585 OriginAttributes originAttributes;
586 if (!aOriginAttributes.isObject() ||
587 !originAttributes.Init(aCx, aOriginAttributes)) {
588 return NS_ERROR_INVALID_ARG;
591 // Reset per tab state for tabs matching the given OriginAttributes.
592 BounceTrackingState::ResetAllForOriginAttributes(originAttributes);
594 return mStorage->ClearBySiteHost(aSiteHost, &originAttributes);
597 NS_IMETHODIMP
598 BounceTrackingProtection::ClearBySiteHostAndOriginAttributesPattern(
599 const nsACString& aSiteHost, JS::Handle<JS::Value> aOriginAttributesPattern,
600 JSContext* aCx) {
601 NS_ENSURE_ARG_POINTER(aCx);
603 OriginAttributesPattern pattern;
604 if (!aOriginAttributesPattern.isObject() ||
605 !pattern.Init(aCx, aOriginAttributesPattern)) {
606 return NS_ERROR_INVALID_ARG;
609 // Clear per-tab state.
610 BounceTrackingState::ResetAllForOriginAttributesPattern(pattern);
612 // Clear global state including on-disk state.
613 return mStorage->ClearByOriginAttributesPattern(pattern,
614 Some(nsCString(aSiteHost)));
617 NS_IMETHODIMP
618 BounceTrackingProtection::ClearByTimeRange(PRTime aFrom, PRTime aTo) {
619 NS_ENSURE_TRUE(aFrom >= 0, NS_ERROR_INVALID_ARG);
620 NS_ENSURE_TRUE(aFrom < aTo, NS_ERROR_INVALID_ARG);
622 // Clear all BounceTrackingState, we don't keep track of time ranges.
623 BounceTrackingState::ResetAll();
625 return mStorage->ClearByTimeRange(aFrom, aTo);
628 NS_IMETHODIMP
629 BounceTrackingProtection::ClearByOriginAttributesPattern(
630 const nsAString& aPattern) {
631 OriginAttributesPattern pattern;
632 if (!pattern.Init(aPattern)) {
633 return NS_ERROR_INVALID_ARG;
636 // Reset all per-tab state matching the given OriginAttributesPattern.
637 BounceTrackingState::ResetAllForOriginAttributesPattern(pattern);
639 return mStorage->ClearByOriginAttributesPattern(pattern);
642 NS_IMETHODIMP
643 BounceTrackingProtection::AddSiteHostExceptions(
644 const nsTArray<nsCString>& aSiteHosts) {
645 for (const auto& host : aSiteHosts) {
646 mRemoteSiteHostExceptions.Insert(host);
649 return NS_OK;
652 NS_IMETHODIMP
653 BounceTrackingProtection::RemoveSiteHostExceptions(
654 const nsTArray<nsCString>& aSiteHosts) {
655 for (const auto& host : aSiteHosts) {
656 mRemoteSiteHostExceptions.Remove(host);
659 return NS_OK;
662 NS_IMETHODIMP
663 BounceTrackingProtection::HasRecentlyPurgedSite(const nsACString& aSiteHost,
664 bool* aResult) {
665 NS_ENSURE_ARG_POINTER(aResult);
666 *aResult = false;
667 NS_ENSURE_TRUE(!aSiteHost.IsEmpty(), NS_ERROR_INVALID_ARG);
669 // Look for a purge log entry matching the site and return greedily if we find
670 // one. We look through all state globals because we want to search across all
671 // OriginAttributes.
672 for (const auto& entry : mStorage->StateGlobalMapRef()) {
673 RefPtr<BounceTrackingStateGlobal> stateGlobal = entry.GetData();
674 MOZ_ASSERT(stateGlobal);
676 if (stateGlobal->RecentPurgesMapRef().Contains(aSiteHost)) {
677 *aResult = true;
678 return NS_OK;
681 return NS_OK;
684 NS_IMETHODIMP
685 BounceTrackingProtection::TestGetSiteHostExceptions(
686 nsTArray<nsCString>& aSiteHostExceptions) {
687 aSiteHostExceptions.Clear();
689 for (const auto& host : mRemoteSiteHostExceptions) {
690 aSiteHostExceptions.AppendElement(host);
693 return NS_OK;
696 NS_IMETHODIMP
697 BounceTrackingProtection::TestRunPurgeBounceTrackers(
698 JSContext* aCx, mozilla::dom::Promise** aPromise) {
699 NS_ENSURE_ARG_POINTER(aCx);
700 NS_ENSURE_ARG_POINTER(aPromise);
702 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
703 if (!globalObject) {
704 return NS_ERROR_UNEXPECTED;
707 ErrorResult result;
708 RefPtr<dom::Promise> promise = dom::Promise::Create(globalObject, result);
709 if (result.Failed()) {
710 return result.StealNSResult();
713 // PurgeBounceTrackers returns a MozPromise, wrap it in a dom::Promise
714 // required for XPCOM.
715 PurgeBounceTrackers()->Then(
716 GetMainThreadSerialEventTarget(), __func__,
717 [promise](const PurgeBounceTrackersMozPromise::ResolveValueType&
718 purgedEntries) {
719 // Convert array of BounceTrackingMapEntry to array of site host
720 // strings for the JS caller. We can't pass an XPCOM type as the
721 // resolver value of a JS Promise.
722 nsTArray<nsCString> purgedSitesHosts;
723 for (const auto& entry : purgedEntries) {
724 purgedSitesHosts.AppendElement(entry->SiteHostRef());
726 promise->MaybeResolve(purgedSitesHosts);
728 [promise](const PurgeBounceTrackersMozPromise::RejectValueType& error) {
729 promise->MaybeReject(error);
732 promise.forget(aPromise);
733 return NS_OK;
736 NS_IMETHODIMP
737 BounceTrackingProtection::TestClearExpiredUserActivations() {
738 return ClearExpiredUserInteractions();
741 NS_IMETHODIMP
742 BounceTrackingProtection::TestAddBounceTrackerCandidate(
743 JS::Handle<JS::Value> aOriginAttributes, const nsACString& aHost,
744 const PRTime aBounceTime, JSContext* aCx) {
745 MOZ_ASSERT(aCx);
747 OriginAttributes oa;
748 if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
749 return NS_ERROR_INVALID_ARG;
752 RefPtr<BounceTrackingStateGlobal> stateGlobal =
753 mStorage->GetOrCreateStateGlobal(oa);
755 // Ensure aHost is lowercase to match nsIURI and nsIPrincipal.
756 nsAutoCString host(aHost);
757 ToLowerCase(host);
759 // Can not have a host in both maps.
760 nsresult rv = stateGlobal->TestRemoveUserActivation(host);
761 NS_ENSURE_SUCCESS(rv, rv);
762 return stateGlobal->RecordBounceTracker(host, aBounceTime);
765 NS_IMETHODIMP
766 BounceTrackingProtection::TestAddUserActivation(
767 JS::Handle<JS::Value> aOriginAttributes, const nsACString& aHost,
768 const PRTime aActivationTime, JSContext* aCx) {
769 MOZ_ASSERT(aCx);
771 OriginAttributes oa;
772 if (!aOriginAttributes.isObject() || !oa.Init(aCx, aOriginAttributes)) {
773 return NS_ERROR_INVALID_ARG;
776 RefPtr<BounceTrackingStateGlobal> stateGlobal =
777 mStorage->GetOrCreateStateGlobal(oa);
778 MOZ_ASSERT(stateGlobal);
780 // Ensure aHost is lowercase to match nsIURI and nsIPrincipal.
781 nsAutoCString host(aHost);
782 ToLowerCase(host);
784 return stateGlobal->RecordUserActivation(host, aActivationTime);
787 NS_IMETHODIMP
788 BounceTrackingProtection::TestMaybeMigrateUserInteractionPermissions() {
789 return MaybeMigrateUserInteractionPermissions();
792 // static
793 nsresult BounceTrackingProtection::LogBounceTrackersClassifiedToWebConsole(
794 BounceTrackingState* aBounceTrackingState,
795 const nsTArray<nsCString>& aSiteHosts) {
796 NS_ENSURE_ARG(aBounceTrackingState);
798 // Nothing to log.
799 if (aSiteHosts.IsEmpty()) {
800 return NS_OK;
803 RefPtr<dom::BrowsingContext> browsingContext =
804 aBounceTrackingState->CurrentBrowsingContext();
805 if (!browsingContext) {
806 return NS_OK;
809 // Get the localized copy from antiTracking.ftl and insert the variables.
810 nsTArray<nsCString> resourceIDs = {"toolkit/global/antiTracking.ftl"_ns};
811 RefPtr<intl::Localization> l10n =
812 intl::Localization::Create(resourceIDs, true);
814 for (const nsACString& siteHost : aSiteHosts) {
815 auto l10nArgs = dom::Optional<intl::L10nArgs>();
816 l10nArgs.Construct();
818 auto siteHostArg = l10nArgs.Value().Entries().AppendElement();
819 siteHostArg->mKey = "siteHost";
820 siteHostArg->mValue.SetValue().SetAsUTF8String().Assign(siteHost);
822 auto gracePeriodArg = l10nArgs.Value().Entries().AppendElement();
823 gracePeriodArg->mKey = "gracePeriodSeconds";
824 gracePeriodArg->mValue.SetValue().SetAsDouble() = StaticPrefs::
825 privacy_bounceTrackingProtection_bounceTrackingGracePeriodSec();
827 // Construct the localized string.
828 nsAutoCString message;
829 ErrorResult errorResult;
830 l10n->FormatValueSync("btp-warning-tracker-classified"_ns, l10nArgs,
831 message, errorResult);
832 if (NS_WARN_IF(errorResult.Failed())) {
833 return errorResult.StealNSResult();
836 // Log to the console via nsIScriptError object.
837 nsresult rv = NS_OK;
838 nsCOMPtr<nsIScriptError> error =
839 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
840 NS_ENSURE_SUCCESS(rv, rv);
842 rv = error->InitWithWindowID(
843 NS_ConvertUTF8toUTF16(message), ""_ns, 0, 0,
844 nsIScriptError::warningFlag, "bounceTrackingProtection",
845 browsingContext->GetCurrentInnerWindowId(), true);
846 NS_ENSURE_SUCCESS(rv, rv);
848 nsCOMPtr<nsIConsoleService> consoleService =
849 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
850 NS_ENSURE_SUCCESS(rv, rv);
852 // The actual log call.
853 rv = consoleService->LogMessage(error);
854 NS_ENSURE_SUCCESS(rv, rv);
857 return NS_OK;
860 RefPtr<GenericNonExclusivePromise>
861 BounceTrackingProtection::EnsureRemoteExceptionListService() {
862 // mRemoteExceptionList already initialized or currently initializing.
863 if (mRemoteExceptionListInitPromise) {
864 return mRemoteExceptionListInitPromise;
867 // Create the service instance.
868 nsresult rv;
869 mRemoteExceptionList =
870 do_GetService(NS_NSIBTPEXCEPTIONLISTSERVICE_CONTRACTID, &rv);
871 if (NS_WARN_IF(NS_FAILED(rv))) {
872 mRemoteExceptionListInitPromise =
873 GenericNonExclusivePromise::CreateAndReject(rv, __func__);
874 return mRemoteExceptionListInitPromise;
877 // Call the init method and get the Promise. It resolves once the allow-list
878 // entries have been imported.
879 RefPtr<dom::Promise> jsPromise;
880 rv = mRemoteExceptionList->Init(this, getter_AddRefs(jsPromise));
881 if (NS_WARN_IF(NS_FAILED(rv))) {
882 mRemoteExceptionListInitPromise =
883 GenericNonExclusivePromise::CreateAndReject(rv, __func__);
884 return mRemoteExceptionListInitPromise;
886 MOZ_ASSERT(jsPromise);
888 // Convert to MozPromise so it can be handled from C++ side. Also store the
889 // promise so that subsequent calls to this method can wait for init too.
890 mRemoteExceptionListInitPromise =
891 PromiseNativeWrapper::ConvertJSPromiseToMozPromise(jsPromise);
893 return mRemoteExceptionListInitPromise;
896 RefPtr<BounceTrackingProtection::PurgeBounceTrackersMozPromise>
897 BounceTrackingProtection::PurgeBounceTrackers() {
898 // Only purge when the feature is actually enabled.
899 if (StaticPrefs::privacy_bounceTrackingProtection_mode() !=
900 nsIBounceTrackingProtection::MODE_ENABLED &&
901 StaticPrefs::privacy_bounceTrackingProtection_mode() !=
902 nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN) {
903 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
904 ("%s: Skip: Purging disabled via mode pref.", __FUNCTION__));
905 return PurgeBounceTrackersMozPromise::CreateAndReject(
906 nsresult::NS_ERROR_NOT_AVAILABLE, __func__);
909 // Prevent multiple purge operations from running at the same time.
910 if (mPurgeInProgress) {
911 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
912 ("%s: Skip: Purge already in progress.", __FUNCTION__));
913 return PurgeBounceTrackersMozPromise::CreateAndReject(
914 nsresult::NS_ERROR_NOT_AVAILABLE, __func__);
916 mPurgeInProgress = true;
918 RefPtr<PurgeBounceTrackersMozPromise::Private> resultPromise =
919 new PurgeBounceTrackersMozPromise::Private(__func__);
921 RefPtr<BounceTrackingProtection> self = this;
923 // Wait for the remote exception list service to be ready before purging.
924 EnsureRemoteExceptionListService()->Then(
925 GetCurrentSerialEventTarget(), __func__,
926 [self, resultPromise](
927 const GenericNonExclusivePromise::ResolveOrRejectValue& aResult) {
928 if (aResult.IsReject()) {
929 nsresult rv = aResult.RejectValue();
930 resultPromise->Reject(rv, __func__);
931 return;
933 // Remote exception list is ready.
935 // Obtain a cache of allow-list permissions so we only need to
936 // fetch permissions once even when we do multiple base domain lookups.
937 BounceTrackingAllowList bounceTrackingAllowList;
939 // Collect promises for all clearing operations to later await on.
940 nsTArray<RefPtr<ClearDataMozPromise>> clearPromises;
942 // Run the purging algorithm for all global state objects.
943 for (const auto& entry : self->mStorage->StateGlobalMapRef()) {
944 const OriginAttributes& originAttributes = entry.GetKey();
945 BounceTrackingStateGlobal* stateGlobal = entry.GetData();
946 MOZ_ASSERT(stateGlobal);
948 if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
949 nsAutoCString oaSuffix;
950 originAttributes.CreateSuffix(oaSuffix);
951 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
952 ("%s: Running purge algorithm for OA: '%s'", __FUNCTION__,
953 oaSuffix.get()));
956 nsresult rv = self->PurgeBounceTrackersForStateGlobal(
957 stateGlobal, bounceTrackingAllowList, clearPromises);
958 if (NS_WARN_IF(NS_FAILED(rv))) {
959 resultPromise->Reject(rv, __func__);
960 return;
964 // Wait for all data clearing operations to complete. mClearPromises
965 // contains one promise per host / clear task.
966 ClearDataMozPromise::AllSettled(GetCurrentSerialEventTarget(),
967 clearPromises)
968 ->Then(
969 GetCurrentSerialEventTarget(), __func__,
970 [resultPromise,
971 self](ClearDataMozPromise::AllSettledPromiseType::
972 ResolveOrRejectValue&& aResults) {
973 MOZ_ASSERT(aResults.IsResolve(), "AllSettled never rejects");
975 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
976 ("%s: Done. Cleared %zu hosts.", __FUNCTION__,
977 aResults.ResolveValue().Length()));
979 if (!aResults.ResolveValue().IsEmpty()) {
980 glean::bounce_tracking_protection::num_hosts_per_purge_run
981 .AccumulateSingleSample(
982 aResults.ResolveValue().Length());
985 // Check if any clear call failed.
986 bool anyFailed = false;
988 nsTArray<RefPtr<BounceTrackingPurgeEntry>> purgedSites;
990 // If any clear call failed reject.
991 for (auto& result : aResults.ResolveValue()) {
992 if (result.IsReject()) {
993 anyFailed = true;
994 } else {
995 purgedSites.AppendElement(result.ResolveValue());
999 if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
1000 nsIBounceTrackingProtection::MODE_ENABLED) {
1001 // Log successful purges.
1002 for (const auto& entry : purgedSites) {
1003 RefPtr<BounceTrackingStateGlobal> stateGlobal =
1004 self->mStorage->GetOrCreateStateGlobal(
1005 entry->OriginAttributesRef());
1006 MOZ_ASSERT(stateGlobal);
1007 DebugOnly<nsresult> rv =
1008 stateGlobal->RecordPurgedTracker(entry);
1009 NS_WARNING_ASSERTION(
1010 NS_SUCCEEDED(rv),
1011 "Failed to record purged tracker in log.");
1014 if (purgedSites.Length() > 0) {
1015 // Record successful purges via nsITrackingDBService for
1016 // tracker stats.
1017 ReportPurgedTrackersToAntiTrackingDB(purgedSites);
1019 // Record exposure of the feature for Nimbus
1020 // experimentation.
1021 // The error result returned by this method isn't very
1022 // useful, so we ignore it. Thee method will also return
1023 // errors if the client is not enrolled in an experiment
1024 // involving BTP which we don't consider a failure state.
1025 Unused << NimbusFeatures::RecordExposureEvent(
1026 "bounceTrackingProtection"_ns, false);
1030 self->mPurgeInProgress = false;
1032 // If any clear call failed reject the promise.
1033 if (anyFailed) {
1034 resultPromise->Reject(NS_ERROR_FAILURE, __func__);
1035 return;
1037 resultPromise->Resolve(std::move(purgedSites), __func__);
1040 return resultPromise.forget();
1043 // static
1044 void BounceTrackingProtection::ReportPurgedTrackersToAntiTrackingDB(
1045 const nsTArray<RefPtr<BounceTrackingPurgeEntry>>& aPurgedSiteHosts) {
1046 MOZ_ASSERT(!aPurgedSiteHosts.IsEmpty());
1047 MOZ_ASSERT(StaticPrefs::privacy_bounceTrackingProtection_mode() ==
1048 nsIBounceTrackingProtection::MODE_ENABLED);
1050 ContentBlockingLog log;
1051 for (const RefPtr<BounceTrackingPurgeEntry>& entry : aPurgedSiteHosts) {
1052 nsAutoCString origin("https://");
1053 origin.Append(entry->SiteHostRef());
1055 log.RecordLogParent(
1056 origin, nsIWebProgressListener::STATE_PURGED_BOUNCETRACKER, true);
1058 log.ReportLog();
1061 nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
1062 BounceTrackingStateGlobal* aStateGlobal,
1063 BounceTrackingAllowList& aBounceTrackingAllowList,
1064 nsTArray<RefPtr<ClearDataMozPromise>>& aClearPromises) {
1065 MOZ_ASSERT(aStateGlobal);
1066 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
1067 ("%s: %s", __FUNCTION__, aStateGlobal->Describe().get()));
1069 // Ensure we only purge when pref configuration allows it.
1070 if (StaticPrefs::privacy_bounceTrackingProtection_mode() !=
1071 nsIBounceTrackingProtection::MODE_ENABLED &&
1072 StaticPrefs::privacy_bounceTrackingProtection_mode() !=
1073 nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN) {
1074 return NS_ERROR_NOT_AVAILABLE;
1077 const PRTime now = PR_Now();
1079 // 1. Remove hosts from the user activation map whose user activation flag has
1080 // expired.
1081 nsresult rv = ClearExpiredUserInteractions(aStateGlobal);
1082 NS_ENSURE_SUCCESS(rv, rv);
1084 // 2. Go over bounce tracker candidate map and purge state.
1086 // Collect hosts to remove from the bounce trackers map. We can not remove
1087 // them while iterating over the map.
1088 nsTArray<nsCString> bounceTrackerCandidatesToRemove;
1090 for (auto hostIter = aStateGlobal->BounceTrackersMapRef().ConstIter();
1091 !hostIter.Done(); hostIter.Next()) {
1092 const nsACString& host = hostIter.Key();
1093 const PRTime& bounceTime = hostIter.Data();
1095 // If bounceTime + bounce tracking grace period is after now, then continue.
1096 // The host is still within the grace period and must not be purged.
1097 if (bounceTime +
1098 StaticPrefs::
1099 privacy_bounceTrackingProtection_bounceTrackingGracePeriodSec() *
1100 PR_USEC_PER_SEC >
1101 now) {
1102 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
1103 ("%s: Skip host within bounce tracking grace period %s",
1104 __FUNCTION__, PromiseFlatCString(host).get()));
1106 continue;
1109 // If there is a top-level traversable whose active document's origin's
1110 // site's host equals host, then continue.
1111 // TODO: Bug 1842047: Implement a more accurate check that calls into the
1112 // browser implementations to determine whether the site is currently open
1113 // on the top level.
1114 bool hostIsActive;
1115 rv = BounceTrackingState::HasBounceTrackingStateForSite(
1116 host, aStateGlobal->OriginAttributesRef(), hostIsActive);
1117 if (NS_WARN_IF(NS_FAILED(rv))) {
1118 hostIsActive = false;
1120 if (hostIsActive) {
1121 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
1122 ("%s: Skip host which is active %s", __FUNCTION__,
1123 PromiseFlatCString(host).get()));
1124 continue;
1127 // Gecko specific: If the host is on the content blocking allow-list or
1128 // allow-listed via RemoteSettings continue.
1129 bool isAllowListed = mRemoteSiteHostExceptions.Contains(host);
1130 // If remote settings doesn't allowlist also check the content blocking
1131 // allow-list.
1132 if (!isAllowListed) {
1133 rv = aBounceTrackingAllowList.CheckForBaseDomain(
1134 host, aStateGlobal->OriginAttributesRef(), isAllowListed);
1135 if (NS_WARN_IF(NS_FAILED(rv))) {
1136 continue;
1139 if (isAllowListed) {
1140 if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
1141 nsAutoCString originAttributeSuffix;
1142 aStateGlobal->OriginAttributesRef().CreateSuffix(originAttributeSuffix);
1143 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
1144 ("%s: Skip allow-listed: host: %s, "
1145 "originAttributes: %s",
1146 __FUNCTION__, PromiseFlatCString(host).get(),
1147 originAttributeSuffix.get()));
1149 // Remove allow-listed host so we don't need to check in again next purge
1150 // run. If it gets classified again and the allow-list entry gets removed
1151 // it will be purged in the next run.
1152 bounceTrackerCandidatesToRemove.AppendElement(host);
1153 continue;
1156 // No exception above applies, clear state for the given host.
1157 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
1158 ("%s: Purging bounce tracker. siteHost: %s, bounceTime: %" PRIu64
1159 " aStateGlobal: %s",
1160 __FUNCTION__, PromiseFlatCString(host).get(), bounceTime,
1161 aStateGlobal->Describe().get()));
1163 // Remove it from the bounce trackers map, it's about to be purged. If the
1164 // clear call fails still remove it. We want to avoid an ever growing list
1165 // of hosts in case of repeated failures.
1166 bounceTrackerCandidatesToRemove.AppendElement(host);
1168 RefPtr<ClearDataMozPromise> clearDataPromise;
1169 rv = PurgeStateForHostAndOriginAttributes(
1170 host, bounceTime, aStateGlobal->OriginAttributesRef(),
1171 getter_AddRefs(clearDataPromise));
1172 if (NS_WARN_IF(NS_FAILED(rv))) {
1173 continue;
1175 MOZ_ASSERT(clearDataPromise);
1177 aClearPromises.AppendElement(clearDataPromise);
1180 // Remove hosts from the bounce trackers map which we executed purge calls
1181 // for.
1182 return aStateGlobal->RemoveBounceTrackers(bounceTrackerCandidatesToRemove);
1185 nsresult BounceTrackingProtection::PurgeStateForHostAndOriginAttributes(
1186 const nsACString& aHost, PRTime bounceTime,
1187 const OriginAttributes& aOriginAttributes,
1188 ClearDataMozPromise** aClearPromise) {
1189 MOZ_ASSERT(!aHost.IsEmpty());
1190 MOZ_ASSERT(aClearPromise);
1192 RefPtr<ClearDataMozPromise::Private> clearPromise =
1193 new ClearDataMozPromise::Private(__func__);
1194 RefPtr<ClearDataCallback> cb =
1195 new ClearDataCallback(clearPromise, aOriginAttributes, aHost, bounceTime);
1197 if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
1198 nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN) {
1199 // In dry-run mode, we don't actually clear the data, but we still want to
1200 // resolve the promise to indicate that the data would have been cleared.
1201 cb->OnDataDeleted(0);
1203 clearPromise.forget(aClearPromise);
1204 return NS_OK;
1207 nsresult rv = NS_OK;
1208 nsCOMPtr<nsIClearDataService> clearDataService =
1209 do_GetService("@mozilla.org/clear-data-service;1", &rv);
1210 NS_ENSURE_SUCCESS(rv, rv);
1212 // nsIClearDataService expects a schemeless site which for IPV6 addresses
1213 // includes brackets. Add them if needed.
1214 nsAutoCString hostToPurge(aHost);
1215 nsContentUtils::MaybeFixIPv6Host(hostToPurge);
1217 // When clearing data for a specific site host we need to ensure that we
1218 // only clear for matching OriginAttributes. For example if the current
1219 // state global is private browsing only we must not clear normal browsing
1220 // data. To restrict clearing we pass in a (stringified)
1221 // OriginAttributesPattern which matches the state global's
1222 // OriginAttributes.
1223 nsAutoString oaPatternString;
1224 OriginAttributesPattern pattern;
1226 // partitionKey and firstPartyDomain are omitted making them wildcards. We
1227 // want to clear across partitions since BTP should clear all data for a
1228 // given top level.
1229 pattern.mUserContextId.Construct(aOriginAttributes.mUserContextId);
1230 pattern.mPrivateBrowsingId.Construct(aOriginAttributes.mPrivateBrowsingId);
1231 pattern.mGeckoViewSessionContextId.Construct(
1232 aOriginAttributes.mGeckoViewSessionContextId);
1234 NS_ENSURE_TRUE(pattern.ToJSON(oaPatternString), NS_ERROR_FAILURE);
1236 rv = clearDataService->DeleteDataFromSiteAndOriginAttributesPatternString(
1237 hostToPurge, oaPatternString, false,
1238 // Exempt purging our own state for the given tracker since we already
1239 // update it ourselves. Additionally a nested call to the
1240 // BounceTrackingProtectionCleaner while iterating over the candidate set
1241 // may lead to crashes.
1242 nsIClearDataService::CLEAR_STATE_FOR_TRACKER_PURGING &
1243 ~nsIClearDataService::CLEAR_BOUNCE_TRACKING_PROTECTION_STATE,
1244 cb);
1245 NS_ENSURE_SUCCESS(rv, rv);
1247 clearPromise.forget(aClearPromise);
1249 return NS_OK;
1252 nsresult BounceTrackingProtection::ClearExpiredUserInteractions(
1253 BounceTrackingStateGlobal* aStateGlobal) {
1254 if (!aStateGlobal && mStorage->StateGlobalMapRef().IsEmpty()) {
1255 // Nothing to clear.
1256 return NS_OK;
1259 const PRTime now = PR_Now();
1261 // Convert the user activation lifetime into microseconds for calculation with
1262 // PRTime values. The pref is a 32-bit value. Cast into 64-bit before
1263 // multiplying so we get the correct result.
1264 int64_t activationLifetimeUsec =
1265 static_cast<int64_t>(
1266 StaticPrefs::
1267 privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec()) *
1268 PR_USEC_PER_SEC;
1270 // Clear user activation for the given state global.
1271 if (aStateGlobal) {
1272 return aStateGlobal->ClearUserActivationBefore(now -
1273 activationLifetimeUsec);
1276 // aStateGlobal not passed, clear user activation for all state globals.
1277 for (const auto& entry : mStorage->StateGlobalMapRef()) {
1278 const RefPtr<BounceTrackingStateGlobal>& stateGlobal = entry.GetData();
1279 MOZ_ASSERT(stateGlobal);
1281 nsresult rv =
1282 stateGlobal->ClearUserActivationBefore(now - activationLifetimeUsec);
1283 NS_ENSURE_SUCCESS(rv, rv);
1286 return NS_OK;
1289 void BounceTrackingProtection::MaybeLogPurgedWarningForSite(
1290 nsIPrincipal* aPrincipal, BounceTrackingState* aBounceTrackingState) {
1291 NS_ENSURE_TRUE_VOID(aPrincipal);
1292 NS_ENSURE_TRUE_VOID(aBounceTrackingState);
1294 // Ensure that the BTS is connected to a BrowsingContext. We will later use
1295 // this BC to get the DevTools console to log to.
1296 RefPtr<dom::BrowsingContext> browsingContext =
1297 aBounceTrackingState->CurrentBrowsingContext();
1298 if (!browsingContext) {
1299 return;
1302 // Check if the site is in the recently purged log. If it is we should log a
1303 // console warning.
1304 RefPtr<BounceTrackingStateGlobal> stateGlobal =
1305 mStorage->GetStateGlobal(aPrincipal);
1306 if (!stateGlobal) {
1307 // No state global which means we don't have any purge data for the given
1308 // OriginAttributes on record.
1309 return;
1312 nsAutoCString siteHost;
1313 nsresult rv = aPrincipal->GetBaseDomain(siteHost);
1314 NS_ENSURE_SUCCESS_VOID(rv);
1316 if (!stateGlobal->RecentPurgesMapRef().Contains(siteHost)) {
1317 // No recent purge found for the given site.
1318 return;
1321 // Recently purged the site. Log a warning.
1323 // Get the localized copy from antiTracking.ftl and insert the variables.
1324 nsTArray<nsCString> resourceIDs = {"toolkit/global/antiTracking.ftl"_ns};
1325 RefPtr<intl::Localization> l10n =
1326 intl::Localization::Create(resourceIDs, true);
1328 auto l10nArgs = dom::Optional<intl::L10nArgs>();
1329 l10nArgs.Construct();
1331 auto siteHostArg = l10nArgs.Value().Entries().AppendElement();
1332 siteHostArg->mKey = "siteHost";
1333 siteHostArg->mValue.SetValue().SetAsUTF8String().Assign(siteHost);
1335 // Construct the localized string.
1336 nsAutoCString message;
1337 ErrorResult errorResult;
1338 l10n->FormatValueSync("btp-warning-tracker-purged"_ns, l10nArgs, message,
1339 errorResult);
1340 if (NS_WARN_IF(errorResult.Failed())) {
1341 return;
1344 rv = nsContentUtils::ReportToConsoleByWindowID(
1345 NS_ConvertUTF8toUTF16(message), nsIScriptError::warningFlag,
1346 "bounceTrackingProtection"_ns,
1347 browsingContext->GetCurrentInnerWindowId());
1349 NS_ENSURE_SUCCESS_VOID(rv);
1352 nsresult BounceTrackingProtection::MaybeMigrateUserInteractionPermissions() {
1353 // Only run the migration once.
1354 if (StaticPrefs::
1355 privacy_bounceTrackingProtection_hasMigratedUserActivationData()) {
1356 return NS_OK;
1359 MOZ_LOG(
1360 gBounceTrackingProtectionLog, LogLevel::Debug,
1361 ("%s: Importing user activation data from permissions", __FUNCTION__));
1363 // Get all user activation permissions that are within our user activation
1364 // lifetime. We don't care about the rest since they are considered expired
1365 // for BTP.
1367 nsresult rv = NS_OK;
1368 nsCOMPtr<nsIPermissionManager> permManager =
1369 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
1370 NS_ENSURE_SUCCESS(rv, rv);
1371 NS_ENSURE_TRUE(permManager, NS_ERROR_FAILURE);
1373 // Construct the since time param. The permission manager expects epoch in
1374 // miliseconds.
1375 int64_t nowMS = PR_Now() / PR_USEC_PER_MSEC;
1376 int64_t activationLifetimeMS =
1377 static_cast<int64_t>(
1378 StaticPrefs::
1379 privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec()) *
1380 PR_MSEC_PER_SEC;
1381 int64_t since = nowMS - activationLifetimeMS;
1382 MOZ_ASSERT(since > 0);
1384 // Get all user activation permissions last modified between "since" and now.
1385 nsTArray<RefPtr<nsIPermission>> userActivationPermissions;
1386 rv = permManager->GetAllByTypeSince("storageAccessAPI"_ns, since,
1387 userActivationPermissions);
1388 NS_ENSURE_SUCCESS(rv, rv);
1390 MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
1391 ("%s: Found %zu (non-expired) user activation permissions",
1392 __FUNCTION__, userActivationPermissions.Length()));
1394 for (const auto& perm : userActivationPermissions) {
1395 nsCOMPtr<nsIPrincipal> permPrincipal;
1397 rv = perm->GetPrincipal(getter_AddRefs(permPrincipal));
1398 if (NS_WARN_IF(NS_FAILED(rv))) {
1399 continue;
1401 MOZ_ASSERT(permPrincipal);
1403 // The time the permission was last modified is the time of last user
1404 // activation.
1405 int64_t modificationTimeMS;
1406 rv = perm->GetModificationTime(&modificationTimeMS);
1407 NS_ENSURE_SUCCESS(rv, rv);
1408 MOZ_ASSERT(modificationTimeMS >= since,
1409 "Unexpected permission modification time");
1411 // We may end up with duplicates here since user activation permissions are
1412 // tracked by origin, while BTP tracks user activation by site host.
1413 // RecordUserActivation is responsible for only keeping the most recent user
1414 // activation flag for a given site host and needs to make sure existing
1415 // activation flags are not overwritten by older timestamps.
1416 // RecordUserActivation expects epoch in microseconds.
1417 rv = RecordUserActivation(permPrincipal,
1418 Some(modificationTimeMS * PR_USEC_PER_MSEC));
1419 if (NS_WARN_IF(NS_FAILED(rv))) {
1420 continue;
1424 // Migration successful, set the pref to indicate that we have migrated.
1425 return mozilla::Preferences::SetBool(
1426 "privacy.bounceTrackingProtection.hasMigratedUserActivationData", true);
1429 } // namespace mozilla