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