Backed out 12 changesets (bug 1843308, bug 1848406, bug 1888500, bug 1888504, bug 1890546) for causing AddressSanitizer @ xpcom/base/nsISupportsImpl.cpp & bc failures @ toolkit/components/antitracking/bouncetrackingprotection/test/browser/<...> CLOSED TREE

Backed out changeset 4537591cca64 (bug 1890546)
Backed out changeset b29ebdf1439e (bug 1843308)
Backed out changeset 7c22cf88677f (bug 1888504)
Backed out changeset b85173dc6c16 (bug 1888500)
Backed out changeset 02d68d4511c7 (bug 1888500)
Backed out changeset 42ab5bd4b856 (bug 1848406)
Backed out changeset 6ce0fba99d02 (bug 1848406)
Backed out changeset affbf180e519 (bug 1848406)
Backed out changeset 80365ce68377 (bug 1848406)
Backed out changeset d75faab0301f (bug 1848406)
Backed out changeset 940c5fd39d25 (bug 1848406)
Backed out changeset 31016e129e99 (bug 1848406)
This commit is contained in:
Sandor Molnar 2024-04-10 22:10:33 +03:00
parent db55afa3bd
commit 29d972749f
28 changed files with 148 additions and 1064 deletions

View File

@ -170,10 +170,7 @@ void BrowsingContextWebProgress::ContextReplaced(
already_AddRefed<BounceTrackingState>
BrowsingContextWebProgress::GetBounceTrackingState() {
if (!mBounceTrackingState) {
nsresult rv = NS_OK;
mBounceTrackingState = BounceTrackingState::GetOrCreate(this, rv);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"Failed to get BounceTrackingState.");
mBounceTrackingState = BounceTrackingState::GetOrCreate(this);
}
return do_AddRef(mBounceTrackingState);
}

View File

@ -48,7 +48,6 @@
#include "mozilla/Attributes.h"
#include "mozilla/BaseProfilerMarkersPrerequisites.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/BounceTrackingStorageObserver.h"
#include "mozilla/CallState.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/DOMEventTargetHelper.h"
@ -4649,19 +4648,6 @@ already_AddRefed<nsICSSDeclaration> nsGlobalWindowInner::GetComputedStyleHelper(
aError, nullptr);
}
void nsGlobalWindowInner::MaybeNotifyStorageKeyUsed() {
// Only notify once per window lifetime.
if (hasNotifiedStorageKeyUsed) {
return;
}
nsresult rv =
BounceTrackingStorageObserver::OnInitialStorageAccess(GetWindowContext());
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
hasNotifiedStorageKeyUsed = true;
}
Storage* nsGlobalWindowInner::GetSessionStorage(ErrorResult& aError) {
nsIPrincipal* principal = GetPrincipal();
nsIPrincipal* storagePrincipal;
@ -4784,8 +4770,6 @@ Storage* nsGlobalWindowInner::GetSessionStorage(ErrorResult& aError) {
}
}
MaybeNotifyStorageKeyUsed();
MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
("nsGlobalWindowInner %p returns %p sessionStorage", this,
mSessionStorage.get()));
@ -4954,8 +4938,6 @@ Storage* nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError) {
new PartitionedLocalStorage(this, principal, storagePrincipal, cache);
}
MaybeNotifyStorageKeyUsed();
MOZ_ASSERT(mLocalStorage);
MOZ_ASSERT(
mLocalStorage->Type() ==
@ -4975,8 +4957,6 @@ IDBFactory* nsGlobalWindowInner::GetIndexedDB(JSContext* aCx,
}
}
MaybeNotifyStorageKeyUsed();
return mIndexedDB;
}

View File

@ -728,9 +728,6 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
mozilla::ErrorResult& aError);
void Btoa(const nsAString& aBinaryData, nsAString& aAsciiBase64String,
mozilla::ErrorResult& aError);
void MaybeNotifyStorageKeyUsed();
mozilla::dom::Storage* GetSessionStorage(mozilla::ErrorResult& aError);
mozilla::dom::Storage* GetLocalStorage(mozilla::ErrorResult& aError);
mozilla::dom::Selection* GetSelection(mozilla::ErrorResult& aError);
@ -1392,11 +1389,6 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
mozilla::Maybe<mozilla::StorageAccess> mStorageAllowedCache;
uint32_t mStorageAllowedReasonCache;
// When window associated storage is accessed we need to notify the parent
// process. This flag is used to ensure we only do it once per window
// lifetime.
bool hasNotifiedStorageKeyUsed{false};
RefPtr<mozilla::dom::DebuggerNotificationManager>
mDebuggerNotificationManager;

View File

@ -219,10 +219,6 @@ parent:
bool fromHttp,
CookieStruct[] cookies);
// Notify parent of storage access in the content process. This only happens
// once per window lifetime to avoid redundant IPC.
async OnInitialStorageAccess();
child:
async NotifyPermissionChange(nsCString type, uint32_t permission);
};

View File

@ -10,7 +10,6 @@
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/BounceTrackingStorageObserver.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/dom/InProcessParent.h"
@ -1706,13 +1705,6 @@ IPCResult WindowGlobalParent::RecvSetCookies(
aCookies, GetBrowsingContext());
}
IPCResult WindowGlobalParent::RecvOnInitialStorageAccess() {
DebugOnly<nsresult> rv =
BounceTrackingStorageObserver::OnInitialStorageAccess(this);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to notify storage access");
return IPC_OK();
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(WindowGlobalParent, WindowContext,
mPageUseCountersWindow)

View File

@ -329,8 +329,6 @@ class WindowGlobalParent final : public WindowContext,
const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes,
nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies);
mozilla::ipc::IPCResult RecvOnInitialStorageAccess();
private:
WindowGlobalParent(CanonicalBrowsingContext* aBrowsingContext,
uint64_t aInnerWindowId, uint64_t aOuterWindowId,

View File

@ -227,8 +227,6 @@ StorageManager* WorkerNavigator::Storage() {
MOZ_ASSERT(global);
mStorageManager = new StorageManager(global);
workerPrivate->NotifyStorageKeyUsed();
}
return mStorageManager;

View File

@ -1948,29 +1948,6 @@ void WorkerPrivate::PropagateStorageAccessPermissionGranted() {
Unused << NS_WARN_IF(!runnable->Dispatch());
}
void WorkerPrivate::NotifyStorageKeyUsed() {
AssertIsOnWorkerThread();
// Only notify once per global.
if (hasNotifiedStorageKeyUsed) {
return;
}
hasNotifiedStorageKeyUsed = true;
// Notify about storage access on the main thread.
RefPtr<StrongWorkerRef> strongRef =
StrongWorkerRef::Create(this, "WorkerPrivate::NotifyStorageKeyUsed");
RefPtr<ThreadSafeWorkerRef> ref = new ThreadSafeWorkerRef(strongRef);
DispatchToMainThread(NS_NewRunnableFunction(
"WorkerPrivate::NotifyStorageKeyUsed", [ref = std::move(ref)] {
nsGlobalWindowInner* window =
nsGlobalWindowInner::Cast(ref->Private()->GetAncestorWindow());
if (window) {
window->MaybeNotifyStorageKeyUsed();
}
}));
}
bool WorkerPrivate::Close() {
mMutex.AssertCurrentThreadOwns();
if (mParentStatus < Closing) {

View File

@ -996,8 +996,6 @@ class WorkerPrivate final
void PropagateStorageAccessPermissionGranted();
void NotifyStorageKeyUsed();
void EnableDebugger();
void DisableDebugger();
@ -1620,10 +1618,6 @@ class WorkerPrivate final
// The flag indicates if the worke is idle for events in the main event loop.
bool mWorkerLoopIsIdle MOZ_GUARDED_BY(mMutex){false};
// This flag is used to ensure we only call NotifyStorageKeyUsed once per
// global.
bool hasNotifiedStorageKeyUsed{false};
};
class AutoSyncLoopHolder {

View File

@ -468,7 +468,6 @@ already_AddRefed<CacheStorage> WorkerGlobalScope::GetCaches(ErrorResult& aRv) {
if (!mCacheStorage) {
mCacheStorage = CacheStorage::CreateOnWorker(cache::DEFAULT_NAMESPACE, this,
mWorkerPrivate, aRv);
mWorkerPrivate->NotifyStorageKeyUsed();
}
RefPtr<CacheStorage> ref = mCacheStorage;
@ -764,8 +763,6 @@ already_AddRefed<IDBFactory> WorkerGlobalScope::GetIndexedDB(
mIndexedDB = indexedDB;
}
mWorkerPrivate->NotifyStorageKeyUsed();
return indexedDB.forget();
}

View File

@ -14110,19 +14110,11 @@
# Whether only bounces that access storage should be considered trackers.
- name: privacy.bounceTrackingProtection.requireStatefulBounces
type: bool
value: true
mirror: always
# Enables a mode where if bounce tracking protection is enabled it classifies
# but does not purge trackers. This mode is helpful for testing the feature
# without risking data loss. Telemetry is still collected normally.
- name: privacy.bounceTrackingProtection.enableDryRunMode
type: bool
value: false
mirror: always
# To be used in automated test environments to enable observer messages.
# To be used in test environments to enable observer messages.
- name: privacy.bounceTrackingProtection.enableTestMode
type: bool
value: false

View File

@ -134,11 +134,6 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
// For each host in navigables bounce tracking record's bounce set:
for (const nsACString& host : record->GetBounceHosts()) {
// Skip "null" entries, they are only used for logging purposes.
if (host.EqualsLiteral("null")) {
continue;
}
// If host equals navigables bounce tracking record's initial host,
// continue.
if (host == record->GetInitialHost()) {
@ -227,11 +222,9 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
nsresult BounceTrackingProtection::RecordUserActivation(
nsIPrincipal* aPrincipal) {
MOZ_ASSERT(XRE_IsParentProcess());
NS_ENSURE_ARG_POINTER(aPrincipal);
if (!BounceTrackingState::ShouldTrackPrincipal(aPrincipal)) {
return NS_OK;
}
NS_ENSURE_ARG_POINTER(aPrincipal);
NS_ENSURE_TRUE(aPrincipal->GetIsContentPrincipal(), NS_ERROR_FAILURE);
nsAutoCString siteHost;
nsresult rv = aPrincipal->GetBaseDomain(siteHost);
@ -592,17 +585,11 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
("%s: Purge state for host: %s", __FUNCTION__,
PromiseFlatCString(host).get()));
if (StaticPrefs::privacy_bounceTrackingProtection_enableDryRunMode()) {
// In dry-run mode, we don't actually clear the data, but we still want to
// resolve the promise to indicate that the data would have been cleared.
clearPromise->Resolve(host, __func__);
} else {
// TODO: Bug 1842067: Clear by site + OA.
rv = clearDataService->DeleteDataFromBaseDomain(host, false,
TRACKER_PURGE_FLAGS, cb);
if (NS_WARN_IF(NS_FAILED(rv))) {
clearPromise->Reject(0, __func__);
}
// TODO: Bug 1842067: Clear by site + OA.
rv = clearDataService->DeleteDataFromBaseDomain(host, false,
TRACKER_PURGE_FLAGS, cb);
if (NS_WARN_IF(NS_FAILED(rv))) {
clearPromise->Reject(0, __func__);
}
aClearPromises.AppendElement(clearPromise);

View File

@ -29,8 +29,6 @@ const nsACString& BounceTrackingRecord::GetFinalHost() const {
}
void BounceTrackingRecord::AddBounceHost(const nsACString& aHost) {
MOZ_ASSERT(!aHost.IsEmpty());
mBounceHosts.Insert(aHost);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: %s", __FUNCTION__, Describe().get()));
@ -56,8 +54,6 @@ nsCString BounceTrackingRecord::DescribeSet(const nsTHashSet<nsCString>& set) {
}
void BounceTrackingRecord::AddStorageAccessHost(const nsACString& aHost) {
MOZ_ASSERT(!aHost.IsEmpty());
mStorageAccessHosts.Insert(aHost);
}

View File

@ -29,7 +29,6 @@
#include "mozilla/ClearOnShutdown.h"
#include "nsTHashMap.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/WindowContext.h"
namespace mozilla {
@ -55,13 +54,8 @@ BounceTrackingState::~BounceTrackingState() {
// static
already_AddRefed<BounceTrackingState> BounceTrackingState::GetOrCreate(
dom::BrowsingContextWebProgress* aWebProgress, nsresult& aRv) {
aRv = NS_OK;
if (!aWebProgress) {
aRv = NS_ERROR_INVALID_ARG;
return nullptr;
}
dom::BrowsingContextWebProgress* aWebProgress) {
MOZ_ASSERT(aWebProgress);
if (!ShouldCreateBounceTrackingStateForWebProgress(aWebProgress)) {
return nullptr;
@ -79,8 +73,8 @@ already_AddRefed<BounceTrackingState> BounceTrackingState::GetOrCreate(
sStorageObserver = new BounceTrackingStorageObserver();
ClearOnShutdown(&sStorageObserver);
aRv = sStorageObserver->Init();
NS_ENSURE_SUCCESS(aRv, nullptr);
DebugOnly<nsresult> rv = sStorageObserver->Init();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to init storage observer");
}
dom::BrowsingContext* browsingContext = aWebProgress->GetBrowsingContext();
@ -88,8 +82,7 @@ already_AddRefed<BounceTrackingState> BounceTrackingState::GetOrCreate(
return nullptr;
}
uint64_t browserId = browsingContext->BrowserId();
bool createdNew = false;
bool createdNew;
RefPtr<BounceTrackingState> bounceTrackingState =
do_AddRef(sBounceTrackingStates->LookupOrInsertWith(browserId, [&] {
createdNew = true;
@ -97,10 +90,8 @@ already_AddRefed<BounceTrackingState> BounceTrackingState::GetOrCreate(
}));
if (createdNew) {
aRv = bounceTrackingState->Init(aWebProgress);
if (NS_FAILED(aRv)) {
NS_WARNING("Failed to initialize BounceTrackingState.");
sBounceTrackingStates->Remove(browserId);
nsresult rv = bounceTrackingState->Init(aWebProgress);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
}
@ -125,10 +116,6 @@ void BounceTrackingState::ResetAllForOriginAttributesPattern(
nsresult BounceTrackingState::Init(
dom::BrowsingContextWebProgress* aWebProgress) {
MOZ_ASSERT(!mIsInitialized,
"BounceTrackingState must not be initialized twice.");
mIsInitialized = true;
NS_ENSURE_ARG_POINTER(aWebProgress);
NS_ENSURE_TRUE(
StaticPrefs::privacy_bounceTrackingProtection_enabled_AtStartup(),
@ -169,10 +156,9 @@ nsCString BounceTrackingState::Describe() {
OriginAttributesRef().CreateSuffix(oaSuffix);
return nsPrintfCString(
"{ mBounceTrackingRecord: %s, mOriginAttributes: %s, mBrowserId: %" PRIu64
" }",
"{ mBounceTrackingRecord: %s, mOriginAttributes: %s }",
mBounceTrackingRecord ? mBounceTrackingRecord->Describe().get() : "null",
oaSuffix.get(), mBrowserId);
oaSuffix.get());
}
// static
@ -225,28 +211,6 @@ bool BounceTrackingState::ShouldCreateBounceTrackingStateForWebProgress(
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;
}
// Skip partitioned principals.
if (!aPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
return false;
}
return true;
}
// static
nsresult BounceTrackingState::HasBounceTrackingStateForSite(
const nsACString& aSiteHost, bool& aResult) {
@ -321,10 +285,6 @@ nsresult BounceTrackingState::OnDocumentStartRequest(nsIChannel* aChannel) {
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<nsCString> siteList;
@ -334,8 +294,8 @@ nsresult BounceTrackingState::OnDocumentStartRequest(nsIChannel* aChannel) {
rv = redirectHistoryEntry->GetPrincipal(getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
if (!BounceTrackingState::ShouldTrackPrincipal(principal)) {
siteList.AppendElement("null"_ns);
// Filter out non-content principals.
if (!principal->GetIsContentPrincipal()) {
continue;
}
@ -343,12 +303,7 @@ nsresult BounceTrackingState::OnDocumentStartRequest(nsIChannel* aChannel) {
rv = principal->GetBaseDomain(baseDomain);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(baseDomain.IsEmpty())) {
siteList.AppendElement("null");
} else {
siteList.AppendElement(baseDomain);
siteListIsEmpty = false;
}
siteList.AppendElement(baseDomain);
}
// Add site via the current URI which is the end of the chain.
@ -356,35 +311,18 @@ nsresult BounceTrackingState::OnDocumentStartRequest(nsIChannel* aChannel) {
rv = aChannel->GetURI(getter_AddRefs(channelURI));
NS_ENSURE_SUCCESS(rv, rv);
if (channelURI->SchemeIs("http") || channelURI->SchemeIs("https")) {
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString siteHost;
rv = tldService->GetSchemelessSite(channelURI, siteHost);
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;
}
if (NS_FAILED(rv)) {
NS_WARNING("Failed to retrieve site for final channel URI.");
}
// 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;
}
siteList.AppendElement(siteHost);
return OnResponseReceived(siteList);
}
@ -499,16 +437,16 @@ nsresult BounceTrackingState::OnStartNavigation(
// 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)) {
// interested in content principals. Other principal types are not considered
// to be trackers.
if (!aTriggeringPrincipal->GetIsContentPrincipal()) {
siteHost = "";
}
// obtain site
nsresult rv = aTriggeringPrincipal->GetBaseDomain(siteHost);
if (NS_WARN_IF(NS_FAILED(rv))) {
siteHost = "";
} else {
// obtain site
nsresult rv = aTriggeringPrincipal->GetBaseDomain(siteHost);
if (NS_WARN_IF(NS_FAILED(rv))) {
siteHost = "";
}
}
// If navigables bounce tracking record is null: Set navigables bounce
@ -536,7 +474,7 @@ nsresult BounceTrackingState::OnStartNavigation(
("%s: site: %s, hasUserActivation? %d", __FUNCTION__, siteHost.get(),
hasUserActivation));
if (hasUserActivation) {
nsresult rv = mBounceTrackingProtection->RecordStatefulBounces(this);
rv = mBounceTrackingProtection->RecordStatefulBounces(this);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(!mBounceTrackingRecord);
@ -547,11 +485,7 @@ nsresult BounceTrackingState::OnStartNavigation(
}
// There is no transient user activation. Add host as a bounce candidate.
if (siteHost.IsEmpty()) {
mBounceTrackingRecord->AddBounceHost("null"_ns);
} else {
mBounceTrackingRecord->AddBounceHost(siteHost);
}
mBounceTrackingRecord->AddBounceHost(siteHost);
return NS_OK;
}
@ -560,12 +494,7 @@ nsresult BounceTrackingState::OnStartNavigation(
nsresult BounceTrackingState::OnResponseReceived(
const nsTArray<nsCString>& 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
NS_ENSURE_TRUE(mBounceTrackingRecord, NS_ERROR_FAILURE);
// Logging
if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
@ -581,9 +510,6 @@ nsresult BounceTrackingState::OnResponseReceived(
siteListStr.get()));
}
// Record should exist by now. It gets created in OnStartNavigation.
NS_ENSURE_TRUE(mBounceTrackingRecord, NS_ERROR_FAILURE);
// Check if there is still an active timeout. This shouldn't happen since
// OnStartNavigation already cancels it.
if (NS_WARN_IF(mClientBounceDetectionTimeout)) {
@ -641,6 +567,9 @@ nsresult BounceTrackingState::OnDocumentLoaded(
nsIPrincipal* aDocumentPrincipal) {
NS_ENSURE_ARG_POINTER(aDocumentPrincipal);
// Assert: navigables bounce tracking record is not null.
NS_ENSURE_TRUE(mBounceTrackingRecord, NS_ERROR_FAILURE);
// Logging
if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
nsAutoCString origin;
@ -649,15 +578,14 @@ nsresult BounceTrackingState::OnDocumentLoaded(
origin = "err";
}
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: origin: %s, this: %s", __FUNCTION__, origin.get(),
Describe().get()));
("%s: origin: %s, mBounceTrackingRecord: %s", __FUNCTION__,
origin.get(),
mBounceTrackingRecord ? mBounceTrackingRecord->Describe().get()
: "null"));
}
// Assert: navigables bounce tracking record is not null.
NS_ENSURE_TRUE(mBounceTrackingRecord, NS_ERROR_FAILURE);
nsAutoCString siteHost;
if (!BounceTrackingState::ShouldTrackPrincipal(aDocumentPrincipal)) {
if (!aDocumentPrincipal->GetIsContentPrincipal()) {
siteHost = "";
} else {
nsresult rv = aDocumentPrincipal->GetBaseDomain(siteHost);
@ -686,36 +614,4 @@ nsresult BounceTrackingState::OnCookieWrite(const nsACString& 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;
}
} // namespace mozilla

View File

@ -45,7 +45,7 @@ class BounceTrackingState : public nsIWebProgressListener,
// return nullptr if the given web progress / browsing context is not suitable
// (see ShouldCreateBounceTrackingStateForWebProgress).
static already_AddRefed<BounceTrackingState> GetOrCreate(
dom::BrowsingContextWebProgress* aWebProgress, nsresult& aRv);
dom::BrowsingContextWebProgress* aWebProgress);
// Reset state for all BounceTrackingState instances this includes resetting
// BounceTrackingRecords and cancelling any running timers.
@ -80,9 +80,6 @@ class BounceTrackingState : public nsIWebProgressListener,
static bool ShouldCreateBounceTrackingStateForBC(
dom::CanonicalBrowsingContext* aBrowsingContext);
// Whether the given principal should be tracked for bounce tracking.
static bool ShouldTrackPrincipal(nsIPrincipal* aPrincipal);
// Check if there is a BounceTrackingState which current browsing context is
// associated with aSiteHost.
// This is an approximation for checking if a given site is currently loaded
@ -102,16 +99,10 @@ class BounceTrackingState : public nsIWebProgressListener,
// Create a string that describes this object. Used for logging.
nsCString Describe();
// Record sites which have accessed storage in the current extended
// navigation.
nsresult OnStorageAccess(nsIPrincipal* aPrincipal);
private:
explicit BounceTrackingState();
virtual ~BounceTrackingState();
bool mIsInitialized{false};
uint64_t mBrowserId{};
// OriginAttributes associated with the browser this state is attached to.
@ -149,6 +140,12 @@ class BounceTrackingState : public nsIWebProgressListener,
// final host.
nsresult OnDocumentLoaded(nsIPrincipal* aDocumentPrincipal);
// TODO: Bug 1839918: Detection of stateful bounces.
// Record sites which have accessed storage in the current extended
// navigation.
nsresult OnStorageAccess();
// Record sites which have activated service workers in the current
// extended navigation.
nsresult OnServiceWorkerActivation();

View File

@ -6,15 +6,14 @@
#include "BounceTrackingState.h"
#include "mozilla/Services.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "nsCOMPtr.h"
#include "nsICookieNotification.h"
#include "nsIObserverService.h"
#include "mozilla/dom/BrowsingContext.h"
#include "nsICookie.h"
#include "nsIPrincipal.h"
namespace mozilla {
@ -70,25 +69,13 @@ BounceTrackingStorageObserver::Observe(nsISupports* aSubject,
return NS_OK;
}
// Filter http(s) cookies
// Check if the cookie is partitioned. Partitioned cookies can not be used for
// bounce tracking.
nsCOMPtr<nsICookie> cookie;
rv = notification->GetCookie(getter_AddRefs(cookie));
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(cookie);
nsICookie::schemeType schemeMap;
rv = cookie->GetSchemeMap(&schemeMap);
NS_ENSURE_SUCCESS(rv, rv);
if (!(schemeMap & (nsICookie::schemeType::SCHEME_HTTP |
nsICookie::schemeType::SCHEME_HTTPS))) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose,
("Skipping non-HTTP(S) cookie."));
return NS_OK;
}
// Check if the cookie is partitioned. Partitioned cookies can not be used for
// bounce tracking.
if (!cookie->OriginAttributesNative().mPartitionKey.IsEmpty()) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose,
("Skipping partitioned cookie."));
@ -117,65 +104,4 @@ BounceTrackingStorageObserver::Observe(nsISupports* aSubject,
return bounceTrackingState->OnCookieWrite(baseDomain);
}
// static
nsresult BounceTrackingStorageObserver::OnInitialStorageAccess(
dom::WindowContext* aWindowContext) {
NS_ENSURE_ARG_POINTER(aWindowContext);
if (!XRE_IsParentProcess()) {
// Check if the principal needs to be tracked for bounce tracking. Checking
// this in the content process may save us IPC to the parent.
nsIPrincipal* storagePrincipal =
aWindowContext->GetInnerWindow()->GetEffectiveStoragePrincipal();
if (!BounceTrackingState::ShouldTrackPrincipal(storagePrincipal)) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose,
("%s: Skipping principal (content process).", __FUNCTION__));
return NS_OK;
}
dom::WindowGlobalChild* windowGlobalChild =
aWindowContext->GetWindowGlobalChild();
NS_ENSURE_TRUE(windowGlobalChild, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(windowGlobalChild->SendOnInitialStorageAccess(),
NS_ERROR_FAILURE);
return NS_OK;
}
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIPrincipal> storagePrincipal =
aWindowContext->Canonical()->DocumentStoragePrincipal();
NS_ENSURE_TRUE(storagePrincipal, NS_ERROR_FAILURE);
if (!BounceTrackingState::ShouldTrackPrincipal(storagePrincipal)) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose,
("%s: Skipping principal.", __FUNCTION__));
return NS_OK;
}
if (!storagePrincipal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Verbose,
("Skipping partitioned storage access."));
return NS_OK;
}
dom::BrowsingContext* browsingContext = aWindowContext->GetBrowsingContext();
NS_ENSURE_TRUE(browsingContext, NS_ERROR_FAILURE);
nsresult rv = NS_OK;
RefPtr<BounceTrackingState> bounceTrackingState =
BounceTrackingState::GetOrCreate(
browsingContext->Top()->Canonical()->GetWebProgress(), rv);
NS_ENSURE_SUCCESS(rv, rv);
// We may not always get a BounceTrackingState, e.g. if the feature is
// disabled or we don't keep track of bounce tracking for the given
// BrowsingContext.
if (!bounceTrackingState) {
return NS_OK;
}
return bounceTrackingState->OnStorageAccess(storagePrincipal);
}
} // namespace mozilla

View File

@ -9,10 +9,6 @@
namespace mozilla {
namespace dom {
class WindowContext;
}
extern LazyLogModule gBounceTrackingProtectionLog;
class BounceTrackingStorageObserver final : public nsIObserver {
@ -23,9 +19,6 @@ class BounceTrackingStorageObserver final : public nsIObserver {
BounceTrackingStorageObserver() = default;
nsresult Init();
[[nodiscard]] static nsresult OnInitialStorageAccess(
dom::WindowContext* aWindowContext);
private:
~BounceTrackingStorageObserver() = default;
};

View File

@ -9,23 +9,12 @@ support-files = [
"file_start.html",
"file_bounce.sjs",
"file_bounce.html",
"file_web_worker.js",
]
["browser_bouncetracking_dry_run.js"]
["browser_bouncetracking_oa_isolation.js"]
["browser_bouncetracking_popup.js"]
["browser_bouncetracking_purge.js"]
["browser_bouncetracking_schemes.js"]
["browser_bouncetracking_simple.js"]
["browser_bouncetracking_stateful_cookies.js"]
["browser_bouncetracking_stateful_storage.js"]
["browser_bouncetracking_stateful_web_worker.js"]
["browser_bouncetracking_stateful.js"]

View File

@ -1,99 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_ORIGIN = "https://itisatracker.org";
const TEST_BASE_DOMAIN = "itisatracker.org";
async function runPurgeTest(expectPurge) {
ok(!SiteDataTestUtils.hasCookies(TEST_ORIGIN), "No cookies initially.");
// Adding a cookie that should later be purged.
info("Add a test cookie to be purged later.");
SiteDataTestUtils.addToCookies({ origin: TEST_ORIGIN });
ok(SiteDataTestUtils.hasCookies(TEST_ORIGIN), "Cookie added.");
// The bounce adds localStorage. Test that there is none initially.
ok(
!SiteDataTestUtils.hasLocalStorage(TEST_ORIGIN),
"No localStorage initially."
);
info("Test client bounce with cookie.");
await runTestBounce({
bounceType: "client",
setState: "localStorage",
skipSiteDataCleanup: true,
postBounceCallback: () => {
info(
"Test that after the bounce but before purging cookies and localStorage are present."
);
ok(SiteDataTestUtils.hasCookies(TEST_ORIGIN), "Cookies not purged.");
ok(
SiteDataTestUtils.hasLocalStorage(TEST_ORIGIN),
"localStorage not purged."
);
Assert.deepEqual(
bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}),
[TEST_BASE_DOMAIN],
`Bounce tracker candidate '${TEST_BASE_DOMAIN}' added`
);
},
});
if (expectPurge) {
info("After purging the site shouldn't have any data.");
ok(!SiteDataTestUtils.hasCookies(TEST_ORIGIN), "Cookies purged.");
ok(!SiteDataTestUtils.hasLocalStorage(TEST_ORIGIN), "localStorage purged.");
} else {
info("Purging did not run meaning the site should still have data.");
ok(SiteDataTestUtils.hasCookies(TEST_ORIGIN), "Cookies still set.");
ok(
SiteDataTestUtils.hasLocalStorage(TEST_ORIGIN),
"localStorage still set."
);
}
info(
"Candidates should have been removed after running the purging algorithm. This is true for both regular and dry-run mode where we pretend to purge."
);
Assert.deepEqual(
bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}),
[],
"No bounce tracker candidates after purging."
);
// Cleanup.
bounceTrackingProtection.clearAll();
await SiteDataTestUtils.clear();
}
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["privacy.bounceTrackingProtection.requireStatefulBounces", true],
["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
// Required to use SiteDataTestUtils localStorage helpers.
["dom.storage.client_validation", false],
],
});
});
add_task(async function test_purge_in_regular_mode() {
await SpecialPowers.pushPrefEnv({
set: [["privacy.bounceTrackingProtection.enableDryRunMode", false]],
});
await runPurgeTest(true);
});
add_task(async function test_purge_in_dry_run_mode() {
await SpecialPowers.pushPrefEnv({
set: [["privacy.bounceTrackingProtection.enableDryRunMode", true]],
});
await runPurgeTest(false);
});

View File

@ -1,124 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let bounceTrackingProtection;
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["privacy.bounceTrackingProtection.requireStatefulBounces", true],
["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
],
});
bounceTrackingProtection = Cc[
"@mozilla.org/bounce-tracking-protection;1"
].getService(Ci.nsIBounceTrackingProtection);
});
async function runTest(spawnWindowType) {
if (!spawnWindowType || !["newTab", "popup"].includes(spawnWindowType)) {
throw new Error(`Invalid option '${spawnWindowType}' for spawnWindowType`);
}
Assert.equal(
bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).length,
0,
"No bounce tracker hosts initially."
);
Assert.equal(
bounceTrackingProtection.testGetUserActivationHosts({}).length,
0,
"No user activation hosts initially."
);
// Spawn a tab with A, the start of the bounce chain.
await BrowserTestUtils.withNewTab(
getBaseUrl(ORIGIN_A) + "file_start.html",
async browser => {
// The destination site C to navigate to after the bounce.
let finalURL = new URL(getBaseUrl(ORIGIN_B) + "file_start.html");
// The middle hop in the bounce chain B that redirects to finalURL C.
let bounceURL = getBounceURL({
bounceType: "client",
targetURL: finalURL,
setState: "cookie-client",
});
// Register a promise for the new popup window. This resolves once the popup
// has opened and the final url (C) has been loaded.
let openPromise;
if (spawnWindowType == "newTab") {
openPromise = BrowserTestUtils.waitForNewTab(gBrowser, finalURL.href);
} else {
openPromise = BrowserTestUtils.waitForNewWindow({ url: finalURL.href });
}
// Navigate through the bounce chain by opening a popup to the bounce URL.
await navigateLinkClick(browser, bounceURL, {
spawnWindow: spawnWindowType,
});
let tabOrWindow = await openPromise;
let tabOrWindowBrowser;
if (spawnWindowType == "newTab") {
tabOrWindowBrowser = tabOrWindow.linkedBrowser;
} else {
tabOrWindowBrowser = tabOrWindow.gBrowser.selectedBrowser;
}
let promiseRecordBounces = waitForRecordBounces(tabOrWindowBrowser);
// Navigate again with user gesture which triggers
// BounceTrackingProtection::RecordStatefulBounces. We could rely on the
// timeout (mClientBounceDetectionTimeout) here but that can cause races
// in debug where the load is quite slow.
await navigateLinkClick(
tabOrWindowBrowser,
new URL(getBaseUrl(ORIGIN_C) + "file_start.html")
);
info("Wait for bounce trackers to be recorded.");
await promiseRecordBounces;
// Cleanup popup or tab.
if (spawnWindowType == "newTab") {
await BrowserTestUtils.removeTab(tabOrWindow);
} else {
await BrowserTestUtils.closeWindow(tabOrWindow);
}
}
);
// Check that the bounce tracker was detected.
Assert.deepEqual(
bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}),
[SITE_TRACKER],
"Bounce tracker in popup detected."
);
// Cleanup.
bounceTrackingProtection.clearAll();
await SiteDataTestUtils.clear();
}
/**
* Tests that bounce trackers which use popups as the first hop in the bounce
* chain can not bypass detection.
*
* A -> popup -> B -> C
*
* A opens a popup and loads B in it. B is the tracker that performs a
* short-lived redirect and C is the final destination.
*/
add_task(async function test_popup() {
await runTest("popup");
});
add_task(async function test_new_tab() {
await runTest("newTab");
});

View File

@ -1,67 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let bounceTrackingProtection;
add_setup(async function () {
bounceTrackingProtection = Cc[
"@mozilla.org/bounce-tracking-protection;1"
].getService(Ci.nsIBounceTrackingProtection);
bounceTrackingProtection.clearAll();
});
async function testInteractWithSite(origin, expectRecorded) {
is(
bounceTrackingProtection.testGetUserActivationHosts({}).length,
0,
"No user activation hosts initially"
);
let baseDomain;
let scheme;
await BrowserTestUtils.withNewTab(origin, async browser => {
baseDomain = browser.contentPrincipal.baseDomain;
scheme = browser.contentPrincipal.URI.scheme;
info(
`Trigger a user activation, which should ${
expectRecorded ? "" : "not "
}be recorded.`
);
await BrowserTestUtils.synthesizeMouseAtPoint(50, 50, {}, browser);
});
if (expectRecorded) {
Assert.deepEqual(
bounceTrackingProtection.testGetUserActivationHosts({}),
[baseDomain],
`User activation should be recorded for ${scheme} scheme.`
);
} else {
Assert.deepEqual(
bounceTrackingProtection.testGetUserActivationHosts({}),
[],
`User activation should not be recorded for ${scheme} scheme.`
);
}
bounceTrackingProtection.clearAll();
}
/**
* Test that we only record user activation for supported schemes.
*/
add_task(async function test_userActivationSchemes() {
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
await testInteractWithSite("http://example.com", true);
await testInteractWithSite("https://example.com", true);
await testInteractWithSite("about:blank", false);
await testInteractWithSite("about:robots", false);
await testInteractWithSite(
"file://" + Services.dirsvc.get("TmpD", Ci.nsIFile).path,
false
);
});

View File

@ -34,15 +34,6 @@ add_task(async function test_bounce_stateful_cookies_client() {
});
});
add_task(async function test_bounce_stateful_cookies_client_sameSiteFrame() {
info("Test client bounce with cookie set in same site frame.");
await runTestBounce({
bounceType: "client",
setState: "cookie-client",
setStateSameSiteFrame: true,
});
});
add_task(async function test_bounce_stateful_cookies_server() {
info("Test server bounce with cookie.");
await runTestBounce({
@ -58,11 +49,15 @@ add_task(async function test_bounce_stateful_cookies_server() {
});
});
add_task(async function test_bounce_stateful_cookies_server_sameSiteFrame() {
info("Test client bounce with cookie set in same site frame.");
// Storage tests.
// TODO: Bug 1848406: Implement stateful bounce detection for localStorage.
add_task(async function test_bounce_stateful_localStorage() {
info("TODO: client bounce with localStorage.");
await runTestBounce({
bounceType: "server",
setState: "cookie-server",
setStateSameSiteFrame: true,
bounceType: "client",
setState: "localStorage",
expectCandidate: false,
expectPurge: false,
});
});

View File

@ -1,54 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let bounceTrackingProtection;
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["privacy.bounceTrackingProtection.requireStatefulBounces", true],
["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
],
});
bounceTrackingProtection = Cc[
"@mozilla.org/bounce-tracking-protection;1"
].getService(Ci.nsIBounceTrackingProtection);
});
// Storage tests.
add_task(async function test_bounce_stateful_localStorage() {
info("Client bounce with localStorage.");
await runTestBounce({
bounceType: "client",
setState: "localStorage",
});
});
add_task(async function test_bounce_stateful_localStorage_sameSiteFrame() {
info("Client bounce with localStorage set in same site frame.");
await runTestBounce({
bounceType: "client",
setState: "localStorage",
setStateSameSiteFrame: true,
});
});
add_task(async function test_bounce_stateful_indexedDB() {
info("Client bounce with indexedDB.");
await runTestBounce({
bounceType: "client",
setState: "indexedDB",
});
});
add_task(async function test_bounce_stateful_indexedDB_sameSiteFrame() {
info("Client bounce with indexedDB populated in same site frame.");
await runTestBounce({
bounceType: "client",
setState: "indexedDB",
setStateSameSiteFrame: true,
});
});

View File

@ -1,38 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let bounceTrackingProtection;
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["privacy.bounceTrackingProtection.requireStatefulBounces", true],
["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
],
});
bounceTrackingProtection = Cc[
"@mozilla.org/bounce-tracking-protection;1"
].getService(Ci.nsIBounceTrackingProtection);
});
add_task(async function test_bounce_stateful_indexedDB() {
info("Client bounce with indexedDB.");
await runTestBounce({
bounceType: "client",
setState: "indexedDB",
setStateInWebWorker: true,
});
});
// FIXME: (Bug 1889898) This test is skipped because it triggers a shutdown
// hang.
add_task(async function test_bounce_stateful_indexedDB_nestedWorker() {
info("Client bounce with indexedDB access from a nested worker.");
await runTestBounce({
bounceType: "client",
setState: "indexedDB",
setStateInNestedWebWorker: true,
});
}).skip();

View File

@ -1,150 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Bounce!</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<p>Nothing to see here...</p>
<script>
// Wrap the entire block so we can run async code.
(async () => {
let url = new URL(location.href);
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Bounce!</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
let redirectDelay = url.searchParams.get("redirectDelay");
if(redirectDelay != null) {
redirectDelay = Number.parseInt(redirectDelay);
} else {
redirectDelay = 50;
}
<body>
<p id="test-config"></p>
<script>
const SET_STATE_HANDLERS = {
"cookie-client": setCookie,
"localStorage": setLocalStorage,
"indexedDB": setIndexedDB,
};
function setCookie(id) {
let cookie = document.cookie;
if (cookie) {
console.info("Received cookie", cookie);
} else {
let newCookie = `id=${id}`;
console.info("Setting new cookie", newCookie);
document.cookie = newCookie;
}
}
function setLocalStorage(id) {
let entry = localStorage.getItem("id");
if (entry) {
console.info("Found localStorage entry. id", entry);
} else {
console.info("Setting new localStorage entry. id", id);
localStorage.setItem(id, id);
}
}
function setIndexedDB() {
return new Promise((resolve, reject) => {
let request = window.indexedDB.open("bounce", 1);
request.onsuccess = () => {
console.info("Opened indexedDB");
resolve()
};
request.onerror = (event) => {
console.error("Error opening indexedDB", event);
reject();
};
request.onupgradeneeded = (event) => {
console.info("Initializing indexedDB");
let db = event.target.result;
db.createObjectStore("bounce");
};
});
}
function setIndexedDBInWorker(nested = false) {
let worker = new Worker("file_web_worker.js");
let msg = nested ? "setIndexedDBNested" : "setIndexedDB";
worker.postMessage(msg);
return new Promise((resolve, reject) => {
worker.onmessage = () => {
console.info("IndexedDB set in worker");
resolve();
};
worker.onerror = (event) => {
console.error("Error setting indexedDB in worker", event);
reject();
};
});
}
/**
* Set a state in a child frame.
*/
function setStateInFrame() {
// Embed self
let iframe = document.createElement("iframe");
let src = new URL(location.href);
// Remove search params we don't need for the iframe.
src.searchParams.delete("target");
src.searchParams.delete("redirectDelay");
src.searchParams.delete("setStateSameSiteFrame");
iframe.src = src.href;
let frameReadyPromise = new Promise((resolve) => {
iframe.addEventListener("load", () => {
iframe.contentWindow.readyPromise.then(resolve);
});
});
document.body.appendChild(iframe);
return frameReadyPromise;
}
// Wrap the entire block so we can run async code. Store the result in a
// promise so that parent windows can wait for us to be ready.
window.readyPromise = (async () => {
let url = new URL(location.href);
// Display the test config in the body.
document.getElementById("test-config").innerText = JSON.stringify(Object.fromEntries(url.searchParams), null, 2);
if (url.searchParams.get("setStateSameSiteFrame") === "true") {
// Set state in a child frame.
await setStateInFrame(url);
} else if(url.searchParams.get("setStateInWebWorker") === "true") {
// Set state in a worker.
await setIndexedDBInWorker();
} else if(url.searchParams.get("setStateInNestedWebWorker") === "true") {
// Set state in a nested worker.
await setIndexedDBInWorker(true);
} else {
// Set a state in this window.
let setState = url.searchParams.get("setState");
if (setState) {
let id = Math.random().toString();
let handler = SET_STATE_HANDLERS[setState];
if (!handler) {
throw new Error("Unknown state handler: " + setState);
if (setState == "cookie-client") {
let cookie = document.cookie;
if (cookie) {
console.info("Received cookie", cookie);
} else {
let newCookie = `id=${id}`;
console.info("Setting new cookie", newCookie);
document.cookie = newCookie;
}
} else if (setState == "localStorage") {
let entry = localStorage.getItem("id");
if (entry) {
console.info("Found localStorage entry. id", entry);
} else {
console.info("Setting new localStorage entry. id", id);
localStorage.setItem(id, id);
}
}
await handler(id);
}
}
// Redirect to the target URL after a delay.
// If no target is specified, do nothing.
let redirectDelay = url.searchParams.get("redirectDelay");
if (redirectDelay != null) {
redirectDelay = Number.parseInt(redirectDelay);
} else {
redirectDelay = 50;
}
let target = url.searchParams.get("target");
if (target) {
console.info("Redirecting to", target);
setTimeout(() => {
location.href = target;
}, redirectDelay);
}
})();
</script>
</body>
let target = url.searchParams.get("target");
if (target) {
console.info("redirecting to", target);
setTimeout(() => {
location.href = target;
}, redirectDelay);
}
})();
</script>
</body>
</html>

View File

@ -1,40 +0,0 @@
// A web worker which can set indexedDB.
function setIndexedDB() {
return new Promise((resolve, reject) => {
let request = self.indexedDB.open("bounce", 1);
request.onsuccess = () => {
console.info("Opened indexedDB");
resolve();
};
request.onerror = event => {
console.error("Error opening indexedDB", event);
reject();
};
request.onupgradeneeded = event => {
console.info("Initializing indexedDB");
let db = event.target.result;
db.createObjectStore("bounce");
};
});
}
self.onmessage = function (event) {
console.info("Web worker received message", event.data);
if (event.data === "setIndexedDB") {
setIndexedDB().then(() => {
self.postMessage("indexedDBSet");
});
} else if (event.data === "setIndexedDBNested") {
console.info("set state nested");
// Rather than setting indexedDB in this worker spawn a nested worker to set
// indexedDB.
let nestedWorker = new Worker("file_web_worker.js");
nestedWorker.postMessage("setIndexedDB");
nestedWorker.onmessage = () => {
console.info("IndexedDB set in nested worker");
self.postMessage("indexedDBSet");
};
}
};

View File

@ -3,17 +3,6 @@
"use strict";
const { SiteDataTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/SiteDataTestUtils.sys.mjs"
);
XPCOMUtils.defineLazyServiceGetter(
this,
"bounceTrackingProtection",
"@mozilla.org/bounce-tracking-protection;1",
"nsIBounceTrackingProtection"
);
const SITE_A = "example.com";
const ORIGIN_A = `https://${SITE_A}`;
@ -36,6 +25,13 @@ const OBSERVER_MSG_RECORD_BOUNCES_FINISHED = "test-record-bounces-finished";
const ROOT_DIR = getRootDirectory(gTestPath);
XPCOMUtils.defineLazyServiceGetter(
this,
"bounceTrackingProtection",
"@mozilla.org/bounce-tracking-protection;1",
"nsIBounceTrackingProtection"
);
/**
* Get the base url for the current test directory using the given origin.
* @param {string} origin - Origin to use in URL.
@ -52,14 +48,8 @@ function getBaseUrl(origin) {
* the bounce.
* @param {string} [options.bounceOrigin] - The origin of the bounce URL.
* @param {string} [options.targetURL] - URL to redirect to after the bounce.
* @param {('cookie-server'|'cookie-client'|'localStorage')} [options.setState]
* Type of state to set during the redirect. Defaults to non stateful redirect.
* @param {boolean} [options.setStateSameSiteFrame=false] - Whether to set the
* state in a sub frame that is same site to the top window.
* @param {boolean} [options.setStateInWebWorker=false] - Whether to set the
* state in a web worker. This only supports setState == "indexedDB".
* @param {boolean} [options.setStateInWebWorker=false] - Whether to set the
* state in a nested web worker. Otherwise the same as setStateInWebWorker.
* @param {("cookie"|null)} [options.setState] - What type of state should be set during
* the bounce. No state by default.
* @param {number} [options.statusCode] - HTTP status code to use for server
* side redirect. Only applies to bounceType == "server".
* @param {number} [options.redirectDelayMS] - How long to wait before
@ -72,9 +62,6 @@ function getBounceURL({
bounceOrigin = ORIGIN_TRACKER,
targetURL = new URL(getBaseUrl(ORIGIN_B) + "file_start.html"),
setState = null,
setStateSameSiteFrame = false,
setStateInWebWorker = false,
setStateInNestedWebWorker = false,
statusCode = 302,
redirectDelayMS = 50,
}) {
@ -92,25 +79,6 @@ function getBounceURL({
if (setState) {
searchParams.set("setState", setState);
}
if (setStateSameSiteFrame) {
searchParams.set("setStateSameSiteFrame", setStateSameSiteFrame);
}
if (setStateInWebWorker) {
if (setState != "indexedDB") {
throw new Error(
"setStateInWebWorker only supports setState == 'indexedDB'"
);
}
searchParams.set("setStateInWebWorker", setStateInWebWorker);
}
if (setStateInNestedWebWorker) {
if (setState != "indexedDB") {
throw new Error(
"setStateInNestedWebWorker only supports setState == 'indexedDB'"
);
}
searchParams.set("setStateInNestedWebWorker", setStateInNestedWebWorker);
}
if (bounceType == "server") {
searchParams.set("statusCode", statusCode);
@ -126,56 +94,23 @@ function getBounceURL({
* click on it.
* @param {MozBrowser} browser - Browser to insert the link in.
* @param {URL} targetURL - Destination for navigation.
* @param {Object} options - Additional options.
* @param {string} [options.spawnWindow] - If set to "newTab" or "popup" the
* link will be opened in a new tab or popup window respectively. If unset the
* link is opened in the given browser.
* @returns {Promise} Resolves once the click is done. Does not wait for
* navigation or load.
*/
async function navigateLinkClick(
browser,
targetURL,
{ spawnWindow = null } = {}
) {
if (spawnWindow && !["newTab", "popup"].includes(spawnWindow)) {
throw new Error(`Invalid option '${spawnWindow}' for spawnWindow`);
}
async function navigateLinkClick(browser, targetURL) {
await SpecialPowers.spawn(browser, [targetURL.href], targetURL => {
let link = content.document.createElement("a");
await SpecialPowers.spawn(
browser,
[targetURL.href, spawnWindow],
async (targetURL, spawnWindow) => {
let link = content.document.createElement("a");
link.href = targetURL;
link.textContent = targetURL;
// The link needs display: block, otherwise synthesizeMouseAtCenter doesn't
// hit it.
link.style.display = "block";
// For opening a popup we attach an event listener to trigger via click.
if (spawnWindow) {
link.href = "#";
link.addEventListener("click", event => {
event.preventDefault();
if (spawnWindow == "newTab") {
// Open a new tab.
content.window.open(targetURL, "bounce");
} else {
// Open a popup window.
content.window.open(targetURL, "bounce", "height=200,width=200");
}
});
} else {
// For regular navigation add href and click.
link.href = targetURL;
}
content.document.body.appendChild(link);
});
link.textContent = targetURL;
// The link needs display: block, otherwise synthesizeMouseAtCenter doesn't
// hit it.
link.style.display = "block";
content.document.body.appendChild(link);
await EventUtils.synthesizeMouse(link, 1, 1, {}, content);
}
);
await BrowserTestUtils.synthesizeMouseAtCenter("a[href]", {}, browser);
}
/**
@ -206,38 +141,25 @@ async function waitForRecordBounces(browser) {
* or server side redirect.
* @param {('cookie-server'|'cookie-client'|'localStorage')} [options.setState]
* Type of state to set during the redirect. Defaults to non stateful redirect.
* @param {boolean} [options.setStateSameSiteFrame=false] - Whether to set the
* state in a sub frame that is same site to the top window.
* @param {boolean} [options.setStateInWebWorker=false] - Whether to set the
* state in a web worker. This only supports setState == "indexedDB".
* @param {boolean} [options.setStateInWebWorker=false] - Whether to set the
* state in a nested web worker. Otherwise the same as setStateInWebWorker.
* @param {boolean} [options.expectCandidate=true] - Expect the redirecting site
* to be identified as a bounce tracker (candidate).
* @param {boolean} [options.expectPurge=true] - Expect the redirecting site to
* have its storage purged.
* @param {boolean} [options.expectCandidate=true] - Expect the redirecting site to be
* identified as a bounce tracker (candidate).
* @param {boolean} [options.expectPurge=true] - Expect the redirecting site to have
* its storage purged.
* @param {OriginAttributes} [options.originAttributes={}] - Origin attributes
* to use for the test. This determines whether the test is run in normal
* browsing, a private window or a container tab. By default the test is run in
* normal browsing.
* @param {function} [options.postBounceCallback] - Optional function to run
* after the bounce has completed.
* @param {boolean} [options.skipSiteDataCleanup=false] - Skip the cleanup of
* site data after the test. When this is enabled the caller is responsible for
* cleaning up site data.
* browsing, a private window or a container tab. By default the test is run
* in normal browsing.
* @param {function} [options.postBounceCallback] - Optional function to run after the
* bounce has completed.
*/
async function runTestBounce(options = {}) {
let {
bounceType,
setState = null,
setStateSameSiteFrame = false,
setStateInWebWorker = false,
setStateInNestedWebWorker = false,
expectCandidate = true,
expectPurge = true,
originAttributes = {},
postBounceCallback = () => {},
skipSiteDataCleanup = false,
} = options;
info(`runTestBounce ${JSON.stringify(options)}`);
@ -286,14 +208,7 @@ async function runTestBounce(options = {}) {
// Navigate through the bounce chain.
await navigateLinkClick(
browser,
getBounceURL({
bounceType,
targetURL,
setState,
setStateSameSiteFrame,
setStateInWebWorker,
setStateInNestedWebWorker,
})
getBounceURL({ bounceType, targetURL, setState })
);
// Wait for the final site to be loaded which complete the BounceTrackingRecord.
@ -357,7 +272,4 @@ async function runTestBounce(options = {}) {
);
}
bounceTrackingProtection.clearAll();
if (!skipSiteDataCleanup) {
await SiteDataTestUtils.clear();
}
}

View File

@ -114,11 +114,7 @@ export var SiteDataTestUtils = {
},
/**
* Adds a new localStorage entry for the specified origin, with the specified
* contents.
*
* This method requires the pref dom.storage.client_validation=false in order
* to access LS without a window.
* Adds a new localStorage entry for the specified origin, with the specified contents.
*
* @param {String} origin - the origin of the site to add test data for
* @param {String} [key] - the localStorage key
@ -143,9 +139,6 @@ export var SiteDataTestUtils = {
* @param {{key: String, value: String}[]} [testEntries] - An array of entries
* to test for.
*
* This method requires the pref dom.storage.client_validation=false in order
* to access LS without a window.
*
* @returns {Boolean} whether the origin has localStorage data
*/
hasLocalStorage(origin, testEntries) {