/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "BounceTrackingProtection.h" #include "BounceTrackingState.h" #include "BounceTrackingRecord.h" #include "BounceTrackingStorageObserver.h" #include "ErrorList.h" #include "mozilla/OriginAttributes.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/BrowsingContextWebProgress.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "nsCOMPtr.h" #include "nsDebug.h" #include "nsError.h" #include "nsIBrowser.h" #include "nsIChannel.h" #include "nsIEffectiveTLDService.h" #include "nsIRedirectHistoryEntry.h" #include "nsICookieManager.h" #include "nsICookieService.h" #include "nsIURI.h" #include "nsIWebProgressListener.h" #include "nsIPrincipal.h" #include "mozilla/BasePrincipal.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/ClearOnShutdown.h" #include "nsTHashMap.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/WindowContext.h" namespace mozilla { // Global map: browserId -> BounceTrackingState static StaticAutoPtr>> sBounceTrackingStates; NS_IMPL_ISUPPORTS(BounceTrackingState, nsIWebProgressListener, nsISupportsWeakReference); BounceTrackingState::BounceTrackingState() { MOZ_ASSERT(StaticPrefs::privacy_bounceTrackingProtection_mode() == nsIBounceTrackingProtection::MODE_ENABLED || StaticPrefs::privacy_bounceTrackingProtection_mode() == nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN); mBounceTrackingProtection = BounceTrackingProtection::GetSingleton(); }; BounceTrackingState::~BounceTrackingState() { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose, ("BounceTrackingState destructor")); if (sBounceTrackingStates) { sBounceTrackingStates->Remove(mBrowserId); } if (mClientBounceDetectionTimeout) { mClientBounceDetectionTimeout->Cancel(); mClientBounceDetectionTimeout = nullptr; } } // static already_AddRefed BounceTrackingState::GetOrCreate( dom::BrowsingContextWebProgress* aWebProgress, nsresult& aRv) { aRv = NS_OK; if (!aWebProgress) { aRv = NS_ERROR_INVALID_ARG; return nullptr; } // Check if we should even apply BTP for this environment. This depends on the // feature prefs and the web progress itself. if (!ShouldCreateBounceTrackingStateForWebProgress(aWebProgress)) { // The call below ensures we record the disabled state in telemetry. In // MODE_DISABLED we may never hit BounceTrackingProtection::GetSingleton and // thus would skip on recording telemetry. if (StaticPrefs::privacy_bounceTrackingProtection_mode() == nsIBounceTrackingProtection::MODE_DISABLED) { BounceTrackingProtection::RecordModePrefTelemetry(); } return nullptr; } dom::BrowsingContext* browsingContext = aWebProgress->GetBrowsingContext(); if (!browsingContext) { return nullptr; } uint64_t browserId = browsingContext->BrowserId(); // Check if there is already a BounceTrackingState for the given browser. if (sBounceTrackingStates) { WeakPtr existingBTS = sBounceTrackingStates->Get(browserId); if (existingBTS) { // Return a strong reference. return do_AddRef(existingBTS.get()); } } // Create a new BounceTracking state, initialize it and insert it into the // map, then return it to the caller. RefPtr newBTS = new BounceTrackingState(); aRv = newBTS->Init(aWebProgress); if (NS_FAILED(aRv)) { NS_WARNING("Failed to initialize BounceTrackingState."); return nullptr; } // Now that we know that we'll keep this BounceTrackingState lazily initialize // the global map if (!sBounceTrackingStates) { sBounceTrackingStates = new nsTHashMap>(); ClearOnShutdown(&sBounceTrackingStates); } sBounceTrackingStates->InsertOrUpdate(browserId, newBTS); return newBTS.forget(); }; // static void BounceTrackingState::ResetAll() { Reset(nullptr, nullptr); } // static void BounceTrackingState::DestroyAll() { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s", __FUNCTION__)); if (!sBounceTrackingStates) { return; } // Fully reset all BounceTrackingStates, so even if some don't get destroyed // straight away things like running timers are stopped. BounceTrackingState::Reset(nullptr, nullptr); // Destroy all BounceTrackingState objects. for (auto iter = sBounceTrackingStates->Iter(); !iter.Done(); iter.Next()) { WeakPtr bts = iter.Data(); // Need to remove the element from the map prior to calling Destroy() // because the destructor also updates the map and we can't iterate and // externally modify the map at the same time. This way the Remove() call of // the destructor is a no-op. iter.Remove(); if (!bts) { continue; } // Destroy the BounceTrackingState by dropping references to it. This is // best effort. If something still holds a reference it still stay alive // longer. // Tell the web progress to drop the BTS reference. RefPtr browsingContext = bts->CurrentBrowsingContext(); if (!browsingContext) { continue; } dom::BrowsingContextWebProgress* webProgress = browsingContext->Canonical()->GetWebProgress(); if (!webProgress) { continue; } webProgress->DropBounceTrackingState(); } // Clean up the map. sBounceTrackingStates = nullptr; } // static void BounceTrackingState::ResetAllForOriginAttributes( const OriginAttributes& aOriginAttributes) { Reset(&aOriginAttributes, nullptr); } // static void BounceTrackingState::ResetAllForOriginAttributesPattern( const OriginAttributesPattern& aPattern) { Reset(nullptr, &aPattern); } nsresult BounceTrackingState::Init( dom::BrowsingContextWebProgress* aWebProgress) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("BounceTrackingState::%s", __FUNCTION__)); MOZ_ASSERT(!mIsInitialized, "BounceTrackingState must not be initialized twice."); mIsInitialized = true; NS_ENSURE_ARG_POINTER(aWebProgress); NS_ENSURE_TRUE(StaticPrefs::privacy_bounceTrackingProtection_mode() == nsIBounceTrackingProtection::MODE_ENABLED || StaticPrefs::privacy_bounceTrackingProtection_mode() == nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN, NS_ERROR_NOT_AVAILABLE); NS_ENSURE_TRUE(mBounceTrackingProtection, NS_ERROR_FAILURE); // Store the browser ID so we can get the associated BC later without having // to hold a reference to aWebProgress. dom::BrowsingContext* browsingContext = aWebProgress->GetBrowsingContext(); NS_ENSURE_TRUE(browsingContext, NS_ERROR_FAILURE); mBrowserId = browsingContext->BrowserId(); // Create a copy of the BC's OriginAttributes so we can use it later without // having to hold a reference to the BC. mOriginAttributes = browsingContext->OriginAttributesRef(); // We don't need first party domain. Many things in BTP are keyed by // OriginAttributes and we don't want to create unecessary partitions. mOriginAttributes.mFirstPartyDomain.Truncate(); MOZ_ASSERT(mOriginAttributes.mPartitionKey.IsEmpty(), "Top level BCs mus not have a partition key."); // Add a listener for window load. See BounceTrackingState::OnStateChange for // the listener code. return aWebProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_WINDOW); } void BounceTrackingState::ResetBounceTrackingRecord() { mBounceTrackingRecord = Nothing(); } void BounceTrackingState::OnBrowsingContextDiscarded() { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s", __FUNCTION__)); // We are about to be destroyed because the tab closed. This marks the end of // the extended navigation (if any). Record stateful bounces. // No ongoing extended navigation, nothing to record. if (!mBounceTrackingRecord) { return; } MOZ_ASSERT(mBounceTrackingProtection); nsresult rv = mBounceTrackingProtection->RecordStatefulBounces(this); if (NS_FAILED(rv)) { NS_WARNING("Failed to record stateful bounces on BrowsingContext discard."); } } const Maybe& BounceTrackingState::GetBounceTrackingRecord() { return mBounceTrackingRecord; } nsCString BounceTrackingState::Describe() { nsAutoCString oaSuffix; OriginAttributesRef().CreateSuffix(oaSuffix); return nsPrintfCString( "{ mBounceTrackingRecord: %s, mOriginAttributes: %s, mBrowserId: %" PRIu64 " }", mBounceTrackingRecord ? mBounceTrackingRecord->Describe().get() : "null", oaSuffix.get(), mBrowserId); } // static void BounceTrackingState::Reset(const OriginAttributes* aOriginAttributes, const OriginAttributesPattern* aPattern) { if (aOriginAttributes || aPattern) { MOZ_ASSERT((aOriginAttributes != nullptr) != (aPattern != nullptr), "Must not pass both aOriginAttributes and aPattern."); } if (!sBounceTrackingStates) { return; } for (const WeakPtr& btsWeak : sBounceTrackingStates->Values()) { if (!btsWeak) { continue; } RefPtr bounceTrackingState(btsWeak); if ((aOriginAttributes && *aOriginAttributes != bounceTrackingState->OriginAttributesRef()) || (aPattern && !aPattern->Matches(bounceTrackingState->OriginAttributesRef()))) { continue; } if (bounceTrackingState->mClientBounceDetectionTimeout) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: mClientBounceDetectionTimeout->Cancel()", __FUNCTION__)); bounceTrackingState->mClientBounceDetectionTimeout->Cancel(); bounceTrackingState->mClientBounceDetectionTimeout = nullptr; } bounceTrackingState->ResetBounceTrackingRecord(); } } // static bool BounceTrackingState::ShouldCreateBounceTrackingStateForWebProgress( dom::BrowsingContextWebProgress* aWebProgress) { NS_ENSURE_TRUE(aWebProgress, false); uint8_t mode = StaticPrefs::privacy_bounceTrackingProtection_mode(); // Classification / purging is disabled. if (mode != nsIBounceTrackingProtection::MODE_ENABLED && mode != nsIBounceTrackingProtection::MODE_ENABLED_DRY_RUN) { return false; } // Only keep track of top level content browsing contexts. dom::BrowsingContext* browsingContext = aWebProgress->GetBrowsingContext(); if (!browsingContext || !browsingContext->IsTopContent()) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose, ("%s: Skip non top-content.", __FUNCTION__)); return false; } bool isPrivate = browsingContext->UsePrivateBrowsing(); uint32_t cookieBehavior = nsICookieManager::GetCookieBehavior(isPrivate); if (cookieBehavior == nsICookieService::BEHAVIOR_ACCEPT || cookieBehavior == nsICookieService::BEHAVIOR_REJECT) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose, ("%s: Skip on cookie behavior %i", __FUNCTION__, cookieBehavior)); return false; } return true; } // static bool BounceTrackingState::ShouldTrackPrincipal(nsIPrincipal* aPrincipal) { MOZ_ASSERT(aPrincipal); // Only track content principals. if (!aPrincipal->GetIsContentPrincipal()) { return false; } // Skip non http schemes. if (!aPrincipal->SchemeIs("http") && !aPrincipal->SchemeIs("https")) { return false; } return true; } // static nsresult BounceTrackingState::HasBounceTrackingStateForSite( const nsACString& aSiteHost, const OriginAttributes& aOriginAttributes, bool& aResult) { aResult = false; NS_ENSURE_TRUE(aSiteHost.Length(), NS_ERROR_FAILURE); if (!sBounceTrackingStates) { return NS_OK; } // Iterate over all browsing contexts which have a bounce tracking state. Use // the content principal base domain field to determine whether a BC has an // active site that matches aSiteHost. for (const WeakPtr& btsWeak : sBounceTrackingStates->Values()) { if (!btsWeak) { continue; } RefPtr state(btsWeak); // Skip BTS of unrelated OA. These are not relevant for the caller as their // state is isolated. if (state->mOriginAttributes != aOriginAttributes) { continue; } // For each active BTS get the current window's document principal. RefPtr browsingContext = state->CurrentBrowsingContext(); if (!browsingContext || browsingContext->IsDiscarded() || browsingContext->IsInBFCache()) { continue; } RefPtr currentWindow = browsingContext->Canonical()->GetCurrentWindowGlobal(); if (!currentWindow) { continue; } nsCOMPtr principal = currentWindow->DocumentPrincipal(); if (NS_WARN_IF(!principal)) { continue; } // Lastly, check if the site matches. nsAutoCString baseDomain; nsresult rv = principal->GetBaseDomain(baseDomain); if (NS_WARN_IF(NS_FAILED(rv))) { continue; } if (aSiteHost.Equals(baseDomain)) { aResult = true; return NS_OK; } } return NS_OK; } already_AddRefed BounceTrackingState::CurrentBrowsingContext() { MOZ_ASSERT(mBrowserId != 0); return dom::BrowsingContext::GetCurrentTopByBrowserId(mBrowserId); } const OriginAttributes& BounceTrackingState::OriginAttributesRef() { return mOriginAttributes; } nsresult BounceTrackingState::OnDocumentStartRequest(nsIChannel* aChannel) { NS_ENSURE_ARG_POINTER(aChannel); MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s", __FUNCTION__)); nsCOMPtr loadInfo; nsresult rv = aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); NS_ENSURE_SUCCESS(rv, rv); // Used to keep track of whether we added entries to the site list that are // not "null". bool siteListIsEmpty = true; // Collect uri list including any redirects. nsTArray siteList; for (const nsCOMPtr& redirectHistoryEntry : loadInfo->RedirectChain()) { nsCOMPtr principal; rv = redirectHistoryEntry->GetPrincipal(getter_AddRefs(principal)); NS_ENSURE_SUCCESS(rv, rv); if (!BounceTrackingState::ShouldTrackPrincipal(principal)) { siteList.AppendElement("null"_ns); continue; } nsAutoCString baseDomain; rv = principal->GetBaseDomain(baseDomain); NS_ENSURE_SUCCESS(rv, rv); if (NS_WARN_IF(baseDomain.IsEmpty())) { siteList.AppendElement("null"); } else { siteList.AppendElement(baseDomain); siteListIsEmpty = false; } } // Add site via the current URI which is the end of the chain. nsCOMPtr channelURI; rv = aChannel->GetURI(getter_AddRefs(channelURI)); NS_ENSURE_SUCCESS(rv, rv); if (mozilla::net::SchemeIsHttpOrHttps(channelURI)) { nsCOMPtr tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString siteHost; rv = tldService->GetSchemelessSite(channelURI, siteHost); // Skip URIs where we can't get a site host. if (NS_FAILED(rv)) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: Failed to get site host from channelURI: %s", __FUNCTION__, channelURI->GetSpecOrDefault().get())); siteList.AppendElement("null"_ns); } else { MOZ_ASSERT(!siteHost.IsEmpty(), "siteHost should not be empty."); siteList.AppendElement(siteHost); siteListIsEmpty = false; } } // Do not record empty site lists. This can happen if none of the principals // are suitable for tracking. It includes when OnDocumentStartRequest is // called for the initial about:blank. if (siteListIsEmpty) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: skip empty site list.", __FUNCTION__)); return NS_OK; } return OnResponseReceived(siteList); } // nsIWebProgressListener NS_IMETHODIMP BounceTrackingState::OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aStateFlags, nsresult aStatus) { NS_ENSURE_ARG_POINTER(aWebProgress); NS_ENSURE_ARG_POINTER(aRequest); bool isTopLevel = false; nsresult rv = aWebProgress->GetIsTopLevel(&isTopLevel); NS_ENSURE_SUCCESS(rv, rv); // Filter for top level loads. if (!isTopLevel) { return NS_OK; } // Filter for window loads. if (!(aStateFlags & nsIWebProgressListener::STATE_STOP) || !(aStateFlags & nsIWebProgressListener::STATE_IS_WINDOW)) { return NS_OK; } MOZ_LOG_FMT(gBounceTrackingProtectionLog, LogLevel::Verbose, "{}: Top level window load: aStateFlags: {}, aStatus: {:#x}", __PRETTY_FUNCTION__, aStateFlags, static_cast(aStatus)); // Discard failed loads. Those are not valid destinations. if (NS_FAILED(aStatus)) { MOZ_LOG_FMT(gBounceTrackingProtectionLog, LogLevel::Verbose, "{}: Discarding failed load. aStatus: {:#x}", __PRETTY_FUNCTION__, static_cast(aStatus)); return NS_OK; } // Get the document principal via the current window global. dom::BrowsingContext* browsingContext = aWebProgress->GetBrowsingContext(); NS_ENSURE_TRUE(browsingContext, NS_ERROR_FAILURE); dom::WindowGlobalParent* windowGlobalParent = browsingContext->Canonical()->GetCurrentWindowGlobal(); NS_ENSURE_TRUE(windowGlobalParent, NS_ERROR_FAILURE); return OnDocumentLoaded(windowGlobalParent->DocumentPrincipal()); } NS_IMETHODIMP BounceTrackingState::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP BounceTrackingState::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsIURI* aLocation, uint32_t aFlags) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP BounceTrackingState::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus, const char16_t* aMessage) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP BounceTrackingState::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aState) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP BounceTrackingState::OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aEvent) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } nsresult BounceTrackingState::OnStartNavigation( nsIPrincipal* aTriggeringPrincipal, const bool aHasValidUserGestureActivation) { NS_ENSURE_ARG_POINTER(aTriggeringPrincipal); // Logging if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) { nsAutoCString origin; nsresult rv = aTriggeringPrincipal->GetOrigin(origin); if (NS_FAILED(rv)) { origin = "err"; } MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: origin: %s, mBounceTrackingRecord: %s", __FUNCTION__, origin.get(), mBounceTrackingRecord ? mBounceTrackingRecord->Describe().get() : "null")); } // Remove any queued global tasks to record stateful bounces for bounce // tracking from the networking task source. if (mClientBounceDetectionTimeout) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: mClientBounceDetectionTimeout->Cancel()", __FUNCTION__)); mClientBounceDetectionTimeout->Cancel(); mClientBounceDetectionTimeout = nullptr; } // Obtain the (schemeless) site to keep track of bounces. nsAutoCString siteHost; // If origin is an opaque origin, set initialHost to empty host. Strictly // speaking we only need to check IsNullPrincipal, but we're generally only // interested in content principals with http/s scheme. Other principal types // or schemes are not considered to be trackers. if (!BounceTrackingState::ShouldTrackPrincipal(aTriggeringPrincipal)) { siteHost = ""; } else { // obtain site nsresult rv = aTriggeringPrincipal->GetBaseDomain(siteHost); if (NS_WARN_IF(NS_FAILED(rv))) { siteHost = ""; } } // If sourceSnapshotParams’s has transient activation is true, // we initialize a new bounce tracking record with the initialHost // having been activated. Also treat system principal navigation as // having user interaction. bool hasUserActivation = aHasValidUserGestureActivation || aTriggeringPrincipal->IsSystemPrincipal(); // If navigable’s bounce tracking record is null: Set navigable’s bounce // tracking record to a new bounce tracking record with initial host set to // initialHost. if (!mBounceTrackingRecord) { mBounceTrackingRecord = Some(BounceTrackingRecord()); mBounceTrackingRecord->SetInitialHost(siteHost); if (hasUserActivation) { mBounceTrackingRecord->AddUserActivationHost(siteHost); } MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: new BounceTrackingRecord(): %s", __FUNCTION__, mBounceTrackingRecord ? mBounceTrackingRecord->Describe().get() : "null")); return NS_OK; } // If sourceSnapshotParams’s has transient activation is true: The user // activation ends the extended navigation. Process the bounce candidates. MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: site: %s, hasUserActivation? %d", __FUNCTION__, siteHost.get(), hasUserActivation)); if (hasUserActivation) { nsresult rv = mBounceTrackingProtection->RecordStatefulBounces(this); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(!mBounceTrackingRecord); mBounceTrackingRecord = Some(BounceTrackingRecord()); mBounceTrackingRecord->SetInitialHost(siteHost); mBounceTrackingRecord->AddUserActivationHost(siteHost); return NS_OK; } // There is no transient user activation. Add host as a bounce candidate. if (siteHost.IsEmpty()) { mBounceTrackingRecord->AddBounceHost("null"_ns); } else { mBounceTrackingRecord->AddBounceHost(siteHost); } return NS_OK; } // Private nsresult BounceTrackingState::OnResponseReceived( const nsTArray& aSiteList) { #ifdef DEBUG MOZ_ASSERT(!aSiteList.IsEmpty(), "siteList should not be empty."); for (const nsCString& site : aSiteList) { MOZ_ASSERT(!site.IsEmpty(), "site should not be an empty string."); } #endif // Logging if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) { nsAutoCString siteListStr; for (const nsACString& site : aSiteList) { siteListStr.Append(site); siteListStr.AppendLiteral(", "); } MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: #%zu siteList: %s", __FUNCTION__, siteListStr.Length(), siteListStr.get())); } // Record should exist by now. It gets created in OnStartNavigation. // TODO: Bug 1894936 if (!mBounceTrackingRecord) { return NS_ERROR_FAILURE; } // Check if there is still an active timeout. This shouldn't happen since // OnStartNavigation already cancels it. // TODO: Bug 1894936 if (mClientBounceDetectionTimeout) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: mClientBounceDetectionTimeout->Cancel()", __FUNCTION__)); mClientBounceDetectionTimeout->Cancel(); mClientBounceDetectionTimeout = nullptr; } // Run steps after a timeout: queue a global task on the networking task // source with global to record stateful bounces for bounce. MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: Scheduling mClientBounceDetectionTimeout", __FUNCTION__)); // Use a weak reference to this to avoid keeping the object alive if the tab // is closed during the timeout. WeakPtr thisWeak = this; nsresult rv = NS_NewTimerWithCallback( getter_AddRefs(mClientBounceDetectionTimeout), [thisWeak](auto) { if (!thisWeak) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: !thisWeak", __FUNCTION__)); return; } MOZ_LOG( gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: Calling RecordStatefulBounces after timeout.", __FUNCTION__)); BounceTrackingState* bounceTrackingState = thisWeak; DebugOnly rv = bounceTrackingState->mBounceTrackingProtection ->RecordStatefulBounces(bounceTrackingState); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "Running RecordStatefulBounces after a timeout failed."); bounceTrackingState->mClientBounceDetectionTimeout = nullptr; }, StaticPrefs:: privacy_bounceTrackingProtection_clientBounceDetectionTimerPeriodMS(), nsITimer::TYPE_ONE_SHOT, "mClientBounceDetectionTimeout"); NS_ENSURE_SUCCESS(rv, rv); // For each URL in URLs: Insert host to the navigable’s bounce tracking // record's bounce set. for (const nsACString& site : aSiteList) { mBounceTrackingRecord->AddBounceHost(site); } return NS_OK; } nsresult BounceTrackingState::OnDocumentLoaded( nsIPrincipal* aDocumentPrincipal) { NS_ENSURE_ARG_POINTER(aDocumentPrincipal); // Logging if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) { nsAutoCString origin; nsresult rv = aDocumentPrincipal->GetOrigin(origin); if (NS_FAILED(rv)) { origin = "err"; } MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: origin: %s, this: %s", __FUNCTION__, origin.get(), Describe().get())); } bool shouldTrackPrincipal = BounceTrackingState::ShouldTrackPrincipal(aDocumentPrincipal); // Check if we need to log a warning to the DevTools console because we have // previously purged this site. This is only relevant to check if we actually // monitor this principal for bounce tracking. if (shouldTrackPrincipal) { mBounceTrackingProtection->MaybeLogPurgedWarningForSite(aDocumentPrincipal, this); } // Assert: navigable’s bounce tracking record is not null. // TODO: Bug 1894936 if (!mBounceTrackingRecord) { return NS_ERROR_FAILURE; } nsAutoCString siteHost; if (!shouldTrackPrincipal) { siteHost = ""; } else { nsresult rv = aDocumentPrincipal->GetBaseDomain(siteHost); NS_ENSURE_SUCCESS(rv, rv); } // Set the navigable’s bounce tracking record's final host to the host of // finalSite. mBounceTrackingRecord->SetFinalHost(siteHost); return NS_OK; } nsresult BounceTrackingState::OnCookieWrite(const nsACString& aSiteHost) { NS_ENSURE_TRUE(!aSiteHost.IsEmpty(), NS_ERROR_FAILURE); MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose, ("%s: OnCookieWrite: %s.", __FUNCTION__, PromiseFlatCString(aSiteHost).get())); if (!mBounceTrackingRecord) { return NS_OK; } mBounceTrackingRecord->AddStorageAccessHost(aSiteHost); return NS_OK; } nsresult BounceTrackingState::OnStorageAccess(nsIPrincipal* aPrincipal) { NS_ENSURE_ARG_POINTER(aPrincipal); // The caller should already filter out principals for us. MOZ_ASSERT(BounceTrackingState::ShouldTrackPrincipal(aPrincipal)); if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) { nsAutoCString origin; nsresult rv = aPrincipal->GetOrigin(origin); if (NS_FAILED(rv)) { origin = "err"; } MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: origin: %s, mBounceTrackingRecord: %s", __FUNCTION__, origin.get(), mBounceTrackingRecord ? mBounceTrackingRecord->Describe().get() : "null")); } if (!mBounceTrackingRecord) { return NS_OK; } nsAutoCString siteHost; nsresult rv = aPrincipal->GetBaseDomain(siteHost); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(!siteHost.IsEmpty(), NS_ERROR_FAILURE); mBounceTrackingRecord->AddStorageAccessHost(siteHost); return NS_OK; } nsresult BounceTrackingState::OnUserActivation(const nsACString& aSiteHost) { MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug, ("%s: aSiteHost: %s, mBounceTrackingRecord: %s", __FUNCTION__, PromiseFlatCString(aSiteHost).get(), mBounceTrackingRecord ? mBounceTrackingRecord->Describe().get() : "null")); if (mBounceTrackingRecord) { mBounceTrackingRecord->AddUserActivationHost(aSiteHost); } return NS_OK; } } // namespace mozilla