diff -r 000000000000 -r 6474c204b198 dom/base/MessagePort.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/base/MessagePort.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,526 @@ +/* -*- 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 "MessagePort.h" +#include "MessageEvent.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/MessageChannel.h" +#include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/MessagePortList.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "nsPresContext.h" + +#include "nsIDocument.h" +#include "nsIDOMFile.h" +#include "nsIDOMFileList.h" +#include "nsIPresShell.h" + +namespace mozilla { +namespace dom { + +class DispatchEventRunnable : public nsRunnable +{ + friend class MessagePort; + + public: + DispatchEventRunnable(MessagePort* aPort) + : mPort(aPort) + { + } + + NS_IMETHOD + Run() + { + nsRefPtr mKungFuDeathGrip(this); + + mPort->mDispatchRunnable = nullptr; + mPort->Dispatch(); + + return NS_OK; + } + + private: + nsRefPtr mPort; +}; + +class PostMessageRunnable : public nsRunnable +{ + friend class MessagePort; + + public: + NS_DECL_NSIRUNNABLE + + PostMessageRunnable() + { + } + + ~PostMessageRunnable() + { + } + + JSAutoStructuredCloneBuffer& Buffer() + { + return mBuffer; + } + + bool StoreISupports(nsISupports* aSupports) + { + mSupportsArray.AppendElement(aSupports); + return true; + } + + void Dispatch(MessagePort* aPort) + { + mPort = aPort; + NS_DispatchToCurrentThread(this); + } + + private: + nsRefPtr mPort; + JSAutoStructuredCloneBuffer mBuffer; + + nsTArray > mSupportsArray; +}; + +namespace { + +struct StructuredCloneInfo +{ + PostMessageRunnable* mEvent; + MessagePort* mPort; + nsRefPtrHashtable, MessagePortBase> mPorts; +}; + +static JSObject* +PostMessageReadStructuredClone(JSContext* cx, + JSStructuredCloneReader* reader, + uint32_t tag, + uint32_t data, + void* closure) +{ + if (tag == SCTAG_DOM_BLOB || tag == SCTAG_DOM_FILELIST) { + NS_ASSERTION(!data, "Data should be empty"); + + nsISupports* supports; + if (JS_ReadBytes(reader, &supports, sizeof(supports))) { + JS::Rooted val(cx); + if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, supports, &val))) { + return JSVAL_TO_OBJECT(val); + } + } + } + + const JSStructuredCloneCallbacks* runtimeCallbacks = + js::GetContextStructuredCloneCallbacks(cx); + + if (runtimeCallbacks) { + return runtimeCallbacks->read(cx, reader, tag, data, nullptr); + } + + return nullptr; +} + +static bool +PostMessageWriteStructuredClone(JSContext* cx, + JSStructuredCloneWriter* writer, + JS::Handle obj, + void *closure) +{ + StructuredCloneInfo* scInfo = static_cast(closure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + nsCOMPtr wrappedNative; + nsContentUtils::XPConnect()-> + GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wrappedNative)); + if (wrappedNative) { + uint32_t scTag = 0; + nsISupports* supports = wrappedNative->Native(); + + nsCOMPtr blob = do_QueryInterface(supports); + if (blob) { + scTag = SCTAG_DOM_BLOB; + } + + nsCOMPtr list = do_QueryInterface(supports); + if (list) { + scTag = SCTAG_DOM_FILELIST; + } + + if (scTag) { + return JS_WriteUint32Pair(writer, scTag, 0) && + JS_WriteBytes(writer, &supports, sizeof(supports)) && + scInfo->mEvent->StoreISupports(supports); + } + } + + const JSStructuredCloneCallbacks* runtimeCallbacks = + js::GetContextStructuredCloneCallbacks(cx); + + if (runtimeCallbacks) { + return runtimeCallbacks->write(cx, writer, obj, nullptr); + } + + return false; +} + +static bool +PostMessageReadTransferStructuredClone(JSContext* aCx, + JSStructuredCloneReader* reader, + uint32_t tag, void* data, + uint64_t unused, + void* aClosure, + JS::MutableHandle returnObject) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + if (tag == SCTAG_DOM_MAP_MESSAGEPORT) { + MessagePort* port = static_cast(data); + port->BindToOwner(scInfo->mPort->GetOwner()); + scInfo->mPorts.Put(port, nullptr); + + JS::Rooted obj(aCx, port->WrapObject(aCx)); + if (JS_WrapObject(aCx, &obj)) { + MOZ_ASSERT(port->GetOwner() == scInfo->mPort->GetOwner()); + returnObject.set(obj); + } + return true; + } + + return false; +} + +static bool +PostMessageTransferStructuredClone(JSContext* aCx, + JS::Handle aObj, + void* aClosure, + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t *aExtraData) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + MessagePortBase *port = nullptr; + nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); + if (NS_SUCCEEDED(rv)) { + nsRefPtr newPort; + if (scInfo->mPorts.Get(port, getter_AddRefs(newPort))) { + // No duplicate. + return false; + } + + newPort = port->Clone(); + scInfo->mPorts.Put(port, newPort); + + *aTag = SCTAG_DOM_MAP_MESSAGEPORT; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = newPort; + *aExtraData = 0; + + return true; + } + + return false; +} + +static void +PostMessageFreeTransferStructuredClone(uint32_t aTag, JS::TransferableOwnership aOwnership, + void* aData, + uint64_t aExtraData, + void* aClosure) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { + MOZ_ASSERT(aOwnership == JS::SCTAG_TMO_CUSTOM); + nsRefPtr port(static_cast(aData)); + scInfo->mPorts.Remove(port); + } +} + +JSStructuredCloneCallbacks kPostMessageCallbacks = { + PostMessageReadStructuredClone, + PostMessageWriteStructuredClone, + nullptr, + PostMessageReadTransferStructuredClone, + PostMessageTransferStructuredClone, + PostMessageFreeTransferStructuredClone +}; + +} // anonymous namespace + +static PLDHashOperator +PopulateMessagePortList(MessagePortBase* aKey, MessagePortBase* aValue, void* aClosure) +{ + nsTArray > *array = + static_cast > *>(aClosure); + + array->AppendElement(aKey); + return PL_DHASH_NEXT; +} + +NS_IMETHODIMP +PostMessageRunnable::Run() +{ + MOZ_ASSERT(mPort); + + // Get the JSContext for the target window + nsCOMPtr sgo = do_QueryInterface(mPort->GetOwner()); + NS_ENSURE_STATE(sgo); + nsCOMPtr scriptContext = sgo->GetContext(); + AutoPushJSContext cx(scriptContext ? scriptContext->GetNativeContext() + : nsContentUtils::GetSafeJSContext()); + + MOZ_ASSERT(cx); + + // Deserialize the structured clone data + JS::Rooted messageData(cx); + StructuredCloneInfo scInfo; + scInfo.mEvent = this; + scInfo.mPort = mPort; + + if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + + // Create the event + nsCOMPtr eventTarget = + do_QueryInterface(mPort->GetOwner()); + nsRefPtr event = + new MessageEvent(eventTarget, nullptr, nullptr); + + event->InitMessageEvent(NS_LITERAL_STRING("message"), false /* non-bubbling */, + false /* cancelable */, messageData, EmptyString(), + EmptyString(), nullptr); + event->SetTrusted(true); + event->SetSource(mPort); + + nsTArray > ports; + scInfo.mPorts.EnumerateRead(PopulateMessagePortList, &ports); + event->SetPorts(new MessagePortList(static_cast(event.get()), ports)); + + bool status; + mPort->DispatchEvent(static_cast(event.get()), &status); + return status ? NS_OK : NS_ERROR_FAILURE; +} + +MessagePortBase::MessagePortBase(nsPIDOMWindow* aWindow) + : DOMEventTargetHelper(aWindow) +{ + // SetIsDOMBinding() is called by DOMEventTargetHelper's ctor. +} + +MessagePortBase::MessagePortBase() +{ + SetIsDOMBinding(); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(MessagePort) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessagePort, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntangledPort) + + // Custom unlink loop because this array contains nsRunnable objects + // which are not cycle colleactable. + while (!tmp->mMessageQueue.IsEmpty()) { + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageQueue[0]->mPort); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageQueue[0]->mSupportsArray); + tmp->mMessageQueue.RemoveElementAt(0); + } + + if (tmp->mDispatchRunnable) { + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDispatchRunnable->mPort); + } + +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessagePort, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntangledPort) + + // Custom unlink loop because this array contains nsRunnable objects + // which are not cycle colleactable. + for (uint32_t i = 0, len = tmp->mMessageQueue.Length(); i < len; ++i) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageQueue[i]->mPort); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageQueue[i]->mSupportsArray); + } + + if (tmp->mDispatchRunnable) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDispatchRunnable->mPort); + } + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MessagePort) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(MessagePort, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(MessagePort, DOMEventTargetHelper) + +MessagePort::MessagePort(nsPIDOMWindow* aWindow) + : MessagePortBase(aWindow) + , mMessageQueueEnabled(false) +{ +} + +MessagePort::~MessagePort() +{ + Close(); +} + +JSObject* +MessagePort::WrapObject(JSContext* aCx) +{ + return MessagePortBinding::Wrap(aCx, this); +} + +void +MessagePort::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, + const Optional>& aTransferable, + ErrorResult& aRv) +{ + nsRefPtr event = new PostMessageRunnable(); + + // We *must* clone the data here, or the JS::Value could be modified + // by script + StructuredCloneInfo scInfo; + scInfo.mEvent = event; + scInfo.mPort = this; + + JS::Rooted transferable(aCx, JS::UndefinedValue()); + if (aTransferable.WasPassed()) { + const Sequence& realTransferable = aTransferable.Value(); + + // 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); + } + + if (!event->Buffer().write(aCx, aMessage, transferable, + &kPostMessageCallbacks, &scInfo)) { + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + return; + } + + if (!mEntangledPort) { + return; + } + + mEntangledPort->mMessageQueue.AppendElement(event); + mEntangledPort->Dispatch(); +} + +void +MessagePort::Start() +{ + if (mMessageQueueEnabled) { + return; + } + + mMessageQueueEnabled = true; + Dispatch(); +} + +void +MessagePort::Dispatch() +{ + if (!mMessageQueueEnabled || mMessageQueue.IsEmpty() || mDispatchRunnable) { + return; + } + + nsRefPtr event = mMessageQueue.ElementAt(0); + mMessageQueue.RemoveElementAt(0); + + event->Dispatch(this); + + mDispatchRunnable = new DispatchEventRunnable(this); + NS_DispatchToCurrentThread(mDispatchRunnable); +} + +void +MessagePort::Close() +{ + if (!mEntangledPort) { + return; + } + + // This avoids loops. + nsRefPtr port = mEntangledPort; + mEntangledPort = nullptr; + + // Let's disentangle the 2 ports symmetrically. + port->Close(); +} + +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(); +} + +void +MessagePort::Entangle(MessagePort* aMessagePort) +{ + MOZ_ASSERT(aMessagePort); + MOZ_ASSERT(aMessagePort != this); + + Close(); + + mEntangledPort = aMessagePort; +} + +already_AddRefed +MessagePort::Clone() +{ + nsRefPtr newPort = new MessagePort(nullptr); + + // Move all the events in the port message queue of original port. + newPort->mMessageQueue.SwapElements(mMessageQueue); + + if (mEntangledPort) { + nsRefPtr port = mEntangledPort; + mEntangledPort = nullptr; + + newPort->Entangle(port); + port->Entangle(newPort); + } + + return newPort.forget(); +} + +} // namespace dom +} // namespace mozilla