/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* 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 "mozilla/dom/MessagePortBinding.h" #include "nsDOMEvent.h" #include "nsEventDispatcher.h" #include "SharedWorker.h" #include "WorkerPrivate.h" using mozilla::dom::EventHandlerNonNull; using mozilla::dom::MessagePortBase; using mozilla::dom::Optional; using mozilla::dom::Sequence; USING_WORKERS_NAMESPACE namespace { class DelayedEventRunnable MOZ_FINAL : public WorkerRunnable { nsRefPtr mMessagePort; nsTArray> mEvents; public: DelayedEventRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget, MessagePort* aMessagePort, nsTArray>& aEvents) : WorkerRunnable(aWorkerPrivate, aTarget, aTarget == WorkerThread ? ModifyBusyCount : UnchangedBusyCount, SkipWhenClearing), mMessagePort(aMessagePort) { AssertIsOnMainThread(); MOZ_ASSERT(aMessagePort); MOZ_ASSERT(aEvents.Length()); mEvents.SwapElements(aEvents); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate); }; } // anonymous namespace MessagePort::MessagePort(nsPIDOMWindow* aWindow, SharedWorker* aSharedWorker, uint64_t aSerial) : MessagePortBase(aWindow), mSharedWorker(aSharedWorker), mWorkerPrivate(nullptr), mSerial(aSerial), mStarted(false) { AssertIsOnMainThread(); MOZ_ASSERT(aSharedWorker); SetIsDOMBinding(); } MessagePort::MessagePort(WorkerPrivate* aWorkerPrivate, uint64_t aSerial) : mWorkerPrivate(aWorkerPrivate), mSerial(aSerial), mStarted(false) { aWorkerPrivate->AssertIsOnWorkerThread(); SetIsDOMBinding(); } MessagePort::~MessagePort() { Close(); } void MessagePort::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, const Optional>& aTransferable, ErrorResult& aRv) { AssertCorrectThread(); if (IsClosed()) { aRv = NS_ERROR_DOM_INVALID_STATE_ERR; return; } if (mSharedWorker) { mSharedWorker->PostMessage(aCx, aMessage, aTransferable, aRv); } else { mWorkerPrivate->PostMessageToParentMessagePort(aCx, Serial(), aMessage, aTransferable, aRv); } } void MessagePort::Start() { AssertCorrectThread(); if (IsClosed()) { NS_WARNING("Called start() after calling close()!"); return; } if (mStarted) { return; } mStarted = true; if (!mQueuedEvents.IsEmpty()) { WorkerRunnable::Target target = WorkerRunnable::WorkerThread; WorkerPrivate* workerPrivate = mWorkerPrivate; if (!workerPrivate) { target = WorkerRunnable::ParentThread; workerPrivate = mSharedWorker->GetWorkerPrivate(); } nsRefPtr runnable = new DelayedEventRunnable(workerPrivate, target, this, mQueuedEvents); runnable->Dispatch(nullptr); } } void MessagePort::Close() { AssertCorrectThread(); if (!IsClosed()) { CloseInternal(); } } void MessagePort::QueueEvent(nsIDOMEvent* aEvent) { AssertCorrectThread(); MOZ_ASSERT(aEvent); MOZ_ASSERT(!IsClosed()); MOZ_ASSERT(!mStarted); mQueuedEvents.AppendElement(aEvent); } EventHandlerNonNull* MessagePort::GetOnmessage() { AssertCorrectThread(); return NS_IsMainThread() ? GetEventHandler(nsGkAtoms::onmessage, EmptyString()) : GetEventHandler(nullptr, NS_LITERAL_STRING("message")); } void MessagePort::SetOnmessage(EventHandlerNonNull* aCallback) { AssertCorrectThread(); if (NS_IsMainThread()) { SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback); } else { SetEventHandler(nullptr, NS_LITERAL_STRING("message"), aCallback); } Start(); } already_AddRefed MessagePort::Clone() { NS_WARNING("Haven't implemented structured clone for these ports yet!"); return nullptr; } void MessagePort::CloseInternal() { AssertCorrectThread(); MOZ_ASSERT(!IsClosed()); MOZ_ASSERT_IF(mStarted, mQueuedEvents.IsEmpty()); NS_WARN_IF_FALSE(mStarted, "Called close() before start()!"); if (!mStarted) { mQueuedEvents.Clear(); } mSharedWorker = nullptr; mWorkerPrivate = nullptr; } #ifdef DEBUG void MessagePort::AssertCorrectThread() const { if (IsClosed()) { return; // Can't assert anything if we nulled out our pointers. } MOZ_ASSERT((mSharedWorker || mWorkerPrivate) && !(mSharedWorker && mWorkerPrivate)); if (mSharedWorker) { AssertIsOnMainThread(); } else { mWorkerPrivate->AssertIsOnWorkerThread(); } } #endif NS_IMPL_ADDREF_INHERITED(MessagePort, nsDOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(MessagePort, nsDOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MessagePort) NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_CLASS(MessagePort) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessagePort, nsDOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSharedWorker) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEvents) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessagePort, nsDOMEventTargetHelper) tmp->Close(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END JSObject* MessagePort::WrapObject(JSContext* aCx, JS::Handle aScope) { AssertCorrectThread(); return MessagePortBinding::Wrap(aCx, aScope, this); } nsresult MessagePort::PreHandleEvent(nsEventChainPreVisitor& aVisitor) { AssertCorrectThread(); nsIDOMEvent*& event = aVisitor.mDOMEvent; if (event) { bool preventDispatch = false; if (IsClosed()) { preventDispatch = true; } else if (NS_IsMainThread() && mSharedWorker->IsSuspended()) { mSharedWorker->QueueEvent(event); preventDispatch = true; } else if (!mStarted) { QueueEvent(event); preventDispatch = true; } if (preventDispatch) { aVisitor.mCanHandle = false; aVisitor.mParentTarget = nullptr; return NS_OK; } } return nsDOMEventTargetHelper::PreHandleEvent(aVisitor); } bool DelayedEventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { MOZ_ASSERT(mMessagePort); mMessagePort->AssertCorrectThread(); MOZ_ASSERT(mEvents.Length()); bool ignored; for (uint32_t i = 0; i < mEvents.Length(); i++) { mMessagePort->DispatchEvent(mEvents[i], &ignored); } return true; }