summaryrefslogtreecommitdiffstats
path: root/dom/messagechannel
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/messagechannel
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/messagechannel')
-rw-r--r--dom/messagechannel/MessageChannel.cpp89
-rw-r--r--dom/messagechannel/MessageChannel.h58
-rw-r--r--dom/messagechannel/MessagePort.cpp826
-rw-r--r--dom/messagechannel/MessagePort.h233
-rw-r--r--dom/messagechannel/MessagePortChild.cpp46
-rw-r--r--dom/messagechannel/MessagePortChild.h45
-rw-r--r--dom/messagechannel/MessagePortParent.cpp178
-rw-r--r--dom/messagechannel/MessagePortParent.h61
-rw-r--r--dom/messagechannel/MessagePortService.cpp408
-rw-r--r--dom/messagechannel/MessagePortService.h60
-rw-r--r--dom/messagechannel/PMessagePort.ipdl47
-rw-r--r--dom/messagechannel/moz.build38
-rw-r--r--dom/messagechannel/tests/chrome.toml15
-rw-r--r--dom/messagechannel/tests/iframe_messageChannel_chrome.html11
-rw-r--r--dom/messagechannel/tests/iframe_messageChannel_cloning.html21
-rw-r--r--dom/messagechannel/tests/iframe_messageChannel_pingpong.html32
-rw-r--r--dom/messagechannel/tests/iframe_messageChannel_post.html23
-rw-r--r--dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html13
-rw-r--r--dom/messagechannel/tests/iframe_messageChannel_transferable.html24
-rw-r--r--dom/messagechannel/tests/mm_messageChannel.js76
-rw-r--r--dom/messagechannel/tests/mm_messageChannelParent.js144
-rw-r--r--dom/messagechannel/tests/mm_messageChannelParent.xhtml12
-rw-r--r--dom/messagechannel/tests/mm_messageChannelParentNotRemote.xhtml12
-rw-r--r--dom/messagechannel/tests/mochitest.toml50
-rw-r--r--dom/messagechannel/tests/moz.build10
-rw-r--r--dom/messagechannel/tests/sharedWorker2_messageChannel.js9
-rw-r--r--dom/messagechannel/tests/sharedWorker_messageChannel.js10
-rw-r--r--dom/messagechannel/tests/test_event_listener_leaks.html50
-rw-r--r--dom/messagechannel/tests/test_messageChannel.html43
-rw-r--r--dom/messagechannel/tests/test_messageChannel.xhtml38
-rw-r--r--dom/messagechannel/tests/test_messageChannelWithMessageManager.xhtml27
-rw-r--r--dom/messagechannel/tests/test_messageChannelWithMessageManagerNotRemote.xhtml27
-rw-r--r--dom/messagechannel/tests/test_messageChannel_any.html115
-rw-r--r--dom/messagechannel/tests/test_messageChannel_bug1178076.html38
-rw-r--r--dom/messagechannel/tests/test_messageChannel_bug1224825.html94
-rw-r--r--dom/messagechannel/tests/test_messageChannel_cloning.html70
-rw-r--r--dom/messagechannel/tests/test_messageChannel_forceClose.html30
-rw-r--r--dom/messagechannel/tests/test_messageChannel_pingpong.html77
-rw-r--r--dom/messagechannel/tests/test_messageChannel_post.html76
-rw-r--r--dom/messagechannel/tests/test_messageChannel_selfTransferring.html32
-rw-r--r--dom/messagechannel/tests/test_messageChannel_sharedWorker.html36
-rw-r--r--dom/messagechannel/tests/test_messageChannel_sharedWorker2.html34
-rw-r--r--dom/messagechannel/tests/test_messageChannel_start.html235
-rw-r--r--dom/messagechannel/tests/test_messageChannel_transferable.html111
-rw-r--r--dom/messagechannel/tests/test_messageChannel_unshipped.html123
-rw-r--r--dom/messagechannel/tests/test_messageChannel_worker.html60
-rw-r--r--dom/messagechannel/tests/test_messageChannel_worker_forceClose.html27
-rw-r--r--dom/messagechannel/tests/test_removedWindow.html55
-rw-r--r--dom/messagechannel/tests/unit/chromeWorker_messageChannel.js14
-rw-r--r--dom/messagechannel/tests/unit/test_messageChannel.js23
-rw-r--r--dom/messagechannel/tests/unit/xpcshell.toml6
-rw-r--r--dom/messagechannel/tests/worker_messageChannel.js110
-rw-r--r--dom/messagechannel/tests/worker_messageChannel_any.js7
53 files changed, 4109 insertions, 0 deletions
diff --git a/dom/messagechannel/MessageChannel.cpp b/dom/messagechannel/MessageChannel.cpp
new file mode 100644
index 0000000000..bf95613082
--- /dev/null
+++ b/dom/messagechannel/MessageChannel.cpp
@@ -0,0 +1,89 @@
+/* -*- 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 "MessageChannel.h"
+
+#include "mozilla/dom/MessageChannelBinding.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/Document.h"
+#include "nsIGlobalObject.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MessageChannel, mGlobal, mPort1, mPort2)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MessageChannel)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MessageChannel)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MessageChannel)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+MessageChannel::MessageChannel(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) {
+ MOZ_ASSERT(aGlobal);
+}
+
+MessageChannel::~MessageChannel() = default;
+
+JSObject* MessageChannel::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MessageChannel_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */
+already_AddRefed<MessageChannel> MessageChannel::Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ return Constructor(global, aRv);
+}
+
+/* static */
+already_AddRefed<MessageChannel> MessageChannel::Constructor(
+ nsIGlobalObject* aGlobal, ErrorResult& aRv) {
+ MOZ_ASSERT(aGlobal);
+
+ nsID portUUID1;
+ aRv = nsID::GenerateUUIDInPlace(portUUID1);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ nsID portUUID2;
+ aRv = nsID::GenerateUUIDInPlace(portUUID2);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<MessageChannel> channel = new MessageChannel(aGlobal);
+
+ channel->mPort1 = MessagePort::Create(aGlobal, portUUID1, portUUID2, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ channel->mPort2 = MessagePort::Create(aGlobal, portUUID2, portUUID1, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ channel->mPort1->UnshippedEntangle(channel->mPort2);
+ channel->mPort2->UnshippedEntangle(channel->mPort1);
+
+ // MessagePorts should not work if created from a disconnected window.
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+ if (window && !window->GetDocShell()) {
+ // The 2 ports are entangled. We can close one of them to close the other
+ // too.
+ channel->mPort1->CloseForced();
+ }
+
+ return channel.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/messagechannel/MessageChannel.h b/dom/messagechannel/MessageChannel.h
new file mode 100644
index 0000000000..a618bd4abd
--- /dev/null
+++ b/dom/messagechannel/MessageChannel.h
@@ -0,0 +1,58 @@
+/* -*- 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_MessageChannel_h
+#define mozilla_dom_MessageChannel_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class MessagePort;
+
+class MessageChannel final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MessageChannel)
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<MessageChannel> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv);
+
+ static already_AddRefed<MessageChannel> Constructor(nsIGlobalObject* aGlobal,
+ ErrorResult& aRv);
+
+ MessagePort* Port1() const { return mPort1; }
+
+ MessagePort* Port2() const { return mPort2; }
+
+ private:
+ explicit MessageChannel(nsIGlobalObject* aGlobal);
+ ~MessageChannel();
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ RefPtr<MessagePort> mPort1;
+ RefPtr<MessagePort> mPort2;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MessageChannel_h
diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp
new file mode 100644
index 0000000000..23c54cde25
--- /dev/null
+++ b/dom/messagechannel/MessagePort.cpp
@@ -0,0 +1,826 @@
+/* -*- 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 "MessagePort.h"
+
+#include "MessageEvent.h"
+#include "MessagePortChild.h"
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageChannel.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/dom/MessagePortChild.h"
+#include "mozilla/dom/PMessagePort.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/dom/RefMessageBodyService.h"
+#include "mozilla/dom/SharedMessageBody.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsPresContext.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+namespace mozilla::dom {
+
+void UniqueMessagePortId::ForceClose() {
+ if (!mIdentifier.neutered()) {
+ MessagePort::ForceClose(mIdentifier);
+ mIdentifier.neutered() = true;
+ }
+}
+
+class PostMessageRunnable final : public CancelableRunnable {
+ friend class MessagePort;
+
+ public:
+ PostMessageRunnable(MessagePort* aPort, SharedMessageBody* aData)
+ : CancelableRunnable("dom::PostMessageRunnable"),
+ mPort(aPort),
+ mData(aData) {
+ MOZ_ASSERT(aPort);
+ MOZ_ASSERT(aData);
+ }
+
+ NS_IMETHOD
+ Run() override {
+ NS_ASSERT_OWNINGTHREAD(Runnable);
+
+ // The port can be cycle collected while this runnable is pending in
+ // the event queue.
+ if (!mPort) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mPort->mPostMessageRunnable == this);
+
+ DispatchMessage();
+
+ // We must check if we were waiting for this message in order to shutdown
+ // the port.
+ mPort->UpdateMustKeepAlive();
+
+ mPort->mPostMessageRunnable = nullptr;
+ mPort->Dispatch();
+
+ return NS_OK;
+ }
+
+ nsresult Cancel() override {
+ NS_ASSERT_OWNINGTHREAD(Runnable);
+
+ mPort = nullptr;
+ mData = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ void DispatchMessage() const {
+ NS_ASSERT_OWNINGTHREAD(Runnable);
+
+ if (NS_FAILED(mPort->CheckCurrentGlobalCorrectness())) {
+ return;
+ }
+
+ nsCOMPtr<nsIGlobalObject> globalObject = mPort->GetParentObject();
+
+ AutoJSAPI jsapi;
+ if (!globalObject || !jsapi.Init(globalObject)) {
+ NS_WARNING("Failed to initialize AutoJSAPI object.");
+ return;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ IgnoredErrorResult rv;
+ JS::Rooted<JS::Value> value(cx);
+
+ mData->Read(cx, &value, mPort->mRefMessageBodyService,
+ SharedMessageBody::ReadMethod::StealRefMessageBody, rv);
+
+ if (NS_WARN_IF(rv.Failed())) {
+ JS_ClearPendingException(cx);
+ mPort->DispatchError();
+ return;
+ }
+
+ // Create the event
+ nsCOMPtr<mozilla::dom::EventTarget> eventTarget =
+ do_QueryInterface(mPort->GetOwner());
+ RefPtr<MessageEvent> event =
+ new MessageEvent(eventTarget, nullptr, nullptr);
+
+ Sequence<OwningNonNull<MessagePort>> ports;
+ if (!mData->TakeTransferredPortsAsSequence(ports)) {
+ mPort->DispatchError();
+ return;
+ }
+
+ event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo,
+ Cancelable::eNo, value, u""_ns, u""_ns, nullptr,
+ ports);
+ event->SetTrusted(true);
+
+ mPort->DispatchEvent(*event);
+ }
+
+ private:
+ ~PostMessageRunnable() = default;
+
+ RefPtr<MessagePort> mPort;
+ RefPtr<SharedMessageBody> mData;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MessagePort)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessagePort,
+ DOMEventTargetHelper)
+ if (tmp->mPostMessageRunnable) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPostMessageRunnable->mPort);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagesForTheOtherPort);
+
+ tmp->CloseForced();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessagePort,
+ DOMEventTargetHelper)
+ if (tmp->mPostMessageRunnable) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPostMessageRunnable->mPort);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnshippedEntangledPort);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MessagePort)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(MessagePort, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MessagePort, DOMEventTargetHelper)
+
+MessagePort::MessagePort(nsIGlobalObject* aGlobal, State aState)
+ : DOMEventTargetHelper(aGlobal),
+ mRefMessageBodyService(RefMessageBodyService::GetOrCreate()),
+ mState(aState),
+ mMessageQueueEnabled(false),
+ mIsKeptAlive(false),
+ mHasBeenTransferredOrClosed(false) {
+ MOZ_ASSERT(aGlobal);
+
+ mIdentifier = MakeUnique<MessagePortIdentifier>();
+ mIdentifier->neutered() = true;
+ mIdentifier->sequenceId() = 0;
+}
+
+MessagePort::~MessagePort() {
+ CloseForced();
+ MOZ_ASSERT(!mActor);
+ if (mActor) {
+ mActor->SetPort(nullptr);
+ mActor = nullptr;
+ }
+ MOZ_ASSERT(!mWorkerRef);
+}
+
+/* static */
+already_AddRefed<MessagePort> MessagePort::Create(nsIGlobalObject* aGlobal,
+ const nsID& aUUID,
+ const nsID& aDestinationUUID,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aGlobal);
+
+ RefPtr<MessagePort> mp = new MessagePort(aGlobal, eStateUnshippedEntangled);
+ mp->Initialize(aUUID, aDestinationUUID, 1 /* 0 is an invalid sequence ID */,
+ false /* Neutered */, aRv);
+ return mp.forget();
+}
+
+/* static */
+already_AddRefed<MessagePort> MessagePort::Create(
+ nsIGlobalObject* aGlobal, UniqueMessagePortId& aIdentifier,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aGlobal);
+
+ RefPtr<MessagePort> mp = new MessagePort(aGlobal, eStateEntangling);
+ mp->Initialize(aIdentifier.uuid(), aIdentifier.destinationUuid(),
+ aIdentifier.sequenceId(), aIdentifier.neutered(), aRv);
+ aIdentifier.neutered() = true;
+ return mp.forget();
+}
+
+void MessagePort::UnshippedEntangle(MessagePort* aEntangledPort) {
+ MOZ_DIAGNOSTIC_ASSERT(aEntangledPort);
+ MOZ_DIAGNOSTIC_ASSERT(!mUnshippedEntangledPort);
+
+ mUnshippedEntangledPort = aEntangledPort;
+}
+
+void MessagePort::Initialize(const nsID& aUUID, const nsID& aDestinationUUID,
+ uint32_t aSequenceID, bool aNeutered,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(mIdentifier);
+ mIdentifier->uuid() = aUUID;
+ mIdentifier->destinationUuid() = aDestinationUUID;
+ mIdentifier->sequenceId() = aSequenceID;
+
+ if (aNeutered) {
+ // If this port is neutered we don't want to keep it alive artificially nor
+ // we want to add listeners or WorkerRefs.
+ mState = eStateDisentangled;
+ return;
+ }
+
+ if (mState == eStateEntangling) {
+ if (!ConnectToPBackground()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ } else {
+ MOZ_ASSERT(mState == eStateUnshippedEntangled);
+ }
+
+ // The port has to keep itself alive until it's entangled.
+ UpdateMustKeepAlive();
+
+ if (WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate()) {
+ RefPtr<MessagePort> self = this;
+
+ // When the callback is executed, we cannot process messages anymore because
+ // we cannot dispatch new runnables. Let's force a Close().
+ RefPtr<StrongWorkerRef> strongWorkerRef = StrongWorkerRef::Create(
+ workerPrivate, "MessagePort", [self]() { self->CloseForced(); });
+ if (NS_WARN_IF(!strongWorkerRef)) {
+ // The worker is shutting down.
+ CloseForced();
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ MOZ_ASSERT(!mWorkerRef);
+ mWorkerRef = std::move(strongWorkerRef);
+ }
+}
+
+JSObject* MessagePort::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MessagePort_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void MessagePort::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable,
+ ErrorResult& aRv) {
+ // We *must* clone the data here, or the JS::Value could be modified
+ // by script
+
+ // Here we want to check if the transerable object list contains
+ // this port.
+ for (uint32_t i = 0; i < aTransferable.Length(); ++i) {
+ JS::Rooted<JSObject*> object(aCx, aTransferable[i]);
+ if (!object) {
+ continue;
+ }
+
+ MessagePort* port = nullptr;
+ nsresult rv = UNWRAP_OBJECT(MessagePort, &object, port);
+ if (NS_SUCCEEDED(rv) && port == this) {
+ aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+ }
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+
+ aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
+ &transferable);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ Maybe<nsID> agentClusterId;
+ nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+ if (global) {
+ agentClusterId = global->GetAgentClusterId();
+ }
+
+ RefPtr<SharedMessageBody> data = new SharedMessageBody(
+ StructuredCloneHolder::TransferringSupported, agentClusterId);
+
+ data->Write(aCx, aMessage, transferable, mIdentifier->uuid(),
+ mRefMessageBodyService, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // This message has to be ignored.
+ if (mState > eStateEntangled) {
+ return;
+ }
+
+ // If we are unshipped we are connected to the other port on the same thread.
+ if (mState == eStateUnshippedEntangled) {
+ MOZ_DIAGNOSTIC_ASSERT(mUnshippedEntangledPort);
+ mUnshippedEntangledPort->mMessages.AppendElement(data);
+ mUnshippedEntangledPort->Dispatch();
+ return;
+ }
+
+ // Not entangled yet, but already closed/disentangled.
+ if (mState == eStateEntanglingForDisentangle ||
+ mState == eStateEntanglingForClose) {
+ return;
+ }
+
+ RemoveDocFromBFCache();
+
+ // Not entangled yet.
+ if (mState == eStateEntangling) {
+ mMessagesForTheOtherPort.AppendElement(data);
+ return;
+ }
+
+ MOZ_ASSERT(mActor);
+ MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty());
+
+ AutoTArray<RefPtr<SharedMessageBody>, 1> array;
+ array.AppendElement(data);
+
+ AutoTArray<MessageData, 1> messages;
+ // note: `messages` will borrow the underlying buffer, but this is okay
+ // because reverse destruction order means `messages` will be destroyed prior
+ // to `array`/`data`.
+ SharedMessageBody::FromSharedToMessagesChild(mActor->Manager(), array,
+ messages);
+ mActor->SendPostMessages(messages);
+}
+
+void MessagePort::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const StructuredSerializeOptions& aOptions,
+ ErrorResult& aRv) {
+ PostMessage(aCx, aMessage, aOptions.mTransfer, aRv);
+}
+
+void MessagePort::Start() {
+ if (mMessageQueueEnabled) {
+ return;
+ }
+
+ mMessageQueueEnabled = true;
+ Dispatch();
+}
+
+void MessagePort::Dispatch() {
+ if (!mMessageQueueEnabled || mMessages.IsEmpty() || mPostMessageRunnable) {
+ return;
+ }
+
+ switch (mState) {
+ case eStateUnshippedEntangled:
+ // Everything is fine here. We have messages because the other
+ // port populates our queue directly.
+ break;
+
+ case eStateEntangling:
+ // Everything is fine here as well. We have messages because the other
+ // port populated our queue directly when we were in the
+ // eStateUnshippedEntangled state.
+ break;
+
+ case eStateEntanglingForDisentangle:
+ // Here we don't want to ship messages because these messages must be
+ // delivered by the cloned version of this one. They will be sent in the
+ // SendDisentangle().
+ return;
+
+ case eStateEntanglingForClose:
+ // We still want to deliver messages if we are closing. These messages
+ // are here from the previous eStateUnshippedEntangled state.
+ break;
+
+ case eStateEntangled:
+ // This port is up and running.
+ break;
+
+ case eStateDisentangling:
+ // If we are in the process to disentangle the port, we cannot dispatch
+ // messages. They will be sent to the cloned version of this port via
+ // SendDisentangle();
+ return;
+
+ case eStateDisentangled:
+ MOZ_CRASH("This cannot happen.");
+ // It cannot happen because Disentangle should take off all the pending
+ // messages.
+ break;
+
+ case eStateDisentangledForClose:
+ // If we are here is because the port has been closed. We can still
+ // process the pending messages.
+ break;
+ }
+
+ RefPtr<SharedMessageBody> data = mMessages.ElementAt(0);
+ mMessages.RemoveElementAt(0);
+
+ mPostMessageRunnable = new PostMessageRunnable(this, data);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mPostMessageRunnable));
+}
+
+void MessagePort::Close() {
+ mHasBeenTransferredOrClosed = true;
+ CloseInternal(true /* aSoftly */);
+}
+
+void MessagePort::CloseForced() { CloseInternal(false /* aSoftly */); }
+
+void MessagePort::CloseInternal(bool aSoftly) {
+ // If we have some messages to send but we don't want a 'soft' close, we have
+ // to flush them now.
+ if (!aSoftly) {
+ mMessages.Clear();
+ }
+
+ // Let's inform the RefMessageBodyService that any our shared messages are
+ // now invalid.
+ mRefMessageBodyService->ForgetPort(mIdentifier->uuid());
+
+ if (mState == eStateUnshippedEntangled) {
+ MOZ_DIAGNOSTIC_ASSERT(mUnshippedEntangledPort);
+
+ // This avoids loops.
+ RefPtr<MessagePort> port = std::move(mUnshippedEntangledPort);
+
+ mState = eStateDisentangledForClose;
+ port->CloseInternal(aSoftly);
+
+ UpdateMustKeepAlive();
+ return;
+ }
+
+ // Not entangled yet, we have to wait.
+ if (mState == eStateEntangling) {
+ mState = eStateEntanglingForClose;
+ return;
+ }
+
+ // Not entangled but already cloned or closed
+ if (mState == eStateEntanglingForDisentangle ||
+ mState == eStateEntanglingForClose) {
+ return;
+ }
+
+ // Maybe we were already closing the port but softly. In this case we call
+ // UpdateMustKeepAlive() to consider the empty pending message queue.
+ if (mState == eStateDisentangledForClose && !aSoftly) {
+ UpdateMustKeepAlive();
+ return;
+ }
+
+ if (mState > eStateEntangled) {
+ return;
+ }
+
+ // We don't care about stopping the sending of messages because from now all
+ // the incoming messages will be ignored.
+ mState = eStateDisentangledForClose;
+
+ MOZ_ASSERT(mActor);
+
+ mActor->SendClose();
+ mActor->SetPort(nullptr);
+ mActor = nullptr;
+
+ UpdateMustKeepAlive();
+}
+
+EventHandlerNonNull* MessagePort::GetOnmessage() {
+ return GetEventHandler(nsGkAtoms::onmessage);
+}
+
+void MessagePort::SetOnmessage(EventHandlerNonNull* aCallback) {
+ SetEventHandler(nsGkAtoms::onmessage, aCallback);
+
+ // When using onmessage, the call to start() is implied.
+ Start();
+}
+
+// This method is called when the PMessagePortChild actor is entangled to
+// another actor. It receives a list of messages to be dispatch. It can be that
+// we were waiting for this entangling step in order to disentangle the port or
+// to close it.
+void MessagePort::Entangled(nsTArray<MessageData>& aMessages) {
+ MOZ_ASSERT(mState == eStateEntangling ||
+ mState == eStateEntanglingForDisentangle ||
+ mState == eStateEntanglingForClose);
+
+ State oldState = mState;
+ mState = eStateEntangled;
+
+ // If we have pending messages, these have to be sent.
+ if (!mMessagesForTheOtherPort.IsEmpty()) {
+ {
+ nsTArray<MessageData> messages;
+ SharedMessageBody::FromSharedToMessagesChild(
+ mActor->Manager(), mMessagesForTheOtherPort, messages);
+ mActor->SendPostMessages(messages);
+ }
+ // Because `messages` borrow the underlying JSStructuredCloneData buffers,
+ // only clear after `messages` have gone out of scope.
+ mMessagesForTheOtherPort.Clear();
+ }
+
+ // We must convert the messages into SharedMessageBodys to avoid leaks.
+ FallibleTArray<RefPtr<SharedMessageBody>> data;
+ if (NS_WARN_IF(
+ !SharedMessageBody::FromMessagesToSharedChild(aMessages, data))) {
+ DispatchError();
+ return;
+ }
+
+ // If the next step is to close the port, we do it ignoring the received
+ // messages.
+ if (oldState == eStateEntanglingForClose) {
+ CloseForced();
+ return;
+ }
+
+ mMessages.AppendElements(data);
+
+ // We were waiting for the entangling callback in order to disentangle this
+ // port immediately after.
+ if (oldState == eStateEntanglingForDisentangle) {
+ StartDisentangling();
+ return;
+ }
+
+ Dispatch();
+}
+
+void MessagePort::StartDisentangling() {
+ MOZ_ASSERT(mActor);
+ MOZ_ASSERT(mState == eStateEntangled);
+
+ mState = eStateDisentangling;
+
+ // Sending this message we communicate to the parent actor that we don't want
+ // to receive any new messages. It is possible that a message has been
+ // already sent but not received yet. So we have to collect all of them and
+ // we send them in the SendDispatch() request.
+ mActor->SendStopSendingData();
+}
+
+void MessagePort::MessagesReceived(nsTArray<MessageData>& aMessages) {
+ MOZ_ASSERT(mState == eStateEntangled || mState == eStateDisentangling ||
+ // This last step can happen only if Close() has been called
+ // manually. At this point SendClose() is sent but we can still
+ // receive something until the Closing request is processed.
+ mState == eStateDisentangledForClose);
+ MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty());
+
+ RemoveDocFromBFCache();
+
+ FallibleTArray<RefPtr<SharedMessageBody>> data;
+ if (NS_WARN_IF(
+ !SharedMessageBody::FromMessagesToSharedChild(aMessages, data))) {
+ DispatchError();
+ return;
+ }
+
+ mMessages.AppendElements(data);
+
+ if (mState == eStateEntangled) {
+ Dispatch();
+ }
+}
+
+void MessagePort::StopSendingDataConfirmed() {
+ MOZ_ASSERT(mState == eStateDisentangling);
+ MOZ_ASSERT(mActor);
+
+ Disentangle();
+}
+
+void MessagePort::Disentangle() {
+ MOZ_ASSERT(mState == eStateDisentangling);
+ MOZ_ASSERT(mActor);
+
+ mState = eStateDisentangled;
+
+ {
+ nsTArray<MessageData> messages;
+ SharedMessageBody::FromSharedToMessagesChild(mActor->Manager(), mMessages,
+ messages);
+ mActor->SendDisentangle(messages);
+ }
+
+ // Let's inform the RefMessageBodyService that any our shared messages are
+ // now invalid.
+ mRefMessageBodyService->ForgetPort(mIdentifier->uuid());
+
+ // Only clear mMessages after the MessageData instances have gone out of scope
+ // because they borrow mMessages' underlying JSStructuredCloneDatas.
+ mMessages.Clear();
+
+ mActor->SetPort(nullptr);
+ mActor = nullptr;
+
+ UpdateMustKeepAlive();
+}
+
+void MessagePort::CloneAndDisentangle(UniqueMessagePortId& aIdentifier) {
+ MOZ_ASSERT(mIdentifier);
+ MOZ_ASSERT(!mHasBeenTransferredOrClosed);
+
+ mHasBeenTransferredOrClosed = true;
+
+ // We can clone a port that has already been transfered. In this case, on the
+ // otherside will have a neutered port. Here we set neutered to true so that
+ // we are safe in case a early return.
+ aIdentifier.neutered() = true;
+
+ if (mState > eStateEntangled) {
+ return;
+ }
+
+ // We already have a 'next step'. We have to consider this port as already
+ // cloned/closed/disentangled.
+ if (mState == eStateEntanglingForDisentangle ||
+ mState == eStateEntanglingForClose) {
+ return;
+ }
+
+ aIdentifier.uuid() = mIdentifier->uuid();
+ aIdentifier.destinationUuid() = mIdentifier->destinationUuid();
+ aIdentifier.sequenceId() = mIdentifier->sequenceId() + 1;
+ aIdentifier.neutered() = false;
+
+ // We have to entangle first.
+ if (mState == eStateUnshippedEntangled) {
+ MOZ_ASSERT(mUnshippedEntangledPort);
+ MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty());
+
+ RefPtr<MessagePort> port = std::move(mUnshippedEntangledPort);
+
+ // Disconnect the entangled port and connect it to PBackground.
+ if (!port->ConnectToPBackground()) {
+ // We are probably shutting down. We cannot proceed.
+ mState = eStateDisentangled;
+ UpdateMustKeepAlive();
+ return;
+ }
+
+ // In this case, we don't need to be connected to the PBackground service.
+ if (mMessages.IsEmpty()) {
+ aIdentifier.sequenceId() = mIdentifier->sequenceId();
+
+ mState = eStateDisentangled;
+ UpdateMustKeepAlive();
+ return;
+ }
+
+ // Register this component to PBackground.
+ if (!ConnectToPBackground()) {
+ // We are probably shutting down. We cannot proceed.
+ return;
+ }
+
+ mState = eStateEntanglingForDisentangle;
+ return;
+ }
+
+ // Not entangled yet, we have to wait.
+ if (mState == eStateEntangling) {
+ mState = eStateEntanglingForDisentangle;
+ return;
+ }
+
+ MOZ_ASSERT(mState == eStateEntangled);
+ StartDisentangling();
+}
+
+void MessagePort::Closed() {
+ if (mState >= eStateDisentangled) {
+ return;
+ }
+
+ mState = eStateDisentangledForClose;
+
+ if (mActor) {
+ mActor->SetPort(nullptr);
+ mActor = nullptr;
+ }
+
+ UpdateMustKeepAlive();
+}
+
+bool MessagePort::ConnectToPBackground() {
+ RefPtr<MessagePort> self = this;
+ auto raii = MakeScopeExit([self] {
+ self->mState = eStateDisentangled;
+ self->UpdateMustKeepAlive();
+ });
+
+ mozilla::ipc::PBackgroundChild* actorChild =
+ mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actorChild)) {
+ return false;
+ }
+
+ PMessagePortChild* actor = actorChild->SendPMessagePortConstructor(
+ mIdentifier->uuid(), mIdentifier->destinationUuid(),
+ mIdentifier->sequenceId());
+ if (NS_WARN_IF(!actor)) {
+ return false;
+ }
+
+ mActor = static_cast<MessagePortChild*>(actor);
+ MOZ_ASSERT(mActor);
+
+ mActor->SetPort(this);
+ mState = eStateEntangling;
+
+ raii.release();
+ return true;
+}
+
+void MessagePort::UpdateMustKeepAlive() {
+ if (mState >= eStateDisentangled && mMessages.IsEmpty() && mIsKeptAlive) {
+ mIsKeptAlive = false;
+
+ // The DTOR of this WorkerRef will release the worker for us.
+ mWorkerRef = nullptr;
+
+ Release();
+ return;
+ }
+
+ if (mState < eStateDisentangled && !mIsKeptAlive) {
+ mIsKeptAlive = true;
+ AddRef();
+ }
+}
+
+void MessagePort::DisconnectFromOwner() {
+ CloseForced();
+ DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+void MessagePort::RemoveDocFromBFCache() {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ if (nsPIDOMWindowInner* window = GetOwner()) {
+ window->RemoveFromBFCacheSync();
+ }
+}
+
+/* static */
+void MessagePort::ForceClose(const MessagePortIdentifier& aIdentifier) {
+ mozilla::ipc::PBackgroundChild* actorChild =
+ mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actorChild)) {
+ MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+ }
+
+ Unused << actorChild->SendMessagePortForceClose(aIdentifier.uuid(),
+ aIdentifier.destinationUuid(),
+ aIdentifier.sequenceId());
+}
+
+void MessagePort::DispatchError() {
+ nsCOMPtr<nsIGlobalObject> globalObject = GetParentObject();
+
+ AutoJSAPI jsapi;
+ if (!globalObject || !jsapi.Init(globalObject)) {
+ NS_WARNING("Failed to initialize AutoJSAPI object.");
+ return;
+ }
+
+ RootedDictionary<MessageEventInit> init(jsapi.cx());
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ RefPtr<Event> event =
+ MessageEvent::Constructor(this, u"messageerror"_ns, init);
+ event->SetTrusted(true);
+
+ DispatchEvent(*event);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/messagechannel/MessagePort.h b/dom/messagechannel/MessagePort.h
new file mode 100644
index 0000000000..b0365e6b81
--- /dev/null
+++ b/dom/messagechannel/MessagePort.h
@@ -0,0 +1,233 @@
+/* -*- 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_MessagePort_h
+#define mozilla_dom_MessagePort_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+class nsIGlobalObject;
+
+namespace mozilla::dom {
+
+class MessageData;
+class MessagePortChild;
+class PostMessageRunnable;
+class RefMessageBodyService;
+class SharedMessageBody;
+class StrongWorkerRef;
+struct StructuredSerializeOptions;
+
+// A class to hold a MessagePortIdentifier from
+// MessagePort::CloneAndDistentangle() and close if neither passed to
+// MessagePort::Create() nor release()ed to send via IPC.
+// When the `neutered` field of the MessagePortIdentifier is false, a close is
+// required.
+// This does not derive from MessagePortIdentifier because
+// MessagePortIdentifier is final and because use of UniqueMessagePortId as a
+// MessagePortIdentifier is intentionally prevented without release of
+// ownership.
+class UniqueMessagePortId final {
+ public:
+ UniqueMessagePortId() { mIdentifier.neutered() = true; }
+ explicit UniqueMessagePortId(const MessagePortIdentifier& aIdentifier)
+ : mIdentifier(aIdentifier) {}
+ UniqueMessagePortId(UniqueMessagePortId&& aOther) noexcept
+ : mIdentifier(aOther.mIdentifier) {
+ aOther.mIdentifier.neutered() = true;
+ }
+ ~UniqueMessagePortId() { ForceClose(); };
+ void ForceClose();
+
+ [[nodiscard]] MessagePortIdentifier release() {
+ MessagePortIdentifier id = mIdentifier;
+ mIdentifier.neutered() = true;
+ return id;
+ }
+ // const member accessors are not required because a const
+ // UniqueMessagePortId is not useful.
+ nsID& uuid() { return mIdentifier.uuid(); }
+ nsID& destinationUuid() { return mIdentifier.destinationUuid(); }
+ uint32_t& sequenceId() { return mIdentifier.sequenceId(); }
+ bool& neutered() { return mIdentifier.neutered(); }
+
+ UniqueMessagePortId(const UniqueMessagePortId& aOther) = delete;
+ void operator=(const UniqueMessagePortId& aOther) = delete;
+
+ private:
+ MessagePortIdentifier mIdentifier;
+};
+
+class MessagePort final : public DOMEventTargetHelper {
+ friend class PostMessageRunnable;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MessagePort, DOMEventTargetHelper)
+
+ static already_AddRefed<MessagePort> Create(nsIGlobalObject* aGlobal,
+ const nsID& aUUID,
+ const nsID& aDestinationUUID,
+ ErrorResult& aRv);
+
+ static already_AddRefed<MessagePort> Create(nsIGlobalObject* aGlobal,
+ UniqueMessagePortId& aIdentifier,
+ ErrorResult& aRv);
+
+ // For IPC.
+ static void ForceClose(const MessagePortIdentifier& aIdentifier);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable, ErrorResult& aRv);
+
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const StructuredSerializeOptions& aOptions,
+ ErrorResult& aRv);
+
+ void Start();
+
+ void Close();
+
+ EventHandlerNonNull* GetOnmessage();
+
+ void SetOnmessage(EventHandlerNonNull* aCallback);
+
+ IMPL_EVENT_HANDLER(messageerror)
+
+ // Non WebIDL methods
+
+ void UnshippedEntangle(MessagePort* aEntangledPort);
+
+ bool CanBeCloned() const { return !mHasBeenTransferredOrClosed; }
+
+ void CloneAndDisentangle(UniqueMessagePortId& aIdentifier);
+
+ void CloseForced();
+
+ // These methods are useful for MessagePortChild
+
+ void Entangled(nsTArray<MessageData>& aMessages);
+ void MessagesReceived(nsTArray<MessageData>& aMessages);
+ void StopSendingDataConfirmed();
+ void Closed();
+
+ private:
+ enum State {
+ // When a port is created by a MessageChannel it is entangled with the
+ // other. They both run on the same thread, same event loop and the
+ // messages are added to the queues without using PBackground actors.
+ // When one of the port is shipped, the state is changed to
+ // StateEntangling.
+ eStateUnshippedEntangled,
+
+ // If the port is closed or cloned when we are in this state, we go in one
+ // of the following 2 steps. EntanglingForClose or ForDisentangle.
+ eStateEntangling,
+
+ // We are not fully entangled yet but are already disentangled.
+ eStateEntanglingForDisentangle,
+
+ // We are not fully entangled yet but are already closed.
+ eStateEntanglingForClose,
+
+ // When entangled() is received we send all the messages in the
+ // mMessagesForTheOtherPort to the actor and we change the state to
+ // StateEntangled. At this point the port is entangled with the other. We
+ // send and receive messages.
+ // If the port queue is not enabled, the received messages are stored in
+ // the mMessages.
+ eStateEntangled,
+
+ // When the port is cloned or disentangled we want to stop receiving
+ // messages. We call 'SendStopSendingData' to the actor and we wait for an
+ // answer. All the messages received between now and the
+ // 'StopSendingDataComfirmed are queued in the mMessages but not
+ // dispatched.
+ eStateDisentangling,
+
+ // When 'StopSendingDataConfirmed' is received, we can disentangle the port
+ // calling SendDisentangle in the actor because we are 100% sure that we
+ // don't receive any other message, so nothing will be lost.
+ // Disentangling the port we send all the messages from the mMessages
+ // though the actor.
+ eStateDisentangled,
+
+ // We are here if Close() has been called. We are disentangled but we can
+ // still send pending messages.
+ eStateDisentangledForClose
+ };
+
+ explicit MessagePort(nsIGlobalObject* aGlobal, State aState);
+ ~MessagePort();
+
+ void DisconnectFromOwner() override;
+
+ void Initialize(const nsID& aUUID, const nsID& aDestinationUUID,
+ uint32_t aSequenceID, bool aNeutered, ErrorResult& aRv);
+
+ bool ConnectToPBackground();
+
+ // Dispatch events from the Message Queue using a nsRunnable.
+ void Dispatch();
+
+ void DispatchError();
+
+ void StartDisentangling();
+ void Disentangle();
+
+ void RemoveDocFromBFCache();
+
+ void CloseInternal(bool aSoftly);
+
+ // This method is meant to keep alive the MessagePort when this object is
+ // creating the actor and until the actor is entangled.
+ // We release the object when the port is closed or disentangled.
+ void UpdateMustKeepAlive();
+
+ bool IsCertainlyAliveForCC() const override { return mIsKeptAlive; }
+
+ RefPtr<StrongWorkerRef> mWorkerRef;
+
+ RefPtr<PostMessageRunnable> mPostMessageRunnable;
+
+ RefPtr<MessagePortChild> mActor;
+
+ RefPtr<MessagePort> mUnshippedEntangledPort;
+
+ RefPtr<RefMessageBodyService> mRefMessageBodyService;
+
+ nsTArray<RefPtr<SharedMessageBody>> mMessages;
+ nsTArray<RefPtr<SharedMessageBody>> mMessagesForTheOtherPort;
+
+ UniquePtr<MessagePortIdentifier> mIdentifier;
+
+ State mState;
+
+ bool mMessageQueueEnabled;
+
+ bool mIsKeptAlive;
+
+ // mHasBeenTransferredOrClosed is used to know if this port has been manually
+ // closed or transferred via postMessage. Note that if the entangled port is
+ // closed, this port is closed as well (see mState) but, just because close()
+ // has not been called directly, by spec, this port can still be transferred.
+ bool mHasBeenTransferredOrClosed;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_MessagePort_h
diff --git a/dom/messagechannel/MessagePortChild.cpp b/dom/messagechannel/MessagePortChild.cpp
new file mode 100644
index 0000000000..2c5e270e68
--- /dev/null
+++ b/dom/messagechannel/MessagePortChild.cpp
@@ -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/. */
+
+#include "MessagePortChild.h"
+#include "MessagePort.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+
+namespace mozilla::dom {
+
+MessagePortChild::MessagePortChild() : mPort(nullptr) {}
+
+mozilla::ipc::IPCResult MessagePortChild::RecvStopSendingDataConfirmed() {
+ MOZ_ASSERT(mPort);
+ mPort->StopSendingDataConfirmed();
+ MOZ_ASSERT(!mPort);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult MessagePortChild::RecvEntangled(
+ nsTArray<MessageData>&& aMessages) {
+ if (mPort) {
+ mPort->Entangled(aMessages);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult MessagePortChild::RecvReceiveData(
+ nsTArray<MessageData>&& aMessages) {
+ if (mPort) {
+ mPort->MessagesReceived(aMessages);
+ }
+ return IPC_OK();
+}
+
+void MessagePortChild::ActorDestroy(ActorDestroyReason aWhy) {
+ if (mPort) {
+ mPort->Closed();
+ MOZ_ASSERT(!mPort);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/messagechannel/MessagePortChild.h b/dom/messagechannel/MessagePortChild.h
new file mode 100644
index 0000000000..74f3683f52
--- /dev/null
+++ b/dom/messagechannel/MessagePortChild.h
@@ -0,0 +1,45 @@
+/* -*- 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_MessagePortChild_h
+#define mozilla_dom_MessagePortChild_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/PMessagePortChild.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+class MessagePort;
+
+class MessagePortChild final : public PMessagePortChild {
+ friend class PMessagePortChild;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(MessagePortChild)
+
+ MessagePortChild();
+
+ void SetPort(MessagePort* aPort) { mPort = aPort; }
+
+ private:
+ ~MessagePortChild() { MOZ_ASSERT(!mPort); }
+
+ mozilla::ipc::IPCResult RecvEntangled(nsTArray<MessageData>&& aMessages);
+
+ mozilla::ipc::IPCResult RecvReceiveData(nsTArray<MessageData>&& aMessages);
+
+ mozilla::ipc::IPCResult RecvStopSendingDataConfirmed();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ // This is a raw pointer because this child is owned by this MessagePort.
+ MessagePort* mPort;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_MessagePortChild_h
diff --git a/dom/messagechannel/MessagePortParent.cpp b/dom/messagechannel/MessagePortParent.cpp
new file mode 100644
index 0000000000..846f1c2cda
--- /dev/null
+++ b/dom/messagechannel/MessagePortParent.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 "MessagePortParent.h"
+#include "MessagePortService.h"
+#include "mozilla/dom/RefMessageBodyService.h"
+#include "mozilla/dom/SharedMessageBody.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla::dom {
+
+MessagePortParent::MessagePortParent(const nsID& aUUID)
+ : mService(MessagePortService::GetOrCreate()),
+ mUUID(aUUID),
+ mEntangled(false),
+ mCanSendData(true) {
+ MOZ_ASSERT(mService);
+}
+
+MessagePortParent::~MessagePortParent() {
+ MOZ_ASSERT(!mService);
+ MOZ_ASSERT(!mEntangled);
+}
+
+bool MessagePortParent::Entangle(const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ if (!mService) {
+ NS_WARNING("Entangle is called after a shutdown!");
+ return false;
+ }
+
+ MOZ_ASSERT(!mEntangled);
+
+ return mService->RequestEntangling(this, aDestinationUUID, aSequenceID);
+}
+
+mozilla::ipc::IPCResult MessagePortParent::RecvPostMessages(
+ nsTArray<MessageData>&& aMessages) {
+ if (!mService) {
+ NS_WARNING("PostMessages is called after a shutdown!");
+ // This implies most probably that CloseAndDelete() has been already called
+ // such that we have no better option than to silently ignore this call.
+ return IPC_OK();
+ }
+
+ if (!mEntangled) {
+ // If we were shut down, the above condition already bailed out. So this
+ // should actually never happen and returning a failure is fine.
+ return IPC_FAIL(this, "RecvPostMessages not entangled");
+ }
+
+ // This converts the object in a data struct where we have BlobImpls.
+ FallibleTArray<RefPtr<SharedMessageBody>> messages;
+ if (NS_WARN_IF(!SharedMessageBody::FromMessagesToSharedParent(aMessages,
+ messages))) {
+ // FromMessagesToSharedParent() returns false only if the array allocation
+ // failed.
+ // See bug 1750497 for further discussion if this is the wanted behavior.
+ return IPC_FAIL(this, "SharedMessageBody::FromMessagesToSharedParent");
+ }
+
+ if (messages.IsEmpty()) {
+ // An empty payload can be safely ignored.
+ return IPC_OK();
+ }
+
+ if (!mService->PostMessages(this, std::move(messages))) {
+ // TODO: Verify if all failure conditions of PostMessages() merit an
+ // IPC_FAIL. See bug 1750499.
+ return IPC_FAIL(this, "RecvPostMessages->PostMessages");
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult MessagePortParent::RecvDisentangle(
+ nsTArray<MessageData>&& aMessages) {
+ if (!mService) {
+ NS_WARNING("Entangle is called after a shutdown!");
+ // This implies most probably that CloseAndDelete() has been already called
+ // such that we can silently ignore this call.
+ return IPC_OK();
+ }
+
+ if (!mEntangled) {
+ // If we were shut down, the above condition already bailed out. So this
+ // should actually never happen and returning a failure is fine.
+ return IPC_FAIL(this, "RecvDisentangle not entangled");
+ }
+
+ // This converts the object in a data struct where we have BlobImpls.
+ FallibleTArray<RefPtr<SharedMessageBody>> messages;
+ if (NS_WARN_IF(!SharedMessageBody::FromMessagesToSharedParent(aMessages,
+ messages))) {
+ // TODO: Verify if failed allocations merit an IPC_FAIL. See bug 1750497.
+ return IPC_FAIL(this, "SharedMessageBody::FromMessagesToSharedParent");
+ }
+
+ if (!mService->DisentanglePort(this, std::move(messages))) {
+ // TODO: Verify if all failure conditions of DisentanglePort() merit an
+ // IPC_FAIL. See bug 1750501.
+ return IPC_FAIL(this, "RecvDisentangle->DisentanglePort");
+ }
+
+ CloseAndDelete();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult MessagePortParent::RecvStopSendingData() {
+ if (!mEntangled) {
+ return IPC_OK();
+ }
+
+ mCanSendData = false;
+ Unused << SendStopSendingDataConfirmed();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult MessagePortParent::RecvClose() {
+ if (mService) {
+ MOZ_ASSERT(mEntangled);
+
+ if (!mService->ClosePort(this)) {
+ return IPC_FAIL(this, "RecvClose->ClosePort");
+ }
+
+ Close();
+ }
+
+ MOZ_ASSERT(!mEntangled);
+
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+void MessagePortParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (mService && mEntangled) {
+ // When the last parent is deleted, this service is freed but this cannot
+ // be done when the hashtables are written by CloseAll.
+ RefPtr<MessagePortService> kungFuDeathGrip = mService;
+ kungFuDeathGrip->ParentDestroy(this);
+ }
+}
+
+bool MessagePortParent::Entangled(nsTArray<MessageData>&& aMessages) {
+ MOZ_ASSERT(!mEntangled);
+ mEntangled = true;
+ return SendEntangled(aMessages);
+}
+
+void MessagePortParent::CloseAndDelete() {
+ Close();
+ Unused << Send__delete__(this);
+}
+
+void MessagePortParent::Close() {
+ mService = nullptr;
+ mEntangled = false;
+}
+
+/* static */
+bool MessagePortParent::ForceClose(const nsID& aUUID,
+ const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ MessagePortService* service = MessagePortService::Get();
+ if (!service) {
+ NS_WARNING(
+ "The service must exist if we want to close an existing MessagePort.");
+ // There is nothing to close so we are ok.
+ return true;
+ }
+
+ return service->ForceClose(aUUID, aDestinationUUID, aSequenceID);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/messagechannel/MessagePortParent.h b/dom/messagechannel/MessagePortParent.h
new file mode 100644
index 0000000000..13c92a1ef6
--- /dev/null
+++ b/dom/messagechannel/MessagePortParent.h
@@ -0,0 +1,61 @@
+/* -*- 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_MessagePortParent_h
+#define mozilla_dom_MessagePortParent_h
+
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/PMessagePortParent.h"
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+
+namespace mozilla::dom {
+
+class MessagePortService;
+
+class MessagePortParent final
+ : public PMessagePortParent,
+ public SupportsWeakPtr,
+ public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
+ friend class PMessagePortParent;
+
+ public:
+ explicit MessagePortParent(const nsID& aUUID);
+ ~MessagePortParent();
+
+ bool Entangle(const nsID& aDestinationUUID, const uint32_t& aSequenceID);
+
+ bool Entangled(nsTArray<MessageData>&& aMessages);
+
+ void Close();
+ void CloseAndDelete();
+
+ bool CanSendData() const { return mCanSendData; }
+
+ const nsID& ID() const { return mUUID; }
+
+ static bool ForceClose(const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID);
+
+ private:
+ mozilla::ipc::IPCResult RecvPostMessages(nsTArray<MessageData>&& aMessages);
+
+ mozilla::ipc::IPCResult RecvDisentangle(nsTArray<MessageData>&& aMessages);
+
+ mozilla::ipc::IPCResult RecvStopSendingData();
+
+ mozilla::ipc::IPCResult RecvClose();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ RefPtr<MessagePortService> mService;
+ const nsID mUUID;
+ bool mEntangled;
+ bool mCanSendData;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_MessagePortParent_h
diff --git a/dom/messagechannel/MessagePortService.cpp b/dom/messagechannel/MessagePortService.cpp
new file mode 100644
index 0000000000..ce7afd4aeb
--- /dev/null
+++ b/dom/messagechannel/MessagePortService.cpp
@@ -0,0 +1,408 @@
+/* -*- 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 "MessagePortService.h"
+#include "MessagePortParent.h"
+#include "mozilla/dom/RefMessageBodyService.h"
+#include "mozilla/dom/SharedMessageBody.h"
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WeakPtr.h"
+#include "nsTArray.h"
+
+using mozilla::ipc::AssertIsOnBackgroundThread;
+
+namespace mozilla::dom {
+
+namespace {
+
+StaticRefPtr<MessagePortService> gInstance;
+
+void AssertIsInMainProcess() {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+}
+
+} // namespace
+
+struct MessagePortService::NextParent {
+ uint32_t mSequenceID;
+ // MessagePortParent keeps the service alive, and we don't want a cycle.
+ WeakPtr<MessagePortParent> mParent;
+};
+
+} // namespace mozilla::dom
+
+namespace mozilla::dom {
+
+class MessagePortService::MessagePortServiceData final {
+ public:
+ explicit MessagePortServiceData(const nsID& aDestinationUUID)
+ : mDestinationUUID(aDestinationUUID),
+ mSequenceID(1),
+ mParent(nullptr)
+ // By default we don't know the next parent.
+ ,
+ mWaitingForNewParent(true),
+ mNextStepCloseAll(false) {
+ MOZ_COUNT_CTOR(MessagePortServiceData);
+ }
+
+ MessagePortServiceData(const MessagePortServiceData& aOther) = delete;
+ MessagePortServiceData& operator=(const MessagePortServiceData&) = delete;
+
+ MOZ_COUNTED_DTOR(MessagePortServiceData)
+
+ nsID mDestinationUUID;
+
+ uint32_t mSequenceID;
+ CheckedUnsafePtr<MessagePortParent> mParent;
+
+ FallibleTArray<NextParent> mNextParents;
+ FallibleTArray<RefPtr<SharedMessageBody>> mMessages;
+
+ bool mWaitingForNewParent;
+ bool mNextStepCloseAll;
+};
+
+/* static */
+MessagePortService* MessagePortService::Get() {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ return gInstance;
+}
+
+/* static */
+MessagePortService* MessagePortService::GetOrCreate() {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ if (!gInstance) {
+ gInstance = new MessagePortService();
+ }
+
+ return gInstance;
+}
+
+bool MessagePortService::RequestEntangling(MessagePortParent* aParent,
+ const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ MOZ_ASSERT(aParent);
+ MessagePortServiceData* data;
+
+ // If we don't have a MessagePortServiceData, we must create 2 of them for
+ // both ports.
+ if (!mPorts.Get(aParent->ID(), &data)) {
+ // Create the MessagePortServiceData for the destination.
+ if (mPorts.Get(aDestinationUUID, nullptr)) {
+ MOZ_ASSERT(false, "The creation of the 2 ports should be in sync.");
+ return false;
+ }
+
+ mPorts.InsertOrUpdate(aDestinationUUID,
+ MakeUnique<MessagePortServiceData>(aParent->ID()));
+
+ data = mPorts
+ .InsertOrUpdate(
+ aParent->ID(),
+ MakeUnique<MessagePortServiceData>(aDestinationUUID))
+ .get();
+ }
+
+ // This is a security check.
+ if (!data->mDestinationUUID.Equals(aDestinationUUID)) {
+ MOZ_ASSERT(false, "DestinationUUIDs do not match!");
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ if (aSequenceID < data->mSequenceID) {
+ MOZ_ASSERT(false, "Invalid sequence ID!");
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ if (aSequenceID == data->mSequenceID) {
+ if (data->mParent) {
+ MOZ_ASSERT(false, "Two ports cannot have the same sequenceID.");
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ // We activate this port, sending all the messages.
+ data->mParent = aParent;
+ data->mWaitingForNewParent = false;
+
+ // We want to ensure we clear data->mMessages even if we early return, while
+ // also ensuring that its contents remain alive until after array's contents
+ // are destroyed because of JSStructuredCloneData borrowing. So we use
+ // Move to initialize things swapped and do it before we declare `array` so
+ // that reverse destruction order works for us.
+ FallibleTArray<RefPtr<SharedMessageBody>> messages(
+ std::move(data->mMessages));
+ nsTArray<MessageData> array;
+ if (!SharedMessageBody::FromSharedToMessagesParent(aParent->Manager(),
+ messages, array)) {
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ // We can entangle the port.
+ if (!aParent->Entangled(std::move(array))) {
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ // If we were waiting for this parent in order to close this channel, this
+ // is the time to do it.
+ if (data->mNextStepCloseAll) {
+ CloseAll(aParent->ID());
+ }
+
+ return true;
+ }
+
+ // This new parent will be the next one when a Disentangle request is
+ // received from the current parent.
+ auto nextParent = data->mNextParents.AppendElement(mozilla::fallible);
+ if (!nextParent) {
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ nextParent->mSequenceID = aSequenceID;
+ nextParent->mParent = aParent;
+
+ return true;
+}
+
+bool MessagePortService::DisentanglePort(
+ MessagePortParent* aParent,
+ FallibleTArray<RefPtr<SharedMessageBody>> aMessages) {
+ MessagePortServiceData* data;
+ if (!mPorts.Get(aParent->ID(), &data)) {
+ MOZ_ASSERT(false, "Unknown MessagePortParent should not happen.");
+ return false;
+ }
+
+ if (data->mParent != aParent) {
+ MOZ_ASSERT(
+ false,
+ "DisentanglePort() should be called just from the correct parent.");
+ return false;
+ }
+
+ // Let's put the messages in the correct order. |aMessages| contains the
+ // unsent messages so they have to go first.
+ if (!aMessages.AppendElements(std::move(data->mMessages),
+ mozilla::fallible)) {
+ return false;
+ }
+
+ ++data->mSequenceID;
+
+ // If we don't have a parent, we have to store the pending messages and wait.
+ uint32_t index = 0;
+ MessagePortParent* nextParent = nullptr;
+ for (; index < data->mNextParents.Length(); ++index) {
+ if (data->mNextParents[index].mSequenceID == data->mSequenceID) {
+ nextParent = data->mNextParents[index].mParent;
+ break;
+ }
+ }
+
+ // We didn't find the parent.
+ if (!nextParent) {
+ data->mMessages = std::move(aMessages);
+ data->mWaitingForNewParent = true;
+ data->mParent = nullptr;
+ return true;
+ }
+
+ data->mParent = nextParent;
+ data->mNextParents.RemoveElementAt(index);
+
+ nsTArray<MessageData> array;
+ if (!SharedMessageBody::FromSharedToMessagesParent(data->mParent->Manager(),
+ aMessages, array)) {
+ return false;
+ }
+
+ Unused << data->mParent->Entangled(std::move(array));
+ return true;
+}
+
+bool MessagePortService::ClosePort(MessagePortParent* aParent) {
+ MessagePortServiceData* data;
+ if (!mPorts.Get(aParent->ID(), &data)) {
+ MOZ_ASSERT(false, "Unknown MessagePortParent should not happend.");
+ return false;
+ }
+
+ if (data->mParent != aParent) {
+ MOZ_ASSERT(false,
+ "ClosePort() should be called just from the correct parent.");
+ return false;
+ }
+
+ if (!data->mNextParents.IsEmpty()) {
+ MOZ_ASSERT(false,
+ "ClosePort() should be called when there are not next parents.");
+ return false;
+ }
+
+ // We don't want to send a message to this parent.
+ data->mParent = nullptr;
+
+ CloseAll(aParent->ID());
+ return true;
+}
+
+void MessagePortService::CloseAll(const nsID& aUUID, bool aForced) {
+ MessagePortServiceData* data;
+ if (!mPorts.Get(aUUID, &data)) {
+ MaybeShutdown();
+ return;
+ }
+
+ if (data->mParent) {
+ data->mParent->Close();
+ data->mParent = nullptr;
+ }
+
+ for (const auto& nextParent : data->mNextParents) {
+ MessagePortParent* const parent = nextParent.mParent;
+ if (parent) {
+ parent->CloseAndDelete();
+ }
+ }
+ data->mNextParents.Clear();
+
+ nsID destinationUUID = data->mDestinationUUID;
+
+ // If we have informations about the other port and that port has some
+ // pending messages to deliver but the parent has not processed them yet,
+ // because its entangling request didn't arrive yet), we cannot close this
+ // channel.
+ MessagePortServiceData* destinationData;
+ if (!aForced && mPorts.Get(destinationUUID, &destinationData) &&
+ !destinationData->mMessages.IsEmpty() &&
+ destinationData->mWaitingForNewParent) {
+ MOZ_ASSERT(!destinationData->mNextStepCloseAll);
+ destinationData->mNextStepCloseAll = true;
+ return;
+ }
+
+ mPorts.Remove(aUUID);
+
+ CloseAll(destinationUUID, aForced);
+
+ // CloseAll calls itself recursively and it can happen that it deletes
+ // itself. Before continuing we must check if we are still alive.
+ if (!gInstance) {
+ return;
+ }
+
+ MOZ_ASSERT(!mPorts.Contains(aUUID));
+
+ MaybeShutdown();
+}
+
+// This service can be dismissed when there are not active ports.
+void MessagePortService::MaybeShutdown() {
+ if (mPorts.Count() == 0) {
+ gInstance = nullptr;
+ }
+}
+
+bool MessagePortService::PostMessages(
+ MessagePortParent* aParent,
+ FallibleTArray<RefPtr<SharedMessageBody>> aMessages) {
+ MessagePortServiceData* data;
+ if (!mPorts.Get(aParent->ID(), &data)) {
+ MOZ_ASSERT(false, "Unknown MessagePortParent should not happend.");
+ return false;
+ }
+
+ if (data->mParent != aParent) {
+ MOZ_ASSERT(false,
+ "PostMessages() should be called just from the correct parent.");
+ return false;
+ }
+
+ MOZ_ALWAYS_TRUE(mPorts.Get(data->mDestinationUUID, &data));
+
+ if (!data->mMessages.AppendElements(std::move(aMessages),
+ mozilla::fallible)) {
+ return false;
+ }
+
+ // If the parent can send data to the child, let's proceed.
+ if (data->mParent && data->mParent->CanSendData()) {
+ {
+ nsTArray<MessageData> messages;
+ if (!SharedMessageBody::FromSharedToMessagesParent(
+ data->mParent->Manager(), data->mMessages, messages)) {
+ return false;
+ }
+
+ Unused << data->mParent->SendReceiveData(messages);
+ }
+ // `messages` borrows the underlying JSStructuredCloneData so we need to
+ // avoid destroying the `mMessages` until after we've destroyed `messages`.
+ data->mMessages.Clear();
+ }
+
+ return true;
+}
+
+void MessagePortService::ParentDestroy(MessagePortParent* aParent) {
+ // This port has already been destroyed.
+ MessagePortServiceData* data;
+ if (!mPorts.Get(aParent->ID(), &data)) {
+ return;
+ }
+
+ if (data->mParent != aParent) {
+ // We don't want to send a message to this parent.
+ for (uint32_t i = 0; i < data->mNextParents.Length(); ++i) {
+ if (aParent == data->mNextParents[i].mParent) {
+ data->mNextParents.RemoveElementAt(i);
+ break;
+ }
+ }
+ }
+
+ CloseAll(aParent->ID());
+}
+
+bool MessagePortService::ForceClose(const nsID& aUUID,
+ const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ MessagePortServiceData* data;
+ if (!mPorts.Get(aUUID, &data)) {
+ NS_WARNING("Unknown MessagePort in ForceClose()");
+ // There is nothing to close so we are ok.
+ return true;
+ }
+
+ NS_ENSURE_TRUE(data->mDestinationUUID.Equals(aDestinationUUID), false);
+
+ // If StructuredCloneData includes a MessagePort, StructuredCloneData
+ // serialization failure in postMessage can trigger MessagePort::ForceClose().
+ // And since the serialized port transfered has started but not finished yet,
+ // the SequenceID will not be synchronized to the parent side, which will
+ // cause the SequenceID to mismatch here. See bug 1872770.
+ NS_WARNING_ASSERTION(data->mSequenceID == aSequenceID,
+ "sequence IDs do not match");
+
+ CloseAll(aUUID, true);
+ return true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/messagechannel/MessagePortService.h b/dom/messagechannel/MessagePortService.h
new file mode 100644
index 0000000000..c98900f366
--- /dev/null
+++ b/dom/messagechannel/MessagePortService.h
@@ -0,0 +1,60 @@
+/* -*- 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_MessagePortService_h
+#define mozilla_dom_MessagePortService_h
+
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+class MessagePortParent;
+class SharedMessageBody;
+
+class MessagePortService final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(MessagePortService)
+
+ // Needs to be public for the MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR
+ // macro.
+ struct NextParent;
+
+ static MessagePortService* Get();
+ static MessagePortService* GetOrCreate();
+
+ bool RequestEntangling(MessagePortParent* aParent,
+ const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID);
+
+ bool DisentanglePort(MessagePortParent* aParent,
+ FallibleTArray<RefPtr<SharedMessageBody>> aMessages);
+
+ bool ClosePort(MessagePortParent* aParent);
+
+ bool PostMessages(MessagePortParent* aParent,
+ FallibleTArray<RefPtr<SharedMessageBody>> aMessages);
+
+ void ParentDestroy(MessagePortParent* aParent);
+
+ bool ForceClose(const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID);
+
+ private:
+ ~MessagePortService() = default;
+
+ void CloseAll(const nsID& aUUID, bool aForced = false);
+ void MaybeShutdown();
+
+ class MessagePortServiceData;
+
+ nsClassHashtable<nsIDHashKey, MessagePortServiceData> mPorts;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_MessagePortService_h
diff --git a/dom/messagechannel/PMessagePort.ipdl b/dom/messagechannel/PMessagePort.ipdl
new file mode 100644
index 0000000000..599e442df3
--- /dev/null
+++ b/dom/messagechannel/PMessagePort.ipdl
@@ -0,0 +1,47 @@
+/* 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;
+include ProtocolTypes;
+
+namespace mozilla {
+namespace dom {
+
+// This protocol is used for the MessageChannel/MessagePort API
+[ManualDealloc]
+protocol PMessagePort
+{
+ manager PBackground;
+
+ /* Many of these methods are used just for the shutdown sequence. The
+ correct sequence for the child actor is:
+ 1. SendStopSendingData();
+ 2. RecvStopSendingDataConfirmed();
+ 3. SendClose();
+ 4. Recv__delete__(); */
+
+ /* When the port is transferred the sequence is:
+ 1. SendStopSendingData();
+ 2. RecvStopSendingDataConfirmed();
+ 3. SendDisentangle();
+ 4. Recv__delete__(); */
+
+parent:
+ async PostMessages(MessageData[] messages);
+ async Disentangle(MessageData[] messages);
+ async StopSendingData();
+ async Close();
+
+child:
+ async Entangled(MessageData[] messages);
+ async ReceiveData(MessageData[] messages);
+ async StopSendingDataConfirmed();
+
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/messagechannel/moz.build b/dom/messagechannel/moz.build
new file mode 100644
index 0000000000..d2726be851
--- /dev/null
+++ b/dom/messagechannel/moz.build
@@ -0,0 +1,38 @@
+# -*- 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")
+
+TEST_DIRS += ["tests"]
+
+EXPORTS.mozilla.dom += [
+ "MessageChannel.h",
+ "MessagePort.h",
+ "MessagePortChild.h",
+ "MessagePortParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "MessageChannel.cpp",
+ "MessagePort.cpp",
+ "MessagePortChild.cpp",
+ "MessagePortParent.cpp",
+ "MessagePortService.cpp",
+]
+
+IPDL_SOURCES += [
+ "PMessagePort.ipdl",
+]
+
+LOCAL_INCLUDES += [
+ "../base",
+ "../events",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/messagechannel/tests/chrome.toml b/dom/messagechannel/tests/chrome.toml
new file mode 100644
index 0000000000..b66ebfbfd3
--- /dev/null
+++ b/dom/messagechannel/tests/chrome.toml
@@ -0,0 +1,15 @@
+[DEFAULT]
+support-files = [
+ "iframe_messageChannel_chrome.html",
+ "mm_messageChannelParent.xhtml",
+ "mm_messageChannelParentNotRemote.xhtml",
+ "mm_messageChannelParent.js",
+ "mm_messageChannel.js",
+]
+
+["test_messageChannel.xhtml"]
+
+["test_messageChannelWithMessageManager.xhtml"]
+skip-if = ["os == 'android'"]
+
+["test_messageChannelWithMessageManagerNotRemote.xhtml"]
diff --git a/dom/messagechannel/tests/iframe_messageChannel_chrome.html b/dom/messagechannel/tests/iframe_messageChannel_chrome.html
new file mode 100644
index 0000000000..5fcd43a34c
--- /dev/null
+++ b/dom/messagechannel/tests/iframe_messageChannel_chrome.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script type="application/javascript">
+
+window.addEventListener('message', receiveMessage);
+function receiveMessage(evt) {
+ evt.data.postMessage("Hello world");
+}
+ </script>
+</body>
diff --git a/dom/messagechannel/tests/iframe_messageChannel_cloning.html b/dom/messagechannel/tests/iframe_messageChannel_cloning.html
new file mode 100644
index 0000000000..3012f9a845
--- /dev/null
+++ b/dom/messagechannel/tests/iframe_messageChannel_cloning.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script type="application/javascript">
+
+ function ok(a, msg) {
+ window.parent.postMessage({ status: a ? "OK" : "KO", message: msg }, "*");
+ }
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ ok (evt.data, "Data received");
+ ok (evt.data.port instanceof MessagePort, "Data contains a MessagePort");
+
+ var a = new MessageChannel();
+ window.parent.postMessage({ status: "FINISH", port: a.port2 }, '*', [a.port2]);
+ }
+
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/iframe_messageChannel_pingpong.html b/dom/messagechannel/tests/iframe_messageChannel_pingpong.html
new file mode 100644
index 0000000000..c4decd35a7
--- /dev/null
+++ b/dom/messagechannel/tests/iframe_messageChannel_pingpong.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script type="application/javascript">
+
+ function ok(what, msg) {
+ window.parent.postMessage({type: what ? 'OK' : 'KO', msg }, '*');
+ }
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ if (evt.data.type == 'PORT') {
+ var port = evt.data.port;
+ var counter = 0;
+ port.onmessage = function(event) {
+ if (counter++ == 0) {
+ ok(!(event.data % 2), "The number " + event.data + " has been received correctly by the iframe");
+
+ window.parent.postMessage({ type: 'PORT', port }, '*', [port]);
+ }
+ else {
+ ok(false, "Wrong message!");
+ }
+ }
+ } else {
+ ok(false, "Unknown message");
+ }
+ }
+
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/iframe_messageChannel_post.html b/dom/messagechannel/tests/iframe_messageChannel_post.html
new file mode 100644
index 0000000000..846b247f0b
--- /dev/null
+++ b/dom/messagechannel/tests/iframe_messageChannel_post.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script type="application/javascript">
+
+ var port;
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ port = evt.data.port;
+
+ port.addEventListener('message', receivePostMessage);
+ function receivePostMessage(event) {
+ port.postMessage(event.data);
+ }
+ port.start();
+
+ window.parent.postMessage({ status: "READY" }, '*');
+ }
+
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html b/dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html
new file mode 100644
index 0000000000..b96026955d
--- /dev/null
+++ b/dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script type="application/javascript">
+
+ var a = new SharedWorker('sharedWorker2_messageChannel.js');
+ a.port.onmessage = function(evt) {
+ evt.ports[0].postMessage("Hello from the iframe!");
+ }
+
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/iframe_messageChannel_transferable.html b/dom/messagechannel/tests/iframe_messageChannel_transferable.html
new file mode 100644
index 0000000000..0a00add71f
--- /dev/null
+++ b/dom/messagechannel/tests/iframe_messageChannel_transferable.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script type="application/javascript">
+
+ function ok(what, msg) {
+ window.parent.postMessage({type: what ? 'OK' : 'KO', msg }, '*');
+ }
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ ok(evt.ports.length == 1, "Port transferred!");
+
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+ evt.ports[0].postMessage('hello world!', [a.port2]);
+ a.port1.onmessage = function(event) {
+ event.target.postMessage(event.data);
+ }
+ }
+
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/mm_messageChannel.js b/dom/messagechannel/tests/mm_messageChannel.js
new file mode 100644
index 0000000000..7acd5df530
--- /dev/null
+++ b/dom/messagechannel/tests/mm_messageChannel.js
@@ -0,0 +1,76 @@
+/* eslint-env mozilla/frame-script */
+
+function debug(msg) {
+ dump("[mmMessageChannelChild]" + msg + "\n");
+}
+
+/**
+ * Preparation Test
+ */
+let port;
+let toString = Object.prototype.toString;
+
+(function prepare() {
+ debug("Script loaded.");
+ addTestReceiver();
+ sendAsyncMessage("mmMessagePort:finishScriptLoad");
+})();
+
+function ok(condition, message) {
+ debug("condition: " + condition + ", " + message + "\n");
+ if (!condition) {
+ sendAsyncMessage("mmMessagePort:fail", { message });
+ throw new Error("failed check: " + message);
+ }
+}
+
+function is(a, b, message) {
+ ok(a === b, message);
+}
+
+/**
+ * Testing codes.
+ */
+function addTestReceiver() {
+ addMessageListener("BasicTest:PortCreated", basicTest);
+ addMessageListener("CloseTest:PortCreated", closeTest);
+ addMessageListener("EmptyTest:PortCreated", emptyTest);
+ addMessageListener("NotTransferableTest:PortCreated", notTransferableTest);
+}
+
+function basicTest(msg) {
+ port = msg.ports[0];
+ is(toString.call(port), "[object MessagePort]", "created MessagePort.");
+
+ port.onmessage = message => {
+ is(message.data, "BasicTest:StartTest", "Replied message is correct.");
+ port.postMessage("BasicTest:TestOK");
+ };
+
+ sendAsyncMessage("BasicTest:FinishPrepare", { message: "OK" });
+}
+
+function closeTest(msg) {
+ port = msg.ports[0];
+ is(toString.call(port), "[object MessagePort]", "created MessagePort.");
+
+ port.onmessage = message => {
+ ok(message.data, "CloseTest:StartTest", "Replied message is correct.");
+ port.postMessage("CloseTest:TestOK");
+ };
+
+ port.close();
+
+ sendAsyncMessage("CloseTest:FinishPrepare", { message: "OK" });
+}
+
+function emptyTest(msg) {
+ let portSize = msg.ports.length;
+ is(portSize, 0, "transfered port size is zero.");
+
+ sendAsyncMessage("EmptyTest:FinishPrepare", { message: "OK" });
+}
+
+function notTransferableTest(msg) {
+ sendAsyncMessage("NotTransferableTest:FinishPrepare", { message: "OK" });
+}
diff --git a/dom/messagechannel/tests/mm_messageChannelParent.js b/dom/messagechannel/tests/mm_messageChannelParent.js
new file mode 100644
index 0000000000..a5595722a2
--- /dev/null
+++ b/dom/messagechannel/tests/mm_messageChannelParent.js
@@ -0,0 +1,144 @@
+let port;
+let mm;
+
+function info(message) {
+ return window.arguments[0].info(message);
+}
+
+function ok(condition, message) {
+ return window.arguments[0].ok(condition, message);
+}
+
+function is(v1, v2, message) {
+ return window.arguments[0].is(v1, v2, message);
+}
+
+function todo_is(v1, v2, message) {
+ return window.arguments[0].todo_is(v1, v2, message);
+}
+
+function cleanUp() {
+ window.arguments[0].setTimeout(function () {
+ this.done();
+ }, 0);
+ window.close();
+}
+
+function debug(msg) {
+ dump("[mmMessageChannelParent]" + msg + "\n");
+}
+
+let tests = [basic_test, close_test, empty_transferable, not_transferable];
+
+// Test Routine
+function run_tests() {
+ let test = tests.shift();
+ if (test === undefined) {
+ cleanUp();
+ return;
+ }
+
+ test(function () {
+ setTimeout(run_tests, 0);
+ });
+}
+
+// Basic communication test.
+function basic_test(finish) {
+ ok(mm, "basic_test");
+
+ let finishPrepare = msg => {
+ is(msg.data.message, "OK", "");
+ ok(port, "");
+ port.onmessage = message => {
+ is(message.data, "BasicTest:TestOK", "");
+ finish();
+ };
+ port.postMessage("BasicTest:StartTest");
+ mm.removeMessageListener("BasicTest:FinishPrepare", finishPrepare);
+ };
+
+ let channel = new MessageChannel();
+ port = channel.port2;
+ mm.addMessageListener("BasicTest:FinishPrepare", finishPrepare);
+ mm.sendAsyncMessage("BasicTest:PortCreated", {}, [channel.port1]);
+}
+
+// Communicate with closed port.
+function close_test(finish) {
+ ok(mm, "close_test");
+
+ let finishPrepare = msg => {
+ is(msg.data.message, "OK", "");
+ ok(port, "");
+
+ port.onmessage = message => {
+ ok(false, "Port is alive.");
+ finish();
+ };
+
+ port.postMessage("CloseTest:StartTest");
+ mm.removeMessageListener("CloseTest:FinishPrepare", finishPrepare);
+ finish();
+ };
+
+ let channel = new MessageChannel();
+ port = channel.port2;
+ mm.addMessageListener("CloseTest:FinishPrepare", finishPrepare);
+ mm.sendAsyncMessage("CloseTest:PortCreated", {}, [channel.port1]);
+}
+
+// Empty transferable object
+function empty_transferable(finish) {
+ ok(mm, "empty_transferable");
+
+ let finishPrepare = msg => {
+ ok(true, "Same basic test.");
+ mm.removeMessageListener("EmptyTest:FinishPrepare", finishPrepare);
+ finish();
+ };
+
+ mm.addMessageListener("EmptyTest:FinishPrepare", finishPrepare);
+ mm.sendAsyncMessage("EmptyTest:PortCreated", {}, []);
+}
+
+// Not transferable object.
+function not_transferable(finish) {
+ ok(mm, "not_transferable");
+
+ let finishPrepare = msg => {
+ ok(true, "Same basic test.");
+ finish();
+ };
+
+ mm.addMessageListener("NotTransferableTest:FinishPrepare", finishPrepare);
+ mm.sendAsyncMessage("NotTransferableTest:PortCreated", {}, [""]);
+}
+
+/*
+ * Test preparation
+ */
+function finishLoad(msg) {
+ run_tests();
+}
+
+function prepare_test() {
+ debug("start run_tests()");
+ var node = document.getElementById("messagechannel_remote");
+ mm = node.messageManager; //Services.ppmm.getChildAt(1);
+ ok(mm, "created MessageManager.");
+
+ mm.addMessageListener("mmMessagePort:finishScriptLoad", finishLoad);
+ mm.addMessageListener("mmMessagePort:fail", failed_test);
+ //mm.loadProcessScript("chrome://mochitests/content/chrome/dom/messagechannel/tests/mm_messageChannel.js", true);
+ mm.loadFrameScript(
+ "chrome://mochitests/content/chrome/dom/messagechannel/tests/mm_messageChannel.js",
+ true
+ );
+ ok(true, "Loaded");
+}
+
+function failed_test() {
+ debug("failed test in child process");
+ ok(false, "");
+}
diff --git a/dom/messagechannel/tests/mm_messageChannelParent.xhtml b/dom/messagechannel/tests/mm_messageChannelParent.xhtml
new file mode 100644
index 0000000000..5f5c5ae5be
--- /dev/null
+++ b/dom/messagechannel/tests/mm_messageChannelParent.xhtml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="Test MessageChannel API with nsFrameMessageManager(bug 1174624)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="prepare_test()">
+
+ <!-- test code goes here -->
+ <script type="application/javascript"
+ src="chrome://mochitests/content/chrome/dom/messagechannel/tests/mm_messageChannelParent.js"></script>
+ <browser type="content" src="about:blank" id="messagechannel_remote" remote="true"/>
+</window>
diff --git a/dom/messagechannel/tests/mm_messageChannelParentNotRemote.xhtml b/dom/messagechannel/tests/mm_messageChannelParentNotRemote.xhtml
new file mode 100644
index 0000000000..cb3b55dea7
--- /dev/null
+++ b/dom/messagechannel/tests/mm_messageChannelParentNotRemote.xhtml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="Test MessageChannel API with nsFrameMessageManager(bug 1174624)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="prepare_test()">
+
+ <!-- test code goes here -->
+ <script type="application/javascript"
+ src="chrome://mochitests/content/chrome/dom/messagechannel/tests/mm_messageChannelParent.js"></script>
+ <browser type="content" src="about:blank" id="messagechannel_remote"/>
+</window>
diff --git a/dom/messagechannel/tests/mochitest.toml b/dom/messagechannel/tests/mochitest.toml
new file mode 100644
index 0000000000..f01cd65786
--- /dev/null
+++ b/dom/messagechannel/tests/mochitest.toml
@@ -0,0 +1,50 @@
+[DEFAULT]
+support-files = [
+ "iframe_messageChannel_cloning.html",
+ "iframe_messageChannel_pingpong.html",
+ "iframe_messageChannel_post.html",
+ "iframe_messageChannel_transferable.html",
+ "worker_messageChannel.js",
+ "worker_messageChannel_any.js",
+ "sharedWorker_messageChannel.js",
+ "sharedWorker2_messageChannel.js",
+ "iframe_messageChannel_sharedWorker2.html",
+ "!/dom/events/test/event_leak_utils.js",
+]
+
+["test_event_listener_leaks.html"]
+skip-if = ["os == 'linux' && bits == 64"] # Bug 1777087
+
+["test_messageChannel.html"]
+
+["test_messageChannel_any.html"]
+
+["test_messageChannel_bug1178076.html"]
+
+["test_messageChannel_bug1224825.html"]
+
+["test_messageChannel_cloning.html"]
+
+["test_messageChannel_forceClose.html"]
+
+["test_messageChannel_pingpong.html"]
+
+["test_messageChannel_post.html"]
+
+["test_messageChannel_selfTransferring.html"]
+
+["test_messageChannel_sharedWorker.html"]
+
+["test_messageChannel_sharedWorker2.html"]
+
+["test_messageChannel_start.html"]
+
+["test_messageChannel_transferable.html"]
+
+["test_messageChannel_unshipped.html"]
+
+["test_messageChannel_worker.html"]
+
+["test_messageChannel_worker_forceClose.html"]
+
+["test_removedWindow.html"]
diff --git a/dom/messagechannel/tests/moz.build b/dom/messagechannel/tests/moz.build
new file mode 100644
index 0000000000..5c3978f075
--- /dev/null
+++ b/dom/messagechannel/tests/moz.build
@@ -0,0 +1,10 @@
+# -*- 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/.
+
+MOCHITEST_MANIFESTS += ["mochitest.toml"]
+MOCHITEST_CHROME_MANIFESTS += ["chrome.toml"]
+
+XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.toml"]
diff --git a/dom/messagechannel/tests/sharedWorker2_messageChannel.js b/dom/messagechannel/tests/sharedWorker2_messageChannel.js
new file mode 100644
index 0000000000..d5084fe766
--- /dev/null
+++ b/dom/messagechannel/tests/sharedWorker2_messageChannel.js
@@ -0,0 +1,9 @@
+/* eslint-env worker */
+
+var mc = new MessageChannel();
+var i = 0;
+
+onconnect = function (evt) {
+ dump("CONNECTING: " + i + "\n");
+ evt.ports[0].postMessage(42, [mc["port" + ++i]]);
+};
diff --git a/dom/messagechannel/tests/sharedWorker_messageChannel.js b/dom/messagechannel/tests/sharedWorker_messageChannel.js
new file mode 100644
index 0000000000..874860e99f
--- /dev/null
+++ b/dom/messagechannel/tests/sharedWorker_messageChannel.js
@@ -0,0 +1,10 @@
+/* eslint-env worker */
+
+onconnect = function (evt) {
+ var mc = new MessageChannel();
+
+ evt.ports[0].postMessage(42, [mc.port2]);
+ mc.port1.onmessage = function (e) {
+ mc.port1.postMessage(e.data);
+ };
+};
diff --git a/dom/messagechannel/tests/test_event_listener_leaks.html b/dom/messagechannel/tests/test_event_listener_leaks.html
new file mode 100644
index 0000000000..93ed5215d3
--- /dev/null
+++ b/dom/messagechannel/tests/test_event_listener_leaks.html
@@ -0,0 +1,50 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1450358 - Test MessageChannel 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 MessageChannel 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.
+async function useMessagePort(contentWindow) {
+ contentWindow.messageCount = 0;
+
+ let mc = new contentWindow.MessageChannel
+ mc.port1.postMessage("foo");
+
+ await new Promise(resolve => {
+ mc.port2.onmessage = e => {
+ contentWindow.messageCount += 1;
+ resolve();
+ };
+ });
+
+ is(contentWindow.messageCount, 1, "message should be received");
+}
+
+async function runTest() {
+ try {
+ await checkForEventListenerLeaks("MessageChannel", useMessagePort);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", runTest, { once: true });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel.html b/dom/messagechannel/tests/test_messageChannel.html
new file mode 100644
index 0000000000..ce6774d33c
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel.html
@@ -0,0 +1,43 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - basic support</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 677638 **/
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ var port1 = a.port1;
+ ok(port1, "MessageChannel.port1 exists");
+ is(port1, a.port1, "MessageChannel.port1 is port1");
+
+ var port2 = a.port2;
+ ok(port2, "MessageChannel.port1 exists");
+ is(port2, a.port2, "MessageChannel.port2 is port2");
+
+ [ 'postMessage', 'start', 'close' ].forEach(function(e) {
+ ok(e in port1, "MessagePort1." + e + " exists");
+ ok(e in port2, "MessagePort2." + e + " exists");
+ });
+
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel.xhtml b/dom/messagechannel/tests/test_messageChannel.xhtml
new file mode 100644
index 0000000000..e51359d67d
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="Test for MessageChannel API"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml" id="body">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ ok("MessageChannel" in window, "Should MessageChannel exist?");
+
+ var channel = new MessageChannel();
+ ok(channel, "MessageChannel is created");
+
+ channel.port1.onmessage = function(evt) {
+ ok(true, "message received!");
+ SimpleTest.finish();
+ }
+
+ var ifr = document.createXULElement('browser');
+ ifr.setAttribute("src", "iframe_messageChannel_chrome.html");
+ ifr.setAttribute("flex", "1");
+ ifr.addEventListener('load', function() {
+ ifr.contentWindow.postMessage(channel.port2, '*', [channel.port2]);
+ });
+
+ var body = document.getElementById("body");
+ body.appendChild(ifr);
+
+ SimpleTest.waitForExplicitFinish();
+
+ ]]></script>
+</window>
diff --git a/dom/messagechannel/tests/test_messageChannelWithMessageManager.xhtml b/dom/messagechannel/tests/test_messageChannelWithMessageManager.xhtml
new file mode 100644
index 0000000000..b95a85645c
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannelWithMessageManager.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Test MessageChannel API with nsFrameMessageManager(bug 1174624)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ function done() {
+ info("done called");
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.openDialog("mm_messageChannelParent.xhtml", "", "chrome,noopener", window);
+ });
+ ]]></script>
+</window>
diff --git a/dom/messagechannel/tests/test_messageChannelWithMessageManagerNotRemote.xhtml b/dom/messagechannel/tests/test_messageChannelWithMessageManagerNotRemote.xhtml
new file mode 100644
index 0000000000..e87fd4d873
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannelWithMessageManagerNotRemote.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Test MessageChannel API with nsFrameMessageManager(bug 1174624)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ function done() {
+ info("done called");
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.openDialog("mm_messageChannelParentNotRemote.xhtml", "", "chrome,noopener", window);
+ });
+ ]]></script>
+</window>
diff --git a/dom/messagechannel/tests/test_messageChannel_any.html b/dom/messagechannel/tests/test_messageChannel_any.html
new file mode 100644
index 0000000000..e4498b243f
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_any.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>MessagePort/Channel any content</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <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 (let p in a) aProps.push(p);
+
+ var bProps = [];
+ for (let p in b) bProps.push(p);
+
+ is (aProps.length, bProps.length, 'Props match');
+ is (aProps.sort().toString(), bProps.sort().toString(), 'Prop names match');
+
+ for (let p in a) {
+ compare(a[p], b[p]);
+ }
+
+ return;
+ }
+
+ if (type != 'null') {
+ is (a, b, 'Same value');
+ }
+}
+
+function runTest() {
+ var mc = new MessageChannel('foobar');
+ ok(mc, "MessageChannel can be created");
+
+ mc.port1.onmessage = function(event) {
+ compare(event.data, currentTest);
+ next();
+ }
+
+ function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ currentTest = tests.shift();
+ mc.port1.postMessage(currentTest);
+ }
+
+ var worker = new Worker("worker_messageChannel_any.js");
+ worker.onmessage = function(event) {
+ if (event.data == "READY") {
+ next();
+ }
+ };
+
+ worker.postMessage(mc.port2, [mc.port2]);
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_bug1178076.html b/dom/messagechannel/tests/test_messageChannel_bug1178076.html
new file mode 100644
index 0000000000..e8d4d3b9fb
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_bug1178076.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1178076
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1178076</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1178076">Mozilla Bug 1178076</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ function runTest() {
+ onmessage = function(e) {
+ is(e.ports.length, 1, "A port has been received!");
+ var port = e.ports[0];
+ ok(port instanceof MessagePort, "This is a port.");
+ SimpleTest.finish();
+ }
+
+ // In this test we want to see if we leak a neutered port closing port1
+ // and sending port2 to the same window. This operation doesn't involve IPC.
+ var mc = new MessageChannel();
+ mc.port1.close();
+ postMessage(42, '*', [mc.port2]);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_bug1224825.html b/dom/messagechannel/tests/test_messageChannel_bug1224825.html
new file mode 100644
index 0000000000..067fb1581f
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_bug1224825.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1224825
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1224825</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1224825">Mozilla Bug 1224825</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+var MAX = 100;
+
+function test_fullDeliveredMessages() {
+ var worker = new Worker('data:javascript,onmessage = function(e) { e.ports[0].onmessage = function(evt) { postMessage(evt.data);}}');
+
+ var count = 0;
+ worker.onmessage = function(e) {
+ is(e.data, count, "Correct value expected!");
+ ok(count < MAX,"No count > MAX messages!");
+ if (++count == MAX) {
+
+ SimpleTest.requestFlakyTimeout("Testing an event not happening");
+ setTimeout(function() {
+ runTests();
+ }, 200);
+
+ info("All the messages correctly received");
+ }
+ }
+
+ var mc = new MessageChannel();
+ worker.postMessage(42, [mc.port2]);
+
+ for (let i = 0; i < MAX; ++i) {
+ mc.port1.postMessage(i);
+ }
+
+ mc.port1.close();
+
+ for (let i = 0; i < MAX * 2; ++i) {
+ mc.port1.postMessage(i);
+ }
+}
+
+function test_closeInBetween() {
+ var mc = new MessageChannel();
+
+ for (var i = 0; i < MAX; ++i) {
+ mc.port1.postMessage(i);
+ }
+
+ mc.port1.onmessage = function(e) {
+ ok (e.data < MAX/2, "Correct message received from port1:" + e.data);
+ }
+
+ mc.port2.onmessage = function(e) {
+ ok (e.data < MAX, "Correct message received from port2:" + e.data);
+ if (e.data == MAX/2) {
+ mc.port2.close();
+ }
+
+ mc.port2.postMessage(e.data);
+
+ if (e.data == MAX - 1) {
+ runTests();
+ }
+ }
+}
+
+var tests = [ test_fullDeliveredMessages, test_closeInBetween ];
+
+function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTests();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_cloning.html b/dom/messagechannel/tests/test_messageChannel_cloning.html
new file mode 100644
index 0000000000..7f59e9e96f
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_cloning.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - port cloning</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ // This test checks if MessagePorts can be shared with iframes
+ function test_iframe() {
+ window.addEventListener('message', receiveMessage);
+ 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 if (evt.data.status == 'FINISH') {
+ ok (evt.data.port instanceof MessagePort, "Data contains a MessagePort");
+ window.removeEventListener('message', receiveMessage);
+ runTest();
+ } else {
+ ok(false, "Unknown message");
+ }
+ }
+
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ var div = document.getElementById("content");
+ ok(div, "Parent exists");
+
+ var ifr = document.createElement("iframe");
+ ifr.addEventListener("load", iframeLoaded);
+ ifr.setAttribute('src', "iframe_messageChannel_cloning.html");
+ div.appendChild(ifr);
+
+ function iframeLoaded() {
+ ifr.contentWindow.postMessage({ port: a.port2 }, '*', [a.port2]);
+ }
+ }
+
+ var tests = [
+ test_iframe
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_forceClose.html b/dom/messagechannel/tests/test_messageChannel_forceClose.html
new file mode 100644
index 0000000000..98c9e87311
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_forceClose.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1176034
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1176034 - start/close</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1176034">Mozilla Bug 1176034</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ var mc = new MessageChannel();
+
+ try {
+ postMessage(42, "*", [ mc.port1, window ]);
+ ok(false, "Something went wrong.");
+ } catch(e) {
+ ok(true, "PostMessage should fail and we should not leak.");
+ }
+
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_pingpong.html b/dom/messagechannel/tests/test_messageChannel_pingpong.html
new file mode 100644
index 0000000000..602478bcb7
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_pingpong.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - port cloning</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ function runTest() {
+ var MAX = 100;
+
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ // Populate the message queue of this port.
+ for (var i = 0; i < MAX; ++i) {
+ a.port1.postMessage(i);
+ }
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+
+ // This test sends the port from this window to the iframe and viceversa.
+ if (evt.data.type == 'PORT') {
+ var port = evt.data.port;
+ var counter = 0;
+ port.onmessage = function(event) {
+ // only 1 message should be received by this port.
+ if (counter++ == 0) {
+ ok(event.data % 2, "The number " + event.data + " has been received correctly by the main window");
+
+ if (event.data < MAX - 1) {
+ ifr.contentWindow.postMessage({ type: 'PORT', port }, '*', [port]);
+ } else {
+ SimpleTest.finish();
+ }
+ } else {
+ ok(false, "Wrong message!");
+ }
+ }
+ } else if (evt.data.type == 'OK') {
+ ok(true, evt.data.msg);
+ } else if (evt.data.type == 'KO') {
+ ok(false, evt.data.msg);
+ } else {
+ ok(false, "Unknown message");
+ }
+ }
+
+ var div = document.getElementById("content");
+ ok(div, "Parent exists");
+
+ var ifr = document.createElement("iframe");
+ ifr.addEventListener("load", iframeLoaded);
+ ifr.setAttribute('src', "iframe_messageChannel_pingpong.html");
+ div.appendChild(ifr);
+
+ function iframeLoaded() {
+ ifr.contentWindow.postMessage({ type: 'PORT', port: a.port2 }, '*', [a.port2]);
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_post.html b/dom/messagechannel/tests/test_messageChannel_post.html
new file mode 100644
index 0000000000..86c27efc3e
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_post.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - port cloning</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ function start() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ if (evt.data.status == 'READY') {
+ runTest();
+ } else {
+ ok(false, "Unknown message");
+ }
+ }
+
+ var div = document.getElementById("content");
+ ok(div, "Parent exists");
+
+ var ifr = document.createElement("iframe");
+ ifr.addEventListener("load", iframeLoaded);
+ ifr.setAttribute('src', "iframe_messageChannel_post.html");
+ div.appendChild(ifr);
+
+ function iframeLoaded() {
+ ifr.contentWindow.postMessage({ port: a.port2 }, '*', [a.port2]);
+ }
+
+ var tests = [ 42,
+ null,
+ undefined,
+ "hello world",
+ new Blob([]),
+ true ];
+
+ a.port1.onmessage = function(evt) {
+ ok(tests.length, "We are waiting for a message");
+ if (typeof(tests[0]) == 'object') {
+ is(typeof(tests[0]), typeof(evt.data), "Value ok: " + tests[0]);
+ } else {
+ is(tests[0], evt.data, "Value ok: " + tests[0]);
+ }
+ tests.shift();
+ runTest();
+ }
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ a.port1.postMessage(tests[0]);
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ start();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_selfTransferring.html b/dom/messagechannel/tests/test_messageChannel_selfTransferring.html
new file mode 100644
index 0000000000..b5c1b1e886
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_selfTransferring.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>MessagePort/Channel no self tranferring</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ var a = new MessageChannel();
+
+ var messageStatus = false;
+ try {
+ a.port1.postMessage('foobar', [a.port1]);
+ } catch(e) {
+ messageStatus = true;
+ }
+
+ ok(messageStatus, "Transfering the same port should throw");
+
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_sharedWorker.html b/dom/messagechannel/tests/test_messageChannel_sharedWorker.html
new file mode 100644
index 0000000000..cf1864579f
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_sharedWorker.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - sharedWorker</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ var a = new SharedWorker('sharedWorker_messageChannel.js');
+ a.port.onmessage = function(evt) {
+ is(evt.ports.length, 1, "We received a port.");
+ evt.ports[0].onmessage = function(e) {
+ is(e.data, 42, "Message reiceved back!");
+ SimpleTest.finish();
+ }
+ evt.ports[0].postMessage(42);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_sharedWorker2.html b/dom/messagechannel/tests/test_messageChannel_sharedWorker2.html
new file mode 100644
index 0000000000..8b8f259308
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_sharedWorker2.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - sharedWorker</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+ <div id="content"></div>
+
+ <script type="application/javascript">
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', "iframe_messageChannel_sharedWorker2.html");
+ document.getElementById('content').appendChild(iframe);
+
+ var a = new SharedWorker('sharedWorker2_messageChannel.js');
+ a.port.onmessage = function(evt) {
+ is(evt.ports.length, 1, "We received a port.");
+ evt.ports[0].onmessage = function(e) {
+ is(e.data, "Hello from the iframe!", "Message reiceved from the iframe!");
+ SimpleTest.finish();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_start.html b/dom/messagechannel/tests/test_messageChannel_start.html
new file mode 100644
index 0000000000..4bc49cc0d6
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_start.html
@@ -0,0 +1,235 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - start/close</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ function testOnMessage() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage(42);
+ a.port2.postMessage(43);
+ ok(true, "MessagePort{1,2}.postmessage() invoked");
+
+ var events = 2;
+
+ a.port1.onmessage = function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ }
+
+ a.port2.onmessage = function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ }
+ }
+
+ function testAddEventListener() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage(42);
+ a.port2.postMessage(43);
+ ok(true, "MessagePort{1,2}.postmessage() invoked");
+
+ a.port1.addEventListener('message', function(evt) {
+ ok(false, "This method should not be called");
+ });
+
+ a.port2.addEventListener('message', function(evt) {
+ ok(false, "This method should not be called");
+ });
+
+ setTimeout(runTests, 0);
+ }
+
+ function testAddEventListenerAndStart() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage(42);
+ a.port2.postMessage(43);
+ ok(true, "MessagePort{1,2}.postmessage() invoked");
+
+ var events = 2;
+
+ a.port1.addEventListener('message', function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ });
+
+ a.port2.addEventListener('message', function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ });
+
+ a.port1.start();
+ a.port2.start();
+ }
+
+ function testAddEventListener1AndStart() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage(42);
+ a.port2.postMessage(43);
+ ok(true, "MessagePort{1,2}.postmessage() invoked");
+
+ var events = 1;
+
+ a.port1.addEventListener('message', function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ });
+
+ a.port2.addEventListener('message', function(evt) {
+ ok(false, "This method should not be called");
+ });
+
+ a.port1.start();
+ }
+
+ function testAddEventListener2AndStart() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage(42);
+ a.port2.postMessage(43);
+ ok(true, "MessagePort{1,2}.postmessage() invoked");
+
+ var events = 1;
+
+ a.port1.addEventListener('message', function(evt) {
+ ok(false, "This method should not be called");
+ });
+
+ a.port2.addEventListener('message', function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ });
+
+ a.port2.start();
+ }
+
+ function testTimer() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage(42);
+ a.port2.postMessage(43);
+ ok(true, "MessagePort{1,2}.postmessage() invoked");
+
+ setTimeout(function() {
+ var events = 2;
+ a.port1.onmessage = function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ }
+
+ a.port2.onmessage = function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ }
+ }, 200);
+ }
+
+ function testAddEventListenerAndStartWrongOrder() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage(42);
+ a.port2.postMessage(43);
+ ok(true, "MessagePort{1,2}.postmessage() invoked");
+
+ var events = 2;
+
+ a.port1.start();
+ a.port1.addEventListener('message', function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ });
+
+ a.port2.start();
+ a.port2.addEventListener('message', function(evt) {
+ ok(true, "This method should be called");
+ if (!--events) runTests();
+ });
+ }
+
+ function testOnMessageClone() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage(42);
+ a.port2.postMessage(43);
+ ok(true, "MessagePort{1,2}.postmessage() invoked");
+
+ var events = 2;
+
+ addEventListener('message', testOnMessageCloneCb, false);
+ function testOnMessageCloneCb(event) {
+ a.port1.onmessage = function(evt) {
+ ok(true, "This method should be called");
+ testOnMessageCloneFinish();
+ }
+
+ event.data.onmessage = function(evt) {
+ ok(true, "This method should be called");
+ testOnMessageCloneFinish();
+ }
+
+ a.port2.onmessage = function(evt) {
+ ok(false, "This method should not be called");
+ }
+ }
+
+ function testOnMessageCloneFinish() {
+ if (!--events) {
+ removeEventListener('message', testOnMessageCloneCb);
+ runTests();
+ }
+ }
+
+ postMessage(a.port2, '*', [a.port2]);
+ }
+
+ var tests = [
+ testOnMessage,
+ testAddEventListener,
+ testAddEventListenerAndStart,
+ testAddEventListener1AndStart,
+ testAddEventListener2AndStart,
+ testTimer,
+ testAddEventListenerAndStartWrongOrder,
+ testOnMessageClone,
+ ];
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+ runTests();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_transferable.html b/dom/messagechannel/tests/test_messageChannel_transferable.html
new file mode 100644
index 0000000000..44a27b34c4
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_transferable.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - port cloning</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ function basic_test() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ if (evt.data.status == 'READY') {
+ a.port1.postMessage({ab, cb: ab}, [ab]);
+ ok(ab.byteLength == 0, "PostMessage - The size is: 0 == " + ab.byteLength)
+ } else {
+ ok(false, "Unknown message");
+ }
+ }
+
+ var div = document.getElementById("content");
+ ok(div, "Parent exists");
+
+ var ifr = document.createElement("iframe");
+ ifr.addEventListener("load", iframeLoaded);
+ ifr.setAttribute('src', "iframe_messageChannel_post.html");
+ div.appendChild(ifr);
+
+ function iframeLoaded() {
+ ifr.contentWindow.postMessage({ port: a.port2 }, '*', [a.port2]);
+ }
+
+ a.port1.addEventListener('message', receivePortMessage);
+ function receivePortMessage(evt) {
+ is(evt.data.ab.byteLength, size, "The size is: " + size + " == " + ab.byteLength);
+ window.removeEventListener('message', receiveMessage);
+ runTests();
+ }
+
+ // Start() is not implicity invoked when addEventListener is used.
+ a.port1.start();
+
+ var size = 1024 * 1024 * 32;
+ var ab = new ArrayBuffer(size);
+ is(ab.byteLength, size, "The size is: " + size + " == " + ab.byteLength);
+ }
+
+ function port_test() {
+ window.addEventListener('message', receiveMessage);
+ function receiveMessage(evt) {
+ ok(evt.data.type == 'OK', evt.data.msg);
+ }
+
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ var div = document.getElementById("content");
+ ok(div, "Parent exists");
+
+ var ifr = document.createElement("iframe");
+ ifr.addEventListener("load", iframeLoaded);
+ ifr.setAttribute('src', "iframe_messageChannel_transferable.html");
+ div.appendChild(ifr);
+
+ function iframeLoaded() {
+ ifr.contentWindow.postMessage('foobar!', '*', [a.port2]);
+ }
+
+ a.port1.onmessage = function(evt) {
+ ok(evt.ports.length == 1, "Iframe sent a new port!");
+ evt.ports[0].onmessage = function(event) {
+ is(event.data, "hello world!", "Message sent and received!");
+ runTests();
+ }
+
+ evt.ports[0].postMessage("hello world!");
+ }
+ }
+
+ var tests = [
+ basic_test,
+ port_test
+ ];
+
+ function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var t = tests.shift();
+ t();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTests();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_unshipped.html b/dom/messagechannel/tests/test_messageChannel_unshipped.html
new file mode 100644
index 0000000000..44bf56979c
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_unshipped.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - unshipped message port queue</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ function test_orderedMessages() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ var b = new MessageChannel();
+ ok(b, "MessageChannel created");
+
+ var expectedNumber = 1;
+ function testEvent(number, id) {
+ is(expectedNumber, number, "This is the right number!");
+ ok(!((expectedNumber - id) % 4), "From the right port: " + expectedNumber + " " + id);
+ expectedNumber++;
+
+ if (expectedNumber >100) {
+ runTests();
+ }
+ }
+
+ a.port1.onmessage = function(evt) {
+ testEvent(evt.data, 2);
+ };
+
+ a.port2.onmessage = function(evt) {
+ testEvent(evt.data, 1);
+ };
+
+ b.port1.onmessage = function(evt) {
+ testEvent(evt.data, 4);
+ };
+
+ b.port2.onmessage = function(evt) {
+ testEvent(evt.data, 3);
+ };
+
+ for (var i = 0; i < 100;) {
+ a.port1.postMessage(++i);
+ a.port2.postMessage(++i);
+ b.port1.postMessage(++i);
+ b.port2.postMessage(++i);
+ }
+ }
+
+ function test_unstarted() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ var b = new MessageChannel();
+ ok(b, "MessageChannel created");
+
+ var expectedNumber = 1;
+ function testEvent(number, id) {
+ is(expectedNumber, number, "This is the right number!");
+ ok(!((expectedNumber - id) % 3), "From the right port: " + expectedNumber + " " + id);
+ expectedNumber++;
+
+ // 102 because it's the first multiple of 3.
+ if (expectedNumber > 102) {
+ runTests();
+ }
+ }
+
+ a.port1.onmessage = function(evt) {
+ testEvent(evt.data, 2);
+ };
+
+ a.port2.onmessage = function(evt) {
+ testEvent(evt.data, 1);
+ };
+
+ b.port1.addEventListener("message", function() {
+ ok(false, "shouldn't be called");
+ });
+
+ b.port2.onmessage = function(evt) {
+ testEvent(evt.data, 3);
+ };
+
+ for (var i = 0; i < 100;) {
+ a.port1.postMessage(++i);
+ a.port2.postMessage(++i);
+ b.port1.postMessage(++i);
+ b.port2.postMessage(1000);
+ }
+ }
+
+ var tests = [
+ test_orderedMessages,
+ test_unstarted
+ ];
+
+ function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTests();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_worker.html b/dom/messagechannel/tests/test_messageChannel_worker.html
new file mode 100644
index 0000000000..742212a5d3
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_worker.html
@@ -0,0 +1,60 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 677638 - basic support</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="x" id="x"></iframe>
+ <iframe name="y" id="y"></iframe>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ var tests = [ 0, 3 ];
+
+ function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var a = new Worker('worker_messageChannel.js');
+ a.onmessage = function(evt) {
+ if (evt.data.type == 'finish') {
+ runTests();
+ } else if (evt.data.type == 'info') {
+ info(evt.data.message);
+ } else if (evt.data.type == 'check') {
+ ok(evt.data.check, evt.data.message);
+ } else if (evt.data.type == 'port') {
+ is(evt.ports.length, 1, "A port has been received!");
+ evt.ports[0].onmessage = function(e) {
+ e.target.postMessage(e.data);
+ }
+ } else if (evt.data.type == 'newport') {
+ var ch = new MessageChannel();
+ ok(ch, "MessageChannel created");
+ ch.port1.postMessage(42);
+ a.postMessage('a gift!', [ch.port2]);
+ }
+ }
+
+ a.postMessage(tests.shift());
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTests();
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_messageChannel_worker_forceClose.html b/dom/messagechannel/tests/test_messageChannel_worker_forceClose.html
new file mode 100644
index 0000000000..8de3450fb5
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_worker_forceClose.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for forcing the closing of the port in workers</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>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ var worker = new Worker('data:javascript,onmessage = function(e) { "doing nothing with this port"; }');
+
+ var mc = new MessageChannel();
+ worker.postMessage(42, [mc.port2]);
+
+ for (var i = 0; i < 10; ++i) {
+ mc.port1.postMessage(i);
+ }
+
+ ok(true, "All the messages are sent! We should shutdown correctly.");
+ </script>
+</body>
+</html>
diff --git a/dom/messagechannel/tests/test_removedWindow.html b/dom/messagechannel/tests/test_removedWindow.html
new file mode 100644
index 0000000000..b660000fd8
--- /dev/null
+++ b/dom/messagechannel/tests/test_removedWindow.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>MessagePort should not work when created from a disconnected window</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<body>
+<script>
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+async function runTest() {
+ let ifr = document.createElement('iframe');
+ await new Promise(resolve => {
+ ifr.onload = resolve;
+ ifr.src = 'support/empty.html';
+ document.body.appendChild(ifr);
+ });
+
+ let w = ifr.contentWindow;
+
+ let pre = new w.MessageChannel();
+ ok(!!pre, "We have a channel");
+
+ ifr.remove();
+
+ let post = new w.MessageChannel();
+ ok(!!post, "We have a channel");
+
+ // This should silently fail.
+ pre.port1.postMessage(42);
+ pre.port2.onmessage = e => {
+ ok(false, "No messages should be received!");
+ }
+
+ // This should silently fail.
+ post.port1.postMessage(42);
+ post.port2.onmessage = e => {
+ ok(false, "No messages should be received!");
+ }
+
+ // Let's use another MessagePort just to be sure no messages are received by
+ // port2.
+
+ let mc = new MessageChannel();
+ mc.port1.postMessage(42);
+ mc.port2.onmessage = e => {
+ ok(true, "Ready to complete the test");
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</body>
diff --git a/dom/messagechannel/tests/unit/chromeWorker_messageChannel.js b/dom/messagechannel/tests/unit/chromeWorker_messageChannel.js
new file mode 100644
index 0000000000..3f557a4fb2
--- /dev/null
+++ b/dom/messagechannel/tests/unit/chromeWorker_messageChannel.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+onmessage = function (pingEvt) {
+ if (pingEvt.data == "ping") {
+ let { port1, port2 } = new MessageChannel();
+ port2.onmessage = helloEvt => {
+ if (helloEvt.data == "hello") {
+ helloEvt.ports[0].postMessage("goodbye");
+ }
+ };
+ pingEvt.ports[0].postMessage("pong", [port1]);
+ }
+};
diff --git a/dom/messagechannel/tests/unit/test_messageChannel.js b/dom/messagechannel/tests/unit/test_messageChannel.js
new file mode 100644
index 0000000000..d672920f71
--- /dev/null
+++ b/dom/messagechannel/tests/unit/test_messageChannel.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_test(function test_messageChannel() {
+ do_test_pending();
+
+ let chromeWorker = new ChromeWorker(
+ "resource://test/chromeWorker_messageChannel.js"
+ );
+ let { port1, port2 } = new MessageChannel();
+ port2.onmessage = pongEvt => {
+ Assert.equal(pongEvt.data, "pong");
+ let { port1: newPort1, port2: newPort2 } = new MessageChannel();
+ newPort2.onmessage = goodbyeEvt => {
+ Assert.equal(goodbyeEvt.data, "goodbye");
+
+ do_test_finished();
+ run_next_test();
+ };
+ pongEvt.ports[0].postMessage("hello", [newPort1]);
+ };
+ chromeWorker.postMessage("ping", [port1]);
+});
diff --git a/dom/messagechannel/tests/unit/xpcshell.toml b/dom/messagechannel/tests/unit/xpcshell.toml
new file mode 100644
index 0000000000..ea6d7d1862
--- /dev/null
+++ b/dom/messagechannel/tests/unit/xpcshell.toml
@@ -0,0 +1,6 @@
+[DEFAULT]
+head = ""
+skip-if = ["os == 'android'"]
+support-files = ["chromeWorker_messageChannel.js"]
+
+["test_messageChannel.js"]
diff --git a/dom/messagechannel/tests/worker_messageChannel.js b/dom/messagechannel/tests/worker_messageChannel.js
new file mode 100644
index 0000000000..f0b97ee36c
--- /dev/null
+++ b/dom/messagechannel/tests/worker_messageChannel.js
@@ -0,0 +1,110 @@
+function ok(a, msg) {
+ postMessage({ type: "check", check: !!a, message: msg });
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function info(msg) {
+ postMessage({ type: "info", message: msg });
+}
+
+function finish() {
+ postMessage({ type: "finish" });
+}
+
+function basic() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ var port1 = a.port1;
+ ok(port1, "MessageChannel.port1 exists");
+ is(port1, a.port1, "MessageChannel.port1 is port1");
+
+ var port2 = a.port2;
+ ok(port2, "MessageChannel.port1 exists");
+ is(port2, a.port2, "MessageChannel.port2 is port2");
+
+ ["postMessage", "start", "close"].forEach(function (e) {
+ ok(e in port1, "MessagePort1." + e + " exists");
+ ok(e in port2, "MessagePort2." + e + " exists");
+ });
+
+ runTests();
+}
+
+function sendMessages() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage("Hello world!");
+ a.port1.onmessage = function (e) {
+ is(e.data, "Hello world!", "The message is back!");
+ runTests();
+ };
+
+ a.port2.onmessage = function (e) {
+ a.port2.postMessage(e.data);
+ };
+}
+
+function transferPort() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ a.port1.postMessage("Hello world!");
+ a.port1.onmessage = function (e) {
+ is(e.data, "Hello world!", "The message is back!");
+ runTests();
+ };
+
+ postMessage({ type: "port" }, [a.port2]);
+}
+
+function transferPort2() {
+ onmessage = function (evt) {
+ is(evt.ports.length, 1, "A port has been received by the worker");
+ evt.ports[0].onmessage = function (e) {
+ is(e.data, 42, "Data is 42!");
+ runTests();
+ };
+ };
+
+ postMessage({ type: "newport" });
+}
+
+var tests = [basic, sendMessages, transferPort, transferPort2];
+
+function runTests() {
+ if (!tests.length) {
+ finish();
+ return;
+ }
+
+ var t = tests.shift();
+ t();
+}
+
+var subworker;
+onmessage = function (evt) {
+ if (evt.data == 0) {
+ runTests();
+ return;
+ }
+
+ if (!subworker) {
+ info("Create a subworkers. ID: " + evt.data);
+ subworker = new Worker("worker_messageChannel.js");
+ subworker.onmessage = function (e) {
+ info("Proxy a message to the parent.");
+ postMessage(e.data, e.ports);
+ };
+
+ subworker.postMessage(evt.data - 1);
+ return;
+ }
+
+ info("Dispatch a message to the subworker.");
+ subworker.postMessage(evt.data, evt.ports);
+};
diff --git a/dom/messagechannel/tests/worker_messageChannel_any.js b/dom/messagechannel/tests/worker_messageChannel_any.js
new file mode 100644
index 0000000000..11018da9a0
--- /dev/null
+++ b/dom/messagechannel/tests/worker_messageChannel_any.js
@@ -0,0 +1,7 @@
+onmessage = function (evt) {
+ evt.data.onmessage = function (event) {
+ evt.data.postMessage(event.data);
+ };
+};
+
+postMessage("READY");