diff options
Diffstat (limited to 'dom/messagechannel')
50 files changed, 4365 insertions, 0 deletions
diff --git a/dom/messagechannel/MessageChannel.cpp b/dom/messagechannel/MessageChannel.cpp new file mode 100644 index 000000000..a0604ae4a --- /dev/null +++ b/dom/messagechannel/MessageChannel.cpp @@ -0,0 +1,91 @@ +/* -*- 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/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "nsContentUtils.h" +#include "nsIDocument.h" +#include "nsIGlobalObject.h" +#include "nsIPrincipal.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { +namespace 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() +{ +} + +JSObject* +MessageChannel::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return MessageChannelBinding::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 = nsContentUtils::GenerateUUIDInPlace(portUUID1); + if (aRv.Failed()) { + return nullptr; + } + + nsID portUUID2; + aRv = nsContentUtils::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); + + return channel.forget(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/messagechannel/MessageChannel.h b/dom/messagechannel/MessageChannel.h new file mode 100644 index 000000000..a9c952181 --- /dev/null +++ b/dom/messagechannel/MessageChannel.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_MessageChannel_h +#define mozilla_dom_MessageChannel_h + +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +class nsIGlobalObject; + +namespace mozilla { +namespace dom { + +class MessagePort; + +class MessageChannel final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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 000000000..56204da99 --- /dev/null +++ b/dom/messagechannel/MessagePort.cpp @@ -0,0 +1,1011 @@ +/* -*- 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/StructuredCloneTags.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerScope.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/MessagePortTimelineMarker.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/TimelineMarker.h" +#include "mozilla/Unused.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "nsPresContext.h" +#include "ScriptSettings.h" +#include "SharedMessagePortMessage.h" + +#include "nsIBFCacheEntry.h" +#include "nsIDocument.h" +#include "nsIDOMFileList.h" +#include "nsIPresShell.h" +#include "nsISupportsPrimitives.h" +#include "nsServiceManagerUtils.h" + +#ifdef XP_WIN +#undef PostMessage +#endif + +using namespace mozilla::dom::workers; + +namespace mozilla { +namespace dom { + +class PostMessageRunnable final : public CancelableRunnable +{ + friend class MessagePort; + +public: + PostMessageRunnable(MessagePort* aPort, SharedMessagePortMessage* aData) + : 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); + + nsresult rv = 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 rv; + } + + nsresult + Cancel() override + { + NS_ASSERT_OWNINGTHREAD(Runnable); + + mPort = nullptr; + mData = nullptr; + return NS_OK; + } + +private: + nsresult + DispatchMessage() const + { + NS_ASSERT_OWNINGTHREAD(Runnable); + + nsCOMPtr<nsIGlobalObject> globalObject = mPort->GetParentObject(); + + AutoJSAPI jsapi; + if (!globalObject || !jsapi.Init(globalObject)) { + NS_WARNING("Failed to initialize AutoJSAPI object."); + return NS_ERROR_FAILURE; + } + + JSContext* cx = jsapi.cx(); + + ErrorResult rv; + JS::Rooted<JS::Value> value(cx); + + UniquePtr<AbstractTimelineMarker> start; + UniquePtr<AbstractTimelineMarker> end; + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + bool isTimelineRecording = timelines && !timelines->IsEmpty(); + + if (isTimelineRecording) { + start = MakeUnique<MessagePortTimelineMarker>( + ProfileTimelineMessagePortOperationType::DeserializeData, + MarkerTracingType::START); + } + + mData->Read(mPort->GetParentObject(), cx, &value, rv); + + if (isTimelineRecording) { + end = MakeUnique<MessagePortTimelineMarker>( + ProfileTimelineMessagePortOperationType::DeserializeData, + MarkerTracingType::END); + timelines->AddMarkerForAllObservedDocShells(start); + timelines->AddMarkerForAllObservedDocShells(end); + } + + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + // 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)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + Nullable<WindowProxyOrMessagePort> source; + source.SetValue().SetAsMessagePort() = mPort; + + event->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"), + false /* non-bubbling */, + false /* cancelable */, value, EmptyString(), + EmptyString(), source, ports); + event->SetTrusted(true); + + bool dummy; + mPort->DispatchEvent(static_cast<dom::Event*>(event.get()), &dummy); + + return NS_OK; + } + +private: + ~PostMessageRunnable() + {} + + RefPtr<MessagePort> mPort; + RefPtr<SharedMessagePortMessage> 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(mMessages); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagesForTheOtherPort); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnshippedEntangledPort); +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_INHERITED(MessagePort) + NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback) + NS_INTERFACE_MAP_ENTRY(nsIObserver) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(MessagePort, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(MessagePort, DOMEventTargetHelper) + +namespace { + +class MessagePortWorkerHolder final : public workers::WorkerHolder +{ + MessagePort* mPort; + +public: + explicit MessagePortWorkerHolder(MessagePort* aPort) + : mPort(aPort) + { + MOZ_ASSERT(aPort); + MOZ_COUNT_CTOR(MessagePortWorkerHolder); + } + + virtual bool Notify(workers::Status aStatus) override + { + if (aStatus > Running) { + // We cannot process messages anymore because we cannot dispatch new + // runnables. Let's force a Close(). + mPort->CloseForced(); + } + + return true; + } + +private: + ~MessagePortWorkerHolder() + { + MOZ_COUNT_DTOR(MessagePortWorkerHolder); + } +}; + +class ForceCloseHelper final : public nsIIPCBackgroundChildCreateCallback +{ +public: + NS_DECL_ISUPPORTS + + static void ForceClose(const MessagePortIdentifier& aIdentifier) + { + PBackgroundChild* actor = + mozilla::ipc::BackgroundChild::GetForCurrentThread(); + if (actor) { + Unused << actor->SendMessagePortForceClose(aIdentifier.uuid(), + aIdentifier.destinationUuid(), + aIdentifier.sequenceId()); + return; + } + + RefPtr<ForceCloseHelper> helper = new ForceCloseHelper(aIdentifier); + if (NS_WARN_IF(!mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(helper))) { + MOZ_CRASH(); + } + } + +private: + explicit ForceCloseHelper(const MessagePortIdentifier& aIdentifier) + : mIdentifier(aIdentifier) + {} + + ~ForceCloseHelper() {} + + void ActorFailed() override + { + MOZ_CRASH("Failed to create a PBackgroundChild actor!"); + } + + void ActorCreated(mozilla::ipc::PBackgroundChild* aActor) override + { + ForceClose(mIdentifier); + } + + const MessagePortIdentifier mIdentifier; +}; + +NS_IMPL_ISUPPORTS(ForceCloseHelper, nsIIPCBackgroundChildCreateCallback) + +} // namespace + +MessagePort::MessagePort(nsIGlobalObject* aGlobal) + : DOMEventTargetHelper(aGlobal) + , mInnerID(0) + , mMessageQueueEnabled(false) + , mIsKeptAlive(false) +{ + MOZ_ASSERT(aGlobal); + + mIdentifier = new MessagePortIdentifier(); + mIdentifier->neutered() = true; + mIdentifier->sequenceId() = 0; +} + +MessagePort::~MessagePort() +{ + CloseForced(); + MOZ_ASSERT(!mWorkerHolder); +} + +/* 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); + mp->Initialize(aUUID, aDestinationUUID, 1 /* 0 is an invalid sequence ID */, + false /* Neutered */, eStateUnshippedEntangled, aRv); + return mp.forget(); +} + +/* static */ already_AddRefed<MessagePort> +MessagePort::Create(nsIGlobalObject* aGlobal, + const MessagePortIdentifier& aIdentifier, + ErrorResult& aRv) +{ + MOZ_ASSERT(aGlobal); + + RefPtr<MessagePort> mp = new MessagePort(aGlobal); + mp->Initialize(aIdentifier.uuid(), aIdentifier.destinationUuid(), + aIdentifier.sequenceId(), aIdentifier.neutered(), + eStateEntangling, aRv); + return mp.forget(); +} + +void +MessagePort::UnshippedEntangle(MessagePort* aEntangledPort) +{ + MOZ_ASSERT(aEntangledPort); + MOZ_ASSERT(!mUnshippedEntangledPort); + + mUnshippedEntangledPort = aEntangledPort; +} + +void +MessagePort::Initialize(const nsID& aUUID, + const nsID& aDestinationUUID, + uint32_t aSequenceID, bool mNeutered, + State aState, ErrorResult& aRv) +{ + MOZ_ASSERT(mIdentifier); + mIdentifier->uuid() = aUUID; + mIdentifier->destinationUuid() = aDestinationUUID; + mIdentifier->sequenceId() = aSequenceID; + + mState = aState; + + if (mNeutered) { + // If this port is neutered we don't want to keep it alive artificially nor + // we want to add listeners or workerWorkerHolders. + mState = eStateDisentangled; + return; + } + + if (mState == eStateEntangling) { + ConnectToPBackground(); + } else { + MOZ_ASSERT(mState == eStateUnshippedEntangled); + } + + // The port has to keep itself alive until it's entangled. + UpdateMustKeepAlive(); + + if (!NS_IsMainThread()) { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + MOZ_ASSERT(!mWorkerHolder); + + nsAutoPtr<WorkerHolder> workerHolder(new MessagePortWorkerHolder(this)); + if (NS_WARN_IF(!workerHolder->HoldWorker(workerPrivate, Closing))) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + mWorkerHolder = Move(workerHolder); + } else if (GetOwner()) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(GetOwner()->IsInnerWindow()); + mInnerID = GetOwner()->WindowID(); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "inner-window-destroyed", false); + } + } +} + +JSObject* +MessagePort::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return MessagePortBinding::Wrap(aCx, this, aGivenProto); +} + +void +MessagePort::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const Optional<Sequence<JS::Value>>& aTransferable, + ErrorResult& aRv) +{ + // We *must* clone the data here, or the JS::Value could be modified + // by script + + JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue()); + if (aTransferable.WasPassed()) { + const Sequence<JS::Value>& realTransferable = aTransferable.Value(); + + // Here we want to check if the transerable object list contains + // this port. No other checks are done. + for (const JS::Value& value : realTransferable) { + if (!value.isObject()) { + continue; + } + + JS::Rooted<JSObject*> object(aCx, &value.toObject()); + + MessagePort* port = nullptr; + nsresult rv = UNWRAP_OBJECT(MessagePort, &object, port); + if (NS_FAILED(rv)) { + continue; + } + + if (port == this) { + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + return; + } + } + + // The input sequence only comes from the generated bindings code, which + // ensures it is rooted. + JS::HandleValueArray elements = + JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(), + realTransferable.Elements()); + + JSObject* array = + JS_NewArrayObject(aCx, elements); + if (!array) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + transferable.setObject(*array); + } + + RefPtr<SharedMessagePortMessage> data = new SharedMessagePortMessage(); + + UniquePtr<AbstractTimelineMarker> start; + UniquePtr<AbstractTimelineMarker> end; + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + bool isTimelineRecording = timelines && !timelines->IsEmpty(); + + if (isTimelineRecording) { + start = MakeUnique<MessagePortTimelineMarker>( + ProfileTimelineMessagePortOperationType::SerializeData, + MarkerTracingType::START); + } + + data->Write(aCx, aMessage, transferable, + JS::CloneDataPolicy().denySharedArrayBuffer(), aRv); + + if (isTimelineRecording) { + end = MakeUnique<MessagePortTimelineMarker>( + ProfileTimelineMessagePortOperationType::SerializeData, + MarkerTracingType::END); + timelines->AddMarkerForAllObservedDocShells(start); + timelines->AddMarkerForAllObservedDocShells(end); + } + + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + // This message has to be ignored. + if (mState > eStateEntangled) { + return; + } + + // If we are unshipped we are connected to the other port on the same thread. + if (mState == eStateUnshippedEntangled) { + MOZ_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<SharedMessagePortMessage>, 1> array; + array.AppendElement(data); + + AutoTArray<MessagePortMessage, 1> messages; + SharedMessagePortMessage::FromSharedToMessagesChild(mActor, array, messages); + mActor->SendPostMessages(messages); +} + +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<SharedMessagePortMessage> data = mMessages.ElementAt(0); + mMessages.RemoveElementAt(0); + + mPostMessageRunnable = new PostMessageRunnable(this, data); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mPostMessageRunnable)); +} + +void +MessagePort::Close() +{ + 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(); + } + + if (mState == eStateUnshippedEntangled) { + MOZ_ASSERT(mUnshippedEntangledPort); + + // This avoids loops. + RefPtr<MessagePort> port = Move(mUnshippedEntangledPort); + MOZ_ASSERT(mUnshippedEntangledPort == nullptr); + + 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() +{ + if (NS_IsMainThread()) { + return GetEventHandler(nsGkAtoms::onmessage, EmptyString()); + } + return GetEventHandler(nullptr, NS_LITERAL_STRING("message")); +} + +void +MessagePort::SetOnmessage(EventHandlerNonNull* aCallback) +{ + if (NS_IsMainThread()) { + SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback); + } else { + SetEventHandler(nullptr, NS_LITERAL_STRING("message"), 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<MessagePortMessage>& 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<MessagePortMessage> messages; + SharedMessagePortMessage::FromSharedToMessagesChild(mActor, + mMessagesForTheOtherPort, + messages); + mMessagesForTheOtherPort.Clear(); + mActor->SendPostMessages(messages); + } + + // We must convert the messages into SharedMessagePortMessages to avoid leaks. + FallibleTArray<RefPtr<SharedMessagePortMessage>> data; + if (NS_WARN_IF(!SharedMessagePortMessage::FromMessagesToSharedChild(aMessages, + data))) { + // OOM, we cannot continue. + 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<MessagePortMessage>& 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<SharedMessagePortMessage>> data; + if (NS_WARN_IF(!SharedMessagePortMessage::FromMessagesToSharedChild(aMessages, + data))) { + // OOM, We cannot continue. + 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<MessagePortMessage> messages; + SharedMessagePortMessage::FromSharedToMessagesChild(mActor, mMessages, + messages); + mMessages.Clear(); + mActor->SendDisentangle(messages); + + mActor->SetPort(nullptr); + mActor = nullptr; + + UpdateMustKeepAlive(); +} + +void +MessagePort::CloneAndDisentangle(MessagePortIdentifier& aIdentifier) +{ + MOZ_ASSERT(mIdentifier); + + // 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()); + + // Disconnect the entangled port and connect it to PBackground. + mUnshippedEntangledPort->ConnectToPBackground(); + mUnshippedEntangledPort = nullptr; + + // 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. + ConnectToPBackground(); + + 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(); +} + +void +MessagePort::ConnectToPBackground() +{ + mState = eStateEntangling; + + PBackgroundChild* actor = + mozilla::ipc::BackgroundChild::GetForCurrentThread(); + if (actor) { + ActorCreated(actor); + } else { + if (NS_WARN_IF( + !mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(this))) { + MOZ_CRASH(); + } + } +} + +void +MessagePort::ActorFailed() +{ + MOZ_CRASH("Failed to create a PBackgroundChild actor!"); +} + +void +MessagePort::ActorCreated(mozilla::ipc::PBackgroundChild* aActor) +{ + MOZ_ASSERT(aActor); + MOZ_ASSERT(!mActor); + MOZ_ASSERT(mIdentifier); + MOZ_ASSERT(mState == eStateEntangling || + mState == eStateEntanglingForDisentangle || + mState == eStateEntanglingForClose); + + PMessagePortChild* actor = + aActor->SendPMessagePortConstructor(mIdentifier->uuid(), + mIdentifier->destinationUuid(), + mIdentifier->sequenceId()); + + mActor = static_cast<MessagePortChild*>(actor); + MOZ_ASSERT(mActor); + + mActor->SetPort(this); +} + +void +MessagePort::UpdateMustKeepAlive() +{ + if (mState >= eStateDisentangled && + mMessages.IsEmpty() && + mIsKeptAlive) { + mIsKeptAlive = false; + + // The DTOR of this WorkerHolder will release the worker for us. + mWorkerHolder = nullptr; + + if (NS_IsMainThread()) { + nsCOMPtr<nsIObserverService> obs = + do_GetService("@mozilla.org/observer-service;1"); + if (obs) { + obs->RemoveObserver(this, "inner-window-destroyed"); + } + } + + Release(); + return; + } + + if (mState < eStateDisentangled && !mIsKeptAlive) { + mIsKeptAlive = true; + AddRef(); + } +} + +NS_IMETHODIMP +MessagePort::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (strcmp(aTopic, "inner-window-destroyed")) { + return NS_OK; + } + + // If the window id destroyed we have to release the reference that we are + // keeping. + if (!mIsKeptAlive) { + return NS_OK; + } + + nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject); + NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); + + uint64_t innerID; + nsresult rv = wrapper->GetData(&innerID); + NS_ENSURE_SUCCESS(rv, rv); + + if (innerID == mInnerID) { + CloseForced(); + } + + return NS_OK; +} + +void +MessagePort::RemoveDocFromBFCache() +{ + if (!NS_IsMainThread()) { + return; + } + + nsPIDOMWindowInner* window = GetOwner(); + if (!window) { + return; + } + + nsIDocument* doc = window->GetExtantDoc(); + if (!doc) { + return; + } + + nsCOMPtr<nsIBFCacheEntry> bfCacheEntry = doc->GetBFCacheEntry(); + if (!bfCacheEntry) { + return; + } + + bfCacheEntry->RemoveFromBFCacheSync(); +} + +/* static */ void +MessagePort::ForceClose(const MessagePortIdentifier& aIdentifier) +{ + ForceCloseHelper::ForceClose(aIdentifier); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/messagechannel/MessagePort.h b/dom/messagechannel/MessagePort.h new file mode 100644 index 000000000..afa909195 --- /dev/null +++ b/dom/messagechannel/MessagePort.h @@ -0,0 +1,192 @@ +/* -*- 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 "nsAutoPtr.h" +#include "nsIIPCBackgroundChildCreateCallback.h" +#include "nsTArray.h" + +#ifdef XP_WIN +#undef PostMessage +#endif + +class nsIGlobalObject; + +namespace mozilla { +namespace dom { + +class MessagePortChild; +class MessagePortIdentifier; +class MessagePortMessage; +class PostMessageRunnable; +class SharedMessagePortMessage; + +namespace workers { +class WorkerHolder; +} // namespace workers + +class MessagePort final : public DOMEventTargetHelper + , public nsIIPCBackgroundChildCreateCallback + , public nsIObserver +{ + friend class PostMessageRunnable; + +public: + NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK + NS_DECL_NSIOBSERVER + 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, + const MessagePortIdentifier& 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 Optional<Sequence<JS::Value>>& aTransferable, + ErrorResult& aRv); + + void Start(); + + void Close(); + + EventHandlerNonNull* GetOnmessage(); + + void SetOnmessage(EventHandlerNonNull* aCallback); + + // Non WebIDL methods + + void UnshippedEntangle(MessagePort* aEntangledPort); + + void CloneAndDisentangle(MessagePortIdentifier& aIdentifier); + + void CloseForced(); + + // These methods are useful for MessagePortChild + + void Entangled(nsTArray<MessagePortMessage>& aMessages); + void MessagesReceived(nsTArray<MessagePortMessage>& aMessages); + void StopSendingDataConfirmed(); + void Closed(); + +private: + explicit MessagePort(nsIGlobalObject* aGlobal); + ~MessagePort(); + + 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 + }; + + void Initialize(const nsID& aUUID, const nsID& aDestinationUUID, + uint32_t aSequenceID, bool mNeutered, State aState, + ErrorResult& aRv); + + void ConnectToPBackground(); + + // Dispatch events from the Message Queue using a nsRunnable. + void Dispatch(); + + 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; + } + + nsAutoPtr<workers::WorkerHolder> mWorkerHolder; + + RefPtr<PostMessageRunnable> mPostMessageRunnable; + + RefPtr<MessagePortChild> mActor; + + RefPtr<MessagePort> mUnshippedEntangledPort; + + nsTArray<RefPtr<SharedMessagePortMessage>> mMessages; + nsTArray<RefPtr<SharedMessagePortMessage>> mMessagesForTheOtherPort; + + nsAutoPtr<MessagePortIdentifier> mIdentifier; + + uint64_t mInnerID; + + State mState; + + bool mMessageQueueEnabled; + + bool mIsKeptAlive; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_MessagePort_h diff --git a/dom/messagechannel/MessagePortChild.cpp b/dom/messagechannel/MessagePortChild.cpp new file mode 100644 index 000000000..4560b3c4d --- /dev/null +++ b/dom/messagechannel/MessagePortChild.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 { +namespace dom { + +bool +MessagePortChild::RecvStopSendingDataConfirmed() +{ + MOZ_ASSERT(mPort); + mPort->StopSendingDataConfirmed(); + MOZ_ASSERT(!mPort); + return true; +} + +bool +MessagePortChild::RecvEntangled(nsTArray<MessagePortMessage>&& aMessages) +{ + if (mPort) { + mPort->Entangled(aMessages); + } + return true; +} + +bool +MessagePortChild::RecvReceiveData(nsTArray<MessagePortMessage>&& aMessages) +{ + if (mPort) { + mPort->MessagesReceived(aMessages); + } + return true; +} + +void +MessagePortChild::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mPort) { + mPort->Closed(); + MOZ_ASSERT(!mPort); + } +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/messagechannel/MessagePortChild.h b/dom/messagechannel/MessagePortChild.h new file mode 100644 index 000000000..4777bc64e --- /dev/null +++ b/dom/messagechannel/MessagePortChild.h @@ -0,0 +1,52 @@ +/* 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 { +namespace dom { + +class MessagePort; + +class MessagePortChild final : public PMessagePortChild +{ +public: + NS_INLINE_DECL_REFCOUNTING(MessagePortChild) + + MessagePortChild() : mPort(nullptr) {} + + void SetPort(MessagePort* aPort) + { + mPort = aPort; + } + +private: + ~MessagePortChild() + { + MOZ_ASSERT(!mPort); + } + + virtual bool + RecvEntangled(nsTArray<MessagePortMessage>&& aMessages) override; + + virtual bool + RecvReceiveData(nsTArray<MessagePortMessage>&& aMessages) override; + + virtual bool RecvStopSendingDataConfirmed() override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + // This is a raw pointer because this child is owned by this MessagePort. + MessagePort* mPort; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_MessagePortChild_h diff --git a/dom/messagechannel/MessagePortParent.cpp b/dom/messagechannel/MessagePortParent.cpp new file mode 100644 index 000000000..bf68c2630 --- /dev/null +++ b/dom/messagechannel/MessagePortParent.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "SharedMessagePortMessage.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace 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); +} + +bool +MessagePortParent::RecvPostMessages(nsTArray<MessagePortMessage>&& aMessages) +{ + // This converts the object in a data struct where we have BlobImpls. + FallibleTArray<RefPtr<SharedMessagePortMessage>> messages; + if (NS_WARN_IF( + !SharedMessagePortMessage::FromMessagesToSharedParent(aMessages, + messages))) { + return false; + } + + if (!mEntangled) { + return false; + } + + if (!mService) { + NS_WARNING("Entangle is called after a shutdown!"); + return false; + } + + if (messages.IsEmpty()) { + return false; + } + + return mService->PostMessages(this, messages); +} + +bool +MessagePortParent::RecvDisentangle(nsTArray<MessagePortMessage>&& aMessages) +{ + // This converts the object in a data struct where we have BlobImpls. + FallibleTArray<RefPtr<SharedMessagePortMessage>> messages; + if (NS_WARN_IF( + !SharedMessagePortMessage::FromMessagesToSharedParent(aMessages, + messages))) { + return false; + } + + if (!mEntangled) { + return false; + } + + if (!mService) { + NS_WARNING("Entangle is called after a shutdown!"); + return false; + } + + if (!mService->DisentanglePort(this, messages)) { + return false; + } + + CloseAndDelete(); + return true; +} + +bool +MessagePortParent::RecvStopSendingData() +{ + if (!mEntangled) { + return true; + } + + mCanSendData = false; + Unused << SendStopSendingDataConfirmed(); + return true; +} + +bool +MessagePortParent::RecvClose() +{ + if (mService) { + MOZ_ASSERT(mEntangled); + + if (!mService->ClosePort(this)) { + return false; + } + + Close(); + } + + MOZ_ASSERT(!mEntangled); + + Unused << Send__delete__(this); + return true; +} + +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(const nsTArray<MessagePortMessage>& 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."); + return false; + } + + return service->ForceClose(aUUID, aDestinationUUID, aSequenceID); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/messagechannel/MessagePortParent.h b/dom/messagechannel/MessagePortParent.h new file mode 100644 index 000000000..4cbebe29b --- /dev/null +++ b/dom/messagechannel/MessagePortParent.h @@ -0,0 +1,65 @@ +/* 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/dom/PMessagePortParent.h" + +namespace mozilla { +namespace dom { + +class MessagePortService; + +class MessagePortParent final : public PMessagePortParent +{ +public: + explicit MessagePortParent(const nsID& aUUID); + ~MessagePortParent(); + + bool Entangle(const nsID& aDestinationUUID, + const uint32_t& aSequenceID); + + bool Entangled(const nsTArray<MessagePortMessage>& 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: + virtual bool RecvPostMessages(nsTArray<MessagePortMessage>&& aMessages) + override; + + virtual bool RecvDisentangle(nsTArray<MessagePortMessage>&& aMessages) + override; + + virtual bool RecvStopSendingData() override; + + virtual bool RecvClose() override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + RefPtr<MessagePortService> mService; + const nsID mUUID; + bool mEntangled; + bool mCanSendData; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_MessagePortParent_h diff --git a/dom/messagechannel/MessagePortService.cpp b/dom/messagechannel/MessagePortService.cpp new file mode 100644 index 000000000..a2d495d77 --- /dev/null +++ b/dom/messagechannel/MessagePortService.cpp @@ -0,0 +1,409 @@ +/* -*- 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 "SharedMessagePortMessage.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "nsTArray.h" + +using mozilla::ipc::AssertIsOnBackgroundThread; + +namespace mozilla { +namespace dom { + +namespace { + +StaticRefPtr<MessagePortService> gInstance; + +void +AssertIsInMainProcess() +{ + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); +} + +} // namespace + +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; + + ~MessagePortServiceData() + { + MOZ_COUNT_DTOR(MessagePortServiceData); + } + + nsID mDestinationUUID; + + uint32_t mSequenceID; + MessagePortParent* mParent; + + struct NextParent + { + uint32_t mSequenceID; + // MessagePortParent keeps the service alive, and we don't want a cycle. + MessagePortParent* mParent; + }; + + FallibleTArray<NextParent> mNextParents; + FallibleTArray<RefPtr<SharedMessagePortMessage>> 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; + } + + data = new MessagePortServiceData(aParent->ID()); + mPorts.Put(aDestinationUUID, data); + + data = new MessagePortServiceData(aDestinationUUID); + mPorts.Put(aParent->ID(), data); + } + + // 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; + FallibleTArray<MessagePortMessage> array; + if (!SharedMessagePortMessage::FromSharedToMessagesParent(aParent, + data->mMessages, + array)) { + CloseAll(aParent->ID()); + return false; + } + + data->mMessages.Clear(); + + // We can entangle the port. + if (!aParent->Entangled(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. + MessagePortServiceData::NextParent* 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<SharedMessagePortMessage>>& 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(data->mMessages, mozilla::fallible)) { + return false; + } + + data->mMessages.Clear(); + + ++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.SwapElements(aMessages); + data->mWaitingForNewParent = true; + data->mParent = nullptr; + return true; + } + + data->mParent = nextParent; + data->mNextParents.RemoveElementAt(index); + + FallibleTArray<MessagePortMessage> array; + if (!SharedMessagePortMessage::FromSharedToMessagesParent(data->mParent, + aMessages, + array)) { + return false; + } + + Unused << data->mParent->Entangled(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(); + } + + for (const MessagePortServiceData::NextParent& parent : data->mNextParents) { + parent.mParent->CloseAndDelete(); + } + + 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; + } + +#ifdef DEBUG + for (auto iter = mPorts.Iter(); !iter.Done(); iter.Next()) { + MOZ_ASSERT(!aUUID.Equals(iter.Key())); + } +#endif + + 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<SharedMessagePortMessage>>& 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(aMessages, mozilla::fallible)) { + return false; + } + + // If the parent can send data to the child, let's proceed. + if (data->mParent && data->mParent->CanSendData()) { + FallibleTArray<MessagePortMessage> messages; + if (!SharedMessagePortMessage::FromSharedToMessagesParent(data->mParent, + data->mMessages, + messages)) { + return false; + } + + data->mMessages.Clear(); + Unused << data->mParent->SendReceiveData(messages); + } + + 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()"); + return true; + } + + if (!data->mDestinationUUID.Equals(aDestinationUUID) || + data->mSequenceID != aSequenceID) { + NS_WARNING("DestinationUUID and/or sequenceID do not match."); + return false; + } + + CloseAll(aUUID, true); + return true; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/messagechannel/MessagePortService.h b/dom/messagechannel/MessagePortService.h new file mode 100644 index 000000000..eedaa40dd --- /dev/null +++ b/dom/messagechannel/MessagePortService.h @@ -0,0 +1,60 @@ +/* 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 { +namespace dom { + +class MessagePortParent; +class SharedMessagePortMessage; + +class MessagePortService final +{ +public: + NS_INLINE_DECL_REFCOUNTING(MessagePortService) + + static MessagePortService* Get(); + static MessagePortService* GetOrCreate(); + + bool RequestEntangling(MessagePortParent* aParent, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID); + + bool DisentanglePort( + MessagePortParent* aParent, + FallibleTArray<RefPtr<SharedMessagePortMessage>>& aMessages); + + bool ClosePort(MessagePortParent* aParent); + + bool PostMessages( + MessagePortParent* aParent, + FallibleTArray<RefPtr<SharedMessagePortMessage>>& aMessages); + + void ParentDestroy(MessagePortParent* aParent); + + bool ForceClose(const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID); + +private: + ~MessagePortService() {} + + void CloseAll(const nsID& aUUID, bool aForced = false); + void MaybeShutdown(); + + class MessagePortServiceData; + + nsClassHashtable<nsIDHashKey, MessagePortServiceData> mPorts; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_MessagePortService_h diff --git a/dom/messagechannel/PMessagePort.ipdl b/dom/messagechannel/PMessagePort.ipdl new file mode 100644 index 000000000..db4cb5a6f --- /dev/null +++ b/dom/messagechannel/PMessagePort.ipdl @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PBackground; +include protocol PBlob; + +include DOMTypes; + +using struct mozilla::SerializedStructuredCloneBuffer + from "ipc/IPCMessageUtils.h"; + +namespace mozilla { +namespace dom { + + +struct MessagePortMessage +{ + SerializedStructuredCloneBuffer data; + PBlob[] blobs; + MessagePortIdentifier[] transferredPorts; +}; + +// This protocol is used for the MessageChannel/MessagePort API +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(MessagePortMessage[] messages); + async Disentangle(MessagePortMessage[] messages); + async StopSendingData(); + async Close(); + +child: + async Entangled(MessagePortMessage[] messages); + async ReceiveData(MessagePortMessage[] messages); + async StopSendingDataConfirmed(); + + async __delete__(); +}; + +} // namespace dom +} // namespace mozilla + diff --git a/dom/messagechannel/SharedMessagePortMessage.cpp b/dom/messagechannel/SharedMessagePortMessage.cpp new file mode 100644 index 000000000..f7ace9dd5 --- /dev/null +++ b/dom/messagechannel/SharedMessagePortMessage.cpp @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "SharedMessagePortMessage.h" +#include "MessagePort.h" +#include "MessagePortChild.h" +#include "MessagePortParent.h" +#include "mozilla/dom/ipc/BlobChild.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/PMessagePort.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundParent.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +/* static */ void +SharedMessagePortMessage::FromSharedToMessagesChild( + MessagePortChild* aActor, + const nsTArray<RefPtr<SharedMessagePortMessage>>& aData, + nsTArray<MessagePortMessage>& aArray) +{ + MOZ_ASSERT(aActor); + MOZ_ASSERT(aArray.IsEmpty()); + aArray.SetCapacity(aData.Length()); + + PBackgroundChild* backgroundManager = aActor->Manager(); + MOZ_ASSERT(backgroundManager); + + for (auto& data : aData) { + MessagePortMessage* message = aArray.AppendElement(); + data->mBuffer->abandon(); + data->mBuffer->steal(&message->data().data); + + const nsTArray<RefPtr<BlobImpl>>& blobImpls = data->BlobImpls(); + if (!blobImpls.IsEmpty()) { + message->blobsChild().SetCapacity(blobImpls.Length()); + + for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) { + PBlobChild* blobChild = + BackgroundChild::GetOrCreateActorForBlobImpl(backgroundManager, + blobImpls[i]); + message->blobsChild().AppendElement(blobChild); + } + } + + message->transferredPorts().AppendElements(data->PortIdentifiers()); + } +} + +/* static */ bool +SharedMessagePortMessage::FromMessagesToSharedChild( + nsTArray<MessagePortMessage>& aArray, + FallibleTArray<RefPtr<SharedMessagePortMessage>>& aData) +{ + MOZ_ASSERT(aData.IsEmpty()); + + if (NS_WARN_IF(!aData.SetCapacity(aArray.Length(), mozilla::fallible))) { + return false; + } + + for (auto& message : aArray) { + RefPtr<SharedMessagePortMessage> data = new SharedMessagePortMessage(); + + data->mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>( + JS::StructuredCloneScope::DifferentProcess, nullptr, nullptr); + data->mBuffer->adopt(Move(message.data().data), JS_STRUCTURED_CLONE_VERSION, + &StructuredCloneHolder::sCallbacks, data.get()); + + const nsTArray<PBlobChild*>& blobs = message.blobsChild(); + if (!blobs.IsEmpty()) { + data->BlobImpls().SetCapacity(blobs.Length()); + + for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) { + RefPtr<BlobImpl> impl = + static_cast<BlobChild*>(blobs[i])->GetBlobImpl(); + data->BlobImpls().AppendElement(impl); + } + } + + data->PortIdentifiers().AppendElements(message.transferredPorts()); + + if (!aData.AppendElement(data, mozilla::fallible)) { + return false; + } + } + + return true; +} + +/* static */ bool +SharedMessagePortMessage::FromSharedToMessagesParent( + MessagePortParent* aActor, + const nsTArray<RefPtr<SharedMessagePortMessage>>& aData, + FallibleTArray<MessagePortMessage>& aArray) +{ + MOZ_ASSERT(aArray.IsEmpty()); + + if (NS_WARN_IF(!aArray.SetCapacity(aData.Length(), mozilla::fallible))) { + return false; + } + + PBackgroundParent* backgroundManager = aActor->Manager(); + MOZ_ASSERT(backgroundManager); + + for (auto& data : aData) { + MessagePortMessage* message = aArray.AppendElement(mozilla::fallible); + data->mBuffer->abandon(); + data->mBuffer->steal(&message->data().data); + + const nsTArray<RefPtr<BlobImpl>>& blobImpls = data->BlobImpls(); + if (!blobImpls.IsEmpty()) { + message->blobsParent().SetCapacity(blobImpls.Length()); + + for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) { + PBlobParent* blobParent = + BackgroundParent::GetOrCreateActorForBlobImpl(backgroundManager, + blobImpls[i]); + message->blobsParent().AppendElement(blobParent); + } + } + + message->transferredPorts().AppendElements(data->PortIdentifiers()); + } + + return true; +} + +/* static */ bool +SharedMessagePortMessage::FromMessagesToSharedParent( + nsTArray<MessagePortMessage>& aArray, + FallibleTArray<RefPtr<SharedMessagePortMessage>>& aData) +{ + MOZ_ASSERT(aData.IsEmpty()); + + if (NS_WARN_IF(!aData.SetCapacity(aArray.Length(), mozilla::fallible))) { + return false; + } + + for (auto& message : aArray) { + RefPtr<SharedMessagePortMessage> data = new SharedMessagePortMessage(); + + data->mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>( + JS::StructuredCloneScope::DifferentProcess, nullptr, nullptr); + data->mBuffer->adopt(Move(message.data().data), JS_STRUCTURED_CLONE_VERSION, + &StructuredCloneHolder::sCallbacks, data.get()); + + const nsTArray<PBlobParent*>& blobs = message.blobsParent(); + if (!blobs.IsEmpty()) { + data->BlobImpls().SetCapacity(blobs.Length()); + + for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) { + RefPtr<BlobImpl> impl = + static_cast<BlobParent*>(blobs[i])->GetBlobImpl(); + data->BlobImpls().AppendElement(impl); + } + } + + data->PortIdentifiers().AppendElements(message.transferredPorts()); + + if (!aData.AppendElement(data, mozilla::fallible)) { + return false; + } + } + + return true; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/messagechannel/SharedMessagePortMessage.h b/dom/messagechannel/SharedMessagePortMessage.h new file mode 100644 index 000000000..7c3214c81 --- /dev/null +++ b/dom/messagechannel/SharedMessagePortMessage.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SharedMessagePortMessage_h +#define mozilla_dom_SharedMessagePortMessage_h + +#include "mozilla/dom/StructuredCloneHolder.h" + +namespace mozilla { +namespace dom { + +class MessagePortChild; +class MessagePortMessage; +class MessagePortParent; + +class SharedMessagePortMessage final : public StructuredCloneHolder +{ +public: + NS_INLINE_DECL_REFCOUNTING(SharedMessagePortMessage) + + SharedMessagePortMessage() + : StructuredCloneHolder(CloningSupported, TransferringSupported, + StructuredCloneScope::DifferentProcess) + {} + + static void + FromSharedToMessagesChild( + MessagePortChild* aActor, + const nsTArray<RefPtr<SharedMessagePortMessage>>& aData, + nsTArray<MessagePortMessage>& aArray); + + static bool + FromMessagesToSharedChild( + nsTArray<MessagePortMessage>& aArray, + FallibleTArray<RefPtr<SharedMessagePortMessage>>& aData); + + static bool + FromSharedToMessagesParent( + MessagePortParent* aActor, + const nsTArray<RefPtr<SharedMessagePortMessage>>& aData, + FallibleTArray<MessagePortMessage>& aArray); + + static bool + FromMessagesToSharedParent( + nsTArray<MessagePortMessage>& aArray, + FallibleTArray<RefPtr<SharedMessagePortMessage>>& aData); + +private: + ~SharedMessagePortMessage() {} +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_SharedMessagePortMessage_h diff --git a/dom/messagechannel/moz.build b/dom/messagechannel/moz.build new file mode 100644 index 000000000..679d7eeb8 --- /dev/null +++ b/dom/messagechannel/moz.build @@ -0,0 +1,37 @@ +# -*- 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/. + +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', + 'SharedMessagePortMessage.cpp', +] + +IPDL_SOURCES += [ + 'PMessagePort.ipdl', +] + +LOCAL_INCLUDES += [ + '../base', + '../events', + '../workers', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' diff --git a/dom/messagechannel/tests/chrome.ini b/dom/messagechannel/tests/chrome.ini new file mode 100644 index 000000000..8613309c5 --- /dev/null +++ b/dom/messagechannel/tests/chrome.ini @@ -0,0 +1,12 @@ +[DEFAULT] +support-files = + iframe_messageChannel_chrome.html + mm_messageChannelParent.xul + mm_messageChannelParentNotRemote.xul + mm_messageChannelParent.js + mm_messageChannel.js + +[test_messageChannel.xul] +[test_messageChannelWithMessageManager.xul] +skip-if = os == 'android' +[test_messageChannelWithMessageManagerNotRemote.xul] diff --git a/dom/messagechannel/tests/iframe_messageChannel_chrome.html b/dom/messagechannel/tests/iframe_messageChannel_chrome.html new file mode 100644 index 000000000..1c9e4d3e1 --- /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, false); +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 000000000..de03cead9 --- /dev/null +++ b/dom/messagechannel/tests/iframe_messageChannel_cloning.html @@ -0,0 +1,22 @@ +<!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, false); + 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 000000000..d83d75c48 --- /dev/null +++ b/dom/messagechannel/tests/iframe_messageChannel_pingpong.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<body> + <script type="application/javascript"> + + function ok(what, msg) { + window.parent.postMessage({type: what ? 'OK' : 'KO', msg: msg }, '*'); + } + + window.addEventListener('message', receiveMessage, false); + function receiveMessage(evt) { + if (evt.data.type == 'PORT') { + var port = evt.data.port; + var counter = 0; + port.onmessage = function(evt) { + if (counter++ == 0) { + ok(!(evt.data % 2), "The number " + evt.data + " has been received correctly by the iframe"); + + window.parent.postMessage({ type: 'PORT', 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 000000000..e551dd61c --- /dev/null +++ b/dom/messagechannel/tests/iframe_messageChannel_post.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<body> + <script type="application/javascript"> + + var port; + + window.addEventListener('message', receiveMessage, false); + function receiveMessage(evt) { + port = evt.data.port; + + port.addEventListener('message', receivePostMessage, false); + function receivePostMessage(evt) { + port.postMessage(evt.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 000000000..a693cba22 --- /dev/null +++ b/dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html @@ -0,0 +1,14 @@ +<!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 000000000..108edeb7e --- /dev/null +++ b/dom/messagechannel/tests/iframe_messageChannel_transferable.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<body> + <script type="application/javascript"> + + function ok(what, msg) { + window.parent.postMessage({type: what ? 'OK' : 'KO', msg: msg }, '*'); + } + + window.addEventListener('message', receiveMessage, false); + 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(evt) { + evt.target.postMessage(evt.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 000000000..914d800fd --- /dev/null +++ b/dom/messagechannel/tests/mm_messageChannel.js @@ -0,0 +1,76 @@ +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: message });
+ throw 'failed check: ' + message;
+ }
+}
+
+function is(a, b, message) {
+ ok(a===b, message);
+}
+
+
+/**
+ * Testing codes.
+ */
+function addTestReceiver() {
+ addMessageListener("BasicTest:PortCreated", basicTest);
+ addMessageListener("CloseTest:PortCreated", closeTest);
+ addMessageListener("EmptyTest:PortCreated", emptyTest);
+ addMessageListener("NotTransferableTest:PortCreated", notTransferableTest);
+}
+
+function basicTest(msg) {
+ port = msg.ports[0];
+ is(toString.call(port), "[object MessagePort]", "created MessagePort.");
+
+ port.onmessage = (msg) => {
+ is(msg.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 = (msg) => {
+ ok(msg.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 000000000..a5ffa2caf --- /dev/null +++ b/dom/messagechannel/tests/mm_messageChannelParent.js @@ -0,0 +1,143 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); +let port; +let mm; + +function info(message) { + return opener.wrappedJSObject.info(message); +} + +function ok(condition, message) { + return opener.wrappedJSObject.ok(condition, message); +} + +function is(v1, v2, message) { + return opener.wrappedJSObject.is(v1, v2, message); +} + +function todo_is(v1, v2, message) { + return opener.wrappedJSObject.todo_is(v1, v2, message); +} + +function finish() { + opener.setTimeout("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) { + finish(); + 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 = (msg) => { + is(msg.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", {}, {}, undefined, [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 = (msg) => { + 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", {}, {}, undefined, [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", {}, {}, undefined, []); +} + +// 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", {}, {}, undefined, [""]); +} + +/* + * 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.xul b/dom/messagechannel/tests/mm_messageChannelParent.xul new file mode 100644 index 000000000..5f5c5ae5b --- /dev/null +++ b/dom/messagechannel/tests/mm_messageChannelParent.xul @@ -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.xul b/dom/messagechannel/tests/mm_messageChannelParentNotRemote.xul new file mode 100644 index 000000000..cb3b55dea --- /dev/null +++ b/dom/messagechannel/tests/mm_messageChannelParentNotRemote.xul @@ -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.ini b/dom/messagechannel/tests/mochitest.ini new file mode 100644 index 000000000..67d18d06f --- /dev/null +++ b/dom/messagechannel/tests/mochitest.ini @@ -0,0 +1,28 @@ +[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 + +[test_messageChannel.html] +[test_messageChannel_cloning.html] +[test_messageChannel_pingpong.html] +[test_messageChannel_post.html] +[test_messageChannel_start.html] +[test_messageChannel_transferable.html] +[test_messageChannel_unshipped.html] +[test_messageChannel_worker.html] +[test_messageChannel_selfTransferring.html] +[test_messageChannel_sharedWorker.html] +[test_messageChannel_sharedWorker2.html] +[test_messageChannel_any.html] +[test_messageChannel_forceClose.html] +[test_messageChannel_bug1178076.html] +[test_messageChannel_bug1224825.html] +[test_messageChannel_worker_forceClose.html] diff --git a/dom/messagechannel/tests/moz.build b/dom/messagechannel/tests/moz.build new file mode 100644 index 000000000..42fcbb69e --- /dev/null +++ b/dom/messagechannel/tests/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOCHITEST_MANIFESTS += ['mochitest.ini'] +MOCHITEST_CHROME_MANIFESTS += ['chrome.ini'] diff --git a/dom/messagechannel/tests/sharedWorker2_messageChannel.js b/dom/messagechannel/tests/sharedWorker2_messageChannel.js new file mode 100644 index 000000000..8cc98aa20 --- /dev/null +++ b/dom/messagechannel/tests/sharedWorker2_messageChannel.js @@ -0,0 +1,7 @@ +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 000000000..4b24642f9 --- /dev/null +++ b/dom/messagechannel/tests/sharedWorker_messageChannel.js @@ -0,0 +1,8 @@ +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_messageChannel.html b/dom/messagechannel/tests/test_messageChannel.html new file mode 100644 index 000000000..3e761e299 --- /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 type="application/javascript" 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.xul b/dom/messagechannel/tests/test_messageChannel.xul new file mode 100644 index 000000000..3d8e3485c --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel.xul @@ -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 type="application/javascript" 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.createElement('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.xul b/dom/messagechannel/tests/test_messageChannelWithMessageManager.xul new file mode 100644 index 000000000..11a1fc585 --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannelWithMessageManager.xul @@ -0,0 +1,28 @@ +<?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 type="application/javascript" + 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.open("mm_messageChannelParent.xul", "", "chrome"); + }); + ]]></script> +</window> diff --git a/dom/messagechannel/tests/test_messageChannelWithMessageManagerNotRemote.xul b/dom/messagechannel/tests/test_messageChannelWithMessageManagerNotRemote.xul new file mode 100644 index 000000000..24ea7ed0d --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannelWithMessageManagerNotRemote.xul @@ -0,0 +1,28 @@ +<?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 type="application/javascript" + 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.open("mm_messageChannelParentNotRemote.xul", "", "chrome"); + }); + ]]></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 000000000..845f5c734 --- /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 type="application/javascript" 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 (var p in a) aProps.push(p); + + var bProps = []; + for (var p in b) bProps.push(p); + + is (aProps.length, bProps.length, 'Props match'); + is (aProps.sort().toSource(), bProps.sort().toSource(), 'Props match - using toSource()'); + + for (var p in a) { + compare(a[p], b[p]); + } + + return; + } + + if (type != 'null') { + is (a.toSource(), b.toSource(), 'Matching using toSource()'); + } +} + +function runTest() { + var 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 000000000..bf7077250 --- /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 type="application/javascript" 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) { + ok(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 000000000..f50115547 --- /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 type="application/javascript" 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 (var i = 0; i < MAX; ++i) { + mc.port1.postMessage(i); + } + + mc.port1.close(); + + for (var 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 000000000..36aa3f0b7 --- /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 type="application/javascript" 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, false); + function receiveMessage(evt) { + if (evt.data.status == 'OK') { + ok(true, evt.data.message); + } else if (evt.data.status == 'KO') { + ok(false, evt.data.message); + } else 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, false); + 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 000000000..fd3efdc70 --- /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 type="application/javascript" 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 000000000..f13eb54e6 --- /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 type="application/javascript" 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, false); + 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(evt) { + // only 1 message should be received by this port. + if (counter++ == 0) { + ok(evt.data % 2, "The number " + evt.data + " has been received correctly by the main window"); + + if (evt.data < MAX - 1) { + ifr.contentWindow.postMessage({ type: 'PORT', 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, false); + 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 000000000..ddbf59dc9 --- /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 type="application/javascript" 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, false); + 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, false); + 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 000000000..d01ca5fc3 --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel_selfTransferring.html @@ -0,0 +1,33 @@ +<!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 type="application/javascript" 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 status = false; + try { + a.port1.postMessage('foobar', [a.port1]); + } catch(e) { + status =true; + } + + ok(status, "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 000000000..3b7b9ea69 --- /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 type="application/javascript" 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 000000000..57a1e606a --- /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 type="application/javascript" 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 000000000..192230adb --- /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 type="application/javascript" 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"); + }, false); + + a.port2.addEventListener('message', function(evt) { + ok(false, "This method should not be called"); + }, false); + + 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(); + }, false); + + a.port2.addEventListener('message', function(evt) { + ok(true, "This method should be called"); + if (!--events) runTests(); + }, false); + + 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(); + }, false); + + a.port2.addEventListener('message', function(evt) { + ok(false, "This method should not be called"); + }, false); + + 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"); + }, false); + + a.port2.addEventListener('message', function(evt) { + ok(true, "This method should be called"); + if (!--events) runTests(); + }, false); + + 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(); + }, false); + + a.port2.start(); + a.port2.addEventListener('message', function(evt) { + ok(true, "This method should be called"); + if (!--events) runTests(); + }, false); + } + + 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(evt) { + a.port1.onmessage = function(evt) { + ok(true, "This method should be called"); + testOnMessageCloneFinish(); + } + + evt.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 000000000..095bf25dc --- /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 type="application/javascript" 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, false); + function receiveMessage(evt) { + if (evt.data.status == 'READY') { + a.port1.postMessage({ab: 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, false); + 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, false); + 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, false); + 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, false); + 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(evt) { + is(evt.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 000000000..6661b7d0a --- /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 type="application/javascript" 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 000000000..2c1c02626 --- /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 type="application/javascript" 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 000000000..1610fc265 --- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<div id="content"></div> +<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/worker_messageChannel.js b/dom/messagechannel/tests/worker_messageChannel.js new file mode 100644 index 000000000..87b0b8eb0 --- /dev/null +++ b/dom/messagechannel/tests/worker_messageChannel.js @@ -0,0 +1,119 @@ +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 000000000..bbb1d50f9 --- /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"); |