summaryrefslogtreecommitdiff
path: root/dom/broadcastchannel
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/broadcastchannel
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/broadcastchannel')
-rw-r--r--dom/broadcastchannel/BroadcastChannel.cpp685
-rw-r--r--dom/broadcastchannel/BroadcastChannel.h134
-rw-r--r--dom/broadcastchannel/BroadcastChannelChild.cpp135
-rw-r--r--dom/broadcastchannel/BroadcastChannelChild.h59
-rw-r--r--dom/broadcastchannel/BroadcastChannelParent.cpp102
-rw-r--r--dom/broadcastchannel/BroadcastChannelParent.h50
-rw-r--r--dom/broadcastchannel/BroadcastChannelService.cpp132
-rw-r--r--dom/broadcastchannel/BroadcastChannelService.h52
-rw-r--r--dom/broadcastchannel/PBroadcastChannel.ipdl29
-rw-r--r--dom/broadcastchannel/moz.build31
-rw-r--r--dom/broadcastchannel/tests/blank.html2
-rw-r--r--dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js12
-rw-r--r--dom/broadcastchannel/tests/broadcastchannel_worker.js18
-rw-r--r--dom/broadcastchannel/tests/broadcastchannel_worker_alive.js8
-rw-r--r--dom/broadcastchannel/tests/broadcastchannel_worker_any.js5
-rw-r--r--dom/broadcastchannel/tests/browser.ini6
-rw-r--r--dom/broadcastchannel/tests/browser_private_browsing.js74
-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.html35
-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.ini24
-rw-r--r--dom/broadcastchannel/tests/test_bfcache.html96
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_any.html138
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_basic.html68
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_close.html61
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_close2.html39
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_self.html38
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html52
-rw-r--r--dom/broadcastchannel/tests/test_broadcastchannel_worker.html62
-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_invalidState.html28
-rw-r--r--dom/broadcastchannel/tests/test_ordering.html65
35 files changed, 2394 insertions, 0 deletions
diff --git a/dom/broadcastchannel/BroadcastChannel.cpp b/dom/broadcastchannel/BroadcastChannel.cpp
new file mode 100644
index 0000000000..c3c2d448b1
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -0,0 +1,685 @@
+/* -*- 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/StructuredCloneHolder.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "nsContentUtils.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+
+#include "nsIBFCacheEntry.h"
+#include "nsIDocument.h"
+#include "nsISupportsPrimitives.h"
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+using namespace workers;
+
+class BroadcastChannelMessage final : public StructuredCloneHolder
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(BroadcastChannelMessage)
+
+ BroadcastChannelMessage()
+ : StructuredCloneHolder(CloningSupported, TransferringNotSupported,
+ StructuredCloneScope::DifferentProcess)
+ {}
+
+private:
+ ~BroadcastChannelMessage()
+ {}
+};
+
+namespace {
+
+nsIPrincipal*
+GetPrincipalFromWorkerPrivate(WorkerPrivate* aWorkerPrivate)
+{
+ nsIPrincipal* principal = aWorkerPrivate->GetPrincipal();
+ if (principal) {
+ return principal;
+ }
+
+ // Walk up to our containing page
+ WorkerPrivate* wp = aWorkerPrivate;
+ while (wp->GetParent()) {
+ wp = wp->GetParent();
+ }
+
+ return wp->GetPrincipal();
+}
+
+class InitializeRunnable final : public WorkerMainThreadRunnable
+{
+public:
+ InitializeRunnable(WorkerPrivate* aWorkerPrivate, nsACString& aOrigin,
+ PrincipalInfo& aPrincipalInfo, ErrorResult& aRv)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("BroadcastChannel :: Initialize"))
+ , mWorkerPrivate(GetCurrentThreadWorkerPrivate())
+ , mOrigin(aOrigin)
+ , mPrincipalInfo(aPrincipalInfo)
+ , mRv(aRv)
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ }
+
+ bool MainThreadRun() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsIPrincipal* principal = GetPrincipalFromWorkerPrivate(mWorkerPrivate);
+ if (!principal) {
+ mRv.Throw(NS_ERROR_FAILURE);
+ return true;
+ }
+
+ if (NS_WARN_IF(principal->GetIsNullPrincipal())) {
+ mRv.Throw(NS_ERROR_FAILURE);
+ return true;
+ }
+
+ mRv = PrincipalToPrincipalInfo(principal, &mPrincipalInfo);
+ if (NS_WARN_IF(mRv.Failed())) {
+ return true;
+ }
+
+ mRv = principal->GetOrigin(mOrigin);
+ if (NS_WARN_IF(mRv.Failed())) {
+ return true;
+ }
+
+ // Walk up to our containing page
+ WorkerPrivate* wp = mWorkerPrivate;
+ while (wp->GetParent()) {
+ wp = wp->GetParent();
+ }
+
+ // Window doesn't exist for some kind of workers (eg: SharedWorkers)
+ nsPIDOMWindowInner* window = wp->GetWindow();
+ if (!window) {
+ return true;
+ }
+
+ return true;
+ }
+
+private:
+ WorkerPrivate* mWorkerPrivate;
+ nsACString& mOrigin;
+ PrincipalInfo& mPrincipalInfo;
+ ErrorResult& mRv;
+};
+
+class BCPostMessageRunnable final : public nsIRunnable,
+ public nsICancelableRunnable
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ BCPostMessageRunnable(BroadcastChannelChild* aActor,
+ BroadcastChannelMessage* aData)
+ : mActor(aActor)
+ , mData(aData)
+ {
+ MOZ_ASSERT(mActor);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mActor);
+ if (mActor->IsActorDestroyed()) {
+ return NS_OK;
+ }
+
+ ClonedMessageData message;
+
+ bool success;
+ SerializedStructuredCloneBuffer& buffer = message.data();
+ auto iter = mData->BufferData().Iter();
+ buffer.data = mData->BufferData().Borrow<js::SystemAllocPolicy>(iter, mData->BufferData().Size(), &success);
+ if (NS_WARN_IF(!success)) {
+ return NS_OK;
+ }
+
+ PBackgroundChild* backgroundManager = mActor->Manager();
+ MOZ_ASSERT(backgroundManager);
+
+ const nsTArray<RefPtr<BlobImpl>>& blobImpls = mData->BlobImpls();
+
+ if (!blobImpls.IsEmpty()) {
+ message.blobsChild().SetCapacity(blobImpls.Length());
+
+ for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) {
+ PBlobChild* blobChild =
+ BackgroundChild::GetOrCreateActorForBlobImpl(backgroundManager,
+ blobImpls[i]);
+ MOZ_ASSERT(blobChild);
+
+ message.blobsChild().AppendElement(blobChild);
+ }
+ }
+
+ mActor->SendPostMessage(message);
+ return NS_OK;
+ }
+
+ nsresult Cancel() override
+ {
+ mActor = nullptr;
+ return NS_OK;
+ }
+
+private:
+ ~BCPostMessageRunnable() {}
+
+ RefPtr<BroadcastChannelChild> mActor;
+ RefPtr<BroadcastChannelMessage> mData;
+};
+
+NS_IMPL_ISUPPORTS(BCPostMessageRunnable, nsICancelableRunnable, nsIRunnable)
+
+class CloseRunnable final : public nsIRunnable,
+ public nsICancelableRunnable
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit CloseRunnable(BroadcastChannel* aBC)
+ : mBC(aBC)
+ {
+ MOZ_ASSERT(mBC);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mBC->Shutdown();
+ return NS_OK;
+ }
+
+ nsresult Cancel() override
+ {
+ mBC = nullptr;
+ return NS_OK;
+ }
+
+private:
+ ~CloseRunnable() {}
+
+ RefPtr<BroadcastChannel> mBC;
+};
+
+NS_IMPL_ISUPPORTS(CloseRunnable, nsICancelableRunnable, nsIRunnable)
+
+class TeardownRunnable final : public nsIRunnable,
+ public nsICancelableRunnable
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit TeardownRunnable(BroadcastChannelChild* aActor)
+ : mActor(aActor)
+ {
+ MOZ_ASSERT(mActor);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mActor);
+ if (!mActor->IsActorDestroyed()) {
+ mActor->SendClose();
+ }
+ return NS_OK;
+ }
+
+ nsresult Cancel() override
+ {
+ mActor = nullptr;
+ return NS_OK;
+ }
+
+private:
+ ~TeardownRunnable() {}
+
+ RefPtr<BroadcastChannelChild> mActor;
+};
+
+NS_IMPL_ISUPPORTS(TeardownRunnable, nsICancelableRunnable, nsIRunnable)
+
+class BroadcastChannelWorkerHolder final : public workers::WorkerHolder
+{
+ BroadcastChannel* mChannel;
+
+public:
+ explicit BroadcastChannelWorkerHolder(BroadcastChannel* aChannel)
+ : mChannel(aChannel)
+ {
+ MOZ_COUNT_CTOR(BroadcastChannelWorkerHolder);
+ }
+
+ virtual bool Notify(workers::Status aStatus) override
+ {
+ if (aStatus >= Closing) {
+ mChannel->Shutdown();
+ }
+
+ return true;
+ }
+
+private:
+ ~BroadcastChannelWorkerHolder()
+ {
+ MOZ_COUNT_DTOR(BroadcastChannelWorkerHolder);
+ }
+};
+
+} // namespace
+
+BroadcastChannel::BroadcastChannel(nsPIDOMWindowInner* aWindow,
+ const PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOrigin,
+ const nsAString& aChannel)
+ : DOMEventTargetHelper(aWindow)
+ , mWorkerHolder(nullptr)
+ , mPrincipalInfo(new PrincipalInfo(aPrincipalInfo))
+ , mOrigin(aOrigin)
+ , mChannel(aChannel)
+ , mIsKeptAlive(false)
+ , mInnerID(0)
+ , mState(StateActive)
+{
+ // Window can be null in workers
+}
+
+BroadcastChannel::~BroadcastChannel()
+{
+ Shutdown();
+ MOZ_ASSERT(!mWorkerHolder);
+}
+
+JSObject*
+BroadcastChannel::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return BroadcastChannelBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<BroadcastChannel>
+BroadcastChannel::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aChannel,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ // Window is null in workers.
+
+ nsAutoCString origin;
+ PrincipalInfo principalInfo;
+ WorkerPrivate* workerPrivate = nullptr;
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal();
+
+ if (!incumbent) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsIPrincipal* principal = incumbent->PrincipalOrNull();
+ if (!principal) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(principal->GetIsNullPrincipal())) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ aRv = principal->GetOrigin(origin);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ aRv = PrincipalToPrincipalInfo(principal, &principalInfo);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ } else {
+ JSContext* cx = aGlobal.Context();
+ workerPrivate = GetWorkerPrivateFromContext(cx);
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<InitializeRunnable> runnable =
+ new InitializeRunnable(workerPrivate, origin, principalInfo, aRv);
+ runnable->Dispatch(aRv);
+ }
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<BroadcastChannel> bc =
+ new BroadcastChannel(window, principalInfo, origin, aChannel);
+
+ // Register this component to PBackground.
+ PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
+ if (actor) {
+ bc->ActorCreated(actor);
+ } else {
+ BackgroundChild::GetOrCreateForCurrentThread(bc);
+ }
+
+ if (!workerPrivate) {
+ MOZ_ASSERT(window);
+ MOZ_ASSERT(window->IsInnerWindow());
+ bc->mInnerID = window->WindowID();
+
+ // Register as observer for inner-window-destroyed.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(bc, "inner-window-destroyed", false);
+ }
+ } else {
+ bc->mWorkerHolder = new BroadcastChannelWorkerHolder(bc);
+ if (NS_WARN_IF(!bc->mWorkerHolder->HoldWorker(workerPrivate, Closing))) {
+ bc->mWorkerHolder = nullptr;
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ 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;
+ }
+
+ PostMessageInternal(aCx, aMessage, aRv);
+}
+
+void
+BroadcastChannel::PostMessageInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ ErrorResult& aRv)
+{
+ RefPtr<BroadcastChannelMessage> data = new BroadcastChannelMessage();
+
+ data->Write(aCx, aMessage, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ PostMessageData(data);
+}
+
+void
+BroadcastChannel::PostMessageData(BroadcastChannelMessage* aData)
+{
+ RemoveDocFromBFCache();
+
+ if (mActor) {
+ RefPtr<BCPostMessageRunnable> runnable =
+ new BCPostMessageRunnable(mActor, aData);
+
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ NS_WARNING("Failed to dispatch to the current thread!");
+ }
+
+ return;
+ }
+
+ mPendingMessages.AppendElement(aData);
+}
+
+void
+BroadcastChannel::Close()
+{
+ if (mState != StateActive) {
+ return;
+ }
+
+ if (mPendingMessages.IsEmpty()) {
+ // 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!");
+ }
+ } else {
+ MOZ_ASSERT(!mActor);
+ mState = StateClosing;
+ }
+}
+
+void
+BroadcastChannel::ActorFailed()
+{
+ MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+}
+
+void
+BroadcastChannel::ActorCreated(PBackgroundChild* aActor)
+{
+ MOZ_ASSERT(aActor);
+
+ if (mState == StateClosed) {
+ return;
+ }
+
+ PBroadcastChannelChild* actor =
+ aActor->SendPBroadcastChannelConstructor(*mPrincipalInfo, mOrigin, mChannel);
+
+ mActor = static_cast<BroadcastChannelChild*>(actor);
+ MOZ_ASSERT(mActor);
+
+ mActor->SetParent(this);
+
+ // Flush pending messages.
+ for (uint32_t i = 0; i < mPendingMessages.Length(); ++i) {
+ PostMessageData(mPendingMessages[i]);
+ }
+
+ mPendingMessages.Clear();
+
+ if (mState == StateClosing) {
+ Shutdown();
+ }
+}
+
+void
+BroadcastChannel::Shutdown()
+{
+ mState = StateClosed;
+
+ // The DTOR of this WorkerHolder will release the worker for us.
+ mWorkerHolder = nullptr;
+
+ if (mActor) {
+ mActor->SetParent(nullptr);
+
+ RefPtr<TeardownRunnable> runnable = new TeardownRunnable(mActor);
+ NS_DispatchToCurrentThread(runnable);
+
+ mActor = nullptr;
+ }
+
+ // If shutdown() is called we have to release the reference if we still keep
+ // it.
+ if (mIsKeptAlive) {
+ mIsKeptAlive = false;
+ Release();
+ }
+}
+
+EventHandlerNonNull*
+BroadcastChannel::GetOnmessage()
+{
+ if (NS_IsMainThread()) {
+ return GetEventHandler(nsGkAtoms::onmessage, EmptyString());
+ }
+ return GetEventHandler(nullptr, NS_LITERAL_STRING("message"));
+}
+
+void
+BroadcastChannel::SetOnmessage(EventHandlerNonNull* aCallback)
+{
+ if (NS_IsMainThread()) {
+ SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback);
+ } else {
+ SetEventHandler(nullptr, NS_LITERAL_STRING("message"), aCallback);
+ }
+
+ UpdateMustKeepAlive();
+}
+
+void
+BroadcastChannel::AddEventListener(const nsAString& aType,
+ EventListener* aCallback,
+ const AddEventListenerOptionsOrBoolean& aOptions,
+ const dom::Nullable<bool>& aWantsUntrusted,
+ ErrorResult& aRv)
+{
+ DOMEventTargetHelper::AddEventListener(aType, aCallback, aOptions,
+ aWantsUntrusted, aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ UpdateMustKeepAlive();
+}
+
+void
+BroadcastChannel::RemoveEventListener(const nsAString& aType,
+ EventListener* aCallback,
+ const EventListenerOptionsOrBoolean& aOptions,
+ ErrorResult& aRv)
+{
+ DOMEventTargetHelper::RemoveEventListener(aType, aCallback, aOptions, aRv);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ UpdateMustKeepAlive();
+}
+
+void
+BroadcastChannel::UpdateMustKeepAlive()
+{
+ bool toKeepAlive = HasListenersFor(NS_LITERAL_STRING("message"));
+ if (toKeepAlive == mIsKeptAlive) {
+ return;
+ }
+
+ mIsKeptAlive = toKeepAlive;
+
+ if (toKeepAlive) {
+ AddRef();
+ } else {
+ Release();
+ }
+}
+
+NS_IMETHODIMP
+BroadcastChannel::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!strcmp(aTopic, "inner-window-destroyed"));
+
+ // If the window is destroyed we have to release the reference that we are
+ // keeping.
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (innerID == mInnerID) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "inner-window-destroyed");
+ }
+
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+void
+BroadcastChannel::RemoveDocFromBFCache()
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ nsPIDOMWindowInner* window = GetOwner();
+ if (!window) {
+ return;
+ }
+
+ nsIDocument* doc = window->GetExtantDoc();
+ if (!doc) {
+ return;
+ }
+
+ nsCOMPtr<nsIBFCacheEntry> bfCacheEntry = doc->GetBFCacheEntry();
+ if (!bfCacheEntry) {
+ return;
+ }
+
+ bfCacheEntry->RemoveFromBFCacheSync();
+}
+
+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_INHERITED(BroadcastChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+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..d7d46eeecc
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannel.h
@@ -0,0 +1,134 @@
+/* -*- 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 "nsAutoPtr.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+namespace workers {
+class WorkerHolder;
+} // namespace workers
+
+class BroadcastChannelChild;
+class BroadcastChannelMessage;
+
+class BroadcastChannel final
+ : public DOMEventTargetHelper
+ , public nsIIPCBackgroundChildCreateCallback
+ , public nsIObserver
+{
+ friend class BroadcastChannelChild;
+
+ NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+ NS_DECL_NSIOBSERVER
+
+ typedef mozilla::ipc::PrincipalInfo 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();
+
+ EventHandlerNonNull* GetOnmessage();
+ void SetOnmessage(EventHandlerNonNull* aCallback);
+
+ using nsIDOMEventTarget::AddEventListener;
+ using nsIDOMEventTarget::RemoveEventListener;
+
+ virtual void AddEventListener(const nsAString& aType,
+ EventListener* aCallback,
+ const AddEventListenerOptionsOrBoolean& aOptions,
+ const Nullable<bool>& aWantsUntrusted,
+ ErrorResult& aRv) override;
+ virtual void RemoveEventListener(const nsAString& aType,
+ EventListener* aCallback,
+ const EventListenerOptionsOrBoolean& aOptions,
+ ErrorResult& aRv) override;
+
+ void Shutdown();
+
+private:
+ BroadcastChannel(nsPIDOMWindowInner* aWindow,
+ const PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOrigin,
+ const nsAString& aChannel);
+
+ ~BroadcastChannel();
+
+ void PostMessageData(BroadcastChannelMessage* aData);
+
+ void PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ ErrorResult& aRv);
+
+ void UpdateMustKeepAlive();
+
+ bool IsCertainlyAliveForCC() const override
+ {
+ return mIsKeptAlive;
+ }
+
+ void RemoveDocFromBFCache();
+
+ RefPtr<BroadcastChannelChild> mActor;
+ nsTArray<RefPtr<BroadcastChannelMessage>> mPendingMessages;
+
+ nsAutoPtr<workers::WorkerHolder> mWorkerHolder;
+
+ nsAutoPtr<PrincipalInfo> mPrincipalInfo;
+
+ nsCString mOrigin;
+ nsString mChannel;
+
+ bool mIsKeptAlive;
+
+ uint64_t mInnerID;
+
+ enum {
+ StateActive,
+ StateClosing,
+ StateClosed
+ } mState;
+};
+
+} // 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..98bd219992
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelChild.cpp
@@ -0,0 +1,135 @@
+/* -*- 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"
+#include "jsapi.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "WorkerPrivate.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+using namespace workers;
+
+BroadcastChannelChild::BroadcastChannelChild(const nsACString& aOrigin)
+ : mBC(nullptr)
+ , mActorDestroyed(false)
+{
+ CopyUTF8toUTF16(aOrigin, mOrigin);
+}
+
+BroadcastChannelChild::~BroadcastChannelChild()
+{
+ MOZ_ASSERT(!mBC);
+}
+
+bool
+BroadcastChannelChild::RecvNotify(const ClonedMessageData& aData)
+{
+ // Make sure to retrieve all blobs from the message before returning to avoid
+ // leaking their actors.
+ nsTArray<RefPtr<BlobImpl>> blobs;
+ if (!aData.blobsChild().IsEmpty()) {
+ blobs.SetCapacity(aData.blobsChild().Length());
+
+ for (uint32_t i = 0, len = aData.blobsChild().Length(); i < len; ++i) {
+ RefPtr<BlobImpl> impl =
+ static_cast<BlobChild*>(aData.blobsChild()[i])->GetBlobImpl();
+
+ blobs.AppendElement(impl);
+ }
+ }
+
+ nsCOMPtr<DOMEventTargetHelper> helper = mBC;
+ nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(helper);
+
+ // The object is going to be deleted soon. No notify is required.
+ if (!eventTarget) {
+ return true;
+ }
+
+ // CheckInnerWindowCorrectness can be used also without a window when
+ // BroadcastChannel is running in a worker. In this case, it's a NOP.
+ if (NS_FAILED(mBC->CheckInnerWindowCorrectness())) {
+ return true;
+ }
+
+ mBC->RemoveDocFromBFCache();
+
+ AutoJSAPI jsapi;
+ nsCOMPtr<nsIGlobalObject> globalObject;
+
+ if (NS_IsMainThread()) {
+ globalObject = do_QueryInterface(mBC->GetParentObject());
+ } else {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ globalObject = workerPrivate->GlobalScope();
+ }
+
+ if (!globalObject || !jsapi.Init(globalObject)) {
+ NS_WARNING("Failed to initialize AutoJSAPI object.");
+ return true;
+ }
+
+ ipc::StructuredCloneData cloneData;
+ cloneData.BlobImpls().AppendElements(blobs);
+
+ const SerializedStructuredCloneBuffer& buffer = aData.data();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> value(cx, JS::NullValue());
+ if (buffer.data.Size()) {
+ ErrorResult rv;
+ cloneData.UseExternalData(buffer.data);
+ cloneData.Read(cx, &value, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return true;
+ }
+ }
+
+ RootedDictionary<MessageEventInit> init(cx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mOrigin = mOrigin;
+ init.mData = value;
+
+ ErrorResult rv;
+ RefPtr<MessageEvent> event =
+ MessageEvent::Constructor(mBC, NS_LITERAL_STRING("message"), init, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return true;
+ }
+
+ event->SetTrusted(true);
+
+ bool status;
+ mBC->DispatchEvent(static_cast<Event*>(event.get()), &status);
+
+ return true;
+}
+
+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..23cef899e7
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelChild.h
@@ -0,0 +1,59 @@
+/* -*- 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 bool RecvNotify(const ClonedMessageData& aData) override;
+
+ bool IsActorDestroyed() const
+ {
+ return mActorDestroyed;
+ }
+
+private:
+ explicit BroadcastChannelChild(const nsACString& aOrigin);
+ ~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;
+
+ nsString mOrigin;
+
+ 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..f26292e1d4
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelParent.cpp
@@ -0,0 +1,102 @@
+/* -*- 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/ipc/BlobParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/Unused.h"
+#include "nsIScriptSecurityManager.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();
+}
+
+bool
+BroadcastChannelParent::RecvPostMessage(const ClonedMessageData& aData)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!mService)) {
+ return false;
+ }
+
+ mService->PostMessage(this, aData, mOriginChannelKey);
+ return true;
+}
+
+bool
+BroadcastChannelParent::RecvClose()
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!mService)) {
+ return false;
+ }
+
+ mService->UnregisterActor(this, mOriginChannelKey);
+ mService = nullptr;
+
+ Unused << Send__delete__(this);
+
+ return true;
+}
+
+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);
+ }
+}
+
+void
+BroadcastChannelParent::Deliver(const ClonedMessageData& aData)
+{
+ AssertIsOnBackgroundThread();
+
+ // Duplicate the data for this parent.
+ ClonedMessageData newData(aData);
+
+ // Create new BlobParent objects for this message.
+ for (uint32_t i = 0, len = newData.blobsParent().Length(); i < len; ++i) {
+ RefPtr<BlobImpl> impl =
+ static_cast<BlobParent*>(newData.blobsParent()[i])->GetBlobImpl();
+
+ PBlobParent* blobParent =
+ BackgroundParent::GetOrCreateActorForBlobImpl(Manager(), impl);
+ if (!blobParent) {
+ return;
+ }
+
+ newData.blobsParent()[i] = blobParent;
+ }
+
+ Unused << SendNotify(newData);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/broadcastchannel/BroadcastChannelParent.h b/dom/broadcastchannel/BroadcastChannelParent.h
new file mode 100644
index 0000000000..e71354ca7a
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelParent.h
@@ -0,0 +1,50 @@
+/* -*- 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;
+
+ typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
+
+public:
+ void Deliver(const ClonedMessageData& aData);
+
+private:
+ explicit BroadcastChannelParent(const nsAString& aOriginChannelKey);
+ ~BroadcastChannelParent();
+
+ virtual bool
+ RecvPostMessage(const ClonedMessageData& aData) override;
+
+ virtual bool 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..f88108c3a3
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelService.cpp
@@ -0,0 +1,132 @@
+/* -*- 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/File.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+BroadcastChannelService* sInstance = nullptr;
+
+} // 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);
+
+ nsTArray<BroadcastChannelParent*>* parents;
+ if (!mAgents.Get(aOriginChannelKey, &parents)) {
+ parents = new nsTArray<BroadcastChannelParent*>();
+ mAgents.Put(aOriginChannelKey, parents);
+ }
+
+ MOZ_ASSERT(!parents->Contains(aParent));
+ parents->AppendElement(aParent);
+}
+
+void
+BroadcastChannelService::UnregisterActor(BroadcastChannelParent* aParent,
+ const nsAString& aOriginChannelKey)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+
+ nsTArray<BroadcastChannelParent*>* parents;
+ if (!mAgents.Get(aOriginChannelKey, &parents)) {
+ MOZ_CRASH("Invalid state");
+ }
+
+ parents->RemoveElement(aParent);
+ if (parents->IsEmpty()) {
+ mAgents.Remove(aOriginChannelKey);
+ }
+}
+
+void
+BroadcastChannelService::PostMessage(BroadcastChannelParent* aParent,
+ const ClonedMessageData& 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>> blobs;
+ if (!aData.blobsParent().IsEmpty()) {
+ blobs.SetCapacity(aData.blobsParent().Length());
+
+ for (uint32_t i = 0, len = aData.blobsParent().Length(); i < len; ++i) {
+ RefPtr<BlobImpl> impl =
+ static_cast<BlobParent*>(aData.blobsParent()[i])->GetBlobImpl();
+ MOZ_ASSERT(impl);
+ blobs.AppendElement(impl);
+ }
+ }
+
+ for (uint32_t i = 0; i < parents->Length(); ++i) {
+ BroadcastChannelParent* parent = parents->ElementAt(i);
+ MOZ_ASSERT(parent);
+
+ if (parent != aParent) {
+ parent->Deliver(aData);
+ }
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/broadcastchannel/BroadcastChannelService.h b/dom/broadcastchannel/BroadcastChannelService.h
new file mode 100644
index 0000000000..3934a7ebfd
--- /dev/null
+++ b/dom/broadcastchannel/BroadcastChannelService.h
@@ -0,0 +1,52 @@
+/* -*- 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 {
+namespace dom {
+
+class BroadcastChannelParent;
+class ClonedMessageData;
+
+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 ClonedMessageData& aData,
+ const nsAString& aOriginChannelKey);
+
+private:
+ BroadcastChannelService();
+ ~BroadcastChannelService();
+
+ // Raw Pointers because the actors keep alive this service.
+ nsClassHashtable<nsStringHashKey,
+ nsTArray<BroadcastChannelParent*>> mAgents;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BroadcastChannelService_h
diff --git a/dom/broadcastchannel/PBroadcastChannel.ipdl b/dom/broadcastchannel/PBroadcastChannel.ipdl
new file mode 100644
index 0000000000..4b4a531067
--- /dev/null
+++ b/dom/broadcastchannel/PBroadcastChannel.ipdl
@@ -0,0 +1,29 @@
+/* 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 protocol PBlob;
+include DOMTypes;
+
+using struct mozilla::SerializedStructuredCloneBuffer from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+// This protocol is used for the BroadcastChannel API
+protocol PBroadcastChannel
+{
+ manager PBackground;
+
+parent:
+ async PostMessage(ClonedMessageData message);
+ async Close();
+
+child:
+ async Notify(ClonedMessageData message);
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/broadcastchannel/moz.build b/dom/broadcastchannel/moz.build
new file mode 100644
index 0000000000..a076331e58
--- /dev/null
+++ b/dom/broadcastchannel/moz.build
@@ -0,0 +1,31 @@
+# -*- 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/.
+
+EXPORTS.mozilla.dom += [
+ 'BroadcastChannel.h',
+]
+
+UNIFIED_SOURCES += [
+ 'BroadcastChannel.cpp',
+ 'BroadcastChannelChild.cpp',
+ 'BroadcastChannelParent.cpp',
+ 'BroadcastChannelService.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PBroadcastChannel.ipdl',
+]
+
+LOCAL_INCLUDES += [
+ '../workers',
+]
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
+
+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..4dad5ae825
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js
@@ -0,0 +1,12 @@
+onconnect = function(evt) {
+ evt.ports[0].onmessage = function(evt) {
+ 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();
+ }, false);
+
+ evt.target.postMessage("READY");
+ }
+}
diff --git a/dom/broadcastchannel/tests/broadcastchannel_worker.js b/dom/broadcastchannel/tests/broadcastchannel_worker.js
new file mode 100644
index 0000000000..4714d59d02
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_worker.js
@@ -0,0 +1,18 @@
+onmessage = function(evt) {
+ if (evt.data != 0) {
+ var worker = new Worker("broadcastchannel_worker.js");
+ worker.onmessage = function(evt) {
+ postMessage(evt.data);
+ }
+ worker.postMessage(evt.data - 1);
+ return;
+ }
+
+ 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();
+ }, false);
+
+ 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..78ab44015c
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_worker_alive.js
@@ -0,0 +1,8 @@
+(new BroadcastChannel('foobar')).postMessage('READY');
+
+(new BroadcastChannel('foobar')).addEventListener('message', function(event) {
+ if (event.data != 'READY') {
+ event.target.postMessage(event.data);
+ }
+}, false);
+
diff --git a/dom/broadcastchannel/tests/broadcastchannel_worker_any.js b/dom/broadcastchannel/tests/broadcastchannel_worker_any.js
new file mode 100644
index 0000000000..da8625c4d8
--- /dev/null
+++ b/dom/broadcastchannel/tests/broadcastchannel_worker_any.js
@@ -0,0 +1,5 @@
+(new BroadcastChannel('foobar')).onmessage = function(event) {
+ event.target.postMessage(event.data);
+}
+
+postMessage("READY");
diff --git a/dom/broadcastchannel/tests/browser.ini b/dom/broadcastchannel/tests/browser.ini
new file mode 100644
index 0000000000..b036a1e9bf
--- /dev/null
+++ b/dom/broadcastchannel/tests/browser.ini
@@ -0,0 +1,6 @@
+[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..5724225fc3
--- /dev/null
+++ b/dom/broadcastchannel/tests/browser_private_browsing.js
@@ -0,0 +1,74 @@
+/* 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(function*() {
+ var win1 = OpenBrowserWindow({private: true});
+ var win1Promise = new win1.Promise(resolve => {
+ win1.addEventListener("load", function onLoad() {
+ win1.removeEventListener("load", onLoad, false);
+ resolve();
+ });
+ });
+ yield win1Promise;
+
+ var win2 = OpenBrowserWindow({private: false});
+ var win2Promise = new win2.Promise(resolve => {
+ win2.addEventListener("load", function onLoad() {
+ win2.removeEventListener("load", onLoad, false);
+ resolve();
+ });
+ });
+ yield win2Promise;
+
+ var tab1 = win1.gBrowser.addTab(URL);
+ yield BrowserTestUtils.browserLoaded(win1.gBrowser.getBrowserForTab(tab1));
+ var browser1 = gBrowser.getBrowserForTab(tab1);
+
+ var tab2 = win2.gBrowser.addTab(URL);
+ yield BrowserTestUtils.browserLoaded(win2.gBrowser.getBrowserForTab(tab2));
+ var browser2 = gBrowser.getBrowserForTab(tab2);
+
+ var p1 = ContentTask.spawn(browser1, null, 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 = ContentTask.spawn(browser2, null, 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); }
+ });
+ });
+
+ yield ContentTask.spawn(browser1, null, function(opts) {
+ return new content.window.Promise(resolve => {
+ var bc = new content.window.BroadcastChannel('foobar');
+ bc.postMessage('hello world from private browsing');
+ resolve();
+ });
+ });
+
+ yield ContentTask.spawn(browser2, null, 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 = yield p1;
+ ok(what1, 'hello world from private browsing', 'No messages received from the other window.');
+
+ var what2 = yield p2;
+ ok(what1, 'hello world from non private browsing', 'No messages received from the other window.');
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.closeWindow(win1);
+
+ yield BrowserTestUtils.removeTab(tab2);
+ yield 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..5f69021320
--- /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;version=1.7">
+
+ 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..85abce7bff
--- /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;version=1.7">
+
+ 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..fb9a12b4bd
--- /dev/null
+++ b/dom/broadcastchannel/tests/iframe_broadcastchannel.html
@@ -0,0 +1,35 @@
+<!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..dbcf63d883
--- /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;version=1.7">
+
+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..dbcf63d883
--- /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;version=1.7">
+
+var bc = new BroadcastChannel('foobar');
+bc.postMessage('This is wrong!');
+
+ </script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/mochitest.ini b/dom/broadcastchannel/tests/mochitest.ini
new file mode 100644
index 0000000000..6dff47db48
--- /dev/null
+++ b/dom/broadcastchannel/tests/mochitest.ini
@@ -0,0 +1,24 @@
+[DEFAULT]
+support-files =
+ iframe_broadcastchannel.html
+ broadcastchannel_sharedWorker.js
+ broadcastchannel_worker.js
+ broadcastchannel_worker_alive.js
+ broadcastchannel_worker_any.js
+ file_mozbrowser.html
+ file_mozbrowser2.html
+ iframe_mozbrowser.html
+ iframe_mozbrowser2.html
+
+[test_broadcastchannel_any.html]
+[test_broadcastchannel_basic.html]
+[test_broadcastchannel_close.html]
+[test_broadcastchannel_close2.html]
+[test_broadcastchannel_self.html]
+[test_broadcastchannel_sharedWorker.html]
+[test_broadcastchannel_worker.html]
+[test_broadcastchannel_worker_alive.html]
+[test_bfcache.html]
+[test_invalidState.html]
+[test_ordering.html]
+[test_dataCloning.html]
diff --git a/dom/broadcastchannel/tests/test_bfcache.html b/dom/broadcastchannel/tests/test_bfcache.html
new file mode 100644
index 0000000000..e42496bac8
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_bfcache.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for bfcache and BroadcastChannel</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+ /* This test is hard to follow so here a quick description of what it is about.
+ * We want to test that when BroadcastChannel is used in a bfcached page,
+ * this page is fully removed by bfcache.
+ *
+ * To test it we have 2 pages (testUrl1 and testUrl2).
+ * The steps are:
+ * - we show testUrl1. When this is shown page1Shown is called.
+ * - page1Shown creates a BroadcastChannel object, then it loads testUrl2.
+ * - page2Shown is called by testUrl2.
+ * - Based on expectedPersisted we use or not the BroadcastChannel object of
+ * testUrl1.
+ * - Then we call history.back() and testUrl1 will be loaded again.
+ * - when page1Shown is called by testUrl1, if BroadcastChannel has been used
+ * when testUrl2 was shown, we want event.persisted be false, otherwise
+ * true.
+ */
+ var testUrl1 = "data:text/html,<script>onpageshow = function(e) { opener.page1Shown(e); };<" + "/script>";
+ var testUrl2 = "data:text/html,<script>onpageshow = function(e) { opener.page2Shown(e); };<" + "/script>";
+
+ var testWin;
+ var counter = 0;
+ var expectedPersisted = false;
+ var bc;
+
+ function page1Shown(e) {
+ info("Page1Shown: " + testWin.location.href);
+
+ if (counter == 0) {
+ ok(!e.persisted, "test page should have been persisted initially");
+
+ bc = new testWin.BroadcastChannel('a');
+
+ SimpleTest.executeSoon(function() {
+ info("New location: " + testUrl2);
+ testWin.location.href = testUrl2;
+ });
+ } else {
+ is(e.persisted, expectedPersisted, "test page should have been persisted in pageshow");
+ testWin.close();
+ runTest();
+ }
+
+ counter++;
+ }
+
+ function page2Shown(e) {
+ info("Page2Shown: " + testWin.location.href);
+
+ if (!expectedPersisted) {
+ SimpleTest.executeSoon(function() {
+ info("Posting a message.");
+ bc.postMessage(42);
+ });
+ }
+
+ SimpleTest.executeSoon(function() {
+ info("Going back");
+ testWin.history.back();
+ });
+ }
+
+ var tests = [
+ { expectedPersisted: true },
+ { expectedPersisted: false },
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+
+ counter = 0;
+ expectedPersisted = test.expectedPersisted;
+ testWin = window.open(testUrl1);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+
+ </script>
+</body>
+</html>
diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_any.html b/dom/broadcastchannel/tests/test_broadcastchannel_any.html
new file mode 100644
index 0000000000..2225e7cad9
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_any.html
@@ -0,0 +1,138 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel</title>
+ <script type="application/javascript" 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 tests = [
+ 'hello world',
+ 123,
+ null,
+ true,
+ new Date(),
+ [ 1, 'test', true, new Date() ],
+ { a: true, b: null, c: new Date(), d: [ true, false, {} ] },
+ new Blob([123], { type: 'plain/text' })
+];
+
+var currentTest = null;
+
+function getType(a) {
+ if (a === null || a === undefined)
+ return 'null';
+
+ if (Array.isArray(a))
+ return 'array';
+
+ if (typeof a == 'object')
+ return 'object';
+
+ return 'primitive';
+}
+
+function compare(a, b) {
+ is (getType(a), getType(b), 'Type matches');
+
+ var type = getType(a);
+ if (type == 'array') {
+ is (a.length, b.length, 'Array.length matches');
+ for (var i = 0; i < a.length; ++i) {
+ compare(a[i], b[i]);
+ }
+
+ return;
+ }
+
+ if (type == 'object') {
+ ok (a !== b, 'They should not match');
+
+ var aProps = [];
+ for (var p in a) aProps.push(p);
+
+ var bProps = [];
+ for (var p in b) bProps.push(p);
+
+ is (aProps.length, bProps.length, 'Props match');
+ is (aProps.sort().toSource(), bProps.sort().toSource(), 'Props match - using toSource()');
+
+ for (var p in a) {
+ compare(a[p], b[p]);
+ }
+
+ return;
+ }
+
+ if (type != 'null') {
+ is (a.toSource(), b.toSource(), 'Matching using toSource()');
+ }
+}
+
+function runTest() {
+ var count = 2;
+
+ var bc = new BroadcastChannel("foobar");
+ ok(bc, "BroadcastChannel can be created");
+
+ bc.onmessage = function(event) {
+ ok(count < 2, "Still comparing...");
+ info("bc: " + currentTest);
+ compare(event.data, currentTest);
+ ++count;
+ next();
+ }
+
+ var bc2 = new BroadcastChannel("foobar");
+ ok(bc2, "BroadcastChannel can be created");
+
+ var toSkip = true;
+ bc2.onmessage = function(event) {
+ toSkip = !toSkip;
+ if (toSkip) return;
+
+ ok(count < 2, "Still comparing...");
+ info("bc2: " + currentTest);
+ compare(event.data, currentTest);
+ ++count;
+ next();
+ }
+
+ function next() {
+ if (count < 2) {
+ return;
+ }
+
+ is(count, 2, "Just 2 comparations");
+ count = 0;
+
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ currentTest = tests.shift();
+ bc.postMessage(currentTest);
+ info("Posted: " + currentTest);
+ }
+
+ var worker = new Worker("broadcastchannel_worker_any.js");
+ worker.onmessage = function(event) {
+ if (event.data == "READY") {
+ next();
+ }
+ };
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</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..45fa3c4a9d
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_basic.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel</title>
+ <script type="application/javascript" 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, false);
+ 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..84d41db4f9
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_close.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel</title>
+ <script type="application/javascript" 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_close2.html b/dom/broadcastchannel/tests/test_broadcastchannel_close2.html
new file mode 100644
index 0000000000..a8a748c462
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_close2.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel</title>
+ <script type="application/javascript" 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 c1 = new BroadcastChannel('foo');
+ var c2 = new BroadcastChannel('foo');
+
+ var status = [];
+ c2.onmessage = function(e) {
+ status.push(e.data);
+ if (status.length == 2) {
+ is (status[0], 'first', "First message has been received");
+ is (status[1], 'second', "Second message has been received");
+ SimpleTest.finish();
+ }
+
+ c2.close();
+ }
+
+ c1.postMessage('first');
+ c1.postMessage('second');
+}
+
+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..32df390be1
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_self.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel</title>
+ <script type="application/javascript" 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() {
+ x = new BroadcastChannel('foo');
+ 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..16f847acae
--- /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 type="text/javascript" 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.html b/dom/broadcastchannel/tests/test_broadcastchannel_worker.html
new file mode 100644
index 0000000000..42e93cc1e1
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_worker.html
@@ -0,0 +1,62 @@
+<!--
+ 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 type="text/javascript" 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 testWorker(x) {
+ var worker = new Worker("broadcastchannel_worker.js");
+
+ var bc = new BroadcastChannel('foobar');
+
+ worker.onmessage = function(event) {
+ if (event.data == "READY") {
+ ok(true, "Worker 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();
+ runTests();
+ }
+
+ worker.postMessage(x);
+}
+
+var tests = [ 0, 3 ];
+function runTests() {
+ if (tests.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+
+ testWorker(tests.shift());
+}
+
+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..8b1b03c4e7
--- /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 type="text/javascript" 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..6aa164e944
--- /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 type="application/javascript" 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_invalidState.html b/dom/broadcastchannel/tests/test_invalidState.html
new file mode 100644
index 0000000000..8e4a1df354
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_invalidState.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel.postMessage invalid State</title>
+ <script type="application/javascript" 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_ordering.html b/dom/broadcastchannel/tests/test_ordering.html
new file mode 100644
index 0000000000..eab1f955e5
--- /dev/null
+++ b/dom/broadcastchannel/tests/test_ordering.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for BroadcastChannel.postMessage invalid State</title>
+ <script type="application/javascript" 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>
+