diff --git a/dom/base/MessageChannel.cpp b/dom/base/MessageChannel.cpp deleted file mode 100644 index e04ca2fc72d3..000000000000 --- a/dom/base/MessageChannel.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* -*- 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/Preferences.h" -#include "mozilla/dom/MessageChannelBinding.h" -#include "mozilla/dom/MessagePort.h" -#include "nsContentUtils.h" -#include "nsPIDOMWindow.h" - -namespace mozilla { -namespace dom { - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MessageChannel, mWindow, 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 - -namespace { - bool gPrefInitialized = false; - bool gPrefEnabled = false; - -} - -/* static */ bool -MessageChannel::Enabled(JSContext* aCx, JSObject* aObj) -{ - if (!gPrefInitialized) { - Preferences::AddBoolVarCache(&gPrefEnabled, "dom.messageChannel.enabled"); - gPrefInitialized = true; - } - - // Enabled by pref - if (gPrefEnabled) { - return true; - } - - // Chrome callers are allowed. - if (nsContentUtils::ThreadsafeIsCallerChrome()) { - return true; - } - - nsCOMPtr principal = nsContentUtils::SubjectPrincipal(); - MOZ_ASSERT(principal); - - nsCOMPtr uri; - if (NS_FAILED(principal->GetURI(getter_AddRefs(uri))) || !uri) { - return false; - } - - bool isResource = false; - if (NS_FAILED(uri->SchemeIs("resource", &isResource))) { - return false; - } - - return isResource; -} - -MessageChannel::MessageChannel(nsPIDOMWindow* aWindow) - : mWindow(aWindow) -{ - MOZ_COUNT_CTOR(MessageChannel); - - mPort1 = new MessagePort(mWindow); - mPort2 = new MessagePort(mWindow); - - mPort1->Entangle(mPort2); - mPort2->Entangle(mPort1); -} - -MessageChannel::~MessageChannel() -{ - MOZ_COUNT_DTOR(MessageChannel); -} - -JSObject* -MessageChannel::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - return MessageChannelBinding::Wrap(aCx, this, aGivenProto); -} - -/* static */ already_AddRefed -MessageChannel::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) -{ - nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); - if (!window) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return nullptr; - } - - nsRefPtr channel = new MessageChannel(window); - return channel.forget(); -} - -} // namespace dom -} // namespace mozilla diff --git a/dom/base/MessagePort.cpp b/dom/base/MessagePort.cpp deleted file mode 100644 index fbbafbdbf464..000000000000 --- a/dom/base/MessagePort.cpp +++ /dev/null @@ -1,564 +0,0 @@ -/* -*- 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 "mozilla/dom/BlobBinding.h" -#include "mozilla/dom/Event.h" -#include "mozilla/dom/File.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 "ScriptSettings.h" - -#include "nsIDocument.h" -#include "nsIDOMFileList.h" -#include "nsIPresShell.h" - -namespace mozilla { -namespace dom { - -class DispatchEventRunnable : public nsRunnable -{ - friend class MessagePort; - - public: - explicit 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) -{ - StructuredCloneInfo* scInfo = static_cast(closure); - NS_ASSERTION(scInfo, "Must have scInfo!"); - - if (tag == SCTAG_DOM_BLOB) { - NS_ASSERTION(!data, "Data should be empty"); - - // What we get back from the reader is a BlobImpl. - // From that we create a new File. - BlobImpl* blobImpl; - if (JS_ReadBytes(reader, &blobImpl, sizeof(blobImpl))) { - MOZ_ASSERT(blobImpl); - - // nsRefPtr needs to go out of scope before toObjectOrNull() is - // called because the static analysis thinks dereferencing XPCOM objects - // can GC (because in some cases it can!), and a return statement with a - // JSObject* type means that JSObject* is on the stack as a raw pointer - // while destructors are running. - JS::Rooted val(cx); - { - nsRefPtr blob = Blob::Create(scInfo->mPort->GetParentObject(), - blobImpl); - if (!ToJSValue(cx, blob, &val)) { - return nullptr; - } - } - - return &val.toObject(); - } - } - - if (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 val.toObjectOrNull(); - } - } - } - - 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!"); - - // See if this is a File/Blob object. - { - Blob* blob = nullptr; - if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, obj, blob))) { - BlobImpl* blobImpl = blob->Impl(); - if (JS_WriteUint32Pair(writer, SCTAG_DOM_BLOB, 0) && - JS_WriteBytes(writer, &blobImpl, sizeof(blobImpl))) { - scInfo->mEvent->StoreISupports(blobImpl); - return true; - } - } - } - - nsCOMPtr wrappedNative; - nsContentUtils::XPConnect()-> - GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wrappedNative)); - if (wrappedNative) { - uint32_t scTag = 0; - nsISupports* supports = wrappedNative->Native(); - - 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, nullptr)); - if (!obj || !JS_WrapObject(aCx, &obj)) { - return false; - } - - 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); - } -} - -const 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); - - AutoJSAPI jsapi; - if (NS_WARN_IF(!jsapi.Init(mPort->GetParentObject()))) { - return NS_ERROR_UNEXPECTED; - } - JSContext* cx = jsapi.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) -{ -} - -MessagePortBase::MessagePortBase() -{ -} - -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, JS::Handle aGivenProto) -{ - return MessagePortBinding::Wrap(aCx, this, aGivenProto); -} - -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 diff --git a/dom/base/MessagePort.h b/dom/base/MessagePort.h deleted file mode 100644 index fe51bc295fd0..000000000000 --- a/dom/base/MessagePort.h +++ /dev/null @@ -1,115 +0,0 @@ -/* -*- 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" - -class nsPIDOMWindow; - -namespace mozilla { -namespace dom { - -class DispatchEventRunnable; -class PostMessageRunnable; - -class MessagePortBase : public DOMEventTargetHelper -{ -protected: - explicit MessagePortBase(nsPIDOMWindow* aWindow); - MessagePortBase(); - -public: - - virtual void - PostMessageMoz(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, - ErrorResult& aRv) = 0; - - virtual void - Start() = 0; - - virtual void - Close() = 0; - - // The 'message' event handler has to call |Start()| method, so we - // cannot use IMPL_EVENT_HANDLER macro here. - virtual EventHandlerNonNull* - GetOnmessage() = 0; - - virtual void - SetOnmessage(EventHandlerNonNull* aCallback) = 0; - - // Duplicate this message port. This method is used by the Structured Clone - // Algorithm and makes the new MessagePort active with the entangled - // MessagePort of this object. - virtual already_AddRefed - Clone() = 0; -}; - -class MessagePort final : public MessagePortBase -{ - friend class DispatchEventRunnable; - friend class PostMessageRunnable; - -public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MessagePort, - DOMEventTargetHelper) - - explicit MessagePort(nsPIDOMWindow* aWindow); - - virtual JSObject* - WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - - virtual void - PostMessageMoz(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, - ErrorResult& aRv) override; - - virtual void - Start() override; - - virtual void - Close() override; - - virtual EventHandlerNonNull* - GetOnmessage() override; - - virtual void - SetOnmessage(EventHandlerNonNull* aCallback) override; - - // Non WebIDL methods - - // This method entangles this MessagePort with another one. - // If it is already entangled, it's disentangled first and enatangle to the - // new one. - void - Entangle(MessagePort* aMessagePort); - - virtual already_AddRefed - Clone() override; - -private: - ~MessagePort(); - - // Dispatch events from the Message Queue using a nsRunnable. - void Dispatch(); - - nsRefPtr mDispatchRunnable; - - nsRefPtr mEntangledPort; - - nsTArray > mMessageQueue; - bool mMessageQueueEnabled; -}; - -} // namespace dom -} // namespace mozilla - -#endif // mozilla_dom_MessagePort_h diff --git a/dom/base/NodeInfo.cpp b/dom/base/NodeInfo.cpp index 8c60eee4f455..00166783758f 100644 --- a/dom/base/NodeInfo.cpp +++ b/dom/base/NodeInfo.cpp @@ -111,7 +111,7 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(NodeInfo) NS_IMPL_CYCLE_COLLECTION_UNLINK_0(NodeInfo) -static const char* kNSURIs[] = { +static const char* kNodeInfoNSURIs[] = { " ([none])", " (xmlns)", " (xml)", @@ -129,8 +129,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(NodeInfo) char name[72]; uint32_t nsid = tmp->NamespaceID(); nsAtomCString localName(tmp->NameAtom()); - if (nsid < ArrayLength(kNSURIs)) { - PR_snprintf(name, sizeof(name), "NodeInfo%s %s", kNSURIs[nsid], + if (nsid < ArrayLength(kNodeInfoNSURIs)) { + PR_snprintf(name, sizeof(name), "NodeInfo%s %s", kNodeInfoNSURIs[nsid], localName.get()); } else { diff --git a/dom/base/PostMessageEvent.cpp b/dom/base/PostMessageEvent.cpp new file mode 100644 index 000000000000..534d389cda14 --- /dev/null +++ b/dom/base/PostMessageEvent.cpp @@ -0,0 +1,390 @@ +/* -*- 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 "PostMessageEvent.h" + +#include "MessageEvent.h" +#include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/PMessagePort.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/EventDispatcher.h" +#include "nsGlobalWindow.h" +#include "nsIPresShell.h" +#include "nsIPrincipal.h" + +namespace mozilla { +namespace dom { + +namespace { + +struct StructuredCloneInfo +{ + PostMessageEvent* event; + bool subsumes; + nsPIDOMWindow* window; + + // This hashtable contains the transferred ports - used to avoid duplicates. + nsTArray> transferredPorts; + + // This array is populated when the ports are cloned. + nsTArray> clonedPorts; +}; + +} // anonymous namespace + +const JSStructuredCloneCallbacks PostMessageEvent::sPostMessageCallbacks = { + PostMessageEvent::ReadStructuredClone, + PostMessageEvent::WriteStructuredClone, + nullptr, + PostMessageEvent::ReadTransferStructuredClone, + PostMessageEvent::TransferStructuredClone, + PostMessageEvent::FreeTransferStructuredClone +}; + +/* static */ JSObject* +PostMessageEvent::ReadStructuredClone(JSContext* cx, + JSStructuredCloneReader* reader, + uint32_t tag, + uint32_t data, + void* closure) +{ + StructuredCloneInfo* scInfo = static_cast(closure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + if (tag == SCTAG_DOM_BLOB) { + NS_ASSERTION(!data, "Data should be empty"); + + // What we get back from the reader is a BlobImpl. + // From that we create a new File. + BlobImpl* blobImpl; + if (JS_ReadBytes(reader, &blobImpl, sizeof(blobImpl))) { + MOZ_ASSERT(blobImpl); + + // nsRefPtr needs to go out of scope before toObjectOrNull() is + // called because the static analysis thinks dereferencing XPCOM objects + // can GC (because in some cases it can!), and a return statement with a + // JSObject* type means that JSObject* is on the stack as a raw pointer + // while destructors are running. + JS::Rooted val(cx); + { + nsRefPtr blob = Blob::Create(scInfo->window, blobImpl); + if (!ToJSValue(cx, blob, &val)) { + return nullptr; + } + } + + return &val.toObject(); + } + } + + if (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 val.toObjectOrNull(); + } + } + } + + const JSStructuredCloneCallbacks* runtimeCallbacks = + js::GetContextStructuredCloneCallbacks(cx); + + if (runtimeCallbacks) { + return runtimeCallbacks->read(cx, reader, tag, data, nullptr); + } + + return nullptr; +} + +/* static */ bool +PostMessageEvent::WriteStructuredClone(JSContext* cx, + JSStructuredCloneWriter* writer, + JS::Handle obj, + void *closure) +{ + StructuredCloneInfo* scInfo = static_cast(closure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + // See if this is a File/Blob object. + { + Blob* blob = nullptr; + if (scInfo->subsumes && NS_SUCCEEDED(UNWRAP_OBJECT(Blob, obj, blob))) { + BlobImpl* blobImpl = blob->Impl(); + if (JS_WriteUint32Pair(writer, SCTAG_DOM_BLOB, 0) && + JS_WriteBytes(writer, &blobImpl, sizeof(blobImpl))) { + scInfo->event->StoreISupports(blobImpl); + return true; + } + } + } + + nsCOMPtr wrappedNative; + nsContentUtils::XPConnect()-> + GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wrappedNative)); + if (wrappedNative) { + uint32_t scTag = 0; + nsISupports* supports = wrappedNative->Native(); + + nsCOMPtr list = do_QueryInterface(supports); + if (list && scInfo->subsumes) + scTag = SCTAG_DOM_FILELIST; + + if (scTag) + return JS_WriteUint32Pair(writer, scTag, 0) && + JS_WriteBytes(writer, &supports, sizeof(supports)) && + scInfo->event->StoreISupports(supports); + } + + const JSStructuredCloneCallbacks* runtimeCallbacks = + js::GetContextStructuredCloneCallbacks(cx); + + if (runtimeCallbacks) { + return runtimeCallbacks->write(cx, writer, obj, nullptr); + } + + return false; +} + +/* static */ bool +PostMessageEvent::ReadTransferStructuredClone(JSContext* aCx, + JSStructuredCloneReader* reader, + uint32_t tag, void* aData, + uint64_t aExtraData, + void* aClosure, + JS::MutableHandle returnObject) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + if (tag == SCTAG_DOM_MAP_MESSAGEPORT) { + MOZ_ASSERT(!aData); + // aExtraData is the index of this port identifier. + ErrorResult rv; + nsRefPtr port = + MessagePort::Create(scInfo->window, + scInfo->event->GetPortIdentifier(aExtraData), + rv); + if (NS_WARN_IF(rv.Failed())) { + return false; + } + + scInfo->clonedPorts.AppendElement(port); + + JS::Rooted value(aCx); + if (!GetOrCreateDOMReflector(aCx, port, &value)) { + JS_ClearPendingException(aCx); + return false; + } + + returnObject.set(&value.toObject()); + return true; + } + + return false; +} + +/* static */ bool +PostMessageEvent::TransferStructuredClone(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)) { + if (scInfo->transferredPorts.Contains(port)) { + // No duplicates. + return false; + } + + // We use aExtraData to store the index of this new port identifier. + MessagePortIdentifier* identifier = + scInfo->event->NewPortIdentifier(aExtraData); + + if (!port->CloneAndDisentangle(*identifier)) { + return false; + } + + scInfo->transferredPorts.AppendElement(port); + + *aTag = SCTAG_DOM_MAP_MESSAGEPORT; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = nullptr; + + return true; + } + + return false; +} + +/* static */ void +PostMessageEvent::FreeTransferStructuredClone(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void *aContent, + uint64_t aExtraData, + void* aClosure) +{ + // Nothing to do. +} + +PostMessageEvent::PostMessageEvent(nsGlobalWindow* aSource, + const nsAString& aCallerOrigin, + nsGlobalWindow* aTargetWindow, + nsIPrincipal* aProvidedPrincipal, + bool aTrustedCaller) +: mSource(aSource), + mCallerOrigin(aCallerOrigin), + mTargetWindow(aTargetWindow), + mProvidedPrincipal(aProvidedPrincipal), + mTrustedCaller(aTrustedCaller) +{ + MOZ_COUNT_CTOR(PostMessageEvent); +} + +PostMessageEvent::~PostMessageEvent() +{ + MOZ_COUNT_DTOR(PostMessageEvent); +} + +const MessagePortIdentifier& +PostMessageEvent::GetPortIdentifier(uint64_t aId) +{ + MOZ_ASSERT(aId < mPortIdentifiers.Length()); + return mPortIdentifiers[aId]; +} + +MessagePortIdentifier* +PostMessageEvent::NewPortIdentifier(uint64_t* aPosition) +{ + *aPosition = mPortIdentifiers.Length(); + return mPortIdentifiers.AppendElement(); +} + +NS_IMETHODIMP +PostMessageEvent::Run() +{ + MOZ_ASSERT(mTargetWindow->IsOuterWindow(), + "should have been passed an outer window!"); + MOZ_ASSERT(!mSource || mSource->IsOuterWindow(), + "should have been passed an outer window!"); + + AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + + // If we bailed before this point we're going to leak mMessage, but + // that's probably better than crashing. + + nsRefPtr targetWindow; + if (mTargetWindow->IsClosedOrClosing() || + !(targetWindow = mTargetWindow->GetCurrentInnerWindowInternal()) || + targetWindow->IsClosedOrClosing()) + return NS_OK; + + MOZ_ASSERT(targetWindow->IsInnerWindow(), + "we ordered an inner window!"); + JSAutoCompartment ac(cx, targetWindow->GetWrapperPreserveColor()); + + // Ensure that any origin which might have been provided is the origin of this + // window's document. Note that we do this *now* instead of when postMessage + // is called because the target window might have been navigated to a + // different location between then and now. If this check happened when + // postMessage was called, it would be fairly easy for a malicious webpage to + // intercept messages intended for another site by carefully timing navigation + // of the target window so it changed location after postMessage but before + // now. + if (mProvidedPrincipal) { + // Get the target's origin either from its principal or, in the case the + // principal doesn't carry a URI (e.g. the system principal), the target's + // document. + nsIPrincipal* targetPrin = targetWindow->GetPrincipal(); + if (NS_WARN_IF(!targetPrin)) + return NS_OK; + + // Note: This is contrary to the spec with respect to file: URLs, which + // the spec groups into a single origin, but given we intentionally + // don't do that in other places it seems better to hold the line for + // now. Long-term, we want HTML5 to address this so that we can + // be compliant while being safer. + if (!targetPrin->Equals(mProvidedPrincipal)) { + return NS_OK; + } + } + + // Deserialize the structured clone data + JS::Rooted messageData(cx); + StructuredCloneInfo scInfo; + scInfo.event = this; + scInfo.window = targetWindow; + + if (!mBuffer.read(cx, &messageData, &sPostMessageCallbacks, &scInfo)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + + // Create the event + nsCOMPtr eventTarget = + do_QueryInterface(static_cast(targetWindow.get())); + nsRefPtr event = + new MessageEvent(eventTarget, nullptr, nullptr); + + event->InitMessageEvent(NS_LITERAL_STRING("message"), false /*non-bubbling */, + false /*cancelable */, messageData, mCallerOrigin, + EmptyString(), mSource); + + event->SetPorts(new MessagePortList(static_cast(event.get()), + scInfo.clonedPorts)); + + // We can't simply call dispatchEvent on the window because doing so ends + // up flipping the trusted bit on the event, and we don't want that to + // happen because then untrusted content can call postMessage on a chrome + // window if it can get a reference to it. + + nsIPresShell *shell = targetWindow->GetExtantDoc()->GetShell(); + nsRefPtr presContext; + if (shell) + presContext = shell->GetPresContext(); + + event->SetTrusted(mTrustedCaller); + WidgetEvent* internalEvent = event->GetInternalNSEvent(); + + nsEventStatus status = nsEventStatus_eIgnore; + EventDispatcher::Dispatch(static_cast(mTargetWindow), + presContext, + internalEvent, + static_cast(event.get()), + &status); + return NS_OK; +} + +bool +PostMessageEvent::Write(JSContext* aCx, JS::Handle aMessage, + JS::Handle aTransfer, bool aSubsumes, + nsPIDOMWindow* aWindow) +{ + // We *must* clone the data here, or the JS::Value could be modified + // by script + StructuredCloneInfo scInfo; + scInfo.event = this; + scInfo.window = aWindow; + scInfo.subsumes = aSubsumes; + + return mBuffer.write(aCx, aMessage, aTransfer, &sPostMessageCallbacks, + &scInfo); +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/base/PostMessageEvent.h b/dom/base/PostMessageEvent.h new file mode 100644 index 000000000000..917d4da05558 --- /dev/null +++ b/dom/base/PostMessageEvent.h @@ -0,0 +1,108 @@ +/* -*- 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_PostMessageEvent_h +#define mozilla_dom_PostMessageEvent_h + +#include "js/StructuredClone.h" +#include "nsCOMPtr.h" +#include "nsRefPtr.h" +#include "nsTArray.h" + +class nsGlobalWindow; +class nsIPrincipal; + +namespace mozilla { +namespace dom { + +class MessagePortBase; +class MessagePortIdentifier; + +/** + * Class used to represent events generated by calls to Window.postMessage, + * which asynchronously creates and dispatches events. + */ +class PostMessageEvent final : public nsRunnable +{ +public: + NS_DECL_NSIRUNNABLE + + PostMessageEvent(nsGlobalWindow* aSource, + const nsAString& aCallerOrigin, + nsGlobalWindow* aTargetWindow, + nsIPrincipal* aProvidedPrincipal, + bool aTrustedCaller); + + bool Write(JSContext* aCx, JS::Handle aMessage, + JS::Handle aTransfer, bool aSubsumes, + nsPIDOMWindow* aWindow); + +private: + ~PostMessageEvent(); + + const MessagePortIdentifier& GetPortIdentifier(uint64_t aId); + + MessagePortIdentifier* NewPortIdentifier(uint64_t* aPosition); + + bool StoreISupports(nsISupports* aSupports) + { + mSupportsArray.AppendElement(aSupports); + return true; + } + + static JSObject* + ReadStructuredClone(JSContext* cx, + JSStructuredCloneReader* reader, + uint32_t tag, + uint32_t data, + void* closure); + + static bool + WriteStructuredClone(JSContext* cx, + JSStructuredCloneWriter* writer, + JS::Handle obj, + void *closure); + + static bool + ReadTransferStructuredClone(JSContext* aCx, + JSStructuredCloneReader* reader, + uint32_t tag, void* aData, + uint64_t aExtraData, + void* aClosure, + JS::MutableHandle returnObject); + + static bool + TransferStructuredClone(JSContext* aCx, + JS::Handle aObj, + void* aClosure, + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData); + + static void + FreeTransferStructuredClone(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void *aContent, + uint64_t aExtraData, + void* aClosure); + + static const JSStructuredCloneCallbacks sPostMessageCallbacks; + + JSAutoStructuredCloneBuffer mBuffer; + nsRefPtr mSource; + nsString mCallerOrigin; + nsRefPtr mTargetWindow; + nsCOMPtr mProvidedPrincipal; + bool mTrustedCaller; + nsTArray> mSupportsArray; + nsTArray mPortIdentifiers; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_PostMessageEvent_h diff --git a/dom/base/ProcessGlobal.cpp b/dom/base/ProcessGlobal.cpp index 21d89565de92..dbd2e6700e50 100644 --- a/dom/base/ProcessGlobal.cpp +++ b/dom/base/ProcessGlobal.cpp @@ -7,6 +7,7 @@ #include "ProcessGlobal.h" #include "nsContentCID.h" +#include "nsDOMClassInfoID.h" using namespace mozilla; using namespace mozilla::dom; diff --git a/dom/base/moz.build b/dom/base/moz.build index 2c53048c3d6d..5218533fc8a6 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -179,9 +179,6 @@ EXPORTS.mozilla.dom += [ 'ImageEncoder.h', 'ImportManager.h', 'Link.h', - 'MessageChannel.h', - 'MessagePort.h', - 'MessagePortList.h', 'NameSpaceConstants.h', 'Navigator.h', 'NodeInfo.h', @@ -237,8 +234,6 @@ UNIFIED_SOURCES += [ 'ImageEncoder.cpp', 'ImportManager.cpp', 'Link.cpp', - 'MessageChannel.cpp', - 'MessagePortList.cpp', 'MultipartBlobImpl.cpp', 'Navigator.cpp', 'NodeInfo.cpp', @@ -329,6 +324,7 @@ UNIFIED_SOURCES += [ 'PerformanceMark.cpp', 'PerformanceMeasure.cpp', 'PerformanceResourceTiming.cpp', + 'PostMessageEvent.cpp', 'ProcessGlobal.cpp', 'ResponsiveImageSelector.cpp', 'SameProcessMessageQueue.cpp', @@ -353,8 +349,6 @@ if CONFIG['MOZ_WEBRTC']: # these files couldn't be in UNIFIED_SOURCES for now for reasons given below: SOURCES += [ - # this file doesn't like windows.h - 'MessagePort.cpp', # Because of OS X headers. 'nsContentUtils.cpp', # this file doesn't like windows.h diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 042b9dfff0ae..14852b32ea89 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -218,6 +218,7 @@ nsIPrincipal *nsContentUtils::sNullSubjectPrincipal; nsIParserService *nsContentUtils::sParserService = nullptr; nsNameSpaceManager *nsContentUtils::sNameSpaceManager; nsIIOService *nsContentUtils::sIOService; +nsIUUIDGenerator *nsContentUtils::sUUIDGenerator; nsIConsoleService *nsContentUtils::sConsoleService; nsDataHashtable* nsContentUtils::sAtomEventTable = nullptr; nsDataHashtable* nsContentUtils::sStringEventTable = nullptr; @@ -551,6 +552,13 @@ nsContentUtils::Init() Element::InitCCCallbacks(); + nsCOMPtr uuidGenerator = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + uuidGenerator.forget(&sUUIDGenerator); + sInitialized = true; return NS_OK; @@ -1803,6 +1811,7 @@ nsContentUtils::Shutdown() NS_IF_RELEASE(sNullSubjectPrincipal); NS_IF_RELEASE(sParserService); NS_IF_RELEASE(sIOService); + NS_IF_RELEASE(sUUIDGenerator); NS_IF_RELEASE(sLineBreaker); NS_IF_RELEASE(sWordBreaker); NS_IF_RELEASE(sBidiKeyboard); @@ -7150,6 +7159,19 @@ nsContentUtils::IsJavascriptMIMEType(const nsAString& aMIMEType) return false; } +nsresult +nsContentUtils::GenerateUUIDInPlace(nsID& aUUID) +{ + MOZ_ASSERT(sUUIDGenerator); + + nsresult rv = sUUIDGenerator->GenerateUUIDInPlace(&aUUID); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + uint64_t nsContentUtils::GetInnerWindowID(nsIRequest* aRequest) { diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 5104cc3a9dc9..eb769b420295 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -85,6 +85,7 @@ class nsIStringBundleService; class nsISupportsArray; class nsISupportsHashKey; class nsIURI; +class nsIUUIDGenerator; class nsIWidget; class nsIWordBreaker; class nsIXPConnect; @@ -853,6 +854,11 @@ public: */ static uint32_t ParseSandboxAttributeToFlags(const nsAttrValue* sandboxAttr); + /** + * Helper function that generates a UUID. + */ + static nsresult GenerateUUIDInPlace(nsID& aUUID); + /** * Fill (with the parameters given) the localized string named |aKey| in @@ -2443,6 +2449,7 @@ private: static nsNameSpaceManager *sNameSpaceManager; static nsIIOService *sIOService; + static nsIUUIDGenerator *sUUIDGenerator; static bool sImgLoaderInitialized; static void InitImgLoader(); diff --git a/dom/base/nsCopySupport.cpp b/dom/base/nsCopySupport.cpp index bde0e70b9172..0885f275c203 100644 --- a/dom/base/nsCopySupport.cpp +++ b/dom/base/nsCopySupport.cpp @@ -11,6 +11,7 @@ #include "nsIComponentManager.h" #include "nsIServiceManager.h" #include "nsIClipboard.h" +#include "nsIFormControl.h" #include "nsISelection.h" #include "nsWidgetsCID.h" #include "nsXPCOM.h" diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp index e385c7997230..7c6e3d20dfc1 100644 --- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -277,15 +277,15 @@ BuildClonedMessageData(typename BlobTraits::ConcreteContentManagerType* SerializedStructuredCloneBuffer& buffer = aClonedData.data(); buffer.data = aData.mData; buffer.dataLength = aData.mDataLength; - const nsTArray>& blobs = aData.mClosure.mBlobs; - if (!blobs.IsEmpty()) { + const nsTArray>& blobImpls = aData.mClosure.mBlobImpls; + if (!blobImpls.IsEmpty()) { typedef typename BlobTraits::ProtocolType ProtocolType; InfallibleTArray& blobList = DataBlobs::Blobs(aClonedData); - uint32_t length = blobs.Length(); + uint32_t length = blobImpls.Length(); blobList.SetCapacity(length); for (uint32_t i = 0; i < length; ++i) { typename BlobTraits::BlobType* protocolActor = - aManager->GetOrCreateActorForBlob(blobs[i]); + aManager->GetOrCreateActorForBlobImpl(blobImpls[i]); if (!protocolActor) { return false; } @@ -323,7 +323,7 @@ UnpackClonedMessageData(const ClonedMessageData& aData) cloneData.mDataLength = buffer.dataLength; if (!blobs.IsEmpty()) { uint32_t length = blobs.Length(); - cloneData.mClosure.mBlobs.SetCapacity(length); + cloneData.mClosure.mBlobImpls.SetCapacity(length); for (uint32_t i = 0; i < length; ++i) { auto* blob = static_cast::BlobType*>(blobs[i]); @@ -332,10 +332,7 @@ UnpackClonedMessageData(const ClonedMessageData& aData) nsRefPtr blobImpl = blob->GetBlobImpl(); MOZ_ASSERT(blobImpl); - // This object will be duplicated with a correct parent before being - // exposed to JS. - nsRefPtr domBlob = Blob::Create(nullptr, blobImpl); - cloneData.mClosure.mBlobs.AppendElement(domBlob); + cloneData.mClosure.mBlobImpls.AppendElement(blobImpl); } } return cloneData; diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 1f06b53d688b..cc176b50bbed 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -59,7 +59,6 @@ #include "nsLayoutStatics.h" #include "nsCCUncollectableMarker.h" #include "mozilla/dom/workers/Workers.h" -#include "mozilla/dom/MessagePortList.h" #include "mozilla/dom/ToJSValue.h" #include "nsJSPrincipals.h" #include "mozilla/Attributes.h" @@ -69,9 +68,9 @@ #include "mozilla/MouseEvents.h" #include "mozilla/ProcessHangMonitor.h" #include "AudioChannelService.h" -#include "MessageEvent.h" #include "nsAboutProtocolUtils.h" #include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE +#include "PostMessageEvent.h" // Interfaces Needed #include "nsIFrame.h" @@ -179,13 +178,9 @@ #include "prprf.h" #include "mozilla/dom/MessageChannel.h" -#include "mozilla/dom/MessagePort.h" -#include "mozilla/dom/MessagePortBinding.h" #include "mozilla/dom/indexedDB/IDBFactory.h" #include "mozilla/dom/Promise.h" -#include "mozilla/dom/StructuredCloneTags.h" - #ifdef MOZ_GAMEPAD #include "mozilla/dom/Gamepad.h" #include "mozilla/dom/GamepadService.h" @@ -207,7 +202,6 @@ #include "TimeChangeObserver.h" #include "TouchCaret.h" #include "mozilla/dom/AudioContext.h" -#include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/BrowserElementDictionariesBinding.h" #include "mozilla/dom/cache/CacheStorage.h" #include "mozilla/dom/Console.h" @@ -8038,367 +8032,6 @@ nsGlobalWindow::CallerInnerWindow() return static_cast(win.get()); } -/** - * Class used to represent events generated by calls to Window.postMessage, - * which asynchronously creates and dispatches events. - */ -class PostMessageEvent : public nsRunnable -{ - public: - NS_DECL_NSIRUNNABLE - - PostMessageEvent(nsGlobalWindow* aSource, - const nsAString& aCallerOrigin, - nsGlobalWindow* aTargetWindow, - nsIPrincipal* aProvidedPrincipal, - bool aTrustedCaller) - : mSource(aSource), - mCallerOrigin(aCallerOrigin), - mTargetWindow(aTargetWindow), - mProvidedPrincipal(aProvidedPrincipal), - mTrustedCaller(aTrustedCaller) - { - MOZ_COUNT_CTOR(PostMessageEvent); - } - -protected: - ~PostMessageEvent() - { - MOZ_COUNT_DTOR(PostMessageEvent); - } - -public: - JSAutoStructuredCloneBuffer& Buffer() - { - return mBuffer; - } - - bool StoreISupports(nsISupports* aSupports) - { - mSupportsArray.AppendElement(aSupports); - return true; - } - - private: - JSAutoStructuredCloneBuffer mBuffer; - nsRefPtr mSource; - nsString mCallerOrigin; - nsRefPtr mTargetWindow; - nsCOMPtr mProvidedPrincipal; - bool mTrustedCaller; - nsTArray > mSupportsArray; -}; - -namespace { - -struct StructuredCloneInfo { - PostMessageEvent* event; - bool subsumes; - nsPIDOMWindow* window; - nsRefPtrHashtable, MessagePortBase> ports; -}; - -static JSObject* -PostMessageReadStructuredClone(JSContext* cx, - JSStructuredCloneReader* reader, - uint32_t tag, - uint32_t data, - void* closure) -{ - StructuredCloneInfo* scInfo = static_cast(closure); - NS_ASSERTION(scInfo, "Must have scInfo!"); - - if (tag == SCTAG_DOM_BLOB) { - NS_ASSERTION(!data, "Data should be empty"); - - // What we get back from the reader is a BlobImpl. - // From that we create a new File. - BlobImpl* blobImpl; - if (JS_ReadBytes(reader, &blobImpl, sizeof(blobImpl))) { - MOZ_ASSERT(blobImpl); - - // nsRefPtr needs to go out of scope before toObjectOrNull() is - // called because the static analysis thinks dereferencing XPCOM objects - // can GC (because in some cases it can!), and a return statement with a - // JSObject* type means that JSObject* is on the stack as a raw pointer - // while destructors are running. - JS::Rooted val(cx); - { - nsRefPtr blob = Blob::Create(scInfo->window, blobImpl); - if (!ToJSValue(cx, blob, &val)) { - return nullptr; - } - } - - return &val.toObject(); - } - } - - if (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 val.toObjectOrNull(); - } - } - } - - 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!"); - - // See if this is a File/Blob object. - { - Blob* blob = nullptr; - if (scInfo->subsumes && NS_SUCCEEDED(UNWRAP_OBJECT(Blob, obj, blob))) { - BlobImpl* blobImpl = blob->Impl(); - if (JS_WriteUint32Pair(writer, SCTAG_DOM_BLOB, 0) && - JS_WriteBytes(writer, &blobImpl, sizeof(blobImpl))) { - scInfo->event->StoreISupports(blobImpl); - return true; - } - } - } - - nsCOMPtr wrappedNative; - nsContentUtils::XPConnect()-> - GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wrappedNative)); - if (wrappedNative) { - uint32_t scTag = 0; - nsISupports* supports = wrappedNative->Native(); - - nsCOMPtr list = do_QueryInterface(supports); - if (list && scInfo->subsumes) - scTag = SCTAG_DOM_FILELIST; - - if (scTag) - return JS_WriteUint32Pair(writer, scTag, 0) && - JS_WriteBytes(writer, &supports, sizeof(supports)) && - scInfo->event->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* aData, - uint64_t aExtraData, - 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(aData); - port->BindToOwner(scInfo->window); - scInfo->ports.Put(port, nullptr); - - JS::Rooted obj(aCx, port->WrapObject(aCx, nullptr)); - if (JS_WrapObject(aCx, &obj)) { - MOZ_ASSERT(port->GetOwner() == scInfo->window); - 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->ports.Get(port, getter_AddRefs(newPort))) { - // No duplicate. - return false; - } - - newPort = port->Clone(); - scInfo->ports.Put(port, newPort); - - *aTag = SCTAG_DOM_MAP_MESSAGEPORT; - *aOwnership = JS::SCTAG_TMO_CUSTOM; - *aContent = newPort; - *aExtraData = 0; - - return true; - } - - return false; -} - -void -PostMessageFreeTransferStructuredClone(uint32_t aTag, JS::TransferableOwnership aOwnership, - void *aContent, uint64_t aExtraData, void* aClosure) -{ - StructuredCloneInfo* scInfo = static_cast(aClosure); - NS_ASSERTION(scInfo, "Must have scInfo!"); - - if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { - nsRefPtr port(static_cast(aContent)); - scInfo->ports.Remove(port); - } -} - -const 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 -PostMessageEvent::Run() -{ - MOZ_ASSERT(mTargetWindow->IsOuterWindow(), - "should have been passed an outer window!"); - MOZ_ASSERT(!mSource || mSource->IsOuterWindow(), - "should have been passed an outer window!"); - - AutoJSAPI jsapi; - jsapi.Init(); - JSContext* cx = jsapi.cx(); - - // If we bailed before this point we're going to leak mMessage, but - // that's probably better than crashing. - - nsRefPtr targetWindow; - if (mTargetWindow->IsClosedOrClosing() || - !(targetWindow = mTargetWindow->GetCurrentInnerWindowInternal()) || - targetWindow->IsClosedOrClosing()) - return NS_OK; - - MOZ_ASSERT(targetWindow->IsInnerWindow(), - "we ordered an inner window!"); - JSAutoCompartment ac(cx, targetWindow->GetWrapperPreserveColor()); - - // Ensure that any origin which might have been provided is the origin of this - // window's document. Note that we do this *now* instead of when postMessage - // is called because the target window might have been navigated to a - // different location between then and now. If this check happened when - // postMessage was called, it would be fairly easy for a malicious webpage to - // intercept messages intended for another site by carefully timing navigation - // of the target window so it changed location after postMessage but before - // now. - if (mProvidedPrincipal) { - // Get the target's origin either from its principal or, in the case the - // principal doesn't carry a URI (e.g. the system principal), the target's - // document. - nsIPrincipal* targetPrin = targetWindow->GetPrincipal(); - if (NS_WARN_IF(!targetPrin)) - return NS_OK; - - // Note: This is contrary to the spec with respect to file: URLs, which - // the spec groups into a single origin, but given we intentionally - // don't do that in other places it seems better to hold the line for - // now. Long-term, we want HTML5 to address this so that we can - // be compliant while being safer. - if (!targetPrin->Equals(mProvidedPrincipal)) { - return NS_OK; - } - } - - // Deserialize the structured clone data - JS::Rooted messageData(cx); - StructuredCloneInfo scInfo; - scInfo.event = this; - scInfo.window = targetWindow; - - if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) { - return NS_ERROR_DOM_DATA_CLONE_ERR; - } - - // Create the event - nsCOMPtr eventTarget = - do_QueryInterface(static_cast(targetWindow.get())); - nsRefPtr event = - new MessageEvent(eventTarget, nullptr, nullptr); - - event->InitMessageEvent(NS_LITERAL_STRING("message"), false /*non-bubbling */, - false /*cancelable */, messageData, mCallerOrigin, - EmptyString(), mSource); - - nsTArray > ports; - scInfo.ports.EnumerateRead(PopulateMessagePortList, &ports); - event->SetPorts(new MessagePortList(static_cast(event.get()), ports)); - - // We can't simply call dispatchEvent on the window because doing so ends - // up flipping the trusted bit on the event, and we don't want that to - // happen because then untrusted content can call postMessage on a chrome - // window if it can get a reference to it. - - nsIPresShell *shell = targetWindow->mDoc->GetShell(); - nsRefPtr presContext; - if (shell) - presContext = shell->GetPresContext(); - - event->SetTrusted(mTrustedCaller); - WidgetEvent* internalEvent = event->GetInternalNSEvent(); - - nsEventStatus status = nsEventStatus_eIgnore; - EventDispatcher::Dispatch(static_cast(mTargetWindow), - presContext, - internalEvent, - static_cast(event.get()), - &status); - return NS_OK; -} - void nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, const nsAString& aTargetOrigin, @@ -8522,18 +8155,13 @@ nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, providedPrincipal, nsContentUtils::IsCallerChrome()); - // We *must* clone the data here, or the JS::Value could be modified - // by script - StructuredCloneInfo scInfo; - scInfo.event = event; - scInfo.window = this; - nsIPrincipal* principal = GetPrincipal(); JS::Rooted message(aCx, aMessage); JS::Rooted transfer(aCx, aTransfer); - if (NS_FAILED(callerPrin->Subsumes(principal, &scInfo.subsumes)) || - !event->Buffer().write(aCx, message, transfer, &kPostMessageCallbacks, - &scInfo)) { + bool subsumes; + + if (NS_FAILED(callerPrin->Subsumes(principal, &subsumes)) || + !event->Write(aCx, message, transfer, subsumes, this)) { aError.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 15871c2c8ff1..b38ab2e77d3a 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -111,6 +111,7 @@ class MozSelfSupport; class Navigator; class OwningExternalOrWindowProxy; class Promise; +class PostMessageEvent; struct RequestInit; class RequestOrUSVString; class Selection; @@ -1752,7 +1753,7 @@ protected: friend class nsDOMScriptableHelper; friend class nsDOMWindowUtils; - friend class PostMessageEvent; + friend class mozilla::dom::PostMessageEvent; friend class DesktopNotification; static WindowByIdTable* sWindowsById; diff --git a/dom/base/test/chrome.ini b/dom/base/test/chrome.ini index 34f81ead9d99..9684ee8dba5a 100644 --- a/dom/base/test/chrome.ini +++ b/dom/base/test/chrome.ini @@ -13,7 +13,6 @@ support-files = [test_domrequesthelper.xul] [test_url.xul] [test_console.xul] -[test_messageChannel.xul] [test_navigator_resolve_identity_xrays.xul] [test_sendQueryContentAndSelectionSetEvent.html] [test_bug1016960.html] diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index b256fb1a14e7..85be178d8dd4 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -4,10 +4,6 @@ support-files = iframe_bug976673.html iframe_main_bug1022229.html iframe_sandbox_bug1022229.html - iframe_messageChannel_cloning.html - iframe_messageChannel_chrome.html - iframe_messageChannel_pingpong.html - iframe_messageChannel_post.html file_empty.html iframe_postMessage_solidus.html file_setname.html @@ -285,15 +281,7 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e1 [test_history_state_null.html] [test_Image_constructor.html] [test_innersize_scrollport.html] -[test_messageChannel.html] -[test_messageChannel_cloning.html] -[test_messageChannel_pingpong.html] -[test_messageChannel_post.html] -[test_messageChannel_pref.html] -[test_messageChannel_start.html] [test_messagemanager_targetchain.html] -[test_messageChannel_transferable.html] -[test_messageChannel_unshipped.html] [test_named_frames.html] [test_navigator_resolve_identity.html] [test_navigator_language.html] diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 2b0181dd8a95..110bb45572a8 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -729,9 +729,6 @@ DOMInterfaces = { 'MessagePort': { 'nativeType': 'mozilla::dom::MessagePortBase', 'headerFile': 'mozilla/dom/MessagePort.h', - 'binaryNames': { - 'postMessage': 'postMessageMoz', - }, }, 'MimeType': { diff --git a/dom/broadcastchannel/BroadcastChannel.cpp b/dom/broadcastchannel/BroadcastChannel.cpp index 325722cfcaab..ae00470858a6 100644 --- a/dom/broadcastchannel/BroadcastChannel.cpp +++ b/dom/broadcastchannel/BroadcastChannel.cpp @@ -212,14 +212,15 @@ public: PBackgroundChild* backgroundManager = mActor->Manager(); MOZ_ASSERT(backgroundManager); - const nsTArray>& blobs = mData->mClosure.mBlobs; + const nsTArray>& blobImpls = mData->mClosure.mBlobImpls; - if (!blobs.IsEmpty()) { - message.blobsChild().SetCapacity(blobs.Length()); + if (!blobImpls.IsEmpty()) { + message.blobsChild().SetCapacity(blobImpls.Length()); - for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) { + for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) { PBlobChild* blobChild = - BackgroundChild::GetOrCreateActorForBlob(backgroundManager, blobs[i]); + BackgroundChild::GetOrCreateActorForBlobImpl(backgroundManager, + blobImpls[i]); MOZ_ASSERT(blobChild); message.blobsChild().AppendElement(blobChild); @@ -541,9 +542,9 @@ BroadcastChannel::PostMessageInternal(JSContext* aCx, return; } - const nsTArray>& blobs = data->mClosure.mBlobs; - for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) { - if (!blobs[i]->Impl()->MayBeClonedToOtherThreads()) { + const nsTArray>& blobImpls = data->mClosure.mBlobImpls; + for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) { + if (!blobImpls[i]->MayBeClonedToOtherThreads()) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } diff --git a/dom/broadcastchannel/BroadcastChannelChild.cpp b/dom/broadcastchannel/BroadcastChannelChild.cpp index 40c394e68729..6b8647064581 100644 --- a/dom/broadcastchannel/BroadcastChannelChild.cpp +++ b/dom/broadcastchannel/BroadcastChannelChild.cpp @@ -42,7 +42,7 @@ BroadcastChannelChild::RecvNotify(const ClonedMessageData& aData) { // Make sure to retrieve all blobs from the message before returning to avoid // leaking their actors. - nsTArray> blobs; + nsTArray> blobs; if (!aData.blobsChild().IsEmpty()) { blobs.SetCapacity(aData.blobsChild().Length()); @@ -50,8 +50,7 @@ BroadcastChannelChild::RecvNotify(const ClonedMessageData& aData) nsRefPtr impl = static_cast(aData.blobsChild()[i])->GetBlobImpl(); - nsRefPtr blob = Blob::Create(mBC ? mBC->GetOwner() : nullptr, impl); - blobs.AppendElement(blob); + blobs.AppendElement(impl); } } @@ -92,7 +91,7 @@ BroadcastChannelChild::RecvNotify(const ClonedMessageData& aData) StructuredCloneData cloneData; cloneData.mData = buffer.data; cloneData.mDataLength = buffer.dataLength; - cloneData.mClosure.mBlobs.SwapElements(blobs); + cloneData.mClosure.mBlobImpls.SwapElements(blobs); JS::Rooted value(cx, JS::NullValue()); if (cloneData.mDataLength && !ReadStructuredClone(cx, cloneData, &value)) { diff --git a/dom/ipc/StructuredCloneUtils.cpp b/dom/ipc/StructuredCloneUtils.cpp index cba261c1f7a0..f7ced0cdb034 100644 --- a/dom/ipc/StructuredCloneUtils.cpp +++ b/dom/ipc/StructuredCloneUtils.cpp @@ -50,14 +50,14 @@ Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, // while destructors are running. JS::Rooted val(aCx); { - MOZ_ASSERT(aData < closure->mBlobs.Length()); - nsRefPtr blob = closure->mBlobs[aData]; + MOZ_ASSERT(aData < closure->mBlobImpls.Length()); + nsRefPtr blobImpl = closure->mBlobImpls[aData]; #ifdef DEBUG { // Blob should not be mutable. bool isMutable; - MOZ_ASSERT(NS_SUCCEEDED(blob->GetMutable(&isMutable))); + MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable))); MOZ_ASSERT(!isMutable); } #endif @@ -66,7 +66,7 @@ Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); MOZ_ASSERT(global); - nsRefPtr newBlob = Blob::Create(global, blob->Impl()); + nsRefPtr newBlob = Blob::Create(global, blobImpl); if (!ToJSValue(aCx, newBlob, &val)) { return nullptr; } @@ -93,8 +93,8 @@ Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) && NS_SUCCEEDED(blob->SetMutable(false)) && JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, - closure->mBlobs.Length())) { - closure->mBlobs.AppendElement(blob); + closure->mBlobImpls.Length())) { + closure->mBlobImpls.AppendElement(blob->Impl()); return true; } } diff --git a/dom/ipc/StructuredCloneUtils.h b/dom/ipc/StructuredCloneUtils.h index b103d54a4122..f8d1c37d7dbe 100644 --- a/dom/ipc/StructuredCloneUtils.h +++ b/dom/ipc/StructuredCloneUtils.h @@ -19,7 +19,7 @@ namespace dom { struct StructuredCloneClosure { - nsTArray> mBlobs; + nsTArray> mBlobImpls; }; struct diff --git a/dom/messagechannel/MessageChannel.cpp b/dom/messagechannel/MessageChannel.cpp new file mode 100644 index 000000000000..a2635d10c2e5 --- /dev/null +++ b/dom/messagechannel/MessageChannel.cpp @@ -0,0 +1,228 @@ +/* -*- 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/Preferences.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 "nsIPrincipal.h" +#include "nsPIDOMWindow.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MessageChannel, mWindow, 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 + +namespace { +bool gPrefInitialized = false; +bool gPrefEnabled = false; + +bool +CheckPermission(nsIPrincipal* aPrincipal, bool aCallerChrome) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!gPrefInitialized) { + Preferences::AddBoolVarCache(&gPrefEnabled, "dom.messageChannel.enabled"); + gPrefInitialized = true; + } + + // Enabled by pref + if (gPrefEnabled) { + return true; + } + + // Chrome callers are allowed. + if (aCallerChrome) { + return true; + } + + nsCOMPtr uri; + if (NS_FAILED(aPrincipal->GetURI(getter_AddRefs(uri))) || !uri) { + return false; + } + + bool isResource = false; + if (NS_FAILED(uri->SchemeIs("resource", &isResource))) { + return false; + } + + return isResource; +} + +nsIPrincipal* +GetPrincipalFromWorkerPrivate(workers::WorkerPrivate* aWorkerPrivate) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsIPrincipal* principal = aWorkerPrivate->GetPrincipal(); + if (principal) { + return principal; + } + + // Walk up to our containing page + workers::WorkerPrivate* wp = aWorkerPrivate; + while (wp->GetParent()) { + wp = wp->GetParent(); + } + + nsPIDOMWindow* window = wp->GetWindow(); + if (!window) { + return nullptr; + } + + nsIDocument* doc = window->GetExtantDoc(); + if (!doc) { + return nullptr; + } + + return doc->NodePrincipal(); +} + +// A WorkerMainThreadRunnable to synchronously dispatch the call of +// CheckPermission() from the worker thread to the main thread. +class CheckPermissionRunnable final : public workers::WorkerMainThreadRunnable +{ +public: + bool mResult; + bool mCallerChrome; + + explicit CheckPermissionRunnable(workers::WorkerPrivate* aWorkerPrivate) + : workers::WorkerMainThreadRunnable(aWorkerPrivate) + , mResult(false) + , mCallerChrome(false) + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + mCallerChrome = aWorkerPrivate->UsesSystemPrincipal(); + } + +protected: + virtual bool + MainThreadRun() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsIPrincipal* principal = GetPrincipalFromWorkerPrivate(mWorkerPrivate); + if (!principal) { + return true; + } + + bool isNullPrincipal; + nsresult rv = principal->GetIsNullPrincipal(&isNullPrincipal); + if (NS_WARN_IF(NS_FAILED(rv))) { + return true; + } + + if (NS_WARN_IF(isNullPrincipal)) { + return true; + } + + mResult = CheckPermission(principal, mCallerChrome); + return true; + } +}; + +} // anonymous namespace + +/* static */ bool +MessageChannel::Enabled(JSContext* aCx, JSObject* aGlobal) +{ + if (NS_IsMainThread()) { + JS::Rooted global(aCx, aGlobal); + + nsCOMPtr win = Navigator::GetWindowFromGlobal(global); + if (!win) { + return false; + } + + nsIDocument* doc = win->GetExtantDoc(); + if (!doc) { + return false; + } + + return CheckPermission(doc->NodePrincipal(), + nsContentUtils::IsCallerChrome()); + } + + workers::WorkerPrivate* workerPrivate = + workers::GetWorkerPrivateFromContext(aCx); + workerPrivate->AssertIsOnWorkerThread(); + + nsRefPtr runnable = + new CheckPermissionRunnable(workerPrivate); + runnable->Dispatch(aCx); + + return runnable->mResult; +} + +MessageChannel::MessageChannel(nsPIDOMWindow* aWindow) + : mWindow(aWindow) +{ +} + +MessageChannel::~MessageChannel() +{ +} + +JSObject* +MessageChannel::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return MessageChannelBinding::Wrap(aCx, this, aGivenProto); +} + +/* static */ already_AddRefed +MessageChannel::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) +{ + // window can be null in workers. + nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); + + nsID portUUID1; + aRv = nsContentUtils::GenerateUUIDInPlace(portUUID1); + if (aRv.Failed()) { + return nullptr; + } + + nsID portUUID2; + aRv = nsContentUtils::GenerateUUIDInPlace(portUUID2); + if (aRv.Failed()) { + return nullptr; + } + + nsRefPtr channel = new MessageChannel(window); + + channel->mPort1 = MessagePort::Create(window, portUUID1, portUUID2, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + channel->mPort2 = MessagePort::Create(window, 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/base/MessageChannel.h b/dom/messagechannel/MessageChannel.h similarity index 99% rename from dom/base/MessageChannel.h rename to dom/messagechannel/MessageChannel.h index 14a14b5c5d6c..a20921dfa8e8 100644 --- a/dom/base/MessageChannel.h +++ b/dom/messagechannel/MessageChannel.h @@ -31,8 +31,6 @@ public: static bool Enabled(JSContext* aCx, JSObject* aGlobal); public: - explicit MessageChannel(nsPIDOMWindow* aWindow); - nsPIDOMWindow* GetParentObject() const { @@ -58,6 +56,7 @@ public: } private: + explicit MessageChannel(nsPIDOMWindow* aWindow); ~MessageChannel(); nsCOMPtr mWindow; diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp new file mode 100644 index 000000000000..6a328bb969ee --- /dev/null +++ b/dom/messagechannel/MessagePort.cpp @@ -0,0 +1,867 @@ +/* -*- 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/MessagePortBinding.h" +#include "mozilla/dom/MessagePortChild.h" +#include "mozilla/dom/MessagePortList.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 "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 DispatchEventRunnable final : public nsICancelableRunnable +{ + friend class MessagePort; + +public: + NS_DECL_ISUPPORTS + + explicit DispatchEventRunnable(MessagePort* aPort) + : mPort(aPort) + { } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(mPort); + MOZ_ASSERT(mPort->mDispatchRunnable == this); + mPort->mDispatchRunnable = nullptr; + mPort->Dispatch(); + + return NS_OK; + } + + NS_IMETHOD + Cancel() override + { + mPort = nullptr; + return NS_OK; + } + +private: + ~DispatchEventRunnable() + {} + + nsRefPtr mPort; +}; + +NS_IMPL_ISUPPORTS(DispatchEventRunnable, nsICancelableRunnable, nsIRunnable) + +class PostMessageRunnable final : public nsICancelableRunnable +{ +public: + NS_DECL_ISUPPORTS + + PostMessageRunnable(MessagePort* aPort, SharedMessagePortMessage* aData) + : mPort(aPort) + , mData(aData) + { + MOZ_ASSERT(aPort); + MOZ_ASSERT(aData); + } + + NS_IMETHOD + Run() override + { + nsCOMPtr globalObject; + + if (NS_IsMainThread()) { + globalObject = do_QueryInterface(mPort->GetParentObject()); + } else { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + globalObject = workerPrivate->GlobalScope(); + } + + AutoJSAPI jsapi; + if (!globalObject || !jsapi.Init(globalObject)) { + NS_WARNING("Failed to initialize AutoJSAPI object."); + return NS_ERROR_FAILURE; + } + + JSContext* cx = jsapi.cx(); + + nsTArray> ports; + nsCOMPtr window = + do_QueryInterface(mPort->GetParentObject()); + + JS::Rooted value(cx); + if (!mData->mData.IsEmpty()) { + bool ok = ReadStructuredCloneWithTransfer(cx, mData->mData, + mData->mClosure, + &value, window, ports); + FreeStructuredClone(mData->mData, mData->mClosure); + + if (!ok) { + return NS_ERROR_FAILURE; + } + } + + // The data should be already be cleaned. + MOZ_ASSERT(!mData->mData.Length()); + + // 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 */, value, EmptyString(), + EmptyString(), nullptr); + event->SetTrusted(true); + event->SetSource(mPort); + + nsTArray> array; + array.SetCapacity(ports.Length()); + for (uint32_t i = 0; i < ports.Length(); ++i) { + array.AppendElement(ports[i]); + } + + nsRefPtr portList = + new MessagePortList(static_cast(event.get()), array); + event->SetPorts(portList); + + bool dummy; + mPort->DispatchEvent(static_cast(event.get()), &dummy); + return NS_OK; + } + + NS_IMETHOD + Cancel() override + { + mPort = nullptr; + mData = nullptr; + return NS_OK; + } + +private: + ~PostMessageRunnable() + {} + + nsRefPtr mPort; + nsRefPtr mData; +}; + +NS_IMPL_ISUPPORTS(PostMessageRunnable, nsICancelableRunnable, nsIRunnable) + +MessagePortBase::MessagePortBase(nsPIDOMWindow* aWindow) + : DOMEventTargetHelper(aWindow) +{ +} + +MessagePortBase::MessagePortBase() +{ +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(MessagePort) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessagePort, + MessagePortBase) + if (tmp->mDispatchRunnable) { + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDispatchRunnable->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, + MessagePortBase) + if (tmp->mDispatchRunnable) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDispatchRunnable->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(MessagePortBase) + +NS_IMPL_ADDREF_INHERITED(MessagePort, MessagePortBase) +NS_IMPL_RELEASE_INHERITED(MessagePort, MessagePortBase) + +namespace { + +class MessagePortFeature final : public workers::WorkerFeature +{ + MessagePort* mPort; + +public: + explicit MessagePortFeature(MessagePort* aPort) + : mPort(aPort) + { + MOZ_ASSERT(aPort); + MOZ_COUNT_CTOR(MessagePortFeature); + } + + virtual bool Notify(JSContext* aCx, workers::Status aStatus) override + { + if (mPort && aStatus > Running) { + mPort->Close(); + } + + return true; + } + +private: + ~MessagePortFeature() + { + MOZ_COUNT_DTOR(MessagePortFeature); + } +}; + +} // anonymous namespace + +MessagePort::MessagePort(nsPIDOMWindow* aWindow) + : MessagePortBase(aWindow) + , mInnerID(0) + , mMessageQueueEnabled(false) + , mIsKeptAlive(false) +{ + mIdentifier = new MessagePortIdentifier(); + mIdentifier->neutered() = true; + mIdentifier->sequenceId() = 0; +} + +MessagePort::~MessagePort() +{ + Close(); + MOZ_ASSERT(!mWorkerFeature); +} + +/* static */ already_AddRefed +MessagePort::Create(nsPIDOMWindow* aWindow, const nsID& aUUID, + const nsID& aDestinationUUID, ErrorResult& aRv) +{ + nsRefPtr mp = new MessagePort(aWindow); + mp->Initialize(aUUID, aDestinationUUID, 1 /* 0 is an invalid sequence ID */, + false /* Neutered */, eStateUnshippedEntangled, aRv); + return mp.forget(); +} + +/* static */ already_AddRefed +MessagePort::Create(nsPIDOMWindow* aWindow, + const MessagePortIdentifier& aIdentifier, + ErrorResult& aRv) +{ + nsRefPtr mp = new MessagePort(aWindow); + 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; + mNextStep = eNextStepNone; + + if (mNeutered) { + mState = eStateDisentangled; + } else if (mState == eStateEntangling) { + ConnectToPBackground(); + } else { + MOZ_ASSERT(mState == eStateUnshippedEntangled); + } + + // The port has to keep itself alive until it's entangled. + UpdateMustKeepAlive(); + + if (NS_IsMainThread()) { + MOZ_ASSERT(GetOwner()); + MOZ_ASSERT(GetOwner()->IsInnerWindow()); + mInnerID = GetOwner()->WindowID(); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "inner-window-destroyed", false); + } + } else { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + MOZ_ASSERT(!mWorkerFeature); + + nsAutoPtr feature(new MessagePortFeature(this)); + JSContext* cx = workerPrivate->GetJSContext(); + if (NS_WARN_IF(!workerPrivate->AddFeature(cx, feature))) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + mWorkerFeature = Move(feature); + } +} + +JSObject* +MessagePort::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return MessagePortBinding::Wrap(aCx, this, aGivenProto); +} + +void +MessagePort::PostMessage(JSContext* aCx, JS::Handle aMessage, + const Optional>& aTransferable, + ErrorResult& aRv) +{ + // We *must* clone the data here, or the JS::Value could be modified + // by script + + JS::Rooted transferable(aCx, JS::UndefinedValue()); + if (aTransferable.WasPassed()) { + const Sequence& 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; + } + + MessagePortBase* port = nullptr; + nsresult rv = UNWRAP_OBJECT(MessagePort, &value.toObject(), 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); + } + + nsRefPtr data = new SharedMessagePortMessage(); + + if (!WriteStructuredCloneWithTransfer(aCx, aMessage, transferable, + data->mData, data->mClosure)) { + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + 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. + if (mNextStep != eNextStepNone) { + return; + } + + RemoveDocFromBFCache(); + + // Not entangled yet. + if (mState == eStateEntangling) { + mMessagesForTheOtherPort.AppendElement(data); + return; + } + + MOZ_ASSERT(mActor); + MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty()); + + nsAutoTArray, 1> array; + array.AppendElement(data); + + nsAutoTArray 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() || mDispatchRunnable || + mState > eStateEntangled || mNextStep != eNextStepNone) { + return; + } + + nsRefPtr data = mMessages.ElementAt(0); + mMessages.RemoveElementAt(0); + + nsRefPtr runnable = new PostMessageRunnable(this, data); + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(runnable))); + + mDispatchRunnable = new DispatchEventRunnable(this); + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(mDispatchRunnable))); +} + +void +MessagePort::Close() +{ + // Not entangled yet, but already closed. + if (mNextStep != eNextStepNone) { + return; + } + + if (mState == eStateUnshippedEntangled) { + MOZ_ASSERT(mUnshippedEntangledPort); + + // This avoids loops. + nsRefPtr port = Move(mUnshippedEntangledPort); + MOZ_ASSERT(mUnshippedEntangledPort == nullptr); + + mState = eStateDisentangled; + port->Close(); + + UpdateMustKeepAlive(); + return; + } + + // Not entangled yet, we have to wait. + if (mState < eStateEntangling) { + mNextStep = eNextStepClose; + 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 = eStateDisentangled; + + 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& aMessages) +{ + MOZ_ASSERT(mState == eStateEntangling); + + mState = eStateEntangled; + + // If we have pending messages, these have to be sent. + if (!mMessagesForTheOtherPort.IsEmpty()) { + nsTArray messages; + SharedMessagePortMessage::FromSharedToMessagesChild(mActor, + mMessagesForTheOtherPort, + messages); + mMessagesForTheOtherPort.Clear(); + mActor->SendPostMessages(messages); + } + + // We must convert the messages into SharedMessagePortMessages to avoid leaks. + FallibleTArray> data; + if (NS_WARN_IF(!SharedMessagePortMessage::FromMessagesToSharedChild(aMessages, + data))) { + // OOM, we cannot continue. + return; + } + + if (mNextStep == eNextStepClose) { + Close(); + return; + } + + mMessages.AppendElements(data); + + // We were waiting for the entangling callback in order to disentangle this + // port immediately after. + if (mNextStep == eNextStepDisentangle) { + StartDisentangling(); + return; + } + + MOZ_ASSERT(mNextStep == eNextStepNone); + Dispatch(); +} + +void +MessagePort::StartDisentangling() +{ + MOZ_ASSERT(mActor); + MOZ_ASSERT(mState == eStateEntangled); + + mState = eStateDisentangling; + mNextStep = eNextStepNone; + + // 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& aMessages) +{ + MOZ_ASSERT(mState == eStateEntangled || mState == eStateDisentangling); + MOZ_ASSERT(mNextStep == eNextStepNone); + MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty()); + + RemoveDocFromBFCache(); + + FallibleTArray> 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 messages; + SharedMessagePortMessage::FromSharedToMessagesChild(mActor, mMessages, + messages); + mMessages.Clear(); + mActor->SendDisentangle(messages); + + mActor->SetPort(nullptr); + mActor = nullptr; + + UpdateMustKeepAlive(); +} + +bool +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 true; + } + + // We already have a 'next step'. We have to consider this port as already + // cloned/closed/disentangled. + if (mNextStep != eNextStepNone) { + return true; + } + + 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 true; + } + + // Register this component to PBackground. + ConnectToPBackground(); + + mNextStep = eNextStepDisentangle; + return true; + } + + // Not entangled yet, we have to wait. + if (mState < eStateEntangled) { + mNextStep = eNextStepDisentangle; + return true; + } + + StartDisentangling(); + return true; +} + +void +MessagePort::Closed() +{ + if (mState == eStateDisentangled) { + return; + } + + mState = eStateDisentangled; + + 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); + + PMessagePortChild* actor = + aActor->SendPMessagePortConstructor(mIdentifier->uuid(), + mIdentifier->destinationUuid(), + mIdentifier->sequenceId()); + + mActor = static_cast(actor); + MOZ_ASSERT(mActor); + + mActor->SetPort(this); +} + +void +MessagePort::UpdateMustKeepAlive() +{ + if (mState == eStateDisentangled && mIsKeptAlive) { + mIsKeptAlive = false; + + if (mWorkerFeature) { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + workerPrivate->RemoveFeature(workerPrivate->GetJSContext(), + mWorkerFeature); + mWorkerFeature = nullptr; + } + + 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 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) { + nsCOMPtr obs = + do_GetService("@mozilla.org/observer-service;1"); + if (obs) { + obs->RemoveObserver(this, "inner-window-destroyed"); + } + + Close(); + } + + return NS_OK; +} + +void +MessagePort::RemoveDocFromBFCache() +{ + if (!NS_IsMainThread()) { + return; + } + + nsPIDOMWindow* window = GetOwner(); + MOZ_ASSERT(window); + + nsIDocument* doc = window->GetExtantDoc(); + if (!doc) { + return; + } + + nsCOMPtr bfCacheEntry = doc->GetBFCacheEntry(); + if (!bfCacheEntry) { + return; + } + + bfCacheEntry->RemoveFromBFCacheSync(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/messagechannel/MessagePort.h b/dom/messagechannel/MessagePort.h new file mode 100644 index 000000000000..63081a083323 --- /dev/null +++ b/dom/messagechannel/MessagePort.h @@ -0,0 +1,210 @@ +/* -*- 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 "nsIIPCBackgroundChildCreateCallback.h" +#include "nsTArray.h" + +#ifdef XP_WIN +#undef PostMessage +#endif + +class nsPIDOMWindow; + +namespace mozilla { +namespace dom { + +class DispatchEventRunnable; +class MessagePortChild; +class MessagePortIdentifier; +class MessagePortMessage; +class SharedMessagePortMessage; + +namespace workers { +class WorkerFeature; +} + +class MessagePortBase : public DOMEventTargetHelper +{ +protected: + explicit MessagePortBase(nsPIDOMWindow* aWindow); + MessagePortBase(); + +public: + + virtual void + PostMessage(JSContext* aCx, JS::Handle aMessage, + const Optional>& aTransferable, + ErrorResult& aRv) = 0; + + virtual void + Start() = 0; + + virtual void + Close() = 0; + + // The 'message' event handler has to call |Start()| method, so we + // cannot use IMPL_EVENT_HANDLER macro here. + virtual EventHandlerNonNull* + GetOnmessage() = 0; + + virtual void + SetOnmessage(EventHandlerNonNull* aCallback) = 0; + + // Duplicate this message port. This method is used by the Structured Clone + // Algorithm and populates a MessagePortIdentifier object with the information + // useful to create new MessagePort. + virtual bool + CloneAndDisentangle(MessagePortIdentifier& aIdentifier) = 0; +}; + +class MessagePort final : public MessagePortBase + , public nsIIPCBackgroundChildCreateCallback + , public nsIObserver +{ + friend class DispatchEventRunnable; + +public: + NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK + NS_DECL_NSIOBSERVER + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MessagePort, + DOMEventTargetHelper) + + static already_AddRefed + Create(nsPIDOMWindow* aWindow, const nsID& aUUID, + const nsID& aDestinationUUID, ErrorResult& aRv); + + static already_AddRefed + Create(nsPIDOMWindow* aWindow, const MessagePortIdentifier& aIdentifier, + ErrorResult& aRv); + + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + virtual void + PostMessage(JSContext* aCx, JS::Handle aMessage, + const Optional>& aTransferable, + ErrorResult& aRv) override; + + virtual void Start() override; + + virtual void Close() override; + + virtual EventHandlerNonNull* GetOnmessage() override; + + virtual void SetOnmessage(EventHandlerNonNull* aCallback) override; + + // Non WebIDL methods + + void UnshippedEntangle(MessagePort* aEntangledPort); + + virtual bool CloneAndDisentangle(MessagePortIdentifier& aIdentifier) override; + + // These methods are useful for MessagePortChild + + void Entangled(nsTArray& aMessages); + void MessagesReceived(nsTArray& aMessages); + void StopSendingDataConfirmed(); + void Closed(); + +private: + explicit MessagePort(nsPIDOMWindow* aWindow); + ~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 set the + // mNextStep. This 'next' operation will be done when entangled() message + // is received. + eStateEntangling, + + // 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 + }; + + 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(); + + // 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(); + + nsAutoPtr mWorkerFeature; + + nsRefPtr mDispatchRunnable; + + nsRefPtr mActor; + + nsRefPtr mUnshippedEntangledPort; + + nsTArray> mMessages; + nsTArray> mMessagesForTheOtherPort; + + nsAutoPtr mIdentifier; + + uint64_t mInnerID; + + State mState; + + // This 'nextStep' is used when we are waiting to be entangled but the + // content has called Clone() or Close(). + enum { + eNextStepNone, + eNextStepDisentangle, + eNextStepClose + } mNextStep; + + 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 000000000000..baa1b3771316 --- /dev/null +++ b/dom/messagechannel/MessagePortChild.cpp @@ -0,0 +1,49 @@ +/* -*- 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&& aMessages) +{ + MOZ_ASSERT(mPort); + mPort->Entangled(aMessages); + return true; +} + +bool +MessagePortChild::RecvReceiveData(nsTArray&& aMessages) +{ + MOZ_ASSERT(mPort); + mPort->MessagesReceived(aMessages); + return true; +} + +void +MessagePortChild::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mPort) { + mPort->Closed(); + MOZ_ASSERT(!mPort); + } +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/messagechannel/MessagePortChild.h b/dom/messagechannel/MessagePortChild.h new file mode 100644 index 000000000000..7ab954ad0687 --- /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() {} + + void SetPort(MessagePort* aPort) + { + mPort = aPort; + } + +private: + ~MessagePortChild() + { + MOZ_ASSERT(!mPort); + } + + virtual bool + RecvEntangled(nsTArray&& aMessages) override; + + virtual bool + RecvReceiveData(nsTArray&& 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; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_MessagePortChild_h diff --git a/dom/base/MessagePortList.cpp b/dom/messagechannel/MessagePortList.cpp similarity index 100% rename from dom/base/MessagePortList.cpp rename to dom/messagechannel/MessagePortList.cpp diff --git a/dom/base/MessagePortList.h b/dom/messagechannel/MessagePortList.h similarity index 100% rename from dom/base/MessagePortList.h rename to dom/messagechannel/MessagePortList.h diff --git a/dom/messagechannel/MessagePortParent.cpp b/dom/messagechannel/MessagePortParent.cpp new file mode 100644 index 000000000000..25dadafdab3b --- /dev/null +++ b/dom/messagechannel/MessagePortParent.cpp @@ -0,0 +1,163 @@ +/* -*- 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&& aMessages) +{ + // This converts the object in a data struct where we have BlobImpls. + FallibleTArray> 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&& aMessages) +{ + // This converts the object in a data struct where we have BlobImpls. + FallibleTArray> 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. + nsRefPtr kungFuDeathGrip = mService; + mService->ParentDestroy(this); + } +} + +bool +MessagePortParent::Entangled(const nsTArray& aMessages) +{ + MOZ_ASSERT(!mEntangled); + mEntangled = true; + return SendEntangled(aMessages); +} + +void +MessagePortParent::CloseAndDelete() +{ + Close(); + unused << Send__delete__(this); +} + +void +MessagePortParent::Close() +{ + mService = nullptr; + mEntangled = false; +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/messagechannel/MessagePortParent.h b/dom/messagechannel/MessagePortParent.h new file mode 100644 index 000000000000..46dbb19ff0cc --- /dev/null +++ b/dom/messagechannel/MessagePortParent.h @@ -0,0 +1,61 @@ +/* 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& aMessages); + + void Close(); + void CloseAndDelete(); + + bool CanSendData() const + { + return mCanSendData; + } + + const nsID& ID() const + { + return mUUID; + } + +private: + virtual bool RecvPostMessages(nsTArray&& aMessages) + override; + + virtual bool RecvDisentangle(nsTArray&& aMessages) + override; + + virtual bool RecvStopSendingData() override; + + virtual bool RecvClose() override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + nsRefPtr mService; + const nsID mUUID; + bool mEntangled; + bool mCanSendData; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_MessagePortParent_h diff --git a/dom/messagechannel/MessagePortService.cpp b/dom/messagechannel/MessagePortService.cpp new file mode 100644 index 000000000000..0d75e6ba1951 --- /dev/null +++ b/dom/messagechannel/MessagePortService.cpp @@ -0,0 +1,328 @@ +/* -*- 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 "MessagePortService.h" +#include "MessagePortParent.h" +#include "SharedMessagePortMessage.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/unused.h" +#include "nsDataHashtable.h" +#include "nsTArray.h" + +namespace mozilla { +namespace dom { + +namespace { +StaticRefPtr gInstance; +} // anonymous namespace + +class MessagePortService::MessagePortServiceData final +{ +public: + explicit MessagePortServiceData(const nsID& aDestinationUUID) + : mDestinationUUID(aDestinationUUID) + , mSequenceID(1) + , mParent(nullptr) + { + 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 mNextParents; + FallibleTArray> mMessages; +}; + +/* static */ MessagePortService* +MessagePortService::GetOrCreate() +{ + 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!"); + return false; + } + + if (aSequenceID < data->mSequenceID) { + MOZ_ASSERT(false, "Invalid sequence ID!"); + return false; + } + + if (aSequenceID == data->mSequenceID) { + if (data->mParent) { + MOZ_ASSERT(false, "Two ports cannot have the same sequenceID."); + return false; + } + + // We activate this port, sending all the messages. + data->mParent = aParent; + FallibleTArray array; + if (!SharedMessagePortMessage::FromSharedToMessagesParent(aParent, + data->mMessages, + array)) { + return false; + } + + data->mMessages.Clear(); + return aParent->Entangled(array); + } + + // 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) { + return false; + } + + nextParent->mSequenceID = aSequenceID; + nextParent->mParent = aParent; + + return true; +} + +bool +MessagePortService::DisentanglePort( + MessagePortParent* aParent, + FallibleTArray>& 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->mParent = nullptr; + return true; + } + + data->mParent = nextParent; + data->mNextParents.RemoveElementAt(index); + + FallibleTArray 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; +} + +#ifdef DEBUG +PLDHashOperator +MessagePortService::CloseAllDebugCheck(const nsID& aID, + MessagePortServiceData* aData, + void* aPtr) +{ + nsID* id = static_cast(aPtr); + MOZ_ASSERT(!id->Equals(aID)); + return PL_DHASH_NEXT; +} +#endif + +void +MessagePortService::CloseAll(const nsID& aUUID) +{ + 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; + mPorts.Remove(aUUID); + + CloseAll(destinationUUID); + +#ifdef DEBUG + mPorts.EnumerateRead(CloseAllDebugCheck, const_cast(&aUUID)); +#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>& 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 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()); +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/messagechannel/MessagePortService.h b/dom/messagechannel/MessagePortService.h new file mode 100644 index 000000000000..437cf2393c11 --- /dev/null +++ b/dom/messagechannel/MessagePortService.h @@ -0,0 +1,61 @@ +/* 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* GetOrCreate(); + + bool RequestEntangling(MessagePortParent* aParent, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID); + + bool DisentanglePort( + MessagePortParent* aParent, + FallibleTArray>& aMessages); + + bool ClosePort(MessagePortParent* aParent); + + bool PostMessages( + MessagePortParent* aParent, + FallibleTArray>& aMessages); + + void ParentDestroy(MessagePortParent* aParent); + +private: + ~MessagePortService() {} + + void CloseAll(const nsID& aUUID); + void MaybeShutdown(); + + class MessagePortServiceData; + +#ifdef DEBUG + static PLDHashOperator + CloseAllDebugCheck(const nsID& aID, MessagePortServiceData* aData, + void* aPtr); +#endif + + nsClassHashtable mPorts; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_MessagePortService_h diff --git a/dom/messagechannel/MessagePortUtils.cpp b/dom/messagechannel/MessagePortUtils.cpp new file mode 100644 index 000000000000..06b330b7f426 --- /dev/null +++ b/dom/messagechannel/MessagePortUtils.cpp @@ -0,0 +1,277 @@ +/* -*- 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 "MessagePortUtils.h" +#include "MessagePort.h" +#include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/StructuredCloneTags.h" + +namespace mozilla { +namespace dom { +namespace messageport { + +namespace { + +struct MOZ_STACK_CLASS StructuredCloneClosureInternal +{ + StructuredCloneClosureInternal( + StructuredCloneClosure& aClosure, nsPIDOMWindow* aWindow) + : mClosure(aClosure) + , mWindow(aWindow) + { } + + StructuredCloneClosure& mClosure; + nsPIDOMWindow* mWindow; + nsTArray> mMessagePorts; + nsTArray> mTransferredPorts; +}; + +struct MOZ_STACK_CLASS StructuredCloneClosureInternalReadOnly +{ + StructuredCloneClosureInternalReadOnly( + const StructuredCloneClosure& aClosure, nsPIDOMWindow* aWindow) + : mClosure(aClosure) + , mWindow(aWindow) + { } + + const StructuredCloneClosure& mClosure; + nsPIDOMWindow* mWindow; + nsTArray> mMessagePorts; + nsTArray> mTransferredPorts; +}; + +void +Error(JSContext* aCx, uint32_t aErrorId) +{ + if (NS_IsMainThread()) { + NS_DOMStructuredCloneError(aCx, aErrorId); + } else { + Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + } +} + +JSObject* +Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, + uint32_t aData, void* aClosure) +{ + MOZ_ASSERT(aClosure); + + auto* closure = static_cast(aClosure); + + if (aTag == SCTAG_DOM_BLOB) { + // nsRefPtr needs to go out of scope before toObjectOrNull() is + // called because the static analysis thinks dereferencing XPCOM objects + // can GC (because in some cases it can!), and a return statement with a + // JSObject* type means that JSObject* is on the stack as a raw pointer + // while destructors are running. + JS::Rooted val(aCx); + { + MOZ_ASSERT(aData < closure->mClosure.mBlobImpls.Length()); + nsRefPtr blobImpl = closure->mClosure.mBlobImpls[aData]; + +#ifdef DEBUG + { + // Blob should not be mutable. + bool isMutable; + MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + } +#endif + + // Let's create a new blob with the correct parent. + nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); + MOZ_ASSERT(global); + + nsRefPtr newBlob = Blob::Create(global, blobImpl); + if (!ToJSValue(aCx, newBlob, &val)) { + return nullptr; + } + } + + return &val.toObject(); + } + + return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nullptr); +} + +bool +Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, + JS::Handle aObj, void* aClosure) +{ + MOZ_ASSERT(aClosure); + + auto* closure = static_cast(aClosure); + + // See if the wrapped native is a File/Blob. + { + Blob* blob = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) && + NS_SUCCEEDED(blob->SetMutable(false)) && + JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, + closure->mClosure.mBlobImpls.Length())) { + closure->mClosure.mBlobImpls.AppendElement(blob->Impl()); + return true; + } + } + + return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nullptr); +} + +bool +ReadTransfer(JSContext* aCx, JSStructuredCloneReader* aReader, + uint32_t aTag, void* aContent, uint64_t aExtraData, + void* aClosure, JS::MutableHandle aReturnObject) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aClosure); + + auto* closure = static_cast(aClosure); + + if (aTag != SCTAG_DOM_MAP_MESSAGEPORT) { + return false; + } + + MOZ_ASSERT(aContent == 0); + MOZ_ASSERT(aExtraData < closure->mClosure.mMessagePortIdentifiers.Length()); + + ErrorResult rv; + nsRefPtr port = + MessagePort::Create(closure->mWindow, + closure->mClosure.mMessagePortIdentifiers[(uint32_t)aExtraData], + rv); + if (NS_WARN_IF(rv.Failed())) { + return false; + } + + closure->mMessagePorts.AppendElement(port); + + JS::Rooted value(aCx); + if (!GetOrCreateDOMReflector(aCx, port, &value)) { + JS_ClearPendingException(aCx); + return false; + } + + aReturnObject.set(&value.toObject()); + return true; +} + +bool +WriteTransfer(JSContext* aCx, JS::Handle aObj, void* aClosure, + uint32_t* aTag, JS::TransferableOwnership* aOwnership, + void** aContent, uint64_t* aExtraData) +{ + MOZ_ASSERT(aClosure); + + auto* closure = static_cast(aClosure); + + MessagePortBase* port = nullptr; + nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); + if (NS_FAILED(rv)) { + return false; + } + + if (closure->mTransferredPorts.Contains(port)) { + // No duplicates. + return false; + } + + MessagePortIdentifier identifier; + if (!port->CloneAndDisentangle(identifier)) { + return false; + } + + closure->mClosure.mMessagePortIdentifiers.AppendElement(identifier); + closure->mTransferredPorts.AppendElement(port); + + *aTag = SCTAG_DOM_MAP_MESSAGEPORT; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = nullptr; + *aExtraData = closure->mClosure.mMessagePortIdentifiers.Length() - 1; + + return true; +} + +const JSStructuredCloneCallbacks gCallbacks = { + Read, + Write, + Error, + ReadTransfer, + WriteTransfer, + nullptr +}; + +} // anonymous namespace + +bool +ReadStructuredCloneWithTransfer(JSContext* aCx, nsTArray& aData, + const StructuredCloneClosure& aClosure, + JS::MutableHandle aClone, + nsPIDOMWindow* aParentWindow, + nsTArray>& aMessagePorts) +{ + auto* data = reinterpret_cast(aData.Elements()); + size_t dataLen = aData.Length(); + MOZ_ASSERT(!(dataLen % sizeof(*data))); + + StructuredCloneClosureInternalReadOnly internalClosure(aClosure, + aParentWindow); + + bool rv = JS_ReadStructuredClone(aCx, data, dataLen, + JS_STRUCTURED_CLONE_VERSION, aClone, + &gCallbacks, &internalClosure); + if (rv) { + aMessagePorts.SwapElements(internalClosure.mMessagePorts); + } + + return rv; +} + +bool +WriteStructuredCloneWithTransfer(JSContext* aCx, JS::Handle aSource, + JS::Handle aTransferable, + nsTArray& aData, + StructuredCloneClosure& aClosure) +{ + StructuredCloneClosureInternal internalClosure(aClosure, nullptr); + JSAutoStructuredCloneBuffer buffer(&gCallbacks, &internalClosure); + + if (!buffer.write(aCx, aSource, aTransferable, &gCallbacks, + &internalClosure)) { + return false; + } + + FallibleTArray cloneData; + if (NS_WARN_IF(!cloneData.SetLength(buffer.nbytes(), mozilla::fallible))) { + return false; + } + + uint64_t* data; + size_t size; + buffer.steal(&data, &size); + + memcpy(cloneData.Elements(), data, size); + js_free(data); + + MOZ_ASSERT(aData.IsEmpty()); + aData.SwapElements(cloneData); + return true; +} + +void +FreeStructuredClone(nsTArray& aData, StructuredCloneClosure& aClosure) +{ + auto* data = reinterpret_cast(aData.Elements()); + size_t dataLen = aData.Length(); + MOZ_ASSERT(!(dataLen % sizeof(*data))); + + JS_ClearStructuredClone(data, dataLen, &gCallbacks, &aClosure, false); + aData.Clear(); +} + +} // messageport namespace +} // dom namespace +} // mozilla namespace diff --git a/dom/messagechannel/MessagePortUtils.h b/dom/messagechannel/MessagePortUtils.h new file mode 100644 index 000000000000..9c9442700e73 --- /dev/null +++ b/dom/messagechannel/MessagePortUtils.h @@ -0,0 +1,55 @@ +/* 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_MessagePortUtils_h +#define mozilla_dom_MessagePortUtils_h + +#include "MessagePort.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/PMessagePort.h" + +class nsPIDOMWindow; + +namespace mozilla { +namespace dom { +namespace messageport { + +struct +StructuredCloneClosure +{ + nsTArray> mBlobImpls; + nsTArray mMessagePortIdentifiers; +}; + +struct +StructuredCloneData +{ + StructuredCloneData() : mData(nullptr), mDataLength(0) {} + uint64_t* mData; + size_t mDataLength; + StructuredCloneClosure mClosure; +}; + +bool +ReadStructuredCloneWithTransfer(JSContext* aCx, nsTArray& aData, + const StructuredCloneClosure& aClosure, + JS::MutableHandle aClone, + nsPIDOMWindow* aParentWindow, + nsTArray>& aMessagePorts); + +bool +WriteStructuredCloneWithTransfer(JSContext* aCx, JS::Handle aSource, + JS::Handle aTransferable, + nsTArray& aData, + StructuredCloneClosure& aClosure); + +void +FreeStructuredClone(nsTArray& aData, + StructuredCloneClosure& aClosure); + +} // messageport namespace +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_MessagePortUtils_h diff --git a/dom/messagechannel/PMessagePort.ipdl b/dom/messagechannel/PMessagePort.ipdl new file mode 100644 index 000000000000..299b00fedf14 --- /dev/null +++ b/dom/messagechannel/PMessagePort.ipdl @@ -0,0 +1,62 @@ +/* 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; + +using struct nsID from "nsID.h"; + +namespace mozilla { +namespace dom { + +struct MessagePortIdentifier +{ + nsID uuid; + nsID destinationUuid; + uint32_t sequenceId; + bool neutered; +}; + +struct MessagePortMessage +{ + MessagePortIdentifier[] transferredPorts; + uint8_t[] data; + PBlob[] blobs; +}; + +// 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: + PostMessages(MessagePortMessage[] messages); + Disentangle(MessagePortMessage[] messages); + StopSendingData(); + Close(); + +child: + Entangled(MessagePortMessage[] messages); + ReceiveData(MessagePortMessage[] messages); + StopSendingDataConfirmed(); + + __delete__(); +}; + +} // namespace dom +} // namespace mozilla + diff --git a/dom/messagechannel/SharedMessagePortMessage.cpp b/dom/messagechannel/SharedMessagePortMessage.cpp new file mode 100644 index 000000000000..bbd8fbdbdd5d --- /dev/null +++ b/dom/messagechannel/SharedMessagePortMessage.cpp @@ -0,0 +1,180 @@ +/* -*- 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 { + +SharedMessagePortMessage::~SharedMessagePortMessage() +{ + if (!mData.IsEmpty()) { + FreeStructuredClone(mData, mClosure); + } +} + +/* static */ void +SharedMessagePortMessage::FromSharedToMessagesChild( + MessagePortChild* aActor, + const nsTArray>& aData, + nsTArray& 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(); + message->data().SwapElements(data->mData); + + const nsTArray>& blobImpls = + data->mClosure.mBlobImpls; + 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->mClosure.mMessagePortIdentifiers); + } +} + +/* static */ bool +SharedMessagePortMessage::FromMessagesToSharedChild( + nsTArray& aArray, + FallibleTArray>& aData) +{ + MOZ_ASSERT(aData.IsEmpty()); + + if (NS_WARN_IF(!aData.SetCapacity(aArray.Length(), mozilla::fallible))) { + return false; + } + + for (auto& message : aArray) { + nsRefPtr data = new SharedMessagePortMessage(); + + data->mData.SwapElements(message.data()); + + const nsTArray& blobs = message.blobsChild(); + if (!blobs.IsEmpty()) { + data->mClosure.mBlobImpls.SetCapacity(blobs.Length()); + + for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) { + nsRefPtr impl = + static_cast(blobs[i])->GetBlobImpl(); + data->mClosure.mBlobImpls.AppendElement(impl); + } + } + + data->mClosure.mMessagePortIdentifiers.AppendElements( + message.transferredPorts()); + + if (!aData.AppendElement(data, mozilla::fallible)) { + return false; + } + } + + return true; +} + +/* static */ bool +SharedMessagePortMessage::FromSharedToMessagesParent( + MessagePortParent* aActor, + const nsTArray>& aData, + FallibleTArray& 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); + message->data().SwapElements(data->mData); + + const nsTArray>& blobImpls = data->mClosure.mBlobImpls; + 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->mClosure.mMessagePortIdentifiers); + } + + return true; +} + +/* static */ bool +SharedMessagePortMessage::FromMessagesToSharedParent( + nsTArray& aArray, + FallibleTArray>& aData) +{ + MOZ_ASSERT(aData.IsEmpty()); + + if (NS_WARN_IF(!aData.SetCapacity(aArray.Length(), mozilla::fallible))) { + return false; + } + + for (auto& message : aArray) { + nsRefPtr data = new SharedMessagePortMessage(); + + data->mData.SwapElements(message.data()); + + const nsTArray& blobs = message.blobsParent(); + if (!blobs.IsEmpty()) { + data->mClosure.mBlobImpls.SetCapacity(blobs.Length()); + + for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) { + nsRefPtr impl = + static_cast(blobs[i])->GetBlobImpl(); + data->mClosure.mBlobImpls.AppendElement(impl); + } + } + + data->mClosure.mMessagePortIdentifiers.AppendElements( + message.transferredPorts()); + + if (!aData.AppendElement(data, mozilla::fallible)) { + return false; + } + } + + return true; +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/messagechannel/SharedMessagePortMessage.h b/dom/messagechannel/SharedMessagePortMessage.h new file mode 100644 index 000000000000..c2516874edf5 --- /dev/null +++ b/dom/messagechannel/SharedMessagePortMessage.h @@ -0,0 +1,58 @@ +/* -*- 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 "MessagePortUtils.h" + +namespace mozilla { +namespace dom { + +class MessagePortChild; +class MessagePortMessage; +class MessagePortParent; + +class SharedMessagePortMessage final +{ +public: + NS_INLINE_DECL_REFCOUNTING(SharedMessagePortMessage) + + nsTArray mData; + messageport::StructuredCloneClosure mClosure; + + SharedMessagePortMessage() + {} + + static void + FromSharedToMessagesChild( + MessagePortChild* aActor, + const nsTArray>& aData, + nsTArray& aArray); + + static bool + FromMessagesToSharedChild( + nsTArray& aArray, + FallibleTArray>& aData); + + static bool + FromSharedToMessagesParent( + MessagePortParent* aActor, + const nsTArray>& aData, + FallibleTArray& aArray); + + static bool + FromMessagesToSharedParent( + nsTArray& aArray, + FallibleTArray>& aData); + +private: + ~SharedMessagePortMessage(); +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_SharedMessagePortMessage_h diff --git a/dom/messagechannel/moz.build b/dom/messagechannel/moz.build new file mode 100644 index 000000000000..c5850dbe0bce --- /dev/null +++ b/dom/messagechannel/moz.build @@ -0,0 +1,41 @@ +# -*- Mode: python; c-basic-offset: 4; 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', + 'MessagePortList.h', + 'MessagePortParent.h', +] + +UNIFIED_SOURCES += [ + 'MessageChannel.cpp', + 'MessagePort.cpp', + 'MessagePortChild.cpp', + 'MessagePortList.cpp', + 'MessagePortParent.cpp', + 'MessagePortService.cpp', + 'MessagePortUtils.cpp', + 'SharedMessagePortMessage.cpp', +] + +IPDL_SOURCES += [ + 'PMessagePort.ipdl', +] + +LOCAL_INCLUDES += [ + '../base', + '../events', + '../workers', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +FAIL_ON_WARNINGS = True diff --git a/dom/messagechannel/tests/chrome.ini b/dom/messagechannel/tests/chrome.ini new file mode 100644 index 000000000000..8d7140d76f29 --- /dev/null +++ b/dom/messagechannel/tests/chrome.ini @@ -0,0 +1,5 @@ +[DEFAULT] +support-files = + iframe_messageChannel_chrome.html + +[test_messageChannel.xul] diff --git a/dom/base/test/iframe_messageChannel_chrome.html b/dom/messagechannel/tests/iframe_messageChannel_chrome.html similarity index 100% rename from dom/base/test/iframe_messageChannel_chrome.html rename to dom/messagechannel/tests/iframe_messageChannel_chrome.html diff --git a/dom/base/test/iframe_messageChannel_cloning.html b/dom/messagechannel/tests/iframe_messageChannel_cloning.html similarity index 100% rename from dom/base/test/iframe_messageChannel_cloning.html rename to dom/messagechannel/tests/iframe_messageChannel_cloning.html diff --git a/dom/base/test/iframe_messageChannel_pingpong.html b/dom/messagechannel/tests/iframe_messageChannel_pingpong.html similarity index 100% rename from dom/base/test/iframe_messageChannel_pingpong.html rename to dom/messagechannel/tests/iframe_messageChannel_pingpong.html diff --git a/dom/base/test/iframe_messageChannel_post.html b/dom/messagechannel/tests/iframe_messageChannel_post.html similarity index 100% rename from dom/base/test/iframe_messageChannel_post.html rename to dom/messagechannel/tests/iframe_messageChannel_post.html diff --git a/dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html b/dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html new file mode 100644 index 000000000000..a693cba22c2e --- /dev/null +++ b/dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html @@ -0,0 +1,14 @@ + + + + + + + diff --git a/dom/messagechannel/tests/iframe_messageChannel_transferable.html b/dom/messagechannel/tests/iframe_messageChannel_transferable.html new file mode 100644 index 000000000000..108edeb7e6ae --- /dev/null +++ b/dom/messagechannel/tests/iframe_messageChannel_transferable.html @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/dom/messagechannel/tests/mochitest.ini b/dom/messagechannel/tests/mochitest.ini new file mode 100644 index 000000000000..fc5cecd1304a --- /dev/null +++ b/dom/messagechannel/tests/mochitest.ini @@ -0,0 +1,25 @@ +[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_pref.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] diff --git a/dom/messagechannel/tests/moz.build b/dom/messagechannel/tests/moz.build new file mode 100644 index 000000000000..846268289f1d --- /dev/null +++ b/dom/messagechannel/tests/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; c-basic-offset: 4; 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 000000000000..8cc98aa209b8 --- /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 000000000000..4b24642f9d26 --- /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/base/test/test_messageChannel.html b/dom/messagechannel/tests/test_messageChannel.html similarity index 100% rename from dom/base/test/test_messageChannel.html rename to dom/messagechannel/tests/test_messageChannel.html diff --git a/dom/base/test/test_messageChannel.xul b/dom/messagechannel/tests/test_messageChannel.xul similarity index 91% rename from dom/base/test/test_messageChannel.xul rename to dom/messagechannel/tests/test_messageChannel.xul index ab2fae4d93f7..3d8e3485c6be 100644 --- a/dom/base/test/test_messageChannel.xul +++ b/dom/messagechannel/tests/test_messageChannel.xul @@ -23,7 +23,7 @@ } var ifr = document.createElement('browser'); - ifr.setAttribute("src", "http://mochi.test:8888/tests/dom/base/test/iframe_messageChannel_chrome.html"); + ifr.setAttribute("src", "iframe_messageChannel_chrome.html"); ifr.setAttribute("flex", "1"); ifr.addEventListener('load', function() { ifr.contentWindow.postMessage(channel.port2, '*', [channel.port2]); diff --git a/dom/messagechannel/tests/test_messageChannel_any.html b/dom/messagechannel/tests/test_messageChannel_any.html new file mode 100644 index 000000000000..2cc34443ddb4 --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel_any.html @@ -0,0 +1,115 @@ + + + + + + MessagePort/Channel any content + + + + +Mozilla Bug 677638 +
+
+
+ + + diff --git a/dom/base/test/test_messageChannel_cloning.html b/dom/messagechannel/tests/test_messageChannel_cloning.html similarity index 100% rename from dom/base/test/test_messageChannel_cloning.html rename to dom/messagechannel/tests/test_messageChannel_cloning.html diff --git a/dom/base/test/test_messageChannel_pingpong.html b/dom/messagechannel/tests/test_messageChannel_pingpong.html similarity index 100% rename from dom/base/test/test_messageChannel_pingpong.html rename to dom/messagechannel/tests/test_messageChannel_pingpong.html diff --git a/dom/base/test/test_messageChannel_post.html b/dom/messagechannel/tests/test_messageChannel_post.html similarity index 100% rename from dom/base/test/test_messageChannel_post.html rename to dom/messagechannel/tests/test_messageChannel_post.html diff --git a/dom/base/test/test_messageChannel_pref.html b/dom/messagechannel/tests/test_messageChannel_pref.html similarity index 100% rename from dom/base/test/test_messageChannel_pref.html rename to dom/messagechannel/tests/test_messageChannel_pref.html diff --git a/dom/messagechannel/tests/test_messageChannel_selfTransferring.html b/dom/messagechannel/tests/test_messageChannel_selfTransferring.html new file mode 100644 index 000000000000..d84a616e4255 --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel_selfTransferring.html @@ -0,0 +1,38 @@ + + + + + + MessagePort/Channel no self tranferring + + + + +Mozilla Bug 677638 +
+
+
+ + + + diff --git a/dom/messagechannel/tests/test_messageChannel_sharedWorker.html b/dom/messagechannel/tests/test_messageChannel_sharedWorker.html new file mode 100644 index 000000000000..9bb330a851fb --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel_sharedWorker.html @@ -0,0 +1,39 @@ + + + + + + Test for Bug 677638 - sharedWorker + + + + +Mozilla Bug 677638 +

+ +
+
+ + + diff --git a/dom/messagechannel/tests/test_messageChannel_sharedWorker2.html b/dom/messagechannel/tests/test_messageChannel_sharedWorker2.html new file mode 100644 index 000000000000..d8a4c624b1ab --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel_sharedWorker2.html @@ -0,0 +1,37 @@ + + + + + + Test for Bug 677638 - sharedWorker + + + + + Mozilla Bug 677638 +
+ + + + diff --git a/dom/base/test/test_messageChannel_start.html b/dom/messagechannel/tests/test_messageChannel_start.html similarity index 100% rename from dom/base/test/test_messageChannel_start.html rename to dom/messagechannel/tests/test_messageChannel_start.html diff --git a/dom/base/test/test_messageChannel_transferable.html b/dom/messagechannel/tests/test_messageChannel_transferable.html similarity index 57% rename from dom/base/test/test_messageChannel_transferable.html rename to dom/messagechannel/tests/test_messageChannel_transferable.html index c1e661ead89e..82b575bac666 100644 --- a/dom/base/test/test_messageChannel_transferable.html +++ b/dom/messagechannel/tests/test_messageChannel_transferable.html @@ -16,14 +16,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 diff --git a/dom/base/test/test_messageChannel_unshipped.html b/dom/messagechannel/tests/test_messageChannel_unshipped.html similarity index 100% rename from dom/base/test/test_messageChannel_unshipped.html rename to dom/messagechannel/tests/test_messageChannel_unshipped.html diff --git a/dom/messagechannel/tests/test_messageChannel_worker.html b/dom/messagechannel/tests/test_messageChannel_worker.html new file mode 100644 index 000000000000..0eb8489f5170 --- /dev/null +++ b/dom/messagechannel/tests/test_messageChannel_worker.html @@ -0,0 +1,60 @@ + + + + + + + Test for Bug 677638 - basic support + + + + +Mozilla Bug 677638 +

+ +
+
+ + + diff --git a/dom/messagechannel/tests/worker_messageChannel.js b/dom/messagechannel/tests/worker_messageChannel.js new file mode 100644 index 000000000000..87b0b8eb0c27 --- /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 000000000000..bbb1d50f974e --- /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"); diff --git a/dom/moz.build b/dom/moz.build index 41b1d209db7b..236e9d352f7b 100644 --- a/dom/moz.build +++ b/dom/moz.build @@ -96,6 +96,7 @@ DIRS += [ 'camera', 'audiochannel', 'broadcastchannel', + 'messagechannel', 'promise', 'smil', 'telephony', diff --git a/dom/webidl/MessageChannel.webidl b/dom/webidl/MessageChannel.webidl index 34de4b46e207..64b1262e3611 100644 --- a/dom/webidl/MessageChannel.webidl +++ b/dom/webidl/MessageChannel.webidl @@ -7,7 +7,8 @@ * http://www.whatwg.org/specs/web-apps/current-work/#channel-messaging */ -[Constructor, Func="MessageChannel::Enabled"] +[Constructor, Func="MessageChannel::Enabled", + Exposed=(Window,Worker)] interface MessageChannel { readonly attribute MessagePort port1; readonly attribute MessagePort port2; diff --git a/dom/workers/MessagePort.cpp b/dom/workers/MessagePort.cpp index 136787777615..19aa73559e31 100644 --- a/dom/workers/MessagePort.cpp +++ b/dom/workers/MessagePort.cpp @@ -17,6 +17,7 @@ using mozilla::dom::EventHandlerNonNull; using mozilla::dom::MessagePortBase; +using mozilla::dom::MessagePortIdentifier; using mozilla::dom::Optional; using mozilla::dom::Sequence; using mozilla::dom::AutoNoJSAPI; @@ -96,9 +97,9 @@ MessagePort::~MessagePort() } void -MessagePort::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, - ErrorResult& aRv) +MessagePort::PostMessage(JSContext* aCx, JS::Handle aMessage, + const Optional>& aTransferable, + ErrorResult& aRv) { AssertCorrectThread(); @@ -198,11 +199,11 @@ MessagePort::SetOnmessage(EventHandlerNonNull* aCallback) Start(); } -already_AddRefed -MessagePort::Clone() +bool +MessagePort::CloneAndDisentangle(MessagePortIdentifier& aIdentifier) { NS_WARNING("Haven't implemented structured clone for these ports yet!"); - return nullptr; + return false; } void diff --git a/dom/workers/MessagePort.h b/dom/workers/MessagePort.h index 51b1d6c9c018..30dcc3a2e13c 100644 --- a/dom/workers/MessagePort.h +++ b/dom/workers/MessagePort.h @@ -43,9 +43,9 @@ public: PrefEnabled(); virtual void - PostMessageMoz(JSContext* aCx, JS::Handle aMessage, - const Optional>& aTransferable, - ErrorResult& aRv) override; + PostMessage(JSContext* aCx, JS::Handle aMessage, + const Optional>& aTransferable, + ErrorResult& aRv) override; virtual void Start() override; @@ -71,8 +71,8 @@ public: virtual void SetOnmessage(EventHandlerNonNull* aCallback) override; - virtual already_AddRefed - Clone() override; + virtual bool + CloneAndDisentangle(MessagePortIdentifier& aIdentifier) override; bool IsClosed() const diff --git a/dom/workers/ServiceWorkerClient.cpp b/dom/workers/ServiceWorkerClient.cpp index 41d992ac74f8..1bfe6457a8af 100644 --- a/dom/workers/ServiceWorkerClient.cpp +++ b/dom/workers/ServiceWorkerClient.cpp @@ -12,6 +12,7 @@ #include "nsGlobalWindow.h" #include "nsIDocument.h" #include "WorkerPrivate.h" +#include "WorkerStructuredClone.h" using namespace mozilla; using namespace mozilla::dom; @@ -75,16 +76,18 @@ class ServiceWorkerClientPostMessageRunnable final : public nsRunnable { uint64_t mWindowId; JSAutoStructuredCloneBuffer mBuffer; - nsTArray> mClonedObjects; + WorkerStructuredCloneClosure mClosure; public: ServiceWorkerClientPostMessageRunnable(uint64_t aWindowId, JSAutoStructuredCloneBuffer&& aData, - nsTArray>& aClonedObjects) + WorkerStructuredCloneClosure& aClosure) : mWindowId(aWindowId), mBuffer(Move(aData)) { - mClonedObjects.SwapElements(aClonedObjects); + mClosure.mClonedObjects.SwapElements(aClosure.mClonedObjects); + MOZ_ASSERT(aClosure.mMessagePorts.IsEmpty()); + mClosure.mMessagePortIdentifiers.SwapElements(aClosure.mMessagePortIdentifiers); } NS_IMETHOD @@ -118,8 +121,10 @@ private: // Release reference to objects that were AddRef'd for // cloning into worker when array goes out of scope. - nsTArray> clonedObjects; - clonedObjects.SwapElements(mClonedObjects); + WorkerStructuredCloneClosure closure; + closure.mClonedObjects.SwapElements(mClosure.mClonedObjects); + MOZ_ASSERT(mClosure.mMessagePorts.IsEmpty()); + closure.mMessagePortIdentifiers.SwapElements(mClosure.mMessagePortIdentifiers); JS::Rooted messageData(aCx); if (!mBuffer.read(aCx, &messageData, @@ -185,16 +190,17 @@ ServiceWorkerClient::PostMessage(JSContext* aCx, JS::Handle aMessage, const JSStructuredCloneCallbacks* callbacks = WorkerStructuredCloneCallbacks(false); - nsTArray> clonedObjects; + WorkerStructuredCloneClosure closure; JSAutoStructuredCloneBuffer buffer; - if (!buffer.write(aCx, aMessage, transferable, callbacks, &clonedObjects)) { + if (!buffer.write(aCx, aMessage, transferable, callbacks, &closure)) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } nsRefPtr runnable = - new ServiceWorkerClientPostMessageRunnable(mWindowId, Move(buffer), clonedObjects); + new ServiceWorkerClientPostMessageRunnable(mWindowId, Move(buffer), + closure); nsresult rv = NS_DispatchToMainThread(runnable); if (NS_FAILED(rv)) { aRv.Throw(NS_ERROR_FAILURE); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index ac29fc04989c..a917d6eccc52 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -52,6 +52,8 @@ #include "mozilla/dom/ImageDataBinding.h" #include "mozilla/dom/MessageEvent.h" #include "mozilla/dom/MessageEventBinding.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/MessagePortBinding.h" #include "mozilla/dom/MessagePortList.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseDebugging.h" @@ -105,6 +107,7 @@ #include "WorkerFeature.h" #include "WorkerRunnable.h" #include "WorkerScope.h" +#include "WorkerStructuredClone.h" #include "WorkerThread.h" #ifdef XP_WIN @@ -511,7 +514,7 @@ bool WriteBlobOrFile(JSContext* aCx, JSStructuredCloneWriter* aWriter, BlobImpl* aBlobOrBlobImpl, - nsTArray>& aClonedObjects) + WorkerStructuredCloneClosure& aClosure) { MOZ_ASSERT(aCx); MOZ_ASSERT(aWriter); @@ -529,7 +532,7 @@ WriteBlobOrFile(JSContext* aCx, return false; } - aClonedObjects.AppendElement(aBlobOrBlobImpl); + aClosure.mClonedObjects.AppendElement(aBlobOrBlobImpl); return true; } @@ -547,7 +550,7 @@ bool WriteFormData(JSContext* aCx, JSStructuredCloneWriter* aWriter, nsFormData* aFormData, - nsTArray>& aClonedObjects) + WorkerStructuredCloneClosure& aClosure) { MOZ_ASSERT(aCx); MOZ_ASSERT(aWriter); @@ -560,11 +563,11 @@ WriteFormData(JSContext* aCx, class MOZ_STACK_CLASS Closure { JSContext* mCx; JSStructuredCloneWriter* mWriter; - nsTArray>& mClones; + WorkerStructuredCloneClosure& mClones; public: Closure(JSContext* aCx, JSStructuredCloneWriter* aWriter, - nsTArray>& aClones) + WorkerStructuredCloneClosure& aClones) : mCx(aCx), mWriter(aWriter), mClones(aClones) { } @@ -595,7 +598,7 @@ WriteFormData(JSContext* aCx, } }; - Closure closure(aCx, aWriter, aClonedObjects); + Closure closure(aCx, aWriter, aClosure); return aFormData->ForEach(Closure::Write, &closure); } @@ -639,9 +642,7 @@ struct WorkerStructuredCloneCallbacks { NS_ASSERTION(aClosure, "Null pointer!"); - // We'll stash any nsISupports pointers that need to be AddRef'd here. - auto* clonedObjects = - static_cast>*>(aClosure); + auto* closure = static_cast(aClosure); // See if this is a Blob/File object. { @@ -650,7 +651,7 @@ struct WorkerStructuredCloneCallbacks BlobImpl* blobImpl = blob->Impl(); MOZ_ASSERT(blobImpl); - if (WriteBlobOrFile(aCx, aWriter, blobImpl, *clonedObjects)) { + if (WriteBlobOrFile(aCx, aWriter, blobImpl, *closure)) { return true; } } @@ -668,7 +669,7 @@ struct WorkerStructuredCloneCallbacks { nsFormData* formData = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, aObj, formData))) { - if (WriteFormData(aCx, aWriter, formData, *clonedObjects)) { + if (WriteFormData(aCx, aWriter, formData, *closure)) { return true; } } @@ -683,15 +684,96 @@ struct WorkerStructuredCloneCallbacks { Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); } + + static bool + ReadTransfer(JSContext* aCx, JSStructuredCloneReader* aReader, + uint32_t aTag, void* aContent, uint64_t aExtraData, + void* aClosure, JS::MutableHandle aReturnObject) + { + MOZ_ASSERT(aClosure); + + auto* closure = static_cast(aClosure); + + if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { + MOZ_ASSERT(!aContent); + MOZ_ASSERT(aExtraData < closure->mMessagePortIdentifiers.Length()); + + ErrorResult rv; + nsRefPtr port = + dom::MessagePort::Create(closure->mParentWindow, + closure->mMessagePortIdentifiers[aExtraData], + rv); + + if (NS_WARN_IF(rv.Failed())) { + return false; + } + + closure->mMessagePorts.AppendElement(port); + + JS::Rooted value(aCx); + if (!GetOrCreateDOMReflector(aCx, port, &value)) { + JS_ClearPendingException(aCx); + return false; + } + + aReturnObject.set(&value.toObject()); + return true; + } + + return false; + } + + static bool + Transfer(JSContext* aCx, JS::Handle aObj, void* aClosure, + uint32_t* aTag, JS::TransferableOwnership* aOwnership, + void** aContent, uint64_t *aExtraData) + { + MOZ_ASSERT(aClosure); + + auto* closure = static_cast(aClosure); + + MessagePortBase* port; + nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); + if (NS_SUCCEEDED(rv)) { + if (NS_WARN_IF(closure->mTransferredPorts.Contains(port))) { + // No duplicates. + return false; + } + + MessagePortIdentifier identifier; + if (!port->CloneAndDisentangle(identifier)) { + return false; + } + + closure->mMessagePortIdentifiers.AppendElement(identifier); + closure->mTransferredPorts.AppendElement(port); + + *aTag = SCTAG_DOM_MAP_MESSAGEPORT; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = nullptr; + *aExtraData = closure->mMessagePortIdentifiers.Length() - 1; + + return true; + } + + return false; + } + + static void + FreeTransfer(uint32_t aTag, JS::TransferableOwnership aOwnership, + void *aContent, uint64_t aExtraData, void* aClosure) + { + // Nothing to do. + } }; const JSStructuredCloneCallbacks gWorkerStructuredCloneCallbacks = { WorkerStructuredCloneCallbacks::Read, WorkerStructuredCloneCallbacks::Write, WorkerStructuredCloneCallbacks::Error, - nullptr, - nullptr, - nullptr + WorkerStructuredCloneCallbacks::ReadTransfer, + WorkerStructuredCloneCallbacks::Transfer, + WorkerStructuredCloneCallbacks::FreeTransfer }; struct MainThreadWorkerStructuredCloneCallbacks @@ -731,9 +813,7 @@ struct MainThreadWorkerStructuredCloneCallbacks NS_ASSERTION(aClosure, "Null pointer!"); - // We'll stash any nsISupports pointers that need to be AddRef'd here. - auto* clonedObjects = - static_cast>*>(aClosure); + auto* closure = static_cast(aClosure); // See if this is a Blob/File object. { @@ -744,7 +824,7 @@ struct MainThreadWorkerStructuredCloneCallbacks if (!blobImpl->MayBeClonedToOtherThreads()) { NS_WARNING("Not all the blob implementations can be sent between threads."); - } else if (WriteBlobOrFile(aCx, aWriter, blobImpl, *clonedObjects)) { + } else if (WriteBlobOrFile(aCx, aWriter, blobImpl, *closure)) { return true; } } @@ -767,9 +847,9 @@ const JSStructuredCloneCallbacks gMainThreadWorkerStructuredCloneCallbacks = { MainThreadWorkerStructuredCloneCallbacks::Read, MainThreadWorkerStructuredCloneCallbacks::Write, MainThreadWorkerStructuredCloneCallbacks::Error, - nullptr, - nullptr, - nullptr + WorkerStructuredCloneCallbacks::ReadTransfer, + WorkerStructuredCloneCallbacks::Transfer, + WorkerStructuredCloneCallbacks::FreeTransfer }; struct ChromeWorkerStructuredCloneCallbacks @@ -800,9 +880,9 @@ const JSStructuredCloneCallbacks gChromeWorkerStructuredCloneCallbacks = { ChromeWorkerStructuredCloneCallbacks::Read, ChromeWorkerStructuredCloneCallbacks::Write, ChromeWorkerStructuredCloneCallbacks::Error, - nullptr, - nullptr, - nullptr + WorkerStructuredCloneCallbacks::ReadTransfer, + WorkerStructuredCloneCallbacks::Transfer, + WorkerStructuredCloneCallbacks::FreeTransfer }; struct MainThreadChromeWorkerStructuredCloneCallbacks @@ -1153,7 +1233,7 @@ private: class MessageEventRunnable final : public WorkerRunnable { JSAutoStructuredCloneBuffer mBuffer; - nsTArray > mClonedObjects; + WorkerStructuredCloneClosure mClosure; uint64_t mMessagePortSerial; bool mToMessagePort; @@ -1163,15 +1243,24 @@ class MessageEventRunnable final : public WorkerRunnable public: MessageEventRunnable(WorkerPrivate* aWorkerPrivate, TargetAndBusyBehavior aBehavior, - JSAutoStructuredCloneBuffer&& aData, - nsTArray >& aClonedObjects, bool aToMessagePort, uint64_t aMessagePortSerial) : WorkerRunnable(aWorkerPrivate, aBehavior) - , mBuffer(Move(aData)) , mMessagePortSerial(aMessagePortSerial) , mToMessagePort(aToMessagePort) { - mClonedObjects.SwapElements(aClonedObjects); + } + + bool + Write(JSContext* aCx, JS::Handle aValue, + JS::Handle aTransferredValue, + const JSStructuredCloneCallbacks *aCallbacks) + { + bool ok = mBuffer.write(aCx, aValue, aTransferredValue, aCallbacks, + &mClosure); + // This hashtable has to be empty because it could contain MessagePort + // objects that cannot be freed on a different thread. + mClosure.mTransferredPorts.Clear(); + return ok; } void @@ -1186,12 +1275,19 @@ public: { // Release reference to objects that were AddRef'd for // cloning into worker when array goes out of scope. - nsTArray> clonedObjects; - clonedObjects.SwapElements(mClonedObjects); + WorkerStructuredCloneClosure closure; + closure.mClonedObjects.SwapElements(mClosure.mClonedObjects); + MOZ_ASSERT(mClosure.mMessagePorts.IsEmpty()); + closure.mMessagePortIdentifiers.SwapElements(mClosure.mMessagePortIdentifiers); + + if (aIsMainThread) { + closure.mParentWindow = do_QueryInterface(aTarget->GetParentObject()); + } JS::Rooted messageData(aCx); if (!mBuffer.read(aCx, &messageData, - workers::WorkerStructuredCloneCallbacks(aIsMainThread))) { + workers::WorkerStructuredCloneCallbacks(aIsMainThread), + &closure)) { xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); return false; } @@ -1217,7 +1313,8 @@ public: } event->SetTrusted(true); - + event->SetPorts(new MessagePortList(static_cast(event.get()), + closure.mMessagePorts)); nsCOMPtr domEvent = do_QueryObject(event); nsEventStatus dummy = nsEventStatus_eIgnore; @@ -1243,7 +1340,7 @@ private: aWorkerPrivate->DispatchMessageEventToMessagePort(aCx, mMessagePortSerial, Move(mBuffer), - mClonedObjects); + mClosure); } if (aWorkerPrivate->IsFrozen()) { @@ -3379,19 +3476,16 @@ WorkerPrivateParent::PostMessageInternal( transferable.setObject(*array); } - nsTArray> clonedObjects; + nsRefPtr runnable = + new MessageEventRunnable(ParentAsWorkerPrivate(), + WorkerRunnable::WorkerThreadModifyBusyCount, + aToMessagePort, aMessagePortSerial); - JSAutoStructuredCloneBuffer buffer; - if (!buffer.write(aCx, aMessage, transferable, callbacks, &clonedObjects)) { + if (!runnable->Write(aCx, aMessage, transferable, callbacks)) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } - nsRefPtr runnable = - new MessageEventRunnable(ParentAsWorkerPrivate(), - WorkerRunnable::WorkerThreadModifyBusyCount, - Move(buffer), clonedObjects, aToMessagePort, - aMessagePortSerial); runnable->SetMessageSource(aClientInfo); if (!runnable->Dispatch(aCx)) { @@ -3432,14 +3526,16 @@ bool WorkerPrivateParent::DispatchMessageEventToMessagePort( JSContext* aCx, uint64_t aMessagePortSerial, JSAutoStructuredCloneBuffer&& aBuffer, - nsTArray>& aClonedObjects) + WorkerStructuredCloneClosure& aClosure) { AssertIsOnMainThread(); JSAutoStructuredCloneBuffer buffer(Move(aBuffer)); - nsTArray> clonedObjects; - clonedObjects.SwapElements(aClonedObjects); + WorkerStructuredCloneClosure closure; + closure.mClonedObjects.SwapElements(aClosure.mClonedObjects); + MOZ_ASSERT(aClosure.mMessagePorts.IsEmpty()); + closure.mMessagePortIdentifiers.SwapElements(aClosure.mMessagePortIdentifiers); SharedWorker* sharedWorker; if (!mSharedWorkers.Get(aMessagePortSerial, &sharedWorker)) { @@ -3454,6 +3550,8 @@ WorkerPrivateParent::DispatchMessageEventToMessagePort( return true; } + closure.mParentWindow = do_QueryInterface(port->GetParentObject()); + AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(port->GetParentObject()))) { return false; @@ -3461,7 +3559,8 @@ WorkerPrivateParent::DispatchMessageEventToMessagePort( JSContext* cx = jsapi.cx(); JS::Rooted data(cx); - if (!buffer.read(cx, &data, WorkerStructuredCloneCallbacks(true))) { + if (!buffer.read(cx, &data, WorkerStructuredCloneCallbacks(true), + &closure)) { return false; } @@ -3478,11 +3577,7 @@ WorkerPrivateParent::DispatchMessageEventToMessagePort( event->SetTrusted(true); - nsTArray> ports; - ports.AppendElement(port); - - nsRefPtr portList = new MessagePortList(port, ports); - event->SetPorts(portList); + event->SetPorts(new MessagePortList(port, closure.mMessagePorts)); nsCOMPtr domEvent; CallQueryInterface(event.get(), getter_AddRefs(domEvent)); @@ -6182,19 +6277,16 @@ WorkerPrivate::PostMessageToParentInternal( &gChromeWorkerStructuredCloneCallbacks : &gWorkerStructuredCloneCallbacks; - nsTArray> clonedObjects; + nsRefPtr runnable = + new MessageEventRunnable(this, + WorkerRunnable::ParentThreadUnchangedBusyCount, + aToMessagePort, aMessagePortSerial); - JSAutoStructuredCloneBuffer buffer; - if (!buffer.write(aCx, aMessage, transferable, callbacks, &clonedObjects)) { + if (!runnable->Write(aCx, aMessage, transferable, callbacks)) { aRv = NS_ERROR_DOM_DATA_CLONE_ERR; return; } - nsRefPtr runnable = - new MessageEventRunnable(this, - WorkerRunnable::ParentThreadUnchangedBusyCount, - Move(buffer), clonedObjects, aToMessagePort, - aMessagePortSerial); if (!runnable->Dispatch(aCx)) { aRv = NS_ERROR_FAILURE; } @@ -7348,4 +7440,20 @@ ChromeWorkerStructuredCloneCallbacks(bool aMainRuntime) // Force instantiation. template class WorkerPrivateParent; +WorkerStructuredCloneClosure::WorkerStructuredCloneClosure() +{} + +WorkerStructuredCloneClosure::~WorkerStructuredCloneClosure() +{} + +void +WorkerStructuredCloneClosure::Clear() +{ + mParentWindow = nullptr; + mClonedObjects.Clear(); + mMessagePorts.Clear(); + mMessagePortIdentifiers.Clear(); + mTransferredPorts.Clear(); +} + END_WORKERS_NAMESPACE diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index ef04c31ba4e1..976c05813918 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -73,6 +73,7 @@ class WorkerDebuggerGlobalScope; class WorkerGlobalScope; class WorkerPrivate; class WorkerRunnable; +class WorkerStructuredCloneClosure; class WorkerThread; // SharedMutex is a small wrapper around an (internal) reference-counted Mutex @@ -349,7 +350,7 @@ public: JSContext* aCx, uint64_t aMessagePortSerial, JSAutoStructuredCloneBuffer&& aBuffer, - nsTArray>& aClonedObjects); + WorkerStructuredCloneClosure& aClosure); void UpdateRuntimeOptions(JSContext* aCx, diff --git a/dom/workers/WorkerStructuredClone.h b/dom/workers/WorkerStructuredClone.h new file mode 100644 index 000000000000..85b7ab3f7f5f --- /dev/null +++ b/dom/workers/WorkerStructuredClone.h @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_workers_WorkerStructuredClone_h +#define mozilla_dom_workers_WorkerStructuredClone_h + +#include "Workers.h" +#include "mozilla/dom/PMessagePort.h" + +class nsPIDOMWindow; + +namespace mozilla { +namespace dom { + +class MessagePortBase; + +namespace workers { + +// This class is implemented in WorkerPrivate.cpp +class WorkerStructuredCloneClosure final +{ +private: + WorkerStructuredCloneClosure(const WorkerStructuredCloneClosure&) = delete; + WorkerStructuredCloneClosure & operator=(const WorkerStructuredCloneClosure&) = delete; + +public: + WorkerStructuredCloneClosure(); + ~WorkerStructuredCloneClosure(); + + void Clear(); + + // This can be null if the MessagePort is created in a worker. + nsCOMPtr mParentWindow; + + nsTArray> mClonedObjects; + + // The transferred ports. + nsTArray> mMessagePorts; + + // Information for the transferring. + nsTArray mMessagePortIdentifiers; + + // To avoid duplicates in the transferred ports. + nsTArray> mTransferredPorts; +}; + +} // workers namespace +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_workers_WorkerStructuredClone_h diff --git a/dom/workers/XMLHttpRequest.cpp b/dom/workers/XMLHttpRequest.cpp index 03479c0c51d6..6b61e824fe99 100644 --- a/dom/workers/XMLHttpRequest.cpp +++ b/dom/workers/XMLHttpRequest.cpp @@ -27,6 +27,7 @@ #include "RuntimeService.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" +#include "WorkerStructuredClone.h" #include "XMLHttpRequestUpload.h" using namespace mozilla; @@ -413,7 +414,7 @@ class EventRunnable final : public MainThreadProxyRunnable nsString mType; nsString mResponseType; JSAutoStructuredCloneBuffer mResponseBuffer; - nsTArray > mClonedObjects; + WorkerStructuredCloneClosure mResponseClosure; JS::Heap mResponse; nsString mResponseText; nsString mResponseURL; @@ -794,14 +795,14 @@ class SendRunnable final : public WorkerThreadProxySyncRunnable { nsString mStringBody; JSAutoStructuredCloneBuffer mBody; - nsTArray > mClonedObjects; + WorkerStructuredCloneClosure mClosure; nsCOMPtr mSyncLoopTarget; bool mHasUploadListeners; public: SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, const nsAString& aStringBody, JSAutoStructuredCloneBuffer&& aBody, - nsTArray>& aClonedObjects, + WorkerStructuredCloneClosure& aClosure, nsIEventTarget* aSyncLoopTarget, bool aHasUploadListeners) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy) , mStringBody(aStringBody) @@ -809,7 +810,9 @@ public: , mSyncLoopTarget(aSyncLoopTarget) , mHasUploadListeners(aHasUploadListeners) { - mClonedObjects.SwapElements(aClonedObjects); + mClosure.mClonedObjects.SwapElements(aClosure.mClonedObjects); + MOZ_ASSERT(aClosure.mMessagePorts.IsEmpty()); + MOZ_ASSERT(aClosure.mMessagePortIdentifiers.IsEmpty()); } private: @@ -1229,11 +1232,13 @@ EventRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) workers::ChromeWorkerStructuredCloneCallbacks(true) : workers::WorkerStructuredCloneCallbacks(true); - nsTArray > clonedObjects; + WorkerStructuredCloneClosure closure; if (mResponseBuffer.write(aCx, response, transferable, callbacks, - &clonedObjects)) { - mClonedObjects.SwapElements(clonedObjects); + &closure)) { + mResponseClosure.mClonedObjects.SwapElements(closure.mClonedObjects); + MOZ_ASSERT(mResponseClosure.mMessagePorts.IsEmpty()); + MOZ_ASSERT(mResponseClosure.mMessagePortIdentifiers.IsEmpty()); } else { NS_WARNING("Failed to clone response!"); mResponseResult = NS_ERROR_DOM_DATA_CLONE_ERR; @@ -1341,11 +1346,13 @@ EventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) workers::ChromeWorkerStructuredCloneCallbacks(false) : workers::WorkerStructuredCloneCallbacks(false); - nsTArray > clonedObjects; - clonedObjects.SwapElements(mClonedObjects); + WorkerStructuredCloneClosure closure; + closure.mClonedObjects.SwapElements(mResponseClosure.mClonedObjects); + MOZ_ASSERT(mResponseClosure.mMessagePorts.IsEmpty()); + MOZ_ASSERT(mResponseClosure.mMessagePortIdentifiers.IsEmpty()); JS::Rooted response(aCx); - if (!responseBuffer.read(aCx, &response, callbacks, &clonedObjects)) { + if (!responseBuffer.read(aCx, &response, callbacks, &closure)) { return false; } @@ -1526,7 +1533,7 @@ SendRunnable::MainThreadRun() workers::WorkerStructuredCloneCallbacks(true); JS::Rooted body(cx); - if (mBody.read(cx, &body, callbacks, &mClonedObjects)) { + if (mBody.read(cx, &body, callbacks, &mClosure)) { if (NS_FAILED(xpc->JSValToVariant(cx, body, getter_AddRefs(variant)))) { rv = NS_ERROR_DOM_INVALID_STATE_ERR; } @@ -1536,7 +1543,7 @@ SendRunnable::MainThreadRun() } mBody.clear(); - mClonedObjects.Clear(); + mClosure.Clear(); NS_ENSURE_SUCCESS(rv, rv); } @@ -1846,7 +1853,7 @@ XMLHttpRequest::Unpin() void XMLHttpRequest::SendInternal(const nsAString& aStringBody, JSAutoStructuredCloneBuffer&& aBody, - nsTArray >& aClonedObjects, + WorkerStructuredCloneClosure& aClosure, ErrorResult& aRv) { mWorkerPrivate->AssertIsOnWorkerThread(); @@ -1880,7 +1887,7 @@ XMLHttpRequest::SendInternal(const nsAString& aStringBody, nsRefPtr runnable = new SendRunnable(mWorkerPrivate, mProxy, aStringBody, Move(aBody), - aClonedObjects, syncLoopTarget, hasUploadListeners); + aClosure, syncLoopTarget, hasUploadListeners); if (!runnable->Dispatch(cx)) { // Dispatch() may have spun the event loop and we may have already unrooted. // If so we don't want autoUnpin to try again. @@ -2102,9 +2109,9 @@ XMLHttpRequest::Send(ErrorResult& aRv) // Nothing to clone. JSAutoStructuredCloneBuffer buffer; - nsTArray > clonedObjects; + WorkerStructuredCloneClosure closure; - SendInternal(NullString(), Move(buffer), clonedObjects, aRv); + SendInternal(NullString(), Move(buffer), closure, aRv); } void @@ -2124,9 +2131,9 @@ XMLHttpRequest::Send(const nsAString& aBody, ErrorResult& aRv) // Nothing to clone. JSAutoStructuredCloneBuffer buffer; - nsTArray > clonedObjects; + WorkerStructuredCloneClosure closure; - SendInternal(aBody, Move(buffer), clonedObjects, aRv); + SendInternal(aBody, Move(buffer), closure, aRv); } void @@ -2167,15 +2174,15 @@ XMLHttpRequest::Send(JS::Handle aBody, ErrorResult& aRv) ChromeWorkerStructuredCloneCallbacks(false) : WorkerStructuredCloneCallbacks(false); - nsTArray > clonedObjects; + WorkerStructuredCloneClosure closure; JSAutoStructuredCloneBuffer buffer; - if (!buffer.write(cx, valToClone, callbacks, &clonedObjects)) { + if (!buffer.write(cx, valToClone, callbacks, &closure)) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } - SendInternal(EmptyString(), Move(buffer), clonedObjects, aRv); + SendInternal(EmptyString(), Move(buffer), closure, aRv); } void @@ -2213,15 +2220,15 @@ XMLHttpRequest::Send(Blob& aBody, ErrorResult& aRv) ChromeWorkerStructuredCloneCallbacks(false) : WorkerStructuredCloneCallbacks(false); - nsTArray > clonedObjects; + WorkerStructuredCloneClosure closure; JSAutoStructuredCloneBuffer buffer; - if (!buffer.write(cx, value, callbacks, &clonedObjects)) { + if (!buffer.write(cx, value, callbacks, &closure)) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } - SendInternal(EmptyString(), Move(buffer), clonedObjects, aRv); + SendInternal(EmptyString(), Move(buffer), closure, aRv); } void @@ -2251,15 +2258,14 @@ XMLHttpRequest::Send(nsFormData& aBody, ErrorResult& aRv) ChromeWorkerStructuredCloneCallbacks(false) : WorkerStructuredCloneCallbacks(false); - nsTArray> clonedObjects; - JSAutoStructuredCloneBuffer buffer; - if (!buffer.write(cx, value, callbacks, &clonedObjects)) { + WorkerStructuredCloneClosure closure; + if (!buffer.write(cx, value, callbacks, &closure)) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } - SendInternal(EmptyString(), Move(buffer), clonedObjects, aRv); + SendInternal(EmptyString(), Move(buffer), closure, aRv); } void diff --git a/dom/workers/XMLHttpRequest.h b/dom/workers/XMLHttpRequest.h index 9475d9ad3308..a3c4d14b0d68 100644 --- a/dom/workers/XMLHttpRequest.h +++ b/dom/workers/XMLHttpRequest.h @@ -28,6 +28,7 @@ BEGIN_WORKERS_NAMESPACE class Proxy; class XMLHttpRequestUpload; class WorkerPrivate; +class WorkerStructuredCloneClosure; class XMLHttpRequest final: public nsXHREventTarget, public WorkerFeature @@ -292,7 +293,7 @@ private: void SendInternal(const nsAString& aStringBody, JSAutoStructuredCloneBuffer&& aBody, - nsTArray >& aClonedObjects, + WorkerStructuredCloneClosure& aClosure, ErrorResult& aRv); }; diff --git a/dom/workers/test/sharedWorker_sharedWorker.js b/dom/workers/test/sharedWorker_sharedWorker.js index 4df59861658d..6c917036b392 100644 --- a/dom/workers/test/sharedWorker_sharedWorker.js +++ b/dom/workers/test/sharedWorker_sharedWorker.js @@ -79,8 +79,8 @@ onconnect = function(event) { if (!("ports" in event)) { throw new Error("'message' event doesn't have a 'ports' property!"); } - if (!(event.ports === null)) { - throw new Error("'message' event has a non-null 'ports' property!"); + if (event.ports === null) { + throw new Error("'message' event has a null 'ports' property!"); } event.target.postMessage(event.data); throw new Error(event.data); diff --git a/ipc/glue/BackgroundChild.h b/ipc/glue/BackgroundChild.h index 5f3c3274b619..2b572266246d 100644 --- a/ipc/glue/BackgroundChild.h +++ b/ipc/glue/BackgroundChild.h @@ -15,6 +15,7 @@ class nsIIPCBackgroundChildCreateCallback; namespace mozilla { namespace dom { +class BlobImpl; class ContentChild; class ContentParent; class PBlobChild; @@ -67,6 +68,10 @@ public: GetOrCreateActorForBlob(PBackgroundChild* aBackgroundActor, nsIDOMBlob* aBlob); + static mozilla::dom::PBlobChild* + GetOrCreateActorForBlobImpl(PBackgroundChild* aBackgroundActor, + mozilla::dom::BlobImpl* aBlobImpl); + // See above. static void CloseForCurrentThread(); diff --git a/ipc/glue/BackgroundChildImpl.cpp b/ipc/glue/BackgroundChildImpl.cpp index 68411a91612b..a806692a27fe 100644 --- a/ipc/glue/BackgroundChildImpl.cpp +++ b/ipc/glue/BackgroundChildImpl.cpp @@ -14,6 +14,7 @@ #include "mozilla/dom/cache/ActorUtils.h" #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h" #include "mozilla/dom/ipc/BlobChild.h" +#include "mozilla/dom/MessagePortChild.h" #include "mozilla/ipc/PBackgroundTestChild.h" #include "mozilla/layout/VsyncChild.h" #include "mozilla/net/PUDPSocketChild.h" @@ -340,6 +341,28 @@ BackgroundChildImpl::DeallocPMediaChild(media::PMediaChild *aActor) return media::DeallocPMediaChild(aActor); } +// ----------------------------------------------------------------------------- +// MessageChannel/MessagePort API +// ----------------------------------------------------------------------------- + +dom::PMessagePortChild* +BackgroundChildImpl::AllocPMessagePortChild(const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) +{ + nsRefPtr agent = new dom::MessagePortChild(); + return agent.forget().take(); +} + +bool +BackgroundChildImpl::DeallocPMessagePortChild(PMessagePortChild* aActor) +{ + nsRefPtr child = + dont_AddRef(static_cast(aActor)); + MOZ_ASSERT(child); + return true; +} + } // namespace ipc } // namespace mozilla diff --git a/ipc/glue/BackgroundChildImpl.h b/ipc/glue/BackgroundChildImpl.h index 9c2e011ed55e..c40db905a7c5 100644 --- a/ipc/glue/BackgroundChildImpl.h +++ b/ipc/glue/BackgroundChildImpl.h @@ -121,6 +121,13 @@ protected: virtual bool DeallocPCacheStreamControlChild(dom::cache::PCacheStreamControlChild* aActor) override; + + virtual PMessagePortChild* + AllocPMessagePortChild(const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + virtual bool + DeallocPMessagePortChild(PMessagePortChild* aActor) override; }; class BackgroundChildImpl::ThreadLocal final diff --git a/ipc/glue/BackgroundImpl.cpp b/ipc/glue/BackgroundImpl.cpp index bbe6b8d3968e..10db43258268 100644 --- a/ipc/glue/BackgroundImpl.cpp +++ b/ipc/glue/BackgroundImpl.cpp @@ -914,17 +914,27 @@ PBlobChild* BackgroundChild::GetOrCreateActorForBlob(PBackgroundChild* aBackgroundActor, nsIDOMBlob* aBlob) { - MOZ_ASSERT(aBackgroundActor); MOZ_ASSERT(aBlob); + + nsRefPtr blobImpl = static_cast(aBlob)->Impl(); + MOZ_ASSERT(blobImpl); + + return GetOrCreateActorForBlobImpl(aBackgroundActor, blobImpl); +} + +// static +PBlobChild* +BackgroundChild::GetOrCreateActorForBlobImpl(PBackgroundChild* aBackgroundActor, + BlobImpl* aBlobImpl) +{ + MOZ_ASSERT(aBackgroundActor); + MOZ_ASSERT(aBlobImpl); MOZ_ASSERT(GetForCurrentThread(), "BackgroundChild not created on this thread yet!"); MOZ_ASSERT(aBackgroundActor == GetForCurrentThread(), "BackgroundChild is bound to a different thread!"); - nsRefPtr blobImpl = static_cast(aBlob)->Impl(); - MOZ_ASSERT(blobImpl); - - BlobChild* actor = BlobChild::GetOrCreate(aBackgroundActor, blobImpl); + BlobChild* actor = BlobChild::GetOrCreate(aBackgroundActor, aBlobImpl); if (NS_WARN_IF(!actor)) { return nullptr; } diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp index b5da95cc0931..e1b6849d9439 100644 --- a/ipc/glue/BackgroundParentImpl.cpp +++ b/ipc/glue/BackgroundParentImpl.cpp @@ -11,6 +11,7 @@ #include "mozilla/Assertions.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/PBlobParent.h" +#include "mozilla/dom/MessagePortParent.h" #include "mozilla/dom/ServiceWorkerRegistrar.h" #include "mozilla/dom/cache/ActorUtils.h" #include "mozilla/dom/indexedDB/ActorsParent.h" @@ -39,6 +40,8 @@ using mozilla::ipc::AssertIsOnBackgroundThread; using mozilla::dom::cache::PCacheParent; using mozilla::dom::cache::PCacheStorageParent; using mozilla::dom::cache::PCacheStreamControlParent; +using mozilla::dom::MessagePortParent; +using mozilla::dom::PMessagePortParent; using mozilla::dom::UDPSocketParent; namespace { @@ -563,6 +566,41 @@ BackgroundParentImpl::DeallocPCacheStreamControlParent(PCacheStreamControlParent return true; } +PMessagePortParent* +BackgroundParentImpl::AllocPMessagePortParent(const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return new MessagePortParent(aUUID); +} + +bool +BackgroundParentImpl::RecvPMessagePortConstructor(PMessagePortParent* aActor, + const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + MessagePortParent* mp = static_cast(aActor); + return mp->Entangle(aDestinationUUID, aSequenceID); +} + +bool +BackgroundParentImpl::DeallocPMessagePortParent(PMessagePortParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast(aActor); + return true; +} + } // namespace ipc } // namespace mozilla diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h index 5c27512f25d8..b2ad232076ae 100644 --- a/ipc/glue/BackgroundParentImpl.h +++ b/ipc/glue/BackgroundParentImpl.h @@ -129,6 +129,20 @@ protected: const nsCString& aFilter) override; virtual bool DeallocPUDPSocketParent(PUDPSocketParent*) override; + + virtual PMessagePortParent* + AllocPMessagePortParent(const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + virtual bool + RecvPMessagePortConstructor(PMessagePortParent* aActor, + const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + virtual bool + DeallocPMessagePortParent(PMessagePortParent* aActor) override; }; } // namespace ipc diff --git a/ipc/glue/PBackground.ipdl b/ipc/glue/PBackground.ipdl index 084fe99878ac..cd3e8f2b2de4 100644 --- a/ipc/glue/PBackground.ipdl +++ b/ipc/glue/PBackground.ipdl @@ -10,6 +10,7 @@ include protocol PCache; include protocol PCacheStorage; include protocol PCacheStreamControl; include protocol PFileDescriptorSet; +include protocol PMessagePort; include protocol PMedia; include protocol PServiceWorkerManager; include protocol PUDPSocket; @@ -35,6 +36,7 @@ sync protocol PBackground manages PCacheStorage; manages PCacheStreamControl; manages PFileDescriptorSet; + manages PMessagePort; manages PMedia; manages PServiceWorkerManager; manages PUDPSocket; @@ -59,6 +61,8 @@ parent: PCacheStorage(Namespace aNamespace, PrincipalInfo aPrincipalInfo); + PMessagePort(nsID uuid, nsID destinationUuid, uint32_t sequenceId); + child: PCache(); PCacheStreamControl(); diff --git a/js/public/StructuredClone.h b/js/public/StructuredClone.h index 50a7913d5ec4..967eefdaf5af 100644 --- a/js/public/StructuredClone.h +++ b/js/public/StructuredClone.h @@ -148,7 +148,7 @@ JS_WriteStructuredClone(JSContext* cx, JS::HandleValue v, uint64_t** datap, size JS_PUBLIC_API(bool) JS_ClearStructuredClone(uint64_t* data, size_t nbytes, const JSStructuredCloneCallbacks* optionalCallbacks, - void* closure); + void *closure, bool freeData = true); JS_PUBLIC_API(bool) JS_StructuredCloneHasTransferables(const uint64_t* data, size_t nbytes, bool* hasTransferable); diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index ab17cfe40309..fd12bf266106 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -1908,10 +1908,12 @@ JS_WriteStructuredClone(JSContext* cx, HandleValue value, uint64_t** bufp, size_ JS_PUBLIC_API(bool) JS_ClearStructuredClone(uint64_t* data, size_t nbytes, const JSStructuredCloneCallbacks* optionalCallbacks, - void* closure) + void* closure, bool freeData) { DiscardTransferables(data, nbytes, optionalCallbacks, closure); - js_free(data); + if (freeData) { + js_free(data); + } return true; } diff --git a/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html.ini b/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html.ini deleted file mode 100644 index 6dfbdc124e0e..000000000000 --- a/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[event-ports-dedicated.html] - type: testharness - [e.ports in dedicated worker] - expected: FAIL -