/* -*- 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 "mozilla/EventDispatcher.h" #include "mozilla/dom/MessagePortBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "nsIDOMEvent.h" #include "SharedWorker.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" using mozilla::dom::EventHandlerNonNull; using mozilla::dom::MessagePortBase; using mozilla::dom::MessagePortIdentifier; using mozilla::dom::Optional; using mozilla::dom::Sequence; using mozilla::dom::AutoNoJSAPI; using namespace mozilla; USING_WORKERS_NAMESPACE namespace { class DelayedEventRunnable final : public WorkerRunnable { nsRefPtr mMessagePort; nsTArray> mEvents; public: DelayedEventRunnable(WorkerPrivate* aWorkerPrivate, TargetAndBusyBehavior aBehavior, mozilla::dom::workers::MessagePort* aMessagePort, nsTArray>& aEvents) : WorkerRunnable(aWorkerPrivate, aBehavior), mMessagePort(aMessagePort) { AssertIsOnMainThread(); MOZ_ASSERT(aMessagePort); MOZ_ASSERT(aEvents.Length()); mEvents.SwapElements(aEvents); } bool PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { if (mBehavior == WorkerThreadModifyBusyCount) { return aWorkerPrivate->ModifyBusyCount(aCx, true); } return true; } void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aDispatchResult) { if (!aDispatchResult) { if (mBehavior == WorkerThreadModifyBusyCount) { aWorkerPrivate->ModifyBusyCount(aCx, false); } if (aCx) { JS_ReportPendingException(aCx); } } } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate); }; } // namespace BEGIN_WORKERS_NAMESPACE MessagePort::MessagePort(nsPIDOMWindow* aWindow, SharedWorker* aSharedWorker, uint64_t aSerial) : MessagePortBase(aWindow), mSharedWorker(aSharedWorker), mWorkerPrivate(nullptr), mSerial(aSerial), mStarted(false) { AssertIsOnMainThread(); MOZ_ASSERT(aSharedWorker); } MessagePort::MessagePort(WorkerPrivate* aWorkerPrivate, uint64_t aSerial) : mWorkerPrivate(aWorkerPrivate), mSerial(aSerial), mStarted(false) { aWorkerPrivate->AssertIsOnWorkerThread(); } MessagePort::~MessagePort() { Close(); } void MessagePort::PostMessage(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()) { WorkerPrivate* workerPrivate; WorkerRunnable::TargetAndBusyBehavior behavior; if (mWorkerPrivate) { workerPrivate = mWorkerPrivate; behavior = WorkerRunnable::WorkerThreadModifyBusyCount; } else { workerPrivate = mSharedWorker->GetWorkerPrivate(); MOZ_ASSERT(workerPrivate); behavior = WorkerRunnable::ParentThreadUnchangedBusyCount; } nsRefPtr runnable = new DelayedEventRunnable(workerPrivate, behavior, 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(); } bool MessagePort::CloneAndDisentangle(MessagePortIdentifier& aIdentifier) { NS_WARNING("Haven't implemented structured clone for these ports yet!"); return false; } void MessagePort::CloseInternal() { AssertCorrectThread(); MOZ_ASSERT(!IsClosed()); MOZ_ASSERT_IF(mStarted, mQueuedEvents.IsEmpty()); 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(mozilla::dom::workers::MessagePort, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(mozilla::dom::workers::MessagePort, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MessagePort) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_CLASS(MessagePort) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessagePort, DOMEventTargetHelper) 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, DOMEventTargetHelper) tmp->Close(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END JSObject* MessagePort::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { AssertCorrectThread(); return MessagePortBinding::Wrap(aCx, this, aGivenProto); } nsresult MessagePort::PreHandleEvent(EventChainPreVisitor& aVisitor) { AssertCorrectThread(); nsIDOMEvent*& event = aVisitor.mDOMEvent; if (event) { bool preventDispatch = false; if (IsClosed()) { preventDispatch = true; } else if (NS_IsMainThread() && mSharedWorker->IsFrozen()) { mSharedWorker->QueueEvent(event); preventDispatch = true; } else if (!mStarted) { QueueEvent(event); preventDispatch = true; } if (preventDispatch) { aVisitor.mCanHandle = false; aVisitor.mParentTarget = nullptr; return NS_OK; } } return DOMEventTargetHelper::PreHandleEvent(aVisitor); } END_WORKERS_NAMESPACE bool DelayedEventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { MOZ_ASSERT(mMessagePort); mMessagePort->AssertCorrectThread(); MOZ_ASSERT(mEvents.Length()); AutoNoJSAPI nojsapi; bool ignored; for (uint32_t i = 0; i < mEvents.Length(); i++) { mMessagePort->DispatchEvent(mEvents[i], &ignored); } return true; }