From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- dom/messagechannel/MessageChannel.cpp | 89 +++ dom/messagechannel/MessageChannel.h | 58 ++ dom/messagechannel/MessagePort.cpp | 874 +++++++++++++++++++++ dom/messagechannel/MessagePort.h | 233 ++++++ dom/messagechannel/MessagePortChild.cpp | 46 ++ dom/messagechannel/MessagePortChild.h | 45 ++ dom/messagechannel/MessagePortParent.cpp | 178 +++++ dom/messagechannel/MessagePortParent.h | 61 ++ dom/messagechannel/MessagePortService.cpp | 404 ++++++++++ dom/messagechannel/MessagePortService.h | 60 ++ dom/messagechannel/PMessagePort.ipdl | 47 ++ dom/messagechannel/moz.build | 38 + dom/messagechannel/tests/chrome.ini | 12 + .../tests/iframe_messageChannel_chrome.html | 11 + .../tests/iframe_messageChannel_cloning.html | 21 + .../tests/iframe_messageChannel_pingpong.html | 32 + .../tests/iframe_messageChannel_post.html | 23 + .../tests/iframe_messageChannel_sharedWorker2.html | 13 + .../tests/iframe_messageChannel_transferable.html | 24 + dom/messagechannel/tests/mm_messageChannel.js | 76 ++ .../tests/mm_messageChannelParent.js | 144 ++++ .../tests/mm_messageChannelParent.xhtml | 12 + .../tests/mm_messageChannelParentNotRemote.xhtml | 12 + dom/messagechannel/tests/mochitest.ini | 34 + dom/messagechannel/tests/moz.build | 10 + .../tests/sharedWorker2_messageChannel.js | 9 + .../tests/sharedWorker_messageChannel.js | 10 + .../tests/test_event_listener_leaks.html | 50 ++ dom/messagechannel/tests/test_messageChannel.html | 43 + dom/messagechannel/tests/test_messageChannel.xhtml | 38 + .../test_messageChannelWithMessageManager.xhtml | 27 + ...messageChannelWithMessageManagerNotRemote.xhtml | 27 + .../tests/test_messageChannel_any.html | 115 +++ .../tests/test_messageChannel_bug1178076.html | 38 + .../tests/test_messageChannel_bug1224825.html | 94 +++ .../tests/test_messageChannel_cloning.html | 70 ++ .../tests/test_messageChannel_forceClose.html | 30 + .../tests/test_messageChannel_pingpong.html | 77 ++ .../tests/test_messageChannel_post.html | 76 ++ .../test_messageChannel_selfTransferring.html | 32 + .../tests/test_messageChannel_sharedWorker.html | 36 + .../tests/test_messageChannel_sharedWorker2.html | 34 + .../tests/test_messageChannel_start.html | 235 ++++++ .../tests/test_messageChannel_transferable.html | 111 +++ .../tests/test_messageChannel_unshipped.html | 123 +++ .../tests/test_messageChannel_worker.html | 60 ++ .../test_messageChannel_worker_forceClose.html | 27 + dom/messagechannel/tests/test_removedWindow.html | 55 ++ .../tests/unit/chromeWorker_messageChannel.js | 14 + .../tests/unit/test_messageChannel.js | 23 + dom/messagechannel/tests/unit/xpcshell.ini | 7 + dom/messagechannel/tests/worker_messageChannel.js | 110 +++ .../tests/worker_messageChannel_any.js | 7 + 53 files changed, 4135 insertions(+) create mode 100644 dom/messagechannel/MessageChannel.cpp create mode 100644 dom/messagechannel/MessageChannel.h create mode 100644 dom/messagechannel/MessagePort.cpp create mode 100644 dom/messagechannel/MessagePort.h create mode 100644 dom/messagechannel/MessagePortChild.cpp create mode 100644 dom/messagechannel/MessagePortChild.h create mode 100644 dom/messagechannel/MessagePortParent.cpp create mode 100644 dom/messagechannel/MessagePortParent.h create mode 100644 dom/messagechannel/MessagePortService.cpp create mode 100644 dom/messagechannel/MessagePortService.h create mode 100644 dom/messagechannel/PMessagePort.ipdl create mode 100644 dom/messagechannel/moz.build create mode 100644 dom/messagechannel/tests/chrome.ini create mode 100644 dom/messagechannel/tests/iframe_messageChannel_chrome.html create mode 100644 dom/messagechannel/tests/iframe_messageChannel_cloning.html create mode 100644 dom/messagechannel/tests/iframe_messageChannel_pingpong.html create mode 100644 dom/messagechannel/tests/iframe_messageChannel_post.html create mode 100644 dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html create mode 100644 dom/messagechannel/tests/iframe_messageChannel_transferable.html create mode 100644 dom/messagechannel/tests/mm_messageChannel.js create mode 100644 dom/messagechannel/tests/mm_messageChannelParent.js create mode 100644 dom/messagechannel/tests/mm_messageChannelParent.xhtml create mode 100644 dom/messagechannel/tests/mm_messageChannelParentNotRemote.xhtml create mode 100644 dom/messagechannel/tests/mochitest.ini create mode 100644 dom/messagechannel/tests/moz.build create mode 100644 dom/messagechannel/tests/sharedWorker2_messageChannel.js create mode 100644 dom/messagechannel/tests/sharedWorker_messageChannel.js create mode 100644 dom/messagechannel/tests/test_event_listener_leaks.html create mode 100644 dom/messagechannel/tests/test_messageChannel.html create mode 100644 dom/messagechannel/tests/test_messageChannel.xhtml create mode 100644 dom/messagechannel/tests/test_messageChannelWithMessageManager.xhtml create mode 100644 dom/messagechannel/tests/test_messageChannelWithMessageManagerNotRemote.xhtml create mode 100644 dom/messagechannel/tests/test_messageChannel_any.html create mode 100644 dom/messagechannel/tests/test_messageChannel_bug1178076.html create mode 100644 dom/messagechannel/tests/test_messageChannel_bug1224825.html create mode 100644 dom/messagechannel/tests/test_messageChannel_cloning.html create mode 100644 dom/messagechannel/tests/test_messageChannel_forceClose.html create mode 100644 dom/messagechannel/tests/test_messageChannel_pingpong.html create mode 100644 dom/messagechannel/tests/test_messageChannel_post.html create mode 100644 dom/messagechannel/tests/test_messageChannel_selfTransferring.html create mode 100644 dom/messagechannel/tests/test_messageChannel_sharedWorker.html create mode 100644 dom/messagechannel/tests/test_messageChannel_sharedWorker2.html create mode 100644 dom/messagechannel/tests/test_messageChannel_start.html create mode 100644 dom/messagechannel/tests/test_messageChannel_transferable.html create mode 100644 dom/messagechannel/tests/test_messageChannel_unshipped.html create mode 100644 dom/messagechannel/tests/test_messageChannel_worker.html create mode 100644 dom/messagechannel/tests/test_messageChannel_worker_forceClose.html create mode 100644 dom/messagechannel/tests/test_removedWindow.html create mode 100644 dom/messagechannel/tests/unit/chromeWorker_messageChannel.js create mode 100644 dom/messagechannel/tests/unit/test_messageChannel.js create mode 100644 dom/messagechannel/tests/unit/xpcshell.ini create mode 100644 dom/messagechannel/tests/worker_messageChannel.js create mode 100644 dom/messagechannel/tests/worker_messageChannel_any.js (limited to 'dom/messagechannel') 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 aGivenProto) { + return MessageChannel_Binding::Wrap(aCx, this, aGivenProto); +} + +/* static */ +already_AddRefed MessageChannel::Constructor( + const GlobalObject& aGlobal, ErrorResult& aRv) { + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(global, aRv); +} + +/* static */ +already_AddRefed 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 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 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 aGivenProto) override; + + static already_AddRefed Constructor( + const GlobalObject& aGlobal, ErrorResult& aRv); + + static already_AddRefed Constructor(nsIGlobalObject* aGlobal, + ErrorResult& aRv); + + MessagePort* Port1() const { return mPort1; } + + MessagePort* Port2() const { return mPort2; } + + private: + explicit MessageChannel(nsIGlobalObject* aGlobal); + ~MessageChannel(); + + nsCOMPtr mGlobal; + + RefPtr mPort1; + RefPtr 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..736909bc2d --- /dev/null +++ b/dom/messagechannel/MessagePort.cpp @@ -0,0 +1,874 @@ +/* -*- 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/MessagePortTimelineMarker.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/TimelineMarker.h" +#include "mozilla/Unused.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.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 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 value(cx); + + UniquePtr start; + UniquePtr end; + bool isTimelineRecording = !TimelineConsumers::IsEmpty(); + + if (isTimelineRecording) { + start = MakeUnique( + ProfileTimelineMessagePortOperationType::DeserializeData, + MarkerTracingType::START); + } + + mData->Read(cx, &value, mPort->mRefMessageBodyService, + SharedMessageBody::ReadMethod::StealRefMessageBody, rv); + + if (isTimelineRecording) { + end = MakeUnique( + ProfileTimelineMessagePortOperationType::DeserializeData, + MarkerTracingType::END); + TimelineConsumers::AddMarkerForAllObservedDocShells(start); + TimelineConsumers::AddMarkerForAllObservedDocShells(end); + } + + if (NS_WARN_IF(rv.Failed())) { + JS_ClearPendingException(cx); + mPort->DispatchError(); + return; + } + + // Create the event + nsCOMPtr eventTarget = + do_QueryInterface(mPort->GetOwner()); + RefPtr event = + new MessageEvent(eventTarget, nullptr, nullptr); + + Sequence> 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 mPort; + RefPtr 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(); + 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::Create(nsIGlobalObject* aGlobal, + const nsID& aUUID, + const nsID& aDestinationUUID, + ErrorResult& aRv) { + MOZ_ASSERT(aGlobal); + + RefPtr 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::Create( + nsIGlobalObject* aGlobal, UniqueMessagePortId& aIdentifier, + ErrorResult& aRv) { + MOZ_ASSERT(aGlobal); + + RefPtr 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 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::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 aGivenProto) { + return MessagePort_Binding::Wrap(aCx, this, aGivenProto); +} + +void MessagePort::PostMessage(JSContext* aCx, JS::Handle aMessage, + const Sequence& 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 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 transferable(aCx, JS::UndefinedValue()); + + aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable, + &transferable); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + Maybe agentClusterId; + nsCOMPtr global = GetOwnerGlobal(); + if (global) { + agentClusterId = global->GetAgentClusterId(); + } + + RefPtr data = new SharedMessageBody( + StructuredCloneHolder::TransferringSupported, agentClusterId); + + UniquePtr start; + UniquePtr end; + bool isTimelineRecording = !TimelineConsumers::IsEmpty(); + + if (isTimelineRecording) { + start = MakeUnique( + ProfileTimelineMessagePortOperationType::SerializeData, + MarkerTracingType::START); + } + + data->Write(aCx, aMessage, transferable, mIdentifier->uuid(), + mRefMessageBodyService, aRv); + + if (isTimelineRecording) { + end = MakeUnique( + ProfileTimelineMessagePortOperationType::SerializeData, + MarkerTracingType::END); + TimelineConsumers::AddMarkerForAllObservedDocShells(start); + TimelineConsumers::AddMarkerForAllObservedDocShells(end); + } + + 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, 1> array; + array.AppendElement(data); + + AutoTArray 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 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 data = mMessages.ElementAt(0); + mMessages.RemoveElementAt(0); + + mPostMessageRunnable = new PostMessageRunnable(this, data); + + nsCOMPtr global = GetOwnerGlobal(); + if (NS_IsMainThread() && global) { + MOZ_ALWAYS_SUCCEEDS( + global->Dispatch(TaskCategory::Other, do_AddRef(mPostMessageRunnable))); + return; + } + + 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 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& 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 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> 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& 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> 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 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 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 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(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 globalObject = GetParentObject(); + + AutoJSAPI jsapi; + if (!globalObject || !jsapi.Init(globalObject)) { + NS_WARNING("Failed to initialize AutoJSAPI object."); + return; + } + + RootedDictionary init(jsapi.cx()); + init.mBubbles = false; + init.mCancelable = false; + + RefPtr 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 Create(nsIGlobalObject* aGlobal, + const nsID& aUUID, + const nsID& aDestinationUUID, + ErrorResult& aRv); + + static already_AddRefed Create(nsIGlobalObject* aGlobal, + UniqueMessagePortId& aIdentifier, + ErrorResult& aRv); + + // For IPC. + static void ForceClose(const MessagePortIdentifier& aIdentifier); + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + void PostMessage(JSContext* aCx, JS::Handle aMessage, + const Sequence& aTransferable, ErrorResult& aRv); + + void PostMessage(JSContext* aCx, JS::Handle 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& aMessages); + void MessagesReceived(nsTArray& 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 mWorkerRef; + + RefPtr mPostMessageRunnable; + + RefPtr mActor; + + RefPtr mUnshippedEntangledPort; + + RefPtr mRefMessageBodyService; + + nsTArray> mMessages; + nsTArray> mMessagesForTheOtherPort; + + UniquePtr 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&& aMessages) { + if (mPort) { + mPort->Entangled(aMessages); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult MessagePortChild::RecvReceiveData( + nsTArray&& 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&& aMessages); + + mozilla::ipc::IPCResult RecvReceiveData(nsTArray&& 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&& 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> 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&& 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> 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 kungFuDeathGrip = mService; + kungFuDeathGrip->ParentDestroy(this); + } +} + +bool MessagePortParent::Entangled(nsTArray&& 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> { + friend class PMessagePortParent; + + public: + explicit MessagePortParent(const nsID& aUUID); + ~MessagePortParent(); + + bool Entangle(const nsID& aDestinationUUID, const uint32_t& aSequenceID); + + bool Entangled(nsTArray&& 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&& aMessages); + + mozilla::ipc::IPCResult RecvDisentangle(nsTArray&& aMessages); + + mozilla::ipc::IPCResult RecvStopSendingData(); + + mozilla::ipc::IPCResult RecvClose(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + RefPtr 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..3b45102e75 --- /dev/null +++ b/dom/messagechannel/MessagePortService.cpp @@ -0,0 +1,404 @@ +/* -*- 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 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 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 mParent; + + FallibleTArray mNextParents; + FallibleTArray> 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(aParent->ID())); + + data = mPorts + .InsertOrUpdate( + aParent->ID(), + MakeUnique(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> messages( + std::move(data->mMessages)); + nsTArray 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> 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 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> 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 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; + } + + if (!data->mDestinationUUID.Equals(aDestinationUUID) || + data->mSequenceID != aSequenceID) { + NS_WARNING("DestinationUUID and/or sequenceID do not match."); + return false; + } + + 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> aMessages); + + bool ClosePort(MessagePortParent* aParent); + + bool PostMessages(MessagePortParent* aParent, + FallibleTArray> 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 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.ini b/dom/messagechannel/tests/chrome.ini new file mode 100644 index 0000000000..9ec2edcb4d --- /dev/null +++ b/dom/messagechannel/tests/chrome.ini @@ -0,0 +1,12 @@ +[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 @@ + + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + diff --git a/dom/messagechannel/tests/mm_messageChannel.js b/dom/messagechannel/tests/mm_messageChannel.js new file mode 100644 index 0000000000..3bd0f5be47 --- /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 "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 @@ + + + + + + + + + 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 @@ + + + + + + + + + diff --git a/dom/messagechannel/tests/mochitest.ini b/dom/messagechannel/tests/mochitest.ini new file mode 100644 index 0000000000..d4d81552d6 --- /dev/null +++ b/dom/messagechannel/tests/mochitest.ini @@ -0,0 +1,34 @@ +[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 == "win" && processor == "aarch64" #bug 1535784 + os == "linux" && bits == 64 # Bug 1777087 +[test_messageChannel.html] +[test_messageChannel_cloning.html] +[test_messageChannel_pingpong.html] +[test_messageChannel_post.html] +[test_messageChannel_start.html] +[test_messageChannel_transferable.html] +[test_messageChannel_unshipped.html] +[test_messageChannel_worker.html] +[test_messageChannel_selfTransferring.html] +[test_messageChannel_sharedWorker.html] +[test_messageChannel_sharedWorker2.html] +[test_messageChannel_any.html] +[test_messageChannel_forceClose.html] +[test_messageChannel_bug1178076.html] +[test_messageChannel_bug1224825.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..6cf89eae3b --- /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.ini"] +MOCHITEST_CHROME_MANIFESTS += ["chrome.ini"] + +XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"] 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 @@ + + + + + Bug 1450358 - Test MessageChannel event listener leak conditions + + + + + + + + + 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 @@ + + + + + + + Test for Bug 677638 - basic support + + + + +Mozilla Bug 677638 +

+ +
+
+ + + 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 @@ + + + + + + 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 @@ + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + diff --git a/dom/messagechannel/tests/test_messageChannel_any.html b/dom/messagechannel/tests/test_messageChannel_any.html new file mode 100644 index 0000000000..ff83c55288 --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel_any.html @@ -0,0 +1,115 @@ + + + + + + MessagePort/Channel any content + + + + +Mozilla Bug 677638 +
+
+
+ + + 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 @@ + + + + + + Test for Bug 1178076 + + + + +Mozilla Bug 1178076 +
+
+
+ + + diff --git a/dom/messagechannel/tests/test_messageChannel_bug1224825.html b/dom/messagechannel/tests/test_messageChannel_bug1224825.html new file mode 100644 index 0000000000..90b1b51076 --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel_bug1224825.html @@ -0,0 +1,94 @@ + + + + + + Test for Bug 1224825 + + + + +Mozilla Bug 1224825 +
+
+
+ + + 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 @@ + + + + + + Test for Bug 677638 - port cloning + + + + +Mozilla Bug 677638 +
+
+
+ + + 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 @@ + + + + + + Test for Bug 1176034 - start/close + + + + +Mozilla Bug 1176034 +
+
+
+ + + 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 @@ + + + + + + Test for Bug 677638 - port cloning + + + + +Mozilla Bug 677638 +
+
+
+ + + 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 @@ + + + + + + Test for Bug 677638 - port cloning + + + + +Mozilla Bug 677638 +
+
+
+ + + diff --git a/dom/messagechannel/tests/test_messageChannel_selfTransferring.html b/dom/messagechannel/tests/test_messageChannel_selfTransferring.html new file mode 100644 index 0000000000..c3b96f32e6 --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel_selfTransferring.html @@ -0,0 +1,32 @@ + + + + + + MessagePort/Channel no self tranferring + + + + +Mozilla Bug 677638 +
+
+
+ + + 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 @@ + + + + + + Test for Bug 677638 - sharedWorker + + + + +Mozilla Bug 677638 +

+ +
+
+ + + 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 @@ + + + + + + Test for Bug 677638 - sharedWorker + + + + + Mozilla Bug 677638 +
+ + + + 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 @@ + + + + + + Test for Bug 677638 - start/close + + + + +Mozilla Bug 677638 +
+
+
+ + + 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 @@ + + + + + + Test for Bug 677638 - port cloning + + + + +Mozilla Bug 677638 +
+
+
+ + + 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 @@ + + + + + + Test for Bug 677638 - unshipped message port queue + + + + +Mozilla Bug 677638 +
+
+
+ + + 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 @@ + + + + + + + Test for Bug 677638 - basic support + + + + +Mozilla Bug 677638 +

+ +
+
+ + + 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 @@ + + + + + Test for forcing the closing of the port in workers + + + + +
+
+
+ + + 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 @@ + +MessagePort should not work when created from a disconnected window + + + + + + 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.ini b/dom/messagechannel/tests/unit/xpcshell.ini new file mode 100644 index 0000000000..7164d1ea96 --- /dev/null +++ b/dom/messagechannel/tests/unit/xpcshell.ini @@ -0,0 +1,7 @@ +[DEFAULT] +head = +skip-if = toolkit == '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"); -- cgit v1.2.3