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"
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"
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"
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";
64 already_AddRefed
<BounceTrackingProtection
>
65 BounceTrackingProtection::GetSingleton() {
66 MOZ_ASSERT(XRE_IsParentProcess());
68 // Init previously failed, don't try again.
73 RecordModePrefTelemetry();
75 // Feature is disabled.
76 if (StaticPrefs::privacy_bounceTrackingProtection_mode() ==
77 nsIBounceTrackingProtection::MODE_DISABLED
) {
81 // Feature is enabled, lazily create singleton instance.
82 if (!sBounceTrackingProtection
) {
83 sBounceTrackingProtection
= new BounceTrackingProtection();
85 if (sBounceTrackingProtection
&&
86 sBounceTrackingProtection
->mRemoteExceptionList
) {
87 Unused
<< sBounceTrackingProtection
->mRemoteExceptionList
->Shutdown();
89 sBounceTrackingProtection
= nullptr;
92 nsresult rv
= sBounceTrackingProtection
->Init();
93 if (NS_WARN_IF(NS_FAILED(rv
))) {
99 return do_AddRef(sBounceTrackingProtection
);
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.");
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(),
127 privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec(),
129 privacy_bounceTrackingProtection_bounceTrackingGracePeriodSec(),
131 privacy_bounceTrackingProtection_bounceTrackingPurgeTimerPeriodSec(),
133 privacy_bounceTrackingProtection_clientBounceDetectionTimerPeriodMS(),
134 StaticPrefs::privacy_bounceTrackingProtection_requireStatefulBounces(),
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
,
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
) {
174 // Schedule timer for tracker purging. The timer interval is determined by
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) {
184 MOZ_LOG(gBounceTrackingProtectionLog
, LogLevel::Debug
,
185 ("Scheduling mBounceTrackingPurgeTimer. Interval: %d seconds.",
188 return NS_NewTimerWithCallback(
189 getter_AddRefs(mBounceTrackingPurgeTimer
),
191 if (!sBounceTrackingProtection
) {
194 sBounceTrackingProtection
->PurgeBounceTrackers()->Then(
195 GetMainThreadSerialEventTarget(), __func__
,
197 MOZ_LOG(gBounceTrackingProtectionLog
, LogLevel::Debug
,
198 ("%s: PurgeBounceTrackers finished after timer call.",
201 [] { NS_WARNING("RunPurgeBounceTrackers failed"); });
203 purgeTimerPeriod
* PR_MSEC_PER_SEC
, nsITimer::TYPE_REPEATING_SLACK
,
204 "mBounceTrackingPurgeTimer");
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
,
222 nsIBounceTrackingProtection::Modes mode
=
223 static_cast<nsIBounceTrackingProtection::Modes
>(modeInt
);
225 MOZ_LOG(gBounceTrackingProtectionLog
, LogLevel::Debug
,
226 ("%s: mode: %d.", __FUNCTION__
, mode
));
228 return NS_ERROR_FAILURE
;
231 nsresult result
= NS_OK
;
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
239 MOZ_ASSERT(mStorage
);
240 result
= mStorage
->ClearByType(
241 BounceTrackingProtectionStorage::EntryType::BounceTracker
);
245 if (mode
== nsIBounceTrackingProtection::MODE_DISABLED
||
246 mode
== nsIBounceTrackingProtection::MODE_ENABLED_STANDBY
) {
247 // No further cleanup needed if we're just starting up.
249 MOZ_ASSERT(!mStorageObserver
);
250 MOZ_ASSERT(!mBounceTrackingPurgeTimer
);
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
))) {
261 // Even if this step fails try to do more cleanup.
264 // Clear all per-tab state.
265 BounceTrackingState::DestroyAll();
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
);
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")) {
312 // If host equals navigable’s bounce tracking record's initial host,
314 if (host
== record
->GetInitialHost()) {
315 MOZ_LOG(gBounceTrackingProtectionLog
, LogLevel::Debug
,
316 ("%s: Skip host == initialHost: %s", __FUNCTION__
,
317 PromiseFlatCString(host
).get()));
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()));
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()));
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()));
344 // If navigable’s bounce tracking record's storage access set does not
345 // contain host, continue.
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()));
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
))) {
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
,
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
);
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.
419 if (!BounceTrackingState::ShouldTrackPrincipal(aPrincipal
)) {
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
);
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
);
467 MOZ_ASSERT(XRE_IsParentProcess());
469 dom::WindowGlobalParent
* wgp
= aWindowContext
->Canonical();
472 NS_ENSURE_TRUE(wgp
->RecvRecordUserActivationForBTP(), NS_ERROR_FAILURE
);
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
493 BounceTrackingProtection::TestGetBounceTrackerCandidateHosts(
494 JS::Handle
<JS::Value
> aOriginAttributes
, JSContext
* aCx
,
495 nsTArray
<RefPtr
<nsIBounceTrackingMapEntry
>>& aCandidates
) {
499 if (!aOriginAttributes
.isObject() || !oa
.Init(aCx
, aOriginAttributes
)) {
500 return NS_ERROR_INVALID_ARG
;
503 RefPtr
<BounceTrackingStateGlobal
> globalState
= mStorage
->GetStateGlobal(oa
);
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
);
519 BounceTrackingProtection::TestGetUserActivationHosts(
520 JS::Handle
<JS::Value
> aOriginAttributes
, JSContext
* aCx
,
521 nsTArray
<RefPtr
<nsIBounceTrackingMapEntry
>>& aHosts
) {
525 if (!aOriginAttributes
.isObject() || !oa
.Init(aCx
, aOriginAttributes
)) {
526 return NS_ERROR_INVALID_ARG
;
529 RefPtr
<BounceTrackingStateGlobal
> globalState
= mStorage
->GetStateGlobal(oa
);
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
);
545 BounceTrackingProtection::TestGetRecentlyPurgedTrackers(
546 JS::Handle
<JS::Value
> aOriginAttributes
, JSContext
* aCx
,
547 nsTArray
<RefPtr
<nsIBounceTrackingPurgeEntry
>>& aPurgedTrackers
) {
551 if (!aOriginAttributes
.isObject() || !oa
.Init(aCx
, aOriginAttributes
)) {
552 return NS_ERROR_INVALID_ARG
;
555 RefPtr
<BounceTrackingStateGlobal
> globalState
= mStorage
->GetStateGlobal(oa
);
560 nsTArray
<RefPtr
<BounceTrackingPurgeEntry
>> purgeEntriesSorted
;
561 for (auto iter
= globalState
->RecentPurgesMapRef().ConstIter(); !iter
.Done();
563 for (const auto& entry
: iter
.Data()) {
564 purgeEntriesSorted
.InsertElementSorted(entry
, PurgeEntryTimeComparator
{});
568 aPurgedTrackers
.AppendElements(purgeEntriesSorted
);
574 BounceTrackingProtection::ClearAll() {
575 BounceTrackingState::ResetAll();
576 return mStorage
->Clear();
580 BounceTrackingProtection::ClearBySiteHostAndOriginAttributes(
581 const nsACString
& aSiteHost
, JS::Handle
<JS::Value
> aOriginAttributes
,
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
);
598 BounceTrackingProtection::ClearBySiteHostAndOriginAttributesPattern(
599 const nsACString
& aSiteHost
, JS::Handle
<JS::Value
> aOriginAttributesPattern
,
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
)));
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
);
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
);
643 BounceTrackingProtection::AddSiteHostExceptions(
644 const nsTArray
<nsCString
>& aSiteHosts
) {
645 for (const auto& host
: aSiteHosts
) {
646 mRemoteSiteHostExceptions
.Insert(host
);
653 BounceTrackingProtection::RemoveSiteHostExceptions(
654 const nsTArray
<nsCString
>& aSiteHosts
) {
655 for (const auto& host
: aSiteHosts
) {
656 mRemoteSiteHostExceptions
.Remove(host
);
663 BounceTrackingProtection::HasRecentlyPurgedSite(const nsACString
& aSiteHost
,
665 NS_ENSURE_ARG_POINTER(aResult
);
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
672 for (const auto& entry
: mStorage
->StateGlobalMapRef()) {
673 RefPtr
<BounceTrackingStateGlobal
> stateGlobal
= entry
.GetData();
674 MOZ_ASSERT(stateGlobal
);
676 if (stateGlobal
->RecentPurgesMapRef().Contains(aSiteHost
)) {
685 BounceTrackingProtection::TestGetSiteHostExceptions(
686 nsTArray
<nsCString
>& aSiteHostExceptions
) {
687 aSiteHostExceptions
.Clear();
689 for (const auto& host
: mRemoteSiteHostExceptions
) {
690 aSiteHostExceptions
.AppendElement(host
);
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
);
704 return NS_ERROR_UNEXPECTED
;
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
&
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
);
737 BounceTrackingProtection::TestClearExpiredUserActivations() {
738 return ClearExpiredUserInteractions();
742 BounceTrackingProtection::TestAddBounceTrackerCandidate(
743 JS::Handle
<JS::Value
> aOriginAttributes
, const nsACString
& aHost
,
744 const PRTime aBounceTime
, JSContext
* aCx
) {
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
);
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
);
766 BounceTrackingProtection::TestAddUserActivation(
767 JS::Handle
<JS::Value
> aOriginAttributes
, const nsACString
& aHost
,
768 const PRTime aActivationTime
, JSContext
* aCx
) {
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
);
784 return stateGlobal
->RecordUserActivation(host
, aActivationTime
);
788 BounceTrackingProtection::TestMaybeMigrateUserInteractionPermissions() {
789 return MaybeMigrateUserInteractionPermissions();
793 nsresult
BounceTrackingProtection::LogBounceTrackersClassifiedToWebConsole(
794 BounceTrackingState
* aBounceTrackingState
,
795 const nsTArray
<nsCString
>& aSiteHosts
) {
796 NS_ENSURE_ARG(aBounceTrackingState
);
799 if (aSiteHosts
.IsEmpty()) {
803 RefPtr
<dom::BrowsingContext
> browsingContext
=
804 aBounceTrackingState
->CurrentBrowsingContext();
805 if (!browsingContext
) {
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.
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
);
860 RefPtr
<GenericNonExclusivePromise
>
861 BounceTrackingProtection::EnsureRemoteExceptionListService() {
862 // mRemoteExceptionList already initialized or currently initializing.
863 if (mRemoteExceptionListInitPromise
) {
864 return mRemoteExceptionListInitPromise
;
867 // Create the service instance.
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__
);
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__
,
956 nsresult rv
= self
->PurgeBounceTrackersForStateGlobal(
957 stateGlobal
, bounceTrackingAllowList
, clearPromises
);
958 if (NS_WARN_IF(NS_FAILED(rv
))) {
959 resultPromise
->Reject(rv
, __func__
);
964 // Wait for all data clearing operations to complete. mClearPromises
965 // contains one promise per host / clear task.
966 ClearDataMozPromise::AllSettled(GetCurrentSerialEventTarget(),
969 GetCurrentSerialEventTarget(), __func__
,
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()) {
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(
1011 "Failed to record purged tracker in log.");
1014 if (purgedSites
.Length() > 0) {
1015 // Record successful purges via nsITrackingDBService for
1017 ReportPurgedTrackersToAntiTrackingDB(purgedSites
);
1019 // Record exposure of the feature for Nimbus
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.
1034 resultPromise
->Reject(NS_ERROR_FAILURE
, __func__
);
1037 resultPromise
->Resolve(std::move(purgedSites
), __func__
);
1040 return resultPromise
.forget();
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);
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
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.
1099 privacy_bounceTrackingProtection_bounceTrackingGracePeriodSec() *
1102 MOZ_LOG(gBounceTrackingProtectionLog
, LogLevel::Debug
,
1103 ("%s: Skip host within bounce tracking grace period %s",
1104 __FUNCTION__
, PromiseFlatCString(host
).get()));
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.
1115 rv
= BounceTrackingState::HasBounceTrackingStateForSite(
1116 host
, aStateGlobal
->OriginAttributesRef(), hostIsActive
);
1117 if (NS_WARN_IF(NS_FAILED(rv
))) {
1118 hostIsActive
= false;
1121 MOZ_LOG(gBounceTrackingProtectionLog
, LogLevel::Debug
,
1122 ("%s: Skip host which is active %s", __FUNCTION__
,
1123 PromiseFlatCString(host
).get()));
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
1132 if (!isAllowListed
) {
1133 rv
= aBounceTrackingAllowList
.CheckForBaseDomain(
1134 host
, aStateGlobal
->OriginAttributesRef(), isAllowListed
);
1135 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
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
))) {
1175 MOZ_ASSERT(clearDataPromise
);
1177 aClearPromises
.AppendElement(clearDataPromise
);
1180 // Remove hosts from the bounce trackers map which we executed purge calls
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
);
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
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
,
1245 NS_ENSURE_SUCCESS(rv
, rv
);
1247 clearPromise
.forget(aClearPromise
);
1252 nsresult
BounceTrackingProtection::ClearExpiredUserInteractions(
1253 BounceTrackingStateGlobal
* aStateGlobal
) {
1254 if (!aStateGlobal
&& mStorage
->StateGlobalMapRef().IsEmpty()) {
1255 // Nothing to clear.
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>(
1267 privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec()) *
1270 // Clear user activation for the given state global.
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
);
1282 stateGlobal
->ClearUserActivationBefore(now
- activationLifetimeUsec
);
1283 NS_ENSURE_SUCCESS(rv
, rv
);
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
) {
1302 // Check if the site is in the recently purged log. If it is we should log a
1304 RefPtr
<BounceTrackingStateGlobal
> stateGlobal
=
1305 mStorage
->GetStateGlobal(aPrincipal
);
1307 // No state global which means we don't have any purge data for the given
1308 // OriginAttributes on record.
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.
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
,
1340 if (NS_WARN_IF(errorResult
.Failed())) {
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.
1355 privacy_bounceTrackingProtection_hasMigratedUserActivationData()) {
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
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
1375 int64_t nowMS
= PR_Now() / PR_USEC_PER_MSEC
;
1376 int64_t activationLifetimeMS
=
1377 static_cast<int64_t>(
1379 privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec()) *
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
))) {
1401 MOZ_ASSERT(permPrincipal
);
1403 // The time the permission was last modified is the time of last user
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
))) {
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