From 2b5e1cbf04c74316c47a01834b408a22282c1752 Mon Sep 17 00:00:00 2001 From: Benjamin VanderSloot Date: Mon, 27 Jan 2025 13:09:51 +0000 Subject: [PATCH] Bug 1937111 - Implement a test-coordination method without unpartitioning storage - r=asuth Differential Revision: https://phabricator.services.mozilla.com/D231408 --- .../mochitest/test_bfcache_plus_hash.html | 25 +--- dom/broadcastchannel/BroadcastChannel.cpp | 115 ++++++++++++++++++ dom/broadcastchannel/BroadcastChannel.h | 3 + dom/webidl/BroadcastChannel.webidl | 6 + 4 files changed, 128 insertions(+), 21 deletions(-) diff --git a/docshell/test/mochitest/test_bfcache_plus_hash.html b/docshell/test/mochitest/test_bfcache_plus_hash.html index cb5bc06f2189..c916909f0a33 100644 --- a/docshell/test/mochitest/test_bfcache_plus_hash.html +++ b/docshell/test/mochitest/test_bfcache_plus_hash.html @@ -82,8 +82,8 @@ function executeTest() { bc1.postMessage("close"); } - var bc1 = new BroadcastChannel("bug646641_1"); - var bc2 = new BroadcastChannel("bug646641_2"); + var bc1 = SpecialPowers.wrap(BroadcastChannel).unpartitionedTestingChannel("bug646641_1"); + var bc2 = SpecialPowers.wrap(BroadcastChannel).unpartitionedTestingChannel("bug646641_2"); bc1.onmessage = (msgEvent) => { var msg = msgEvent.data.message; var n = msgEvent.data.num; @@ -126,25 +126,8 @@ function executeTest() { gGen.next(); }); } -if (isXOrigin) { - // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) - // Acquire storage access permission here so that the BroadcastChannel used to - // communicate with the opened windows works in xorigin tests. Otherwise, - // the iframe containing this page is isolated from first-party storage access, - // which isolates BroadcastChannel communication. - SpecialPowers.wrap(document).notifyUserGestureActivation(); - SpecialPowers.addPermission("storageAccessAPI", true, window.location.href).then(() => { - SpecialPowers.wrap(document).requestStorageAccess().then(() => { - SpecialPowers.pushPrefEnv({ - set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]], - }).then(() => { - executeTest(); - }); - }); - }); -} else { - executeTest(); -} + +executeTest(); diff --git a/dom/broadcastchannel/BroadcastChannel.cpp b/dom/broadcastchannel/BroadcastChannel.cpp index 5fefc4ced66b..68b1e0514bfa 100644 --- a/dom/broadcastchannel/BroadcastChannel.cpp +++ b/dom/broadcastchannel/BroadcastChannel.cpp @@ -139,6 +139,121 @@ JSObject* BroadcastChannel::WrapObject(JSContext* aCx, return BroadcastChannel_Binding::Wrap(aCx, this, aGivenProto); } +/* static */ +already_AddRefed +BroadcastChannel::UnpartitionedTestingChannel(const GlobalObject& aGlobal, + const nsAString& aChannel, + ErrorResult& aRv) { + // This function must not be used outside of automation. + if (!xpc::IsInAutomation()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + if (NS_WARN_IF(!global)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsID portUUID = {}; + aRv = nsID::GenerateUUIDInPlace(portUUID); + if (aRv.Failed()) { + return nullptr; + } + + RefPtr bc = + new BroadcastChannel(global, aChannel, portUUID); + + nsCOMPtr unpartitionedPrincipal; + + if (NS_IsMainThread()) { + nsCOMPtr window = do_QueryInterface(global); + if (NS_WARN_IF(!window)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr sop = do_QueryInterface(global); + if (NS_WARN_IF(!sop)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + // We are *intentionally* getting the unpartitioned principal here for + // testing purposes, but the correct thing to do normally is to get the + // storage key/principal. Passerby, do not imitate this code. + unpartitionedPrincipal = sop->GetPrincipal(); + if (!unpartitionedPrincipal) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + } else { + JSContext* cx = aGlobal.Context(); + + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); + MOZ_ASSERT(workerPrivate); + + RefPtr workerRef = StrongWorkerRef::Create( + workerPrivate, "BroadcastChannel", [bc]() { bc->Shutdown(); }); + // We are already shutting down the worker. Let's return a non-active + // object. + if (NS_WARN_IF(!workerRef)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + // We are *intentionally* getting the unpartitioned principal here for + // testing purposes, but the correct thing to do normally is to get the + // storage key/principal. Passerby, do not imitate this code. + unpartitionedPrincipal = workerPrivate->GetPrincipal(); + + bc->mWorkerRef = workerRef; + } + + // Register this component to PBackground. + PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!actorChild)) { + // Firefox is probably shutting down. Let's return a 'generic' error. + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsAutoCString origin; + aRv = unpartitionedPrincipal->GetOrigin(origin); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsString originForEvents; + aRv = nsContentUtils::GetWebExposedOriginSerialization(unpartitionedPrincipal, + originForEvents); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + PrincipalInfo unpartitionedPrincipalInfo; + aRv = PrincipalToPrincipalInfo(unpartitionedPrincipal, + &unpartitionedPrincipalInfo); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + PBroadcastChannelChild* actor = actorChild->SendPBroadcastChannelConstructor( + unpartitionedPrincipalInfo, origin, nsString(aChannel)); + if (!actor) { + // The PBackground actor is shutting down, return a 'generic' error. + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + bc->mActor = static_cast(actor); + bc->mActor->SetParent(bc); + bc->mOriginForEvents = std::move(originForEvents); + + return bc.forget(); +} + /* static */ already_AddRefed BroadcastChannel::Constructor( const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv) { diff --git a/dom/broadcastchannel/BroadcastChannel.h b/dom/broadcastchannel/BroadcastChannel.h index 280913e8f8dd..48ecf68b5d77 100644 --- a/dom/broadcastchannel/BroadcastChannel.h +++ b/dom/broadcastchannel/BroadcastChannel.h @@ -43,6 +43,9 @@ class BroadcastChannel final : public DOMEventTargetHelper { static already_AddRefed Constructor( const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv); + static already_AddRefed UnpartitionedTestingChannel( + const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv); + void GetName(nsAString& aName) const { aName = mChannel; } void PostMessage(JSContext* aCx, JS::Handle aMessage, diff --git a/dom/webidl/BroadcastChannel.webidl b/dom/webidl/BroadcastChannel.webidl index 196ff2eff9fe..1a5ea49980f5 100644 --- a/dom/webidl/BroadcastChannel.webidl +++ b/dom/webidl/BroadcastChannel.webidl @@ -12,6 +12,12 @@ interface BroadcastChannel : EventTarget { [Throws] constructor(DOMString channel); + // This function is for simplifying tests that need to coordinate between + // top-level and third-party documents from the same origin. + // This will throw if called outside of automation. + [ChromeOnly,Throws] + static BroadcastChannel unpartitionedTestingChannel(DOMString channel); + readonly attribute DOMString name; [Throws]