Bug 1937111 - Implement a test-coordination method without unpartitioning storage - r=asuth

Differential Revision: https://phabricator.services.mozilla.com/D231408
This commit is contained in:
Benjamin VanderSloot
2025-01-27 13:09:51 +00:00
parent 7b1a4e3b4d
commit 2b5e1cbf04
4 changed files with 128 additions and 21 deletions

View File

@@ -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();
</script>

View File

@@ -139,6 +139,121 @@ JSObject* BroadcastChannel::WrapObject(JSContext* aCx,
return BroadcastChannel_Binding::Wrap(aCx, this, aGivenProto);
}
/* static */
already_AddRefed<BroadcastChannel>
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<nsIGlobalObject> 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<BroadcastChannel> bc =
new BroadcastChannel(global, aChannel, portUUID);
nsCOMPtr<nsIPrincipal> unpartitionedPrincipal;
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
if (NS_WARN_IF(!window)) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsCOMPtr<nsIScriptObjectPrincipal> 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<StrongWorkerRef> 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<BroadcastChannelChild*>(actor);
bc->mActor->SetParent(bc);
bc->mOriginForEvents = std::move(originForEvents);
return bc.forget();
}
/* static */
already_AddRefed<BroadcastChannel> BroadcastChannel::Constructor(
const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv) {

View File

@@ -43,6 +43,9 @@ class BroadcastChannel final : public DOMEventTargetHelper {
static already_AddRefed<BroadcastChannel> Constructor(
const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv);
static already_AddRefed<BroadcastChannel> UnpartitionedTestingChannel(
const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv);
void GetName(nsAString& aName) const { aName = mChannel; }
void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,

View File

@@ -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]