michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "MessagePort.h" michael@0: #include "MessageEvent.h" michael@0: #include "mozilla/dom/Event.h" michael@0: #include "mozilla/dom/MessageChannel.h" michael@0: #include "mozilla/dom/MessagePortBinding.h" michael@0: #include "mozilla/dom/MessagePortList.h" michael@0: #include "mozilla/dom/StructuredCloneTags.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsPresContext.h" michael@0: michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMFile.h" michael@0: #include "nsIDOMFileList.h" michael@0: #include "nsIPresShell.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: class DispatchEventRunnable : public nsRunnable michael@0: { michael@0: friend class MessagePort; michael@0: michael@0: public: michael@0: DispatchEventRunnable(MessagePort* aPort) michael@0: : mPort(aPort) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD michael@0: Run() michael@0: { michael@0: nsRefPtr mKungFuDeathGrip(this); michael@0: michael@0: mPort->mDispatchRunnable = nullptr; michael@0: mPort->Dispatch(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mPort; michael@0: }; michael@0: michael@0: class PostMessageRunnable : public nsRunnable michael@0: { michael@0: friend class MessagePort; michael@0: michael@0: public: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: PostMessageRunnable() michael@0: { michael@0: } michael@0: michael@0: ~PostMessageRunnable() michael@0: { michael@0: } michael@0: michael@0: JSAutoStructuredCloneBuffer& Buffer() michael@0: { michael@0: return mBuffer; michael@0: } michael@0: michael@0: bool StoreISupports(nsISupports* aSupports) michael@0: { michael@0: mSupportsArray.AppendElement(aSupports); michael@0: return true; michael@0: } michael@0: michael@0: void Dispatch(MessagePort* aPort) michael@0: { michael@0: mPort = aPort; michael@0: NS_DispatchToCurrentThread(this); michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mPort; michael@0: JSAutoStructuredCloneBuffer mBuffer; michael@0: michael@0: nsTArray > mSupportsArray; michael@0: }; michael@0: michael@0: namespace { michael@0: michael@0: struct StructuredCloneInfo michael@0: { michael@0: PostMessageRunnable* mEvent; michael@0: MessagePort* mPort; michael@0: nsRefPtrHashtable, MessagePortBase> mPorts; michael@0: }; michael@0: michael@0: static JSObject* michael@0: PostMessageReadStructuredClone(JSContext* cx, michael@0: JSStructuredCloneReader* reader, michael@0: uint32_t tag, michael@0: uint32_t data, michael@0: void* closure) michael@0: { michael@0: if (tag == SCTAG_DOM_BLOB || tag == SCTAG_DOM_FILELIST) { michael@0: NS_ASSERTION(!data, "Data should be empty"); michael@0: michael@0: nsISupports* supports; michael@0: if (JS_ReadBytes(reader, &supports, sizeof(supports))) { michael@0: JS::Rooted val(cx); michael@0: if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, supports, &val))) { michael@0: return JSVAL_TO_OBJECT(val); michael@0: } michael@0: } michael@0: } michael@0: michael@0: const JSStructuredCloneCallbacks* runtimeCallbacks = michael@0: js::GetContextStructuredCloneCallbacks(cx); michael@0: michael@0: if (runtimeCallbacks) { michael@0: return runtimeCallbacks->read(cx, reader, tag, data, nullptr); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: static bool michael@0: PostMessageWriteStructuredClone(JSContext* cx, michael@0: JSStructuredCloneWriter* writer, michael@0: JS::Handle obj, michael@0: void *closure) michael@0: { michael@0: StructuredCloneInfo* scInfo = static_cast(closure); michael@0: NS_ASSERTION(scInfo, "Must have scInfo!"); michael@0: michael@0: nsCOMPtr wrappedNative; michael@0: nsContentUtils::XPConnect()-> michael@0: GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wrappedNative)); michael@0: if (wrappedNative) { michael@0: uint32_t scTag = 0; michael@0: nsISupports* supports = wrappedNative->Native(); michael@0: michael@0: nsCOMPtr blob = do_QueryInterface(supports); michael@0: if (blob) { michael@0: scTag = SCTAG_DOM_BLOB; michael@0: } michael@0: michael@0: nsCOMPtr list = do_QueryInterface(supports); michael@0: if (list) { michael@0: scTag = SCTAG_DOM_FILELIST; michael@0: } michael@0: michael@0: if (scTag) { michael@0: return JS_WriteUint32Pair(writer, scTag, 0) && michael@0: JS_WriteBytes(writer, &supports, sizeof(supports)) && michael@0: scInfo->mEvent->StoreISupports(supports); michael@0: } michael@0: } michael@0: michael@0: const JSStructuredCloneCallbacks* runtimeCallbacks = michael@0: js::GetContextStructuredCloneCallbacks(cx); michael@0: michael@0: if (runtimeCallbacks) { michael@0: return runtimeCallbacks->write(cx, writer, obj, nullptr); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: PostMessageReadTransferStructuredClone(JSContext* aCx, michael@0: JSStructuredCloneReader* reader, michael@0: uint32_t tag, void* data, michael@0: uint64_t unused, michael@0: void* aClosure, michael@0: JS::MutableHandle returnObject) michael@0: { michael@0: StructuredCloneInfo* scInfo = static_cast(aClosure); michael@0: NS_ASSERTION(scInfo, "Must have scInfo!"); michael@0: michael@0: if (tag == SCTAG_DOM_MAP_MESSAGEPORT) { michael@0: MessagePort* port = static_cast(data); michael@0: port->BindToOwner(scInfo->mPort->GetOwner()); michael@0: scInfo->mPorts.Put(port, nullptr); michael@0: michael@0: JS::Rooted obj(aCx, port->WrapObject(aCx)); michael@0: if (JS_WrapObject(aCx, &obj)) { michael@0: MOZ_ASSERT(port->GetOwner() == scInfo->mPort->GetOwner()); michael@0: returnObject.set(obj); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: PostMessageTransferStructuredClone(JSContext* aCx, michael@0: JS::Handle aObj, michael@0: void* aClosure, michael@0: uint32_t* aTag, michael@0: JS::TransferableOwnership* aOwnership, michael@0: void** aContent, michael@0: uint64_t *aExtraData) michael@0: { michael@0: StructuredCloneInfo* scInfo = static_cast(aClosure); michael@0: NS_ASSERTION(scInfo, "Must have scInfo!"); michael@0: michael@0: MessagePortBase *port = nullptr; michael@0: nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsRefPtr newPort; michael@0: if (scInfo->mPorts.Get(port, getter_AddRefs(newPort))) { michael@0: // No duplicate. michael@0: return false; michael@0: } michael@0: michael@0: newPort = port->Clone(); michael@0: scInfo->mPorts.Put(port, newPort); michael@0: michael@0: *aTag = SCTAG_DOM_MAP_MESSAGEPORT; michael@0: *aOwnership = JS::SCTAG_TMO_CUSTOM; michael@0: *aContent = newPort; michael@0: *aExtraData = 0; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static void michael@0: PostMessageFreeTransferStructuredClone(uint32_t aTag, JS::TransferableOwnership aOwnership, michael@0: void* aData, michael@0: uint64_t aExtraData, michael@0: void* aClosure) michael@0: { michael@0: StructuredCloneInfo* scInfo = static_cast(aClosure); michael@0: NS_ASSERTION(scInfo, "Must have scInfo!"); michael@0: michael@0: if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { michael@0: MOZ_ASSERT(aOwnership == JS::SCTAG_TMO_CUSTOM); michael@0: nsRefPtr port(static_cast(aData)); michael@0: scInfo->mPorts.Remove(port); michael@0: } michael@0: } michael@0: michael@0: JSStructuredCloneCallbacks kPostMessageCallbacks = { michael@0: PostMessageReadStructuredClone, michael@0: PostMessageWriteStructuredClone, michael@0: nullptr, michael@0: PostMessageReadTransferStructuredClone, michael@0: PostMessageTransferStructuredClone, michael@0: PostMessageFreeTransferStructuredClone michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: static PLDHashOperator michael@0: PopulateMessagePortList(MessagePortBase* aKey, MessagePortBase* aValue, void* aClosure) michael@0: { michael@0: nsTArray > *array = michael@0: static_cast > *>(aClosure); michael@0: michael@0: array->AppendElement(aKey); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PostMessageRunnable::Run() michael@0: { michael@0: MOZ_ASSERT(mPort); michael@0: michael@0: // Get the JSContext for the target window michael@0: nsCOMPtr sgo = do_QueryInterface(mPort->GetOwner()); michael@0: NS_ENSURE_STATE(sgo); michael@0: nsCOMPtr scriptContext = sgo->GetContext(); michael@0: AutoPushJSContext cx(scriptContext ? scriptContext->GetNativeContext() michael@0: : nsContentUtils::GetSafeJSContext()); michael@0: michael@0: MOZ_ASSERT(cx); michael@0: michael@0: // Deserialize the structured clone data michael@0: JS::Rooted messageData(cx); michael@0: StructuredCloneInfo scInfo; michael@0: scInfo.mEvent = this; michael@0: scInfo.mPort = mPort; michael@0: michael@0: if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) { michael@0: return NS_ERROR_DOM_DATA_CLONE_ERR; michael@0: } michael@0: michael@0: // Create the event michael@0: nsCOMPtr eventTarget = michael@0: do_QueryInterface(mPort->GetOwner()); michael@0: nsRefPtr event = michael@0: new MessageEvent(eventTarget, nullptr, nullptr); michael@0: michael@0: event->InitMessageEvent(NS_LITERAL_STRING("message"), false /* non-bubbling */, michael@0: false /* cancelable */, messageData, EmptyString(), michael@0: EmptyString(), nullptr); michael@0: event->SetTrusted(true); michael@0: event->SetSource(mPort); michael@0: michael@0: nsTArray > ports; michael@0: scInfo.mPorts.EnumerateRead(PopulateMessagePortList, &ports); michael@0: event->SetPorts(new MessagePortList(static_cast(event.get()), ports)); michael@0: michael@0: bool status; michael@0: mPort->DispatchEvent(static_cast(event.get()), &status); michael@0: return status ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: MessagePortBase::MessagePortBase(nsPIDOMWindow* aWindow) michael@0: : DOMEventTargetHelper(aWindow) michael@0: { michael@0: // SetIsDOMBinding() is called by DOMEventTargetHelper's ctor. michael@0: } michael@0: michael@0: MessagePortBase::MessagePortBase() michael@0: { michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(MessagePort) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessagePort, michael@0: DOMEventTargetHelper) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntangledPort) michael@0: michael@0: // Custom unlink loop because this array contains nsRunnable objects michael@0: // which are not cycle colleactable. michael@0: while (!tmp->mMessageQueue.IsEmpty()) { michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageQueue[0]->mPort); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageQueue[0]->mSupportsArray); michael@0: tmp->mMessageQueue.RemoveElementAt(0); michael@0: } michael@0: michael@0: if (tmp->mDispatchRunnable) { michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mDispatchRunnable->mPort); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessagePort, michael@0: DOMEventTargetHelper) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntangledPort) michael@0: michael@0: // Custom unlink loop because this array contains nsRunnable objects michael@0: // which are not cycle colleactable. michael@0: for (uint32_t i = 0, len = tmp->mMessageQueue.Length(); i < len; ++i) { michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageQueue[i]->mPort); michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageQueue[i]->mSupportsArray); michael@0: } michael@0: michael@0: if (tmp->mDispatchRunnable) { michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDispatchRunnable->mPort); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MessagePort) michael@0: NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(MessagePort, DOMEventTargetHelper) michael@0: NS_IMPL_RELEASE_INHERITED(MessagePort, DOMEventTargetHelper) michael@0: michael@0: MessagePort::MessagePort(nsPIDOMWindow* aWindow) michael@0: : MessagePortBase(aWindow) michael@0: , mMessageQueueEnabled(false) michael@0: { michael@0: } michael@0: michael@0: MessagePort::~MessagePort() michael@0: { michael@0: Close(); michael@0: } michael@0: michael@0: JSObject* michael@0: MessagePort::WrapObject(JSContext* aCx) michael@0: { michael@0: return MessagePortBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: MessagePort::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, michael@0: const Optional>& aTransferable, michael@0: ErrorResult& aRv) michael@0: { michael@0: nsRefPtr event = new PostMessageRunnable(); michael@0: michael@0: // We *must* clone the data here, or the JS::Value could be modified michael@0: // by script michael@0: StructuredCloneInfo scInfo; michael@0: scInfo.mEvent = event; michael@0: scInfo.mPort = this; michael@0: michael@0: JS::Rooted transferable(aCx, JS::UndefinedValue()); michael@0: if (aTransferable.WasPassed()) { michael@0: const Sequence& realTransferable = aTransferable.Value(); michael@0: michael@0: // The input sequence only comes from the generated bindings code, which michael@0: // ensures it is rooted. michael@0: JS::HandleValueArray elements = michael@0: JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(), michael@0: realTransferable.Elements()); michael@0: michael@0: JSObject* array = michael@0: JS_NewArrayObject(aCx, elements); michael@0: if (!array) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return; michael@0: } michael@0: transferable.setObject(*array); michael@0: } michael@0: michael@0: if (!event->Buffer().write(aCx, aMessage, transferable, michael@0: &kPostMessageCallbacks, &scInfo)) { michael@0: aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!mEntangledPort) { michael@0: return; michael@0: } michael@0: michael@0: mEntangledPort->mMessageQueue.AppendElement(event); michael@0: mEntangledPort->Dispatch(); michael@0: } michael@0: michael@0: void michael@0: MessagePort::Start() michael@0: { michael@0: if (mMessageQueueEnabled) { michael@0: return; michael@0: } michael@0: michael@0: mMessageQueueEnabled = true; michael@0: Dispatch(); michael@0: } michael@0: michael@0: void michael@0: MessagePort::Dispatch() michael@0: { michael@0: if (!mMessageQueueEnabled || mMessageQueue.IsEmpty() || mDispatchRunnable) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr event = mMessageQueue.ElementAt(0); michael@0: mMessageQueue.RemoveElementAt(0); michael@0: michael@0: event->Dispatch(this); michael@0: michael@0: mDispatchRunnable = new DispatchEventRunnable(this); michael@0: NS_DispatchToCurrentThread(mDispatchRunnable); michael@0: } michael@0: michael@0: void michael@0: MessagePort::Close() michael@0: { michael@0: if (!mEntangledPort) { michael@0: return; michael@0: } michael@0: michael@0: // This avoids loops. michael@0: nsRefPtr port = mEntangledPort; michael@0: mEntangledPort = nullptr; michael@0: michael@0: // Let's disentangle the 2 ports symmetrically. michael@0: port->Close(); michael@0: } michael@0: michael@0: EventHandlerNonNull* michael@0: MessagePort::GetOnmessage() michael@0: { michael@0: if (NS_IsMainThread()) { michael@0: return GetEventHandler(nsGkAtoms::onmessage, EmptyString()); michael@0: } michael@0: return GetEventHandler(nullptr, NS_LITERAL_STRING("message")); michael@0: } michael@0: michael@0: void michael@0: MessagePort::SetOnmessage(EventHandlerNonNull* aCallback) michael@0: { michael@0: if (NS_IsMainThread()) { michael@0: SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback); michael@0: } else { michael@0: SetEventHandler(nullptr, NS_LITERAL_STRING("message"), aCallback); michael@0: } michael@0: michael@0: // When using onmessage, the call to start() is implied. michael@0: Start(); michael@0: } michael@0: michael@0: void michael@0: MessagePort::Entangle(MessagePort* aMessagePort) michael@0: { michael@0: MOZ_ASSERT(aMessagePort); michael@0: MOZ_ASSERT(aMessagePort != this); michael@0: michael@0: Close(); michael@0: michael@0: mEntangledPort = aMessagePort; michael@0: } michael@0: michael@0: already_AddRefed michael@0: MessagePort::Clone() michael@0: { michael@0: nsRefPtr newPort = new MessagePort(nullptr); michael@0: michael@0: // Move all the events in the port message queue of original port. michael@0: newPort->mMessageQueue.SwapElements(mMessageQueue); michael@0: michael@0: if (mEntangledPort) { michael@0: nsRefPtr port = mEntangledPort; michael@0: mEntangledPort = nullptr; michael@0: michael@0: newPort->Entangle(port); michael@0: port->Entangle(newPort); michael@0: } michael@0: michael@0: return newPort.forget(); michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla