summaryrefslogtreecommitdiffstats
path: root/dom/broadcastchannel
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/broadcastchannel
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/broadcastchannel')
-rw-r--r--dom/broadcastchannel/BroadcastChannel.cpp466
-rw-r--r--dom/broadcastchannel/BroadcastChannel.h93
-rw-r--r--dom/broadcastchannel/BroadcastChannelChild.cpp48
-rw-r--r--dom/broadcastchannel/BroadcastChannelChild.h53
-rw-r--r--dom/broadcastchannel/BroadcastChannelParent.cpp71
-rw-r--r--dom/broadcastchannel/BroadcastChannelParent.h46
-rw-r--r--dom/broadcastchannel/BroadcastChannelService.cpp178
-rw-r--r--dom/broadcastchannel/BroadcastChannelService.h47
-rw-r--r--dom/broadcastchannel/PBroadcastChannel.ipdl36
-rw-r--r--dom/broadcastchannel/moz.build30
-rw-r--r--dom/broadcastchannel/tests/blank.html2
-rw-r--r--dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js17
-rw-r--r--dom/broadcastchannel/tests/broadcastchannel_worker_alive.js7
-rw-r--r--dom/broadcastchannel/tests/browser.toml5
-rw-r--r--dom/broadcastchannel/tests/browser_private_browsing.js93
-rw-r--r--dom/broadcastchannel/tests/file_mozbrowser.html20
-rw-r--r--dom/broadcastchannel/tests/file_mozbrowser2.html21
-rw-r--r--dom/broadcastchannel/tests/iframe_broadcastchannel.html33
-rw-r--r--dom/broadcastchannel/tests/iframe_mozbrowser.html15
-rw-r--r--dom/broadcastchannel/tests/iframe_mozbrowser2.html15
-rw-r--r--dom/broadcastchannel/tests/mochitest.toml41
-rw-r--r--dom/broadcastchannel/tests/testUrl1_bfcache.html18
-rw-r--r--dom/broadcastchannel/tests/testUrl2_bfcache.html12
-rw-r--r--dom/broadcastchannel/tests/test_bfcache.html120
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_basic.html67
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_close.html61
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_self.html37
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html52
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_worker_alive.html56
-rw-r--r--dom/broadcastchannel/tests/test_dataCloning.html27
-rw-r--r--dom/broadcastchannel/tests/test_dataURL.html35
-rw-r--r--dom/broadcastchannel/tests/test_event_listener_leaks.html55
-rw-r--r--dom/broadcastchannel/tests/test_invalidState.html27
-rw-r--r--dom/broadcastchannel/tests/test_message_after_close.html31
-rw-r--r--dom/broadcastchannel/tests/test_ordering.html64
35 files changed, 1999 insertions, 0 deletions
diff --git a/dom/broadcastchannel/BroadcastChannel.cpp b/dom/broadcastchannel/BroadcastChannel.cpp
new file mode 100644
index 0000000000..c89231badf
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -0,0 +1,466 @@
+/* -*- 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 "BroadcastChannel.h"
+#include "BroadcastChannelChild.h"
+#include "mozilla/dom/BroadcastChannelBinding.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/dom/RefMessageBodyService.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/SharedMessageBody.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/StorageAccess.h"
+
+#include "nsICookieJarSettings.h"
+#include "mozilla/dom/Document.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+using namespace ipc;
+
+namespace {
+
+class CloseRunnable final : public DiscardableRunnable {
+ public:
+ explicit CloseRunnable(BroadcastChannel* aBC)
+ : DiscardableRunnable("BroadcastChannel CloseRunnable"), mBC(aBC) {
+ MOZ_ASSERT(mBC);
+ }
+
+ NS_IMETHOD Run() override {
+ mBC->Shutdown();
+ return NS_OK;
+ }
+
+ private:
+ ~CloseRunnable() = default;
+
+ RefPtr<BroadcastChannel> mBC;
+};
+
+class TeardownRunnable {
+ protected:
+ explicit TeardownRunnable(BroadcastChannelChild* aActor) : mActor(aActor) {
+ MOZ_ASSERT(mActor);
+ }
+
+ void RunInternal() {
+ MOZ_ASSERT(mActor);
+ if (!mActor->IsActorDestroyed()) {
+ mActor->SendClose();
+ }
+ }
+
+ protected:
+ virtual ~TeardownRunnable() = default;
+
+ private:
+ RefPtr<BroadcastChannelChild> mActor;
+};
+
+class TeardownRunnableOnMainThread final : public Runnable,
+ public TeardownRunnable {
+ public:
+ explicit TeardownRunnableOnMainThread(BroadcastChannelChild* aActor)
+ : Runnable("TeardownRunnableOnMainThread"), TeardownRunnable(aActor) {}
+
+ NS_IMETHOD Run() override {
+ RunInternal();
+ return NS_OK;
+ }
+};
+
+class TeardownRunnableOnWorker final : public WorkerControlRunnable,
+ public TeardownRunnable {
+ public:
+ TeardownRunnableOnWorker(WorkerPrivate* aWorkerPrivate,
+ BroadcastChannelChild* aActor)
+ : WorkerControlRunnable(aWorkerPrivate, "TeardownRunnableOnWorker",
+ WorkerThread),
+ TeardownRunnable(aActor) {}
+
+ bool WorkerRun(JSContext*, WorkerPrivate*) override {
+ RunInternal();
+ return true;
+ }
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; }
+
+ void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {}
+
+ bool PreRun(WorkerPrivate* aWorkerPrivate) override { return true; }
+
+ void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override {}
+};
+
+} // namespace
+
+BroadcastChannel::BroadcastChannel(nsIGlobalObject* aGlobal,
+ const nsAString& aChannel,
+ const nsID& aPortUUID)
+ : DOMEventTargetHelper(aGlobal),
+ mRefMessageBodyService(RefMessageBodyService::GetOrCreate()),
+ mChannel(aChannel),
+ mState(StateActive),
+ mPortUUID(aPortUUID) {
+ MOZ_ASSERT(aGlobal);
+ KeepAliveIfHasListenersFor(nsGkAtoms::onmessage);
+}
+
+BroadcastChannel::~BroadcastChannel() {
+ Shutdown();
+ MOZ_ASSERT(!mWorkerRef);
+}
+
+JSObject* BroadcastChannel::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return BroadcastChannel_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */
+already_AddRefed<BroadcastChannel> BroadcastChannel::Constructor(
+ const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv) {
+ 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> storagePrincipal;
+
+ StorageAccess storageAccess;
+
+ nsCOMPtr<nsICookieJarSettings> cjs;
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal();
+
+ if (!incumbent) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(incumbent);
+ if (NS_WARN_IF(!sop)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ storagePrincipal = sop->GetEffectiveStoragePrincipal();
+ if (!storagePrincipal) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ storageAccess = StorageAllowedForWindow(window);
+
+ Document* doc = window->GetExtantDoc();
+ if (doc) {
+ cjs = doc->CookieJarSettings();
+ }
+ } 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;
+ }
+
+ storageAccess = workerPrivate->StorageAccess();
+
+ storagePrincipal = workerPrivate->GetEffectiveStoragePrincipal();
+
+ bc->mWorkerRef = workerRef;
+
+ cjs = workerPrivate->CookieJarSettings();
+ }
+
+ // We want to allow opaque origins.
+ if (!storagePrincipal->GetIsNullPrincipal() &&
+ (storageAccess == StorageAccess::eDeny ||
+ (ShouldPartitionStorage(storageAccess) &&
+ !StoragePartitioningEnabled(storageAccess, cjs)))) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // 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 = storagePrincipal->GetOrigin(origin);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsString originForEvents;
+ aRv = nsContentUtils::GetWebExposedOriginSerialization(storagePrincipal,
+ originForEvents);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ PrincipalInfo storagePrincipalInfo;
+ aRv = PrincipalToPrincipalInfo(storagePrincipal, &storagePrincipalInfo);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ PBroadcastChannelChild* actor = actorChild->SendPBroadcastChannelConstructor(
+ storagePrincipalInfo, 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();
+}
+
+void BroadcastChannel::PostMessage(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ ErrorResult& aRv) {
+ if (mState != StateActive) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ Maybe<nsID> agentClusterId;
+ nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+ MOZ_ASSERT(global);
+ if (global) {
+ agentClusterId = global->GetAgentClusterId();
+ }
+
+ if (!global->IsEligibleForMessaging()) {
+ return;
+ }
+
+ RefPtr<SharedMessageBody> data = new SharedMessageBody(
+ StructuredCloneHolder::TransferringNotSupported, agentClusterId);
+
+ data->Write(aCx, aMessage, JS::UndefinedHandleValue, mPortUUID,
+ mRefMessageBodyService, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ RemoveDocFromBFCache();
+
+ MessageData message;
+ SharedMessageBody::FromSharedToMessageChild(mActor->Manager(), data, message);
+ mActor->SendPostMessage(message);
+}
+
+void BroadcastChannel::Close() {
+ if (mState != StateActive) {
+ return;
+ }
+
+ // We cannot call Shutdown() immediatelly because we could have some
+ // postMessage runnable already dispatched. Instead, we change the state to
+ // StateClosed and we shutdown the actor asynchrounsly.
+
+ mState = StateClosed;
+ RefPtr<CloseRunnable> runnable = new CloseRunnable(this);
+
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ NS_WARNING("Failed to dispatch to the current thread!");
+ }
+}
+
+void BroadcastChannel::Shutdown() {
+ mState = StateClosed;
+
+ // The DTOR of this WorkerRef will release the worker for us.
+ mWorkerRef = nullptr;
+
+ if (mActor) {
+ mActor->SetParent(nullptr);
+
+ if (NS_IsMainThread()) {
+ RefPtr<TeardownRunnableOnMainThread> runnable =
+ new TeardownRunnableOnMainThread(mActor);
+ NS_DispatchToCurrentThread(runnable);
+ } else {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<TeardownRunnableOnWorker> runnable =
+ new TeardownRunnableOnWorker(workerPrivate, mActor);
+ runnable->Dispatch();
+ }
+
+ mActor = nullptr;
+ }
+
+ IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onmessage);
+}
+
+void BroadcastChannel::RemoveDocFromBFCache() {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ if (nsPIDOMWindowInner* window = GetOwner()) {
+ window->RemoveFromBFCacheSync();
+ }
+}
+
+void BroadcastChannel::DisconnectFromOwner() {
+ Shutdown();
+ DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+void BroadcastChannel::MessageReceived(const MessageData& aData) {
+ if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
+ RemoveDocFromBFCache();
+ return;
+ }
+
+ // Let's ignore messages after a close/shutdown.
+ if (mState != StateActive) {
+ return;
+ }
+
+ nsCOMPtr<nsIGlobalObject> globalObject;
+
+ if (NS_IsMainThread()) {
+ globalObject = GetParentObject();
+ } else {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ globalObject = workerPrivate->GlobalScope();
+ }
+
+ AutoJSAPI jsapi;
+ if (!globalObject || !jsapi.Init(globalObject)) {
+ NS_WARNING("Failed to initialize AutoJSAPI object.");
+ return;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ RefPtr<SharedMessageBody> data = SharedMessageBody::FromMessageToSharedChild(
+ aData, StructuredCloneHolder::TransferringNotSupported);
+ if (NS_WARN_IF(!data)) {
+ DispatchError(cx);
+ return;
+ }
+
+ IgnoredErrorResult rv;
+ JS::Rooted<JS::Value> value(cx);
+
+ data->Read(cx, &value, mRefMessageBodyService,
+ SharedMessageBody::ReadMethod::KeepRefMessageBody, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ JS_ClearPendingException(cx);
+ DispatchError(cx);
+ return;
+ }
+
+ RemoveDocFromBFCache();
+
+ RootedDictionary<MessageEventInit> init(cx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mOrigin = mOriginForEvents;
+ init.mData = value;
+
+ RefPtr<MessageEvent> event =
+ MessageEvent::Constructor(this, u"message"_ns, init);
+
+ event->SetTrusted(true);
+
+ DispatchEvent(*event);
+}
+
+void BroadcastChannel::MessageDelivered(const nsID& aMessageID,
+ uint32_t aOtherBCs) {
+ mRefMessageBodyService->SetMaxCount(aMessageID, aOtherBCs);
+}
+
+void BroadcastChannel::DispatchError(JSContext* aCx) {
+ RootedDictionary<MessageEventInit> init(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mOrigin = mOriginForEvents;
+
+ RefPtr<Event> event =
+ MessageEvent::Constructor(this, u"messageerror"_ns, init);
+ event->SetTrusted(true);
+
+ DispatchEvent(*event);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BroadcastChannel)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BroadcastChannel,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BroadcastChannel,
+ DOMEventTargetHelper)
+ tmp->Shutdown();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BroadcastChannel)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(BroadcastChannel, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(BroadcastChannel, DOMEventTargetHelper)
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/broadcastchannel/BroadcastChannel.h b/dom/broadcastchannel/BroadcastChannel.h
new file mode 100644
index 0000000000..280913e8f8
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannel.h
@@ -0,0 +1,93 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_BroadcastChannel_h
+#define mozilla_dom_BroadcastChannel_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class BroadcastChannelChild;
+class RefMessageBodyService;
+class WorkerRef;
+
+class BroadcastChannel final : public DOMEventTargetHelper {
+ friend class BroadcastChannelChild;
+
+ using PrincipalInfo = mozilla::ipc::PrincipalInfo;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BroadcastChannel,
+ DOMEventTargetHelper)
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<BroadcastChannel> Constructor(
+ const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv);
+
+ void GetName(nsAString& aName) const { aName = mChannel; }
+
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ ErrorResult& aRv);
+
+ void Close();
+
+ IMPL_EVENT_HANDLER(message)
+ IMPL_EVENT_HANDLER(messageerror)
+
+ void Shutdown();
+
+ private:
+ BroadcastChannel(nsIGlobalObject* aGlobal, const nsAString& aChannel,
+ const nsID& aPortUUID);
+
+ ~BroadcastChannel();
+
+ void MessageReceived(const MessageData& aData);
+
+ void MessageDelivered(const nsID& aMessageID, uint32_t aOtherBCs);
+
+ void RemoveDocFromBFCache();
+
+ void DisconnectFromOwner() override;
+
+ void DispatchError(JSContext* aCx);
+
+ RefPtr<BroadcastChannelChild> mActor;
+
+ RefPtr<RefMessageBodyService> mRefMessageBodyService;
+
+ RefPtr<WorkerRef> mWorkerRef;
+
+ nsString mChannel;
+ nsString mOriginForEvents;
+
+ enum { StateActive, StateClosed } mState;
+
+ // This ID is used to identify the messages-by-reference sent by this port.
+ // See RefMessageBodyService.
+ nsID mPortUUID;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BroadcastChannel_h
diff --git a/dom/broadcastchannel/BroadcastChannelChild.cpp b/dom/broadcastchannel/BroadcastChannelChild.cpp
new file mode 100644
index 0000000000..30e2633c5d
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelChild.cpp
@@ -0,0 +1,48 @@
+/* -*- 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 "BroadcastChannelChild.h"
+#include "BroadcastChannel.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+BroadcastChannelChild::BroadcastChannelChild()
+ : mBC(nullptr), mActorDestroyed(false) {}
+
+BroadcastChannelChild::~BroadcastChannelChild() { MOZ_ASSERT(!mBC); }
+
+mozilla::ipc::IPCResult BroadcastChannelChild::RecvNotify(
+ const MessageData& aData) {
+ if (!mBC) {
+ // The object is going to be deleted soon. No notify is required.
+ return IPC_OK();
+ }
+
+ mBC->MessageReceived(aData);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult BroadcastChannelChild::RecvRefMessageDelivered(
+ const nsID& aMessageID, const uint32_t& aOtherBCs) {
+ if (!mBC) {
+ // The object is going to be deleted soon. No notify is required.
+ return IPC_OK();
+ }
+
+ mBC->MessageDelivered(aMessageID, aOtherBCs);
+ return IPC_OK();
+}
+
+void BroadcastChannelChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mActorDestroyed = true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/broadcastchannel/BroadcastChannelChild.h b/dom/broadcastchannel/BroadcastChannelChild.h
new file mode 100644
index 0000000000..5c5cae4fd7
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelChild.h
@@ -0,0 +1,53 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_BroadcastChannelChild_h
+#define mozilla_dom_BroadcastChannelChild_h
+
+#include "mozilla/dom/PBroadcastChannelChild.h"
+
+namespace mozilla {
+
+namespace ipc {
+class BackgroundChildImpl;
+} // namespace ipc
+
+namespace dom {
+
+class BroadcastChannel;
+
+class BroadcastChannelChild final : public PBroadcastChannelChild {
+ friend class mozilla::ipc::BackgroundChildImpl;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(BroadcastChannelChild)
+
+ void SetParent(BroadcastChannel* aBC) { mBC = aBC; }
+
+ virtual mozilla::ipc::IPCResult RecvNotify(const MessageData& aData) override;
+
+ virtual mozilla::ipc::IPCResult RecvRefMessageDelivered(
+ const nsID& aMessageID, const uint32_t& aOtherBCs) override;
+
+ bool IsActorDestroyed() const { return mActorDestroyed; }
+
+ private:
+ BroadcastChannelChild();
+ ~BroadcastChannelChild();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ // This raw pointer is actually the parent object.
+ // It's set to null when the parent object is deleted.
+ BroadcastChannel* mBC;
+
+ bool mActorDestroyed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BroadcastChannelChild_h
diff --git a/dom/broadcastchannel/BroadcastChannelParent.cpp b/dom/broadcastchannel/BroadcastChannelParent.cpp
new file mode 100644
index 0000000000..56c528c933
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelParent.cpp
@@ -0,0 +1,71 @@
+/* -*- 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 "BroadcastChannelParent.h"
+#include "BroadcastChannelService.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/IPCBlobUtils.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+BroadcastChannelParent::BroadcastChannelParent(
+ const nsAString& aOriginChannelKey)
+ : mService(BroadcastChannelService::GetOrCreate()),
+ mOriginChannelKey(aOriginChannelKey) {
+ AssertIsOnBackgroundThread();
+ mService->RegisterActor(this, mOriginChannelKey);
+}
+
+BroadcastChannelParent::~BroadcastChannelParent() {
+ AssertIsOnBackgroundThread();
+}
+
+mozilla::ipc::IPCResult BroadcastChannelParent::RecvPostMessage(
+ const MessageData& aData) {
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!mService)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mService->PostMessage(this, aData, mOriginChannelKey);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult BroadcastChannelParent::RecvClose() {
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!mService)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mService->UnregisterActor(this, mOriginChannelKey);
+ mService = nullptr;
+
+ Unused << Send__delete__(this);
+
+ return IPC_OK();
+}
+
+void BroadcastChannelParent::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnBackgroundThread();
+
+ if (mService) {
+ // This object is about to be released and with it, also mService will be
+ // released too.
+ mService->UnregisterActor(this, mOriginChannelKey);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/broadcastchannel/BroadcastChannelParent.h b/dom/broadcastchannel/BroadcastChannelParent.h
new file mode 100644
index 0000000000..fdca75d213
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelParent.h
@@ -0,0 +1,46 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_BroadcastChannelParent_h
+#define mozilla_dom_BroadcastChannelParent_h
+
+#include "mozilla/dom/PBroadcastChannelParent.h"
+
+namespace mozilla {
+
+namespace ipc {
+class BackgroundParentImpl;
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class BroadcastChannelService;
+
+class BroadcastChannelParent final : public PBroadcastChannelParent {
+ friend class mozilla::ipc::BackgroundParentImpl;
+
+ using PrincipalInfo = mozilla::ipc::PrincipalInfo;
+
+ private:
+ explicit BroadcastChannelParent(const nsAString& aOriginChannelKey);
+ ~BroadcastChannelParent();
+
+ virtual mozilla::ipc::IPCResult RecvPostMessage(
+ const MessageData& aData) override;
+
+ virtual mozilla::ipc::IPCResult RecvClose() override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ RefPtr<BroadcastChannelService> mService;
+ const nsString mOriginChannelKey;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BroadcastChannelParent_h
diff --git a/dom/broadcastchannel/BroadcastChannelService.cpp b/dom/broadcastchannel/BroadcastChannelService.cpp
new file mode 100644
index 0000000000..c3dab2b28a
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelService.cpp
@@ -0,0 +1,178 @@
+/* -*- 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 "BroadcastChannelService.h"
+#include "BroadcastChannelParent.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/IPCBlobUtils.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+BroadcastChannelService* sInstance = nullptr;
+
+ClonedMessageData CloneClonedMessageData(const ClonedMessageData& aOther) {
+ auto cloneData = SerializedStructuredCloneBuffer{};
+ cloneData.data.initScope(aOther.data().data.scope());
+ const bool res = cloneData.data.Append(aOther.data().data);
+ MOZ_RELEASE_ASSERT(res, "out of memory");
+ return {std::move(cloneData), aOther.blobs(), aOther.inputStreams(),
+ aOther.identifiers()};
+}
+
+MessageData CloneMessageData(const MessageData& aOther) {
+ switch (aOther.data().type()) {
+ case MessageDataType::TClonedMessageData:
+ return {aOther.agentClusterId(),
+ CloneClonedMessageData(aOther.data().get_ClonedMessageData())};
+ case MessageDataType::TRefMessageData:
+ return {aOther.agentClusterId(), aOther.data().get_RefMessageData()};
+ default:
+ MOZ_CRASH("Unexpected MessageDataType type");
+ }
+}
+
+} // namespace
+
+BroadcastChannelService::BroadcastChannelService() {
+ AssertIsOnBackgroundThread();
+
+ // sInstance is a raw BroadcastChannelService*.
+ MOZ_ASSERT(!sInstance);
+ sInstance = this;
+}
+
+BroadcastChannelService::~BroadcastChannelService() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(sInstance == this);
+ MOZ_ASSERT(mAgents.Count() == 0);
+
+ sInstance = nullptr;
+}
+
+// static
+already_AddRefed<BroadcastChannelService>
+BroadcastChannelService::GetOrCreate() {
+ AssertIsOnBackgroundThread();
+
+ RefPtr<BroadcastChannelService> instance = sInstance;
+ if (!instance) {
+ instance = new BroadcastChannelService();
+ }
+ return instance.forget();
+}
+
+void BroadcastChannelService::RegisterActor(
+ BroadcastChannelParent* aParent, const nsAString& aOriginChannelKey) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+
+ auto* const parents = mAgents.GetOrInsertNew(aOriginChannelKey);
+
+ MOZ_ASSERT(!parents->Contains(aParent));
+ parents->AppendElement(aParent);
+}
+
+void BroadcastChannelService::UnregisterActor(
+ BroadcastChannelParent* aParent, const nsAString& aOriginChannelKey) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+
+ if (auto entry = mAgents.Lookup(aOriginChannelKey)) {
+ entry.Data()->RemoveElement(aParent);
+ // remove the entry if the array is now empty
+ if (entry.Data()->IsEmpty()) {
+ entry.Remove();
+ }
+ } else {
+ MOZ_CRASH("Invalid state");
+ }
+}
+
+void BroadcastChannelService::PostMessage(BroadcastChannelParent* aParent,
+ const MessageData& aData,
+ const nsAString& aOriginChannelKey) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+
+ nsTArray<BroadcastChannelParent*>* parents;
+ if (!mAgents.Get(aOriginChannelKey, &parents)) {
+ MOZ_CRASH("Invalid state");
+ }
+
+ // We need to keep the array alive for the life-time of this operation.
+ nsTArray<RefPtr<BlobImpl>> blobImpls;
+ if (aData.data().type() == MessageDataType::TClonedMessageData) {
+ const nsTArray<IPCBlob>& blobs =
+ aData.data().get_ClonedMessageData().blobs();
+ if (!blobs.IsEmpty()) {
+ blobImpls.SetCapacity(blobs.Length());
+
+ for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) {
+ RefPtr<BlobImpl> impl = IPCBlobUtils::Deserialize(blobs[i]);
+
+ MOZ_ASSERT(impl);
+ blobImpls.AppendElement(impl);
+ }
+ }
+ }
+
+ uint32_t selectedActorsOnSamePid = 0;
+
+ // For each parent actor, we notify the message.
+ for (uint32_t i = 0; i < parents->Length(); ++i) {
+ BroadcastChannelParent* parent = parents->ElementAt(i);
+ MOZ_ASSERT(parent);
+
+ if (parent == aParent) {
+ continue;
+ }
+
+ if (parent->OtherPid() == aParent->OtherPid()) {
+ ++selectedActorsOnSamePid;
+ }
+
+ // We need to have a copy of the data for this parent.
+ MessageData newData = CloneMessageData(aData);
+ MOZ_ASSERT(newData.data().type() == aData.data().type());
+
+ if (!blobImpls.IsEmpty()) {
+ nsTArray<IPCBlob>& newBlobImpls =
+ newData.data().get_ClonedMessageData().blobs();
+ MOZ_ASSERT(blobImpls.Length() == newBlobImpls.Length());
+
+ // Serialize Blob objects for this message.
+ for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) {
+ nsresult rv = IPCBlobUtils::Serialize(blobImpls[i], newBlobImpls[i]);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ }
+ }
+
+ Unused << parent->SendNotify(newData);
+ }
+
+ // If this is a refMessageData, we need to know when it can be released.
+ if (aData.data().type() == MessageDataType::TRefMessageData) {
+ Unused << aParent->SendRefMessageDelivered(
+ aData.data().get_RefMessageData().uuid(), selectedActorsOnSamePid);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/broadcastchannel/BroadcastChannelService.h b/dom/broadcastchannel/BroadcastChannelService.h
new file mode 100644
index 0000000000..5ec525d799
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelService.h
@@ -0,0 +1,47 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_BroadcastChannelService_h
+#define mozilla_dom_BroadcastChannelService_h
+
+#include "nsISupportsImpl.h"
+#include "nsHashKeys.h"
+#include "nsClassHashtable.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+namespace mozilla::dom {
+
+class BroadcastChannelParent;
+class MessageData;
+
+class BroadcastChannelService final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(BroadcastChannelService)
+
+ static already_AddRefed<BroadcastChannelService> GetOrCreate();
+
+ void RegisterActor(BroadcastChannelParent* aParent,
+ const nsAString& aOriginChannelKey);
+ void UnregisterActor(BroadcastChannelParent* aParent,
+ const nsAString& aOriginChannelKey);
+
+ void PostMessage(BroadcastChannelParent* aParent, const MessageData& aData,
+ const nsAString& aOriginChannelKey);
+
+ private:
+ BroadcastChannelService();
+ ~BroadcastChannelService();
+
+ // Raw Pointers because the actors keep alive this service.
+ nsClassHashtable<nsStringHashKey, nsTArray<BroadcastChannelParent*>> mAgents;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_BroadcastChannelService_h
diff --git a/dom/broadcastchannel/PBroadcastChannel.ipdl b/dom/broadcastchannel/PBroadcastChannel.ipdl
new file mode 100644
index 0000000000..6ff27b45d8
--- /dev/null
+++ b/dom/broadcastchannel/PBroadcastChannel.ipdl
@@ -0,0 +1,36 @@
+/* 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 protocol PBackground;
+
+include DOMTypes;
+
+using struct nsID from "nsID.h";
+
+namespace mozilla {
+namespace dom {
+
+// This protocol is used for the BroadcastChannel API
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PBroadcastChannel
+{
+ manager PBackground;
+
+parent:
+ async PostMessage(MessageData message);
+ async Close();
+
+child:
+ // A message must be delivered.
+ async Notify(MessageData message);
+
+ // A message has been delivered to other channels. It can be removed after
+ // all the actorsOnSamePid have retrieved it.
+ async RefMessageDelivered(nsID messageID, uint32_t actorsOnSamePid);
+
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/broadcastchannel/moz.build b/dom/broadcastchannel/moz.build
new file mode 100644
index 0000000000..573686bf30
--- /dev/null
+++ b/dom/broadcastchannel/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: postMessage")
+
+EXPORTS.mozilla.dom += [
+ "BroadcastChannel.h",
+]
+
+UNIFIED_SOURCES += [
+ "BroadcastChannel.cpp",
+ "BroadcastChannelChild.cpp",
+ "BroadcastChannelParent.cpp",
+ "BroadcastChannelService.cpp",
+]
+
+IPDL_SOURCES += [
+ "PBroadcastChannel.ipdl",
+]
+
+MOCHITEST_MANIFESTS += ["tests/mochitest.toml"]
+BROWSER_CHROME_MANIFESTS += ["tests/browser.toml"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/broadcastchannel/tests/blank.html b/dom/broadcastchannel/tests/blank.html
new file mode 100644
index 0000000000..358db717dd
--- /dev/null
+++ b/dom/broadcastchannel/tests/blank.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<html><body></body></html>
diff --git a/dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js b/dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js
new file mode 100644
index 0000000000..17fb806371
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js
@@ -0,0 +1,17 @@
+/* eslint-env worker */
+
+onconnect = function (evt) {
+ evt.ports[0].onmessage = function (evt1) {
+ var bc = new BroadcastChannel("foobar");
+ bc.addEventListener("message", function (event) {
+ bc.postMessage(
+ event.data == "hello world from the window"
+ ? "hello world from the worker"
+ : "KO"
+ );
+ bc.close();
+ });
+
+ evt1.target.postMessage("READY");
+ };
+};
diff --git a/dom/broadcastchannel/tests/broadcastchannel_worker_alive.js b/dom/broadcastchannel/tests/broadcastchannel_worker_alive.js
new file mode 100644
index 0000000000..da608c6d20
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_worker_alive.js
@@ -0,0 +1,7 @@
+new BroadcastChannel("foobar").addEventListener("message", function (event) {
+ if (event.data != "READY") {
+ event.target.postMessage(event.data);
+ }
+});
+
+new BroadcastChannel("foobar").postMessage("READY");
diff --git a/dom/broadcastchannel/tests/browser.toml b/dom/broadcastchannel/tests/browser.toml
new file mode 100644
index 0000000000..4162a34a68
--- /dev/null
+++ b/dom/broadcastchannel/tests/browser.toml
@@ -0,0 +1,5 @@
+[DEFAULT]
+skip-if = ["os == 'android'"]
+support-files = ["blank.html"]
+
+["browser_private_browsing.js"]
diff --git a/dom/broadcastchannel/tests/browser_private_browsing.js b/dom/broadcastchannel/tests/browser_private_browsing.js
new file mode 100644
index 0000000000..e61e8ae58e
--- /dev/null
+++ b/dom/broadcastchannel/tests/browser_private_browsing.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL =
+ "http://mochi.test:8888/browser/dom/broadcastchannel/tests/blank.html";
+
+add_task(async function () {
+ var win1 = OpenBrowserWindow({ private: true });
+ var win1Promise = new win1.Promise(resolve => {
+ win1.addEventListener(
+ "load",
+ function () {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+ await win1Promise;
+
+ var win2 = OpenBrowserWindow({ private: false });
+ var win2Promise = new win2.Promise(resolve => {
+ win2.addEventListener(
+ "load",
+ function () {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+ await win2Promise;
+
+ var tab1 = BrowserTestUtils.addTab(win1.gBrowser, URL);
+ await BrowserTestUtils.browserLoaded(win1.gBrowser.getBrowserForTab(tab1));
+ var browser1 = gBrowser.getBrowserForTab(tab1);
+
+ var tab2 = BrowserTestUtils.addTab(win2.gBrowser, URL);
+ await BrowserTestUtils.browserLoaded(win2.gBrowser.getBrowserForTab(tab2));
+ var browser2 = gBrowser.getBrowserForTab(tab2);
+
+ var p1 = SpecialPowers.spawn(browser1, [], function (opts) {
+ return new content.window.Promise(resolve => {
+ content.window.bc = new content.window.BroadcastChannel("foobar");
+ content.window.bc.onmessage = function (e) {
+ resolve(e.data);
+ };
+ });
+ });
+
+ var p2 = SpecialPowers.spawn(browser2, [], function (opts) {
+ return new content.window.Promise(resolve => {
+ content.window.bc = new content.window.BroadcastChannel("foobar");
+ content.window.bc.onmessage = function (e) {
+ resolve(e.data);
+ };
+ });
+ });
+
+ await SpecialPowers.spawn(browser1, [], function (opts) {
+ return new content.window.Promise(resolve => {
+ var bc = new content.window.BroadcastChannel("foobar");
+ bc.postMessage("hello world from private browsing");
+ resolve();
+ });
+ });
+
+ await SpecialPowers.spawn(browser2, [], function (opts) {
+ return new content.window.Promise(resolve => {
+ var bc = new content.window.BroadcastChannel("foobar");
+ bc.postMessage("hello world from non private browsing");
+ resolve();
+ });
+ });
+
+ var what1 = await p1;
+ is(
+ what1,
+ "hello world from private browsing",
+ "No messages received from the other window."
+ );
+
+ var what2 = await p2;
+ is(
+ what2,
+ "hello world from non private browsing",
+ "No messages received from the other window."
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+ await BrowserTestUtils.closeWindow(win1);
+
+ BrowserTestUtils.removeTab(tab2);
+ await BrowserTestUtils.closeWindow(win2);
+});
diff --git a/dom/broadcastchannel/tests/file_mozbrowser.html b/dom/broadcastchannel/tests/file_mozbrowser.html
new file mode 100644
index 0000000000..6370500d98
--- /dev/null
+++ b/dom/broadcastchannel/tests/file_mozbrowser.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>MozBrowser iframe</title>
+</head>
+<body>
+<div id="container"></div>
+ <script type="application/javascript">
+
+ var ifr = document.createElement("iframe");
+ ifr.src = "http://mochi.test:8888/tests/dom/broadcastchannel/tests/iframe_mozbrowser.html";
+ ifr.onload = function() { alert("DONE"); };
+
+ var domParent = document.getElementById("container");
+ domParent.appendChild(ifr);
+
+ </script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/file_mozbrowser2.html b/dom/broadcastchannel/tests/file_mozbrowser2.html
new file mode 100644
index 0000000000..2e5f394eb8
--- /dev/null
+++ b/dom/broadcastchannel/tests/file_mozbrowser2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>MozBrowser iframe</title>
+</head>
+<body>
+<div id="container"></div>
+ <script type="application/javascript">
+
+ var ifr = document.createElement("iframe");
+ ifr.setAttribute("mozbrowser", true);
+ ifr.src = "http://mochi.test:8888/tests/dom/broadcastchannel/tests/iframe_mozbrowser2.html";
+ ifr.onload = function() { alert("DONE"); };
+
+ var domParent = document.getElementById("container");
+ domParent.appendChild(ifr);
+
+ </script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/iframe_broadcastchannel.html b/dom/broadcastchannel/tests/iframe_broadcastchannel.html
new file mode 100644
index 0000000000..2d1724da0b
--- /dev/null
+++ b/dom/broadcastchannel/tests/iframe_broadcastchannel.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script type="application/javascript">
+
+function is(a, b, msg) {
+ ok(a == b, msg);
+}
+
+function ok(a, msg) {
+ window.parent.postMessage({ status: a ? "OK" : "KO", message: msg }, "*");
+}
+
+ok("BroadcastChannel" in window, "BroadcastChannel exists");
+
+var bc = new BroadcastChannel("foobar");
+ok(bc, "BroadcastChannel can be created");
+is(bc.name, "foobar", "BroadcastChannel.name is foobar");
+
+ok("postMessage" in bc, "BroadcastChannel has postMessage() method");
+
+bc.onmessage = function(evt) {
+ ok(evt instanceof MessageEvent, "evt is a MessageEvent");
+ is(evt.target, bc, "MessageEvent.target is bc");
+ is(evt.target.name, "foobar", "MessageEvent.target.name is foobar");
+ is(evt.target.name, bc.name, "MessageEvent.target.name is bc.name");
+ is(evt.data, "Hello world from the window!", "Message received from the window");
+ bc.postMessage("Hello world from the iframe!");
+};
+
+ </script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/iframe_mozbrowser.html b/dom/broadcastchannel/tests/iframe_mozbrowser.html
new file mode 100644
index 0000000000..adbb77a061
--- /dev/null
+++ b/dom/broadcastchannel/tests/iframe_mozbrowser.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>MozBrowser iframe</title>
+</head>
+<body>
+ <script type="application/javascript">
+
+var bc = new BroadcastChannel("foobar");
+bc.postMessage("This is wrong!");
+
+ </script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/iframe_mozbrowser2.html b/dom/broadcastchannel/tests/iframe_mozbrowser2.html
new file mode 100644
index 0000000000..adbb77a061
--- /dev/null
+++ b/dom/broadcastchannel/tests/iframe_mozbrowser2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>MozBrowser iframe</title>
+</head>
+<body>
+ <script type="application/javascript">
+
+var bc = new BroadcastChannel("foobar");
+bc.postMessage("This is wrong!");
+
+ </script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/mochitest.toml b/dom/broadcastchannel/tests/mochitest.toml
new file mode 100644
index 0000000000..40264f7a3d
--- /dev/null
+++ b/dom/broadcastchannel/tests/mochitest.toml
@@ -0,0 +1,41 @@
+[DEFAULT]
+support-files = [
+ "iframe_broadcastchannel.html",
+ "broadcastchannel_sharedWorker.js",
+ "broadcastchannel_worker_alive.js",
+ "!/dom/events/test/event_leak_utils.js",
+ "file_mozbrowser.html",
+ "file_mozbrowser2.html",
+ "iframe_mozbrowser.html",
+ "iframe_mozbrowser2.html",
+ "testUrl1_bfcache.html",
+ "testUrl2_bfcache.html",
+]
+
+["test_bfcache.html"]
+
+["test_broadcastchannel_basic.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_broadcastchannel_close.html"]
+
+["test_broadcastchannel_self.html"]
+
+["test_broadcastchannel_sharedWorker.html"]
+
+["test_broadcastchannel_worker_alive.html"]
+
+["test_dataCloning.html"]
+
+["test_dataURL.html"]
+
+["test_event_listener_leaks.html"]
+
+["test_invalidState.html"]
+
+["test_message_after_close.html"]
+
+["test_ordering.html"]
diff --git a/dom/broadcastchannel/tests/testUrl1_bfcache.html b/dom/broadcastchannel/tests/testUrl1_bfcache.html
new file mode 100644
index 0000000000..d31b13bd25
--- /dev/null
+++ b/dom/broadcastchannel/tests/testUrl1_bfcache.html
@@ -0,0 +1,18 @@
+<script>
+var bc = new BroadcastChannel("a");
+onpageshow = function(e) {
+ var bc1 = new BroadcastChannel("testUrl1_bfcache");
+ bc1.onmessage = function(event) {
+ if (event.data == "close") {
+ bc1.postMessage("closed");
+ bc1.close();
+ bc.close();
+ window.close();
+ } else if (event.data == "load") {
+ bc1.close();
+ location.href = "testUrl2_bfcache.html";
+ }
+ };
+ bc1.postMessage({type: e.type, persisted: e.persisted});
+};
+</script>
diff --git a/dom/broadcastchannel/tests/testUrl2_bfcache.html b/dom/broadcastchannel/tests/testUrl2_bfcache.html
new file mode 100644
index 0000000000..a1eed7927a
--- /dev/null
+++ b/dom/broadcastchannel/tests/testUrl2_bfcache.html
@@ -0,0 +1,12 @@
+<script>
+onpageshow = function(e) {
+ var bc2 = new BroadcastChannel("testUrl2_bfcache");
+ bc2.onmessage = function(event) {
+ if (event.data == "back") {
+ bc2.close();
+ history.back();
+ }
+ };
+ bc2.postMessage({type: e.type, persisted: e.persisted});
+};
+</script>
diff --git a/dom/broadcastchannel/tests/test_bfcache.html b/dom/broadcastchannel/tests/test_bfcache.html
new file mode 100644
index 0000000000..0197f343e2
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_bfcache.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for bfcache and BroadcastChannel</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+ /*
+ * The test opens a new window. Then a message 'load' is sent there to load
+ * another page. If expectedPersisted is false, a dummy message is sent
+ * through a BroadcastChannel which the first has registered to use.
+ * That should evict the page from bfcache.
+ * 'back' message is sent to call history.back().
+ * The page which is loaded from session history should be persisted if
+ * expectedPersisted is true.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+ var testUrl1 = "testUrl1_bfcache.html";
+
+
+ function executeTest() {
+ var bc1 = new BroadcastChannel("testUrl1_bfcache");
+ var bc2 = new BroadcastChannel("testUrl2_bfcache");
+ bc1.onmessage = function(event) {
+ if (event.data == "closed") {
+ info("Closed");
+ runTest();
+ return;
+ }
+ page1Shown(event.data);
+ };
+ bc2.onmessage = function(event) { page2Shown(event.data); };
+
+ var counter = 0;
+ var expectedPersisted = false;
+ var bc = new BroadcastChannel("a");
+
+ function page1Shown(e) {
+ if (counter == 0) {
+ ok(!e.persisted, "test page should have been persisted initially");
+ bc1.postMessage("load");
+ } else {
+ is(e.persisted, expectedPersisted, "test page should have been persisted in pageshow");
+ bc1.postMessage("close");
+ }
+
+ counter++;
+ }
+
+ function page2Shown(e) {
+ if (!expectedPersisted) {
+ SimpleTest.executeSoon(function() {
+ info("Posting a message.");
+ bc.postMessage(42);
+ });
+ }
+
+ SimpleTest.executeSoon(function() {
+ info("Going back");
+ bc2.postMessage("back");
+ });
+ }
+
+ var tests = [
+ { expectedPersisted: true },
+ { expectedPersisted: false },
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ bc.close();
+ bc1.close();
+ bc2.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+
+ counter = 0;
+ expectedPersisted = test.expectedPersisted;
+ window.open(testUrl1, "", "noopener");
+ }
+
+
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ runTest();
+ });
+
+ }
+
+ 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();
+ }
+
+ </script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_basic.html b/dom/broadcastchannel/tests/test_broadcastchannel_basic.html
new file mode 100644
index 0000000000..09196b0c52
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_basic.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+function runTest() {
+ addEventListener("message", receiveMessage, false);
+ function receiveMessage(evt) {
+ if (evt.data.status == "OK") {
+ ok(true, evt.data.message);
+ } else if (evt.data.status == "KO") {
+ ok(false, evt.data.message);
+ } else {
+ ok(false, "Unknown message");
+ }
+ }
+
+ ok("BroadcastChannel" in window, "BroadcastChannel exists");
+
+ var bc = new BroadcastChannel("foobar");
+ ok(bc, "BroadcastChannel can be created");
+ is(bc.name, "foobar", "BroadcastChannel.name is foobar");
+
+ ok("postMessage" in bc, "BroadcastChannel has postMessage() method");
+
+ bc.onmessage = function(evt) {
+ ok(evt instanceof MessageEvent, "This is a MessageEvent");
+ is(evt.target, bc, "MessageEvent.target is bc");
+ is(evt.target.name, "foobar", "MessageEvent.target.name is foobar");
+ is(evt.target.name, bc.name, "MessageEvent.target.name == bc.name");
+ ok(evt.origin.indexOf("http://mochi.test:8888") == 0, "MessageEvent.origin is correct");
+ is(evt.data, "Hello world from the iframe!", "The message from the iframe has been received!");
+ SimpleTest.finish();
+ };
+
+ var div = document.getElementById("content");
+ ok(div, "Parent exists");
+
+ var ifr = document.createElement("iframe");
+ ifr.addEventListener("load", iframeLoaded);
+ ifr.setAttribute("src", "iframe_broadcastchannel.html");
+ div.appendChild(ifr);
+
+ function iframeLoaded() {
+ bc.postMessage("Hello world from the window!");
+ }
+
+ // A leak test
+ var dummyBc = new BroadcastChannel("dont_leak_this");
+ dummyBc.foo = "bar";
+ // don't add message listener!
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_close.html b/dom/broadcastchannel/tests/test_broadcastchannel_close.html
new file mode 100644
index 0000000000..91cb9c92a4
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_close.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+function runTest() {
+ var receiver = new BroadcastChannel("foo");
+ var sequence = [ "2", "done" ];
+ receiver.onmessage = function(e) {
+ if (!sequence.length) {
+ ok(false, "No more data is expected");
+ return;
+ }
+
+ var data = sequence.shift();
+ is(e.data, data);
+
+ if (!sequence.length) {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.finish();
+ });
+ }
+ };
+
+ var x = new BroadcastChannel("foo");
+ x.close();
+ try {
+ x.postMessage("1");
+ ok(false, "PostMessage should throw if called after a close().");
+ } catch (e) {
+ ok(true, "PostMessage should throw if called after a close().");
+ }
+
+ var y = new BroadcastChannel("foo");
+ y.postMessage("2");
+ y.close();
+ try {
+ y.postMessage("3");
+ ok(false, "PostMessage should throw if called after a close().");
+ } catch (e) {
+ ok(true, "PostMessage should throw if called after a close().");
+ }
+
+ var z = new BroadcastChannel("foo");
+ z.postMessage("done");
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_self.html b/dom/broadcastchannel/tests/test_broadcastchannel_self.html
new file mode 100644
index 0000000000..501f71f09c
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_self.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+function runTest() {
+ let x = new BroadcastChannel("foo");
+ let y = new BroadcastChannel("foo");
+
+ function func(e) {
+ is(e.target, y, "The target is !x");
+
+ SimpleTest.executeSoon(function() {
+ SimpleTest.finish();
+ });
+ }
+
+ x.onmessage = func;
+ y.onmessage = func;
+
+ x.postMessage("foo");
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html b/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html
new file mode 100644
index 0000000000..f76c17cbd8
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html
@@ -0,0 +1,52 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM BroadcastChannel in SharedWorkers
+-->
+<head>
+ <title>Test for BroadcastChannel in SharedWorkers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+function runTests() {
+ var worker = new SharedWorker("broadcastchannel_sharedWorker.js");
+
+ var bc = new BroadcastChannel("foobar");
+
+ worker.port.onmessage = function(event) {
+ if (event.data == "READY") {
+ ok(true, "SharedWorker is ready!");
+ bc.postMessage("hello world from the window");
+ } else {
+ ok(false, "Something wrong happened");
+ }
+ };
+
+ bc.onmessage = function(event) {
+ is("hello world from the worker", event.data, "The message matches!");
+ bc.close();
+ SimpleTest.finish();
+ };
+
+ worker.port.postMessage("go");
+}
+
+SimpleTest.waitForExplicitFinish();
+runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_worker_alive.html b/dom/broadcastchannel/tests/test_broadcastchannel_worker_alive.html
new file mode 100644
index 0000000000..3667c8ae9d
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_worker_alive.html
@@ -0,0 +1,56 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM BroadcastChannel in workers
+-->
+<head>
+ <title>Test for BroadcastChannel in workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+function runTests() {
+ var id = 0;
+ (new BroadcastChannel("foobar")).onmessage = function(event) {
+ info("MSG: " + event.data);
+
+ if (event.data == "READY") {
+ ok(true, "Worker is ready!");
+ } else {
+ is(id, event.data, "The message is correct: " + id);
+ }
+
+ for (var i = 0; i < 3; ++i) {
+ SpecialPowers.forceCC();
+ SpecialPowers.forceGC();
+ }
+
+ if (id == 5) {
+ SimpleTest.finish();
+ return;
+ }
+
+ event.target.postMessage(++id);
+ };
+
+ new Worker("broadcastchannel_worker_alive.js");
+}
+
+SimpleTest.waitForExplicitFinish();
+runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_dataCloning.html b/dom/broadcastchannel/tests/test_dataCloning.html
new file mode 100644
index 0000000000..26c1ce370a
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_dataCloning.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel.postMessage invalid State</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+
+let c = new BroadcastChannel("foo");
+
+try {
+ c.postMessage(Symbol());
+ ok(false, "This should throw!");
+} catch (e) {
+ ok(true, "This should throw!");
+ is(e.name, "DataCloneError", "Correct DataCloneError exception thrown");
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_dataURL.html b/dom/broadcastchannel/tests/test_dataURL.html
new file mode 100644
index 0000000000..f4f9c71db3
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_dataURL.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel in data: URL</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="dataURL">
+var a = new BroadcastChannel('a');
+var b = new BroadcastChannel('a');
+a.onmessage = function(e) { parent.postMessage(e.data, "*"); };
+b.postMessage(42);
+</div>
+
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+onmessage = function(e) {
+ is(e.data, 42, "BroadcastChannel works with data URLs");
+ SimpleTest.finish();
+};
+
+// eslint-disable-next-line no-useless-concat
+var url = "data:text/html,<script>" + document.getElementById("dataURL").textContent + "</" + "script>";
+
+var ifr = document.createElement("iframe");
+document.body.appendChild(ifr);
+
+ifr.setAttribute("sandbox", "allow-scripts");
+ifr.src = url;
+</script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_event_listener_leaks.html b/dom/broadcastchannel/tests/test_event_listener_leaks.html
new file mode 100644
index 0000000000..33568913fc
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_event_listener_leaks.html
@@ -0,0 +1,55 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1450358 - Test BroadcastChannel event listener leak conditions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+// Manipulate BroadcastChannel objects in the frame's context.
+// Its important here that we create a listener callback from
+// the DOM objects back to the frame's global in order to
+// exercise the leak condition.
+let count = 0;
+async function useBroadcastChannel(contentWindow) {
+ contentWindow.messageCount = 0;
+
+ count += 1;
+ const name = `test_event_listener_leaks-${count}`;
+
+ let bc = new contentWindow.BroadcastChannel(name);
+ let outer = new BroadcastChannel(name);
+ outer.postMessage("foo");
+
+ await new Promise(resolve => {
+ bc.onmessage = e => {
+ contentWindow.messageCount += 1;
+ resolve();
+ };
+ });
+
+ is(contentWindow.messageCount, 1, "message should be received");
+}
+
+async function runTest() {
+ try {
+ await checkForEventListenerLeaks("BroadcastChannel", useBroadcastChannel);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", runTest, { once: true });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_invalidState.html b/dom/broadcastchannel/tests/test_invalidState.html
new file mode 100644
index 0000000000..371a58768a
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_invalidState.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel.postMessage invalid State</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+var c = new BroadcastChannel("foo");
+c.close();
+
+try {
+ c.postMessage("bar");
+ ok(false, "This should throw!");
+} catch (e) {
+ ok(true, "This should throw!");
+ is(e.name, "InvalidStateError", "Correct invalid-state exception thrown");
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_message_after_close.html b/dom/broadcastchannel/tests/test_message_after_close.html
new file mode 100644
index 0000000000..1ef8a018ea
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_message_after_close.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel - message after close</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+let a = new BroadcastChannel('a');
+let b = new BroadcastChannel('a');
+let count = 0;
+
+a.onmessage = function(e) {
+ ++count;
+ a.close();
+
+ setTimeout(() => {
+ is(count, 1, "Only 1 message received");
+ SimpleTest.finish();
+ }, 0);
+}
+
+for (let i = 0; i < 100; ++i) {
+ b.postMessage(42);
+}
+</script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_ordering.html b/dom/broadcastchannel/tests/test_ordering.html
new file mode 100644
index 0000000000..5dd36bc77d
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_ordering.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel.postMessage invalid State</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="content"></div>
+
+<script type="application/javascript">
+
+let c1 = new BroadcastChannel("order");
+let c2 = new BroadcastChannel("order");
+let c3 = new BroadcastChannel("order");
+
+let events = [];
+let doneCount = 0;
+
+function whichBC(bc) {
+ if (bc == c1) return "c1";
+ if (bc == c2) return "c2";
+ if (bc == c3) return "c3";
+ return "What?!?";
+}
+
+function handler(e) {
+ events.push(e);
+ if (e.data == "done") {
+ doneCount++;
+ if (doneCount == 2) {
+ is(events.length, 6, "Correct length");
+ is(whichBC(events[0].target), "c2", "target for event 0");
+ is(events[0].data, "from c1");
+ is(whichBC(events[1].target), "c3", "target for event 1");
+ is(events[1].data, "from c1");
+ is(whichBC(events[2].target), "c1", "target for event 2");
+ is(events[2].data, "from c3");
+ is(whichBC(events[3].target), "c2", "target for event 3");
+ is(events[3].data, "from c3");
+ is(whichBC(events[4].target), "c1", "target for event 4");
+ is(events[4].data, "done");
+ is(whichBC(events[5].target), "c3", "target for event 5");
+ is(events[5].data, "done");
+
+ SimpleTest.finish();
+ }
+ }
+}
+
+c1.onmessage = handler;
+c2.onmessage = handler;
+c3.onmessage = handler;
+
+c1.postMessage("from c1");
+c3.postMessage("from c3");
+c2.postMessage("done");
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>