From 6f81d7d0dc56495048b9fc472fdfe5d28c81d1a1 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Sat, 3 Apr 2021 23:50:45 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - namespace comment (0548ea8a8) - Bug 1167411 - Add JSAutoStructuredCloneBuffer::abandon, r=jorendorff (6589a8900) - Bug 911972 - MessagePort and MessageChannel in workers, r=smaug, r=bent (4c533d3ca) - Bug 1172264 - Track the MDSM's duration as a TimeUnit and eliminate the separate concept of 'end time'. r=jww (49f8f2442) - Bug 1172264 - Require Manual disconnection for all mirrors. r=jww (845e57496) - Bug 1172264 - Switch MediaDecoder's mDuration represenation to a double. r=jww (dfde6482d) - Bug 1172264 - Mirror duration from the MDSM to the MediaDecoder. r=jww (a744fd08f) - No bug. Refactor GC type annotations, re=terrence (b6bc5723e) - Bug 1132744 - Update set of GC types, r=sfink (935175adb) - Bug 967031 - Rename DumpHeapComplete to DumpHeap; r=terrence (337391745) - Bug 1169097 - Remove CountHeap; r=sfink (074fdb34c) - Bug 1169086 - Use virtual dispatch to implement callback tracer; r=jonco, r=mccr8 (667218a33) --- dom/base/MessageChannel.cpp | 104 --- dom/base/MessagePort.cpp | 564 ------------ dom/base/MessagePort.h | 115 --- dom/base/NodeInfo.cpp | 6 +- dom/base/PostMessageEvent.cpp | 390 ++++++++ dom/base/PostMessageEvent.h | 108 +++ dom/base/ProcessGlobal.cpp | 1 + dom/base/WebSocket.cpp | 2 +- dom/base/moz.build | 8 +- dom/base/nsContentUtils.cpp | 22 + dom/base/nsContentUtils.h | 7 + dom/base/nsCopySupport.cpp | 1 + dom/base/nsFrameMessageManager.cpp | 15 +- dom/base/nsGlobalWindow.cpp | 382 +------- dom/base/nsGlobalWindow.h | 3 +- dom/base/test/chrome.ini | 1 - dom/base/test/mochitest.ini | 12 - dom/bindings/BindingUtils.cpp | 10 - dom/bindings/BindingUtils.h | 23 +- dom/bindings/Bindings.conf | 3 - dom/broadcastchannel/BroadcastChannel.cpp | 17 +- .../BroadcastChannelChild.cpp | 7 +- dom/ipc/StructuredCloneUtils.cpp | 12 +- dom/ipc/StructuredCloneUtils.h | 2 +- dom/media/MediaDecoder.cpp | 34 +- dom/media/MediaDecoder.h | 9 +- dom/media/MediaDecoderStateMachine.cpp | 64 +- dom/media/MediaDecoderStateMachine.h | 24 +- dom/media/StateMirroring.h | 13 +- dom/messagechannel/MessageChannel.cpp | 228 +++++ dom/{base => messagechannel}/MessageChannel.h | 3 +- dom/messagechannel/MessagePort.cpp | 867 ++++++++++++++++++ dom/messagechannel/MessagePort.h | 210 +++++ dom/messagechannel/MessagePortChild.cpp | 49 + dom/messagechannel/MessagePortChild.h | 52 ++ .../MessagePortList.cpp | 0 .../MessagePortList.h | 0 dom/messagechannel/MessagePortParent.cpp | 163 ++++ dom/messagechannel/MessagePortParent.h | 61 ++ dom/messagechannel/MessagePortService.cpp | 328 +++++++ dom/messagechannel/MessagePortService.h | 61 ++ dom/messagechannel/MessagePortUtils.cpp | 277 ++++++ dom/messagechannel/MessagePortUtils.h | 55 ++ dom/messagechannel/PMessagePort.ipdl | 62 ++ .../SharedMessagePortMessage.cpp | 180 ++++ dom/messagechannel/SharedMessagePortMessage.h | 58 ++ dom/messagechannel/moz.build | 41 + dom/messagechannel/tests/chrome.ini | 5 + .../tests}/iframe_messageChannel_chrome.html | 0 .../tests}/iframe_messageChannel_cloning.html | 0 .../iframe_messageChannel_pingpong.html | 0 .../tests}/iframe_messageChannel_post.html | 0 .../iframe_messageChannel_sharedWorker2.html | 14 + .../iframe_messageChannel_transferable.html | 26 + dom/messagechannel/tests/mochitest.ini | 25 + dom/messagechannel/tests/moz.build | 8 + .../tests/sharedWorker2_messageChannel.js | 7 + .../tests/sharedWorker_messageChannel.js | 8 + .../tests}/test_messageChannel.html | 0 .../tests}/test_messageChannel.xul | 2 +- .../tests/test_messageChannel_any.html | 115 +++ .../tests}/test_messageChannel_cloning.html | 0 .../tests}/test_messageChannel_pingpong.html | 0 .../tests}/test_messageChannel_post.html | 0 .../tests}/test_messageChannel_pref.html | 0 .../test_messageChannel_selfTransferring.html | 38 + .../test_messageChannel_sharedWorker.html | 39 + .../test_messageChannel_sharedWorker2.html | 37 + .../tests}/test_messageChannel_start.html | 0 .../test_messageChannel_transferable.html | 58 +- .../tests}/test_messageChannel_unshipped.html | 0 .../tests/test_messageChannel_worker.html | 60 ++ .../tests/worker_messageChannel.js | 119 +++ .../tests/worker_messageChannel_any.js | 7 + dom/moz.build | 1 + dom/webidl/MessageChannel.webidl | 3 +- dom/workers/MessagePort.cpp | 13 +- dom/workers/MessagePort.h | 10 +- dom/workers/ServiceWorkerClient.cpp | 22 +- dom/workers/WorkerPrivate.cpp | 226 +++-- dom/workers/WorkerPrivate.h | 3 +- dom/workers/WorkerStructuredClone.h | 53 ++ dom/workers/XMLHttpRequest.cpp | 62 +- dom/workers/XMLHttpRequest.h | 3 +- dom/workers/test/sharedWorker_sharedWorker.js | 4 +- ipc/glue/BackgroundChild.h | 5 + ipc/glue/BackgroundChildImpl.cpp | 23 + ipc/glue/BackgroundChildImpl.h | 7 + ipc/glue/BackgroundImpl.cpp | 20 +- ipc/glue/BackgroundParentImpl.cpp | 38 + ipc/glue/BackgroundParentImpl.h | 14 + ipc/glue/PBackground.ipdl | 4 + js/public/StructuredClone.h | 22 +- js/public/TracingAPI.h | 25 +- js/public/Value.h | 1 + js/src/builtin/TestingFunctions.cpp | 190 +--- js/src/devtools/rootAnalysis/annotations.js | 51 +- .../devtools/rootAnalysis/computeGCTypes.js | 32 +- js/src/gc/GCInternals.h | 11 +- js/src/gc/GCRuntime.h | 3 +- js/src/gc/Marking.cpp | 63 +- js/src/gc/Marking.h | 6 +- js/src/gc/RootMarking.cpp | 37 +- js/src/gc/Tracer.cpp | 22 +- js/src/gc/Verifier.cpp | 50 +- js/src/jit-test/tests/basic/bug734196.js | 4 - js/src/jit-test/tests/basic/bug747926.js | 12 - js/src/jit-test/tests/basic/bug821340.js | 10 - js/src/jit-test/tests/gc/bug-913224.js | 2 +- js/src/jsapi-tests/testGCMarking.cpp | 8 +- js/src/jsfriendapi.cpp | 19 +- js/src/jsfriendapi.h | 2 +- js/src/jsgc.cpp | 37 +- .../ecma_7/TypedObject/referencetypetrace.js | 31 +- .../tests/js1_8/extensions/regress-394709.js | 27 +- js/src/vm/StructuredClone.cpp | 76 +- js/src/vm/UbiNode.cpp | 8 +- .../event-ports-dedicated.html.ini | 5 - xpcom/base/CycleCollectedJSRuntime.cpp | 60 +- 119 files changed, 4621 insertions(+), 1926 deletions(-) delete mode 100644 dom/base/MessageChannel.cpp delete mode 100644 dom/base/MessagePort.cpp delete mode 100644 dom/base/MessagePort.h create mode 100644 dom/base/PostMessageEvent.cpp create mode 100644 dom/base/PostMessageEvent.h create mode 100644 dom/messagechannel/MessageChannel.cpp rename dom/{base => messagechannel}/MessageChannel.h (99%) create mode 100644 dom/messagechannel/MessagePort.cpp create mode 100644 dom/messagechannel/MessagePort.h create mode 100644 dom/messagechannel/MessagePortChild.cpp create mode 100644 dom/messagechannel/MessagePortChild.h rename dom/{base => messagechannel}/MessagePortList.cpp (100%) rename dom/{base => messagechannel}/MessagePortList.h (100%) create mode 100644 dom/messagechannel/MessagePortParent.cpp create mode 100644 dom/messagechannel/MessagePortParent.h create mode 100644 dom/messagechannel/MessagePortService.cpp create mode 100644 dom/messagechannel/MessagePortService.h create mode 100644 dom/messagechannel/MessagePortUtils.cpp create mode 100644 dom/messagechannel/MessagePortUtils.h create mode 100644 dom/messagechannel/PMessagePort.ipdl create mode 100644 dom/messagechannel/SharedMessagePortMessage.cpp create mode 100644 dom/messagechannel/SharedMessagePortMessage.h create mode 100644 dom/messagechannel/moz.build create mode 100644 dom/messagechannel/tests/chrome.ini rename dom/{base/test => messagechannel/tests}/iframe_messageChannel_chrome.html (100%) rename dom/{base/test => messagechannel/tests}/iframe_messageChannel_cloning.html (100%) rename dom/{base/test => messagechannel/tests}/iframe_messageChannel_pingpong.html (100%) rename dom/{base/test => messagechannel/tests}/iframe_messageChannel_post.html (100%) create mode 100644 dom/messagechannel/tests/iframe_messageChannel_sharedWorker2.html create mode 100644 dom/messagechannel/tests/iframe_messageChannel_transferable.html create mode 100644 dom/messagechannel/tests/mochitest.ini create mode 100644 dom/messagechannel/tests/moz.build create mode 100644 dom/messagechannel/tests/sharedWorker2_messageChannel.js create mode 100644 dom/messagechannel/tests/sharedWorker_messageChannel.js rename dom/{base/test => messagechannel/tests}/test_messageChannel.html (100%) rename dom/{base/test => messagechannel/tests}/test_messageChannel.xul (91%) create mode 100644 dom/messagechannel/tests/test_messageChannel_any.html rename dom/{base/test => messagechannel/tests}/test_messageChannel_cloning.html (100%) rename dom/{base/test => messagechannel/tests}/test_messageChannel_pingpong.html (100%) rename dom/{base/test => messagechannel/tests}/test_messageChannel_post.html (100%) rename dom/{base/test => messagechannel/tests}/test_messageChannel_pref.html (100%) create mode 100644 dom/messagechannel/tests/test_messageChannel_selfTransferring.html create mode 100644 dom/messagechannel/tests/test_messageChannel_sharedWorker.html create mode 100644 dom/messagechannel/tests/test_messageChannel_sharedWorker2.html rename dom/{base/test => messagechannel/tests}/test_messageChannel_start.html (100%) rename dom/{base/test => messagechannel/tests}/test_messageChannel_transferable.html (57%) rename dom/{base/test => messagechannel/tests}/test_messageChannel_unshipped.html (100%) create mode 100644 dom/messagechannel/tests/test_messageChannel_worker.html create mode 100644 dom/messagechannel/tests/worker_messageChannel.js create mode 100644 dom/messagechannel/tests/worker_messageChannel_any.js create mode 100644 dom/workers/WorkerStructuredClone.h delete mode 100644 js/src/jit-test/tests/basic/bug734196.js delete mode 100644 js/src/jit-test/tests/basic/bug747926.js delete mode 100644 js/src/jit-test/tests/basic/bug821340.js delete mode 100644 testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html.ini diff --git a/dom/base/MessageChannel.cpp b/dom/base/MessageChannel.cpp deleted file mode 100644 index e04ca2fc72..0000000000 --- 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 fbbafbdbf4..0000000000 --- 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 fe51bc295f..0000000000 --- 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 50ba1e1b4e..0f414c4080 100644 --- a/dom/base/NodeInfo.cpp +++ b/dom/base/NodeInfo.cpp @@ -115,7 +115,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)", @@ -133,8 +133,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 0000000000..534d389cda --- /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 0000000000..1600719d74 --- /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 "mozilla/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 21d89565de..dbd2e6700e 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/WebSocket.cpp b/dom/base/WebSocket.cpp index 7d13d466eb..2958befa32 100644 --- a/dom/base/WebSocket.cpp +++ b/dom/base/WebSocket.cpp @@ -2725,7 +2725,7 @@ private: nsCOMPtr mEvent; }; -} // namespace +} // anonymous namespace NS_IMETHODIMP WebSocketImpl::Dispatch(nsIRunnable* aEvent, uint32_t aFlags) diff --git a/dom/base/moz.build b/dom/base/moz.build index 48a5af62d9..b9b91fb933 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -178,9 +178,6 @@ EXPORTS.mozilla.dom += [ 'ImageEncoder.h', 'ImportManager.h', 'Link.h', - 'MessageChannel.h', - 'MessagePort.h', - 'MessagePortList.h', 'NameSpaceConstants.h', 'Navigator.h', 'NodeInfo.h', @@ -236,8 +233,6 @@ UNIFIED_SOURCES += [ 'ImageEncoder.cpp', 'ImportManager.cpp', 'Link.cpp', - 'MessageChannel.cpp', - 'MessagePortList.cpp', 'MultipartBlobImpl.cpp', 'Navigator.cpp', 'NodeInfo.cpp', @@ -328,6 +323,7 @@ UNIFIED_SOURCES += [ 'PerformanceMark.cpp', 'PerformanceMeasure.cpp', 'PerformanceResourceTiming.cpp', + 'PostMessageEvent.cpp', 'ProcessGlobal.cpp', 'ResponsiveImageSelector.cpp', 'SameProcessMessageQueue.cpp', @@ -352,8 +348,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 620cb278dc..50600c8f67 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -219,6 +219,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; @@ -563,6 +564,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; @@ -1824,6 +1832,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); @@ -7218,6 +7227,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 6eb4d7702d..819f338f65 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 @@ -2534,6 +2540,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 2b66e5efb9..14fe55a5ce 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 a62fc87d6c..9a8b1e368f 100644 --- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -276,15 +276,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; } @@ -322,7 +322,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]); @@ -331,10 +331,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 ccb490488b..4c647110c7 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -61,7 +61,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" @@ -71,9 +70,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" @@ -181,13 +180,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" @@ -209,7 +204,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" @@ -8018,367 +8012,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, @@ -8502,18 +8135,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 e0322cfb16..a4481d5cb6 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; @@ -1743,7 +1744,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 34f81ead9d..9684ee8dba 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 aff737988c..20c1d13318 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 @@ -284,15 +280,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/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 178b0a8fb2..4452b7fbea 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -2363,16 +2363,6 @@ IsInCertifiedApp(JSContext* aCx, JSObject* aObj) Preferences::GetBool("dom.ignore_webidl_scope_checks", false); } -#ifdef DEBUG -void -VerifyTraceProtoAndIfaceCacheCalled(JS::CallbackTracer *trc, void **thingp, - JS::TraceKind kind) -{ - // We don't do anything here, we only want to verify that - // TraceProtoAndIfaceCache was called. -} -#endif - void FinalizeGlobal(JSFreeOp* aFreeOp, JSObject* aObj) { diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 63f2bd5b5a..b5b1a642ab 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -520,17 +520,20 @@ AllocateProtoAndIfaceCache(JSObject* obj, ProtoAndIfaceCache::Kind aKind) } #ifdef DEBUG -void -VerifyTraceProtoAndIfaceCacheCalled(JS::CallbackTracer *trc, void **thingp, - JS::TraceKind kind); - struct VerifyTraceProtoAndIfaceCacheCalledTracer : public JS::CallbackTracer { - bool ok; + bool ok; - explicit VerifyTraceProtoAndIfaceCacheCalledTracer(JSRuntime *rt) - : JS::CallbackTracer(rt, VerifyTraceProtoAndIfaceCacheCalled), ok(false) - {} + explicit VerifyTraceProtoAndIfaceCacheCalledTracer(JSRuntime *rt) + : JS::CallbackTracer(rt), ok(false) + {} + + void trace(void** thingp, JS::TraceKind kind) { + // We don't do anything here, we only want to verify that + // TraceProtoAndIfaceCache was called. + } + + TracerKind getTracerKind() const override { return TracerKind::VerifyTraceProtoAndIface; } }; #endif @@ -541,8 +544,8 @@ TraceProtoAndIfaceCache(JSTracer* trc, JSObject* obj) #ifdef DEBUG if (trc->isCallbackTracer() && - trc->asCallbackTracer()->hasCallback( - VerifyTraceProtoAndIfaceCacheCalled)) { + (trc->asCallbackTracer()->getTracerKind() == + JS::CallbackTracer::TracerKind::VerifyTraceProtoAndIface)) { // We don't do anything here, we only want to verify that // TraceProtoAndIfaceCache was called. static_cast(trc)->ok = true; diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index f0affd086a..ac27b57e9f 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -733,9 +733,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 ec22c17a99..13a124f9e2 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 3532f0e5d4..bda72b77a3 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 5f96d22bc2..dfcb140cb8 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 b103d54a41..f8d1c37d7d 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/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 962fd4e2d9..a05c50c069 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -311,10 +311,8 @@ double MediaDecoder::GetDuration() if (mInfiniteStream) { return std::numeric_limits::infinity(); } - if (mDuration >= 0) { - return static_cast(mDuration) / static_cast(USECS_PER_S); - } - return std::numeric_limits::quiet_NaN(); + + return mDuration; } int64_t MediaDecoder::GetMediaDuration() @@ -347,7 +345,8 @@ MediaDecoder::MediaDecoder() : mVolume(AbstractThread::MainThread(), 0.0, "MediaDecoder::mVolume (Canonical)"), mPlaybackRate(AbstractThread::MainThread(), 1.0, "MediaDecoder::mPlaybackRate (Canonical)"), mPreservesPitch(AbstractThread::MainThread(), true, "MediaDecoder::mPreservesPitch (Canonical)"), - mDuration(-1), + mDuration(std::numeric_limits::quiet_NaN()), + mStateMachineDuration(AbstractThread::MainThread(), NullableTimeUnit(), "MediaDecoder::mStateMachineDuration (Mirror)"), mMediaSeekable(true), mSameOriginMedia(false), mReentrantMonitor("media.decoder"), @@ -649,15 +648,16 @@ void MediaDecoder::MetadataLoaded(nsAutoPtr aInfo, { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1; + if (mStateMachineDuration.Ref().isNothing() && mStateMachineDuration.Ref().ref().IsInfinite()) { + SetInfinite(true); + } else { + mDuration = mStateMachineDuration.Ref().ref().ToSeconds(); + } + // Duration has changed so we should recompute playback rate UpdatePlaybackRate(); } - if (mDuration == -1) { - SetInfinite(true); - } - mInfo = aInfo.forget(); ConstructMediaTracks(); @@ -866,9 +866,9 @@ double MediaDecoder::ComputePlaybackRate(bool* aReliable) MOZ_ASSERT(NS_IsMainThread() || OnStateMachineTaskQueue() || OnDecodeTaskQueue()); int64_t length = mResource ? mResource->GetLength() : -1; - if (mDuration >= 0 && length >= 0) { + if (!IsNaN(mDuration) && !mozilla::IsInfinite(mDuration) && length >= 0) { *aReliable = true; - return length * static_cast(USECS_PER_S) / mDuration; + return length * mDuration; } return mPlaybackStatistics->GetRateAtLastStop(aReliable); } @@ -1080,15 +1080,13 @@ void MediaDecoder::DurationChanged(TimeUnit aNewDuration) { MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - int64_t oldDuration = mDuration; - mDuration = aNewDuration.ToMicroseconds(); + double oldDuration = mDuration; + mDuration = aNewDuration.ToSeconds(); // Duration has changed so we should recompute playback rate UpdatePlaybackRate(); - SetInfinite(mDuration == -1); - if (mOwner && oldDuration != mDuration && !IsInfinite()) { - DECODER_LOG("Duration changed to %lld", mDuration); + DECODER_LOG("Duration changed to %f", mDuration); mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("durationchange")); } @@ -1255,9 +1253,11 @@ MediaDecoder::SetStateMachine(MediaDecoderStateMachine* aStateMachine) mDecoderStateMachine = aStateMachine; if (mDecoderStateMachine) { + mStateMachineDuration.Connect(mDecoderStateMachine->CanonicalDuration()); mNextFrameStatus.Connect(mDecoderStateMachine->CanonicalNextFrameStatus()); mCurrentPosition.Connect(mDecoderStateMachine->CanonicalCurrentPosition()); } else { + mStateMachineDuration.DisconnectIfConnected(); mNextFrameStatus.DisconnectIfConnected(); mCurrentPosition.DisconnectIfConnected(); } diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index 4f08aaf4d9..1b0fe0b5eb 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -929,10 +929,11 @@ public: AbstractCanonical* CanonicalPreservesPitch() { return &mPreservesPitch; } protected: - // Duration of the media resource. Set to -1 if unknown. - // Set when the metadata is loaded. Accessed on the main thread - // only. - int64_t mDuration; + // Official duration of the media resource as observed by script. + double mDuration; + + // Duration of the media resource according to the state machine. + Mirror mStateMachineDuration; // True if the media is seekable (i.e. supports random access). bool mMediaSeekable; diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index 1e249f14c4..28913c98a7 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -187,10 +187,9 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mDelayedScheduler(this), mState(DECODER_STATE_DECODING_NONE, "MediaDecoderStateMachine::mState"), mPlayDuration(0), - mEndTime(-1), - mDurationSet(false), + mDuration(mTaskQueue, NullableTimeUnit(), "MediaDecoderStateMachine::mDuration (Canonical"), mEstimatedDuration(mTaskQueue, NullableTimeUnit(), - "MediaDecoderStateMachine::EstimatedDuration (Mirror)"), + "MediaDecoderStateMachine::mEstimatedDuration (Mirror)"), mExplicitDuration(mTaskQueue, Maybe(), "MediaDecoderStateMachine::mExplicitDuration (Mirror)"), mObservedDuration(TimeUnit(), "MediaDecoderStateMachine::mObservedDuration"), @@ -1383,18 +1382,11 @@ bool MediaDecoderStateMachine::IsRealTime() const int64_t MediaDecoderStateMachine::GetDuration() { AssertCurrentThreadInMonitor(); - - if (mEndTime == -1) + if (mDuration.Ref().isNothing() || Duration().IsInfinite()) { return -1; - return mEndTime; -} - -int64_t MediaDecoderStateMachine::GetEndTime() -{ - if (mEndTime == -1 && mDurationSet) { - return INT64_MAX; } - return mEndTime; + + return Duration().ToMicroseconds(); } void MediaDecoderStateMachine::RecomputeDuration() @@ -1431,11 +1423,10 @@ void MediaDecoderStateMachine::RecomputeDuration() duration = mObservedDuration; fireDurationChanged = true; } - fireDurationChanged = fireDurationChanged && duration.ToMicroseconds() != GetDuration(); + fireDurationChanged = fireDurationChanged && duration != Duration(); MOZ_ASSERT(duration.ToMicroseconds() >= 0); - mEndTime = duration.IsInfinite() ? -1 : duration.ToMicroseconds(); - mDurationSet = true; + mDuration = Some(duration); if (fireDurationChanged) { nsCOMPtr event = @@ -1661,11 +1652,11 @@ void MediaDecoderStateMachine::NotifyDataArrived(const char* aBuffer, NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); mReader->NotifyDataArrived(aBuffer, aLength, aOffset); - // While playing an unseekable stream of unknown duration, mEndTime is + // While playing an unseekable stream of unknown duration, mDuration is // updated (in AdvanceFrame()) as we play. But if data is being downloaded - // faster than played, mEndTime won't reflect the end of playable data + // faster than played, mDuration won't reflect the end of playable data // since we haven't played the frame at the end of buffered data. So update - // mEndTime here as new data is downloaded to prevent such a lag. + // mDuration here as new data is downloaded to prevent such a lag. // // Make sure to only do this if we have a start time, otherwise the reader // doesn't know how to compute GetBuffered. @@ -1680,7 +1671,7 @@ void MediaDecoderStateMachine::NotifyDataArrived(const char* aBuffer, media::TimeUnit end{buffered.GetEnd(&exists)}; if (exists) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - mEndTime = std::max(mEndTime, end.ToMicroseconds()); + mDuration = Some(std::max(Duration(), end)); } } } @@ -1828,7 +1819,7 @@ MediaDecoderStateMachine::InitiateSeek() mCurrentSeek.Steal(mPendingSeek); // Bound the seek time to be inside the media range. - int64_t end = GetEndTime(); + int64_t end = Duration().ToMicroseconds(); NS_ASSERTION(end != -1, "Should know end time by now"); int64_t seekTime = mCurrentSeek.mTarget.mTime; seekTime = std::min(seekTime, end); @@ -1869,7 +1860,7 @@ MediaDecoderStateMachine::InitiateSeek() nsRefPtr self = this; mSeekRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__, &MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime, - GetEndTime()) + Duration().ToMicroseconds()) ->Then(TaskQueue(), __func__, [self] (int64_t) -> void { ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor()); @@ -2080,7 +2071,7 @@ bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs) // If we don't have a duration, GetBuffered is probably not going to produce // a useful buffered range. Return false here so that we don't get stuck in // buffering mode for live streams. - if (GetDuration() < 0) { + if (Duration().IsInfinite()) { return false; } @@ -2101,13 +2092,12 @@ bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs) endOfDecodedAudioData = mDecodedAudioEndTime; } int64_t endOfDecodedData = std::min(endOfDecodedVideoData, endOfDecodedAudioData); - - if (GetDuration() < endOfDecodedData) { + if (Duration().ToMicroseconds() < endOfDecodedData) { // Our duration is not up to date. No point buffering. return false; } media::TimeInterval interval(media::TimeUnit::FromMicroseconds(endOfDecodedData), - media::TimeUnit::FromMicroseconds(std::min(endOfDecodedData + aUsecs, GetDuration()))); + media::TimeUnit::FromMicroseconds(std::min(endOfDecodedData + aUsecs, Duration().ToMicroseconds()))); return endOfDecodedData != INT64_MAX && !buffered.Contains(interval); } @@ -2196,7 +2186,7 @@ MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata) // feeding in the CDM, which we need to decode the first frame (and // thus get the metadata). We could fix this if we could compute the start // time by demuxing without necessaring decoding. - mNotifyMetadataBeforeFirstFrame = mDurationSet || mReader->IsWaitingOnCDMResource(); + mNotifyMetadataBeforeFirstFrame = mDuration.Ref().isSome() || mReader->IsWaitingOnCDMResource(); if (mNotifyMetadataBeforeFirstFrame) { EnqueueLoadedMetadataEvent(); } @@ -2341,13 +2331,14 @@ MediaDecoderStateMachine::FinishDecodeFirstFrame() } } - MOZ_ASSERT(!(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) || - (GetDuration() != -1) || mDurationSet, - "Seekable media should have duration"); - DECODER_LOG("Media goes from %lld to %lld (duration %lld) " + // If we don't know the duration by this point, we assume infinity, per spec. + if (mDuration.Ref().isNothing()) { + mDuration = Some(TimeUnit::FromInfinity()); + } + + DECODER_LOG("Media duration %lld, " "transportSeekable=%d, mediaSeekable=%d", - 0, mEndTime, GetDuration(), - mDecoder->IsTransportSeekable(), mDecoder->IsMediaSeekable()); + Duration().ToMicroseconds(), mDecoder->IsTransportSeekable(), mDecoder->IsMediaSeekable()); if (HasAudio() && !HasVideo()) { // We're playing audio only. We don't need to worry about slow video @@ -2403,7 +2394,7 @@ MediaDecoderStateMachine::SeekCompleted() // Setup timestamp state. nsRefPtr video = VideoQueue().PeekFront(); - if (seekTime == mEndTime) { + if (seekTime == Duration().ToMicroseconds()) { newCurrentTime = mAudioStartTime = seekTime; } else if (HasAudio()) { AudioData* audio = AudioQueue().PeekFront(); @@ -2426,7 +2417,7 @@ MediaDecoderStateMachine::SeekCompleted() // for the seeking. DECODER_LOG("A new seek came along while we were finishing the old one - staying in SEEKING"); SetState(DECODER_STATE_SEEKING); - } else if (GetMediaTime() == mEndTime && !isLiveStream) { + } else if (GetMediaTime() == Duration().ToMicroseconds() && !isLiveStream) { // Seeked to end of media, move to COMPLETED state. Note we don't do // this if we're playing a live stream, since the end of media will advance // once we download more data! @@ -2508,6 +2499,7 @@ MediaDecoderStateMachine::FinishShutdown() mVolume.DisconnectIfConnected(); mLogicalPlaybackRate.DisconnectIfConnected(); mPreservesPitch.DisconnectIfConnected(); + mDuration.DisconnectAll(); mNextFrameStatus.DisconnectAll(); mCurrentPosition.DisconnectAll(); @@ -2689,7 +2681,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine() !mSentPlaybackEndedEvent) { int64_t clockTime = std::max(mAudioEndTime, mVideoFrameEndTime); - clockTime = std::max(int64_t(0), std::max(clockTime, mEndTime)); + clockTime = std::max(int64_t(0), std::max(clockTime, Duration().ToMicroseconds())); UpdatePlaybackPosition(clockTime); nsCOMPtr event = diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h index 96ba8771b4..29363ad718 100644 --- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -197,12 +197,6 @@ public: // must be obtained before calling this. It is in units of microseconds. int64_t GetDuration(); - // Time of the last frame in the media, in microseconds or INT64_MAX if - // media has an infinite duration. - // Accessed on state machine, decode, and main threads. - // Access controlled by decoder monitor. - int64_t GetEndTime(); - // Functions used by assertions to ensure we're calling things // on the appropriate threads. bool OnDecodeTaskQueue() const; @@ -691,6 +685,7 @@ public: NS_NewRunnableMethod(this, &MediaDecoderStateMachine::OnAudioSinkComplete); TaskQueue()->Dispatch(runnable.forget()); } +private: // Called by the AudioSink to signal errors. void OnAudioSinkError(); @@ -944,20 +939,17 @@ public: // buffering. TimeStamp mBufferingStart; - // Time of the last frame in the media, in microseconds. This is the - // end time of the last frame in the media. Accessed on state - // machine, decode, and main threads. Access controlled by decoder monitor. - // It will be set to -1 if the duration is infinite - int64_t mEndTime; + // Duration of the media. This is guaranteed to be non-null after we finish + // decoding the first frame. + Canonical mDuration; + media::TimeUnit Duration() const { MOZ_ASSERT(OnTaskQueue()); return mDuration.Ref().ref(); } +public: + AbstractCanonical* CanonicalDuration() { return &mDuration; } +protected: // Recomputes the canonical duration from various sources. void RecomputeDuration(); - // Will be set when SetDuration has been called with a value != -1 - // mDurationSet false doesn't indicate that we do not have a valid duration - // as mStartTime and mEndTime could have been set separately. - bool mDurationSet; - // The duration according to the demuxer's current estimate, mirrored from the main thread. Mirror mEstimatedDuration; diff --git a/dom/media/StateMirroring.h b/dom/media/StateMirroring.h index a2f44d47e5..6fcddc362a 100644 --- a/dom/media/StateMirroring.h +++ b/dom/media/StateMirroring.h @@ -280,14 +280,11 @@ public: ~Mirror() { - if (mImpl->OwnerThread()->IsCurrentThreadIn()) { - mImpl->DisconnectIfConnected(); - } else { - // If holder destruction happens on a thread other than the mirror's - // owner thread, manual disconnection is mandatory. We should make this - // more automatic by hooking it up to task queue shutdown. - MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected()); - } + // As a member of complex objects, a Mirror may be destroyed on a + // different thread than its owner, or late in shutdown during CC. Given + // that, we require manual disconnection so that callers can put things in + // the right place. + MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected()); } private: diff --git a/dom/messagechannel/MessageChannel.cpp b/dom/messagechannel/MessageChannel.cpp new file mode 100644 index 0000000000..a2635d10c2 --- /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 14a14b5c5d..a20921dfa8 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 0000000000..6a328bb969 --- /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 0000000000..63081a0833 --- /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 0000000000..baa1b37713 --- /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 0000000000..7ab954ad06 --- /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 0000000000..25dadafdab --- /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 0000000000..46dbb19ff0 --- /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 0000000000..0d75e6ba19 --- /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 0000000000..437cf2393c --- /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 0000000000..06b330b7f4 --- /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 0000000000..9c9442700e --- /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 0000000000..299b00fedf --- /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 0000000000..bbd8fbdbdd --- /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 0000000000..c2516874ed --- /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 0000000000..c5850dbe0b --- /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 0000000000..8d7140d76f --- /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 0000000000..a693cba22c --- /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 0000000000..108edeb7e6 --- /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 0000000000..fc5cecd130 --- /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 0000000000..846268289f --- /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 0000000000..8cc98aa209 --- /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 0000000000..4b24642f9d --- /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 ab2fae4d93..3d8e3485c6 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 0000000000..2cc34443dd --- /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 0000000000..d84a616e42 --- /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 0000000000..9bb330a851 --- /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 0000000000..d8a4c624b1 --- /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 c1e661ead8..82b575bac6 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 0000000000..0eb8489f51 --- /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 0000000000..87b0b8eb0c --- /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 0000000000..bbb1d50f97 --- /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 0cea45b05d..af26ccc11b 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 34de4b46e2..64b1262e36 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 c125351f22..e37e9dd9bb 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 51b1d6c9c0..30dcc3a2e1 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 01139ed59e..fd7d172071 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 28711fde64..0ae77ab013 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 @@ -1162,7 +1242,7 @@ private: class MessageEventRunnable final : public WorkerRunnable { JSAutoStructuredCloneBuffer mBuffer; - nsTArray > mClonedObjects; + WorkerStructuredCloneClosure mClosure; uint64_t mMessagePortSerial; bool mToMessagePort; @@ -1172,15 +1252,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 @@ -1195,12 +1284,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; } @@ -1226,7 +1322,8 @@ public: } event->SetTrusted(true); - + event->SetPorts(new MessagePortList(static_cast(event.get()), + closure.mMessagePorts)); nsCOMPtr domEvent = do_QueryObject(event); nsEventStatus dummy = nsEventStatus_eIgnore; @@ -1252,7 +1349,7 @@ private: aWorkerPrivate->DispatchMessageEventToMessagePort(aCx, mMessagePortSerial, Move(mBuffer), - mClonedObjects); + mClosure); } if (aWorkerPrivate->IsFrozen()) { @@ -3353,19 +3450,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)) { @@ -3406,14 +3500,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)) { @@ -3428,6 +3524,8 @@ WorkerPrivateParent::DispatchMessageEventToMessagePort( return true; } + closure.mParentWindow = do_QueryInterface(port->GetParentObject()); + AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(port->GetParentObject()))) { return false; @@ -3435,7 +3533,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; } @@ -3452,11 +3551,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)); @@ -6148,19 +6243,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; } @@ -7304,4 +7396,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 6f5365557d..23410989a4 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -72,6 +72,7 @@ class WorkerDebuggerGlobalScope; class WorkerGlobalScope; class WorkerPrivate; class WorkerRunnable; +class WorkerStructuredCloneClosure; class WorkerThread; // SharedMutex is a small wrapper around an (internal) reference-counted Mutex @@ -346,7 +347,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 0000000000..85b7ab3f7f --- /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 8f9f42eadf..cd694b72bc 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. @@ -2110,9 +2117,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 @@ -2132,9 +2139,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 @@ -2175,15 +2182,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 @@ -2221,15 +2228,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 @@ -2259,15 +2266,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 7f0d8b60b3..330cb5f89b 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 4df5986165..6c917036b3 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 5f3c3274b6..2b57226624 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 c83dc87c81..a1f53a4052 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 9c2e011ed5..c40db905a7 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 b783fc2866..1817aad0d2 100644 --- a/ipc/glue/BackgroundImpl.cpp +++ b/ipc/glue/BackgroundImpl.cpp @@ -899,17 +899,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 f4f94e4604..b698d7f2ff 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 6a109fd01a..6ccafe9f7e 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 084fe99878..cd3e8f2b2d 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 9357d3be4a..967eefdaf5 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); @@ -162,17 +162,25 @@ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) { uint64_t* data_; size_t nbytes_; uint32_t version_; + enum { + OwnsTransferablesIfAny, + IgnoreTransferablesIfAny, + NoTransferables + } ownTransferables_; + const JSStructuredCloneCallbacks* callbacks_; void* closure_; public: JSAutoStructuredCloneBuffer() : data_(nullptr), nbytes_(0), version_(JS_STRUCTURED_CLONE_VERSION), + ownTransferables_(NoTransferables), callbacks_(nullptr), closure_(nullptr) {} JSAutoStructuredCloneBuffer(const JSStructuredCloneCallbacks* callbacks, void* closure) : data_(nullptr), nbytes_(0), version_(JS_STRUCTURED_CLONE_VERSION), + ownTransferables_(NoTransferables), callbacks_(callbacks), closure_(closure) {} @@ -194,11 +202,17 @@ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) { // JSAutoStructuredCloneBuffer::steal). void adopt(uint64_t* data, size_t nbytes, uint32_t version=JS_STRUCTURED_CLONE_VERSION); - // Remove the buffer so that it will not be automatically freed. - // After this, the caller is responsible for feeding the memory back to - // JSAutoStructuredCloneBuffer::adopt. + // Release the buffer and transfer ownership to the caller. The caller is + // responsible for calling JS_ClearStructuredClone or feeding the memory + // back to JSAutoStructuredCloneBuffer::adopt. void steal(uint64_t** datap, size_t* nbytesp, uint32_t* versionp=nullptr); + // Abandon ownership of any transferable objects stored in the buffer, + // without freeing the buffer itself. Useful when copying the data out into + // an external container, though note that you will need to use adopt() or + // JS_ClearStructuredClone to properly release that data eventually. + void abandon() { ownTransferables_ = IgnoreTransferablesIfAny; } + bool read(JSContext* cx, JS::MutableHandleValue vp, const JSStructuredCloneCallbacks* optionalCallbacks=nullptr, void* closure=nullptr); diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index 97090a311f..a88c41ca38 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -127,21 +127,13 @@ class AutoTracingCallback; class JS_PUBLIC_API(CallbackTracer) : public JSTracer { public: - CallbackTracer(JSRuntime* rt, JSTraceCallback traceCallback, - WeakMapTraceKind weakTraceKind = TraceWeakMapValues) - : JSTracer(rt, JSTracer::TracerKindTag::Callback, weakTraceKind), callback(traceCallback), + CallbackTracer(JSRuntime* rt, WeakMapTraceKind weakTraceKind = TraceWeakMapValues) + : JSTracer(rt, JSTracer::TracerKindTag::Callback, weakTraceKind), contextName_(nullptr), contextIndex_(InvalidIndex), contextFunctor_(nullptr) {} - // Test if the given callback is the same as our callback. - bool hasCallback(JSTraceCallback maybeCallback) const { - return maybeCallback == callback; - } - - // Call the callback. - void invoke(void** thing, JS::TraceKind kind) { - callback(this, thing, kind); - } + // Override this method to receive notification when an edge is visited. + virtual void trace(void** thing, JS::TraceKind kind) = 0; // Access to the tracing context: // When tracing with a JS::CallbackTracer, we invoke the callback with the @@ -188,11 +180,12 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer virtual void operator()(CallbackTracer* trc, char* buf, size_t bufsize) = 0; }; - private: - // Exposed publicly for several callers that need to check if the tracer - // calling them is of the right type. - JSTraceCallback callback; +#ifdef DEBUG + enum class TracerKind { DoNotCare, Moving, GrayBuffering, VerifyTraceProtoAndIface }; + virtual TracerKind getTracerKind() const { return TracerKind::DoNotCare; } +#endif + private: friend class AutoTracingName; const char* contextName_; diff --git a/js/public/Value.h b/js/public/Value.h index 5ac7384a5a..d97180e09c 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -1696,6 +1696,7 @@ class ValueOperations JSObject& toObject() const { return value()->toObject(); } JSObject* toObjectOrNull() const { return value()->toObjectOrNull(); } gc::Cell* toGCThing() const { return value()->toGCThing(); } + JS::TraceKind traceKind() const { return value()->traceKind(); } uint64_t asRawBits() const { return value()->asRawBits(); } JSValueType extractNonDoubleType() const { return value()->extractNonDoubleType(); } diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 0361d648c0..4500eba478 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -795,167 +795,39 @@ NondeterministicGetWeakMapKeys(JSContext* cx, unsigned argc, jsval* vp) return true; } -struct JSCountHeapNode { - void* thing; - JS::TraceKind kind; - JSCountHeapNode* next; -}; - -typedef HashSet, SystemAllocPolicy> VisitedSet; - -class CountHeapTracer : public JS::CallbackTracer +class HasChildTracer : public JS::CallbackTracer { - public: - CountHeapTracer(JSRuntime* rt, JSTraceCallback callback) : CallbackTracer(rt, callback) {} + RootedValue child_; + bool found_; - VisitedSet visited; - JSCountHeapNode* traceList; - JSCountHeapNode* recycleList; - bool ok; -}; - -static void -CountHeapNotify(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) -{ - CountHeapTracer* countTracer = (CountHeapTracer*)trc; - void* thing = *thingp; - - if (!countTracer->ok) - return; - - VisitedSet::AddPtr p = countTracer->visited.lookupForAdd(thing); - if (p) - return; - - if (!countTracer->visited.add(p, thing)) { - countTracer->ok = false; - return; + void trace(void** thingp, JS::TraceKind kind) { + if (*thingp == child_.toGCThing()) + found_ = true; } - JSCountHeapNode* node = countTracer->recycleList; - if (node) { - countTracer->recycleList = node->next; - } else { - node = js_pod_malloc(); - if (!node) { - countTracer->ok = false; - return; - } - } - node->thing = thing; - node->kind = kind; - node->next = countTracer->traceList; - countTracer->traceList = node; -} + public: + HasChildTracer(JSRuntime* rt, HandleValue child) + : JS::CallbackTracer(rt, TraceWeakMapKeysValues), child_(rt, child), found_(false) + {} -static const struct TraceKindPair { - const char* name; - int32_t kind; -} traceKindNames[] = { - { "all", -1 }, - { "object", int32_t(JS::TraceKind::Object) }, - { "string", int32_t(JS::TraceKind::String) }, - { "symbol", int32_t(JS::TraceKind::Symbol) }, + bool found() const { return found_; } }; static bool -CountHeap(JSContext* cx, unsigned argc, jsval* vp) +HasChild(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); + RootedValue parent(cx, args.get(0)); + RootedValue child(cx, args.get(1)); - RootedValue startValue(cx, UndefinedValue()); - if (args.length() > 0) { - jsval v = args[0]; - if (v.isMarkable()) { - startValue = v; - } else if (!v.isNull()) { - JS_ReportError(cx, - "the first argument is not null or a heap-allocated " - "thing"); - return false; - } + if (!parent.isMarkable() || !child.isMarkable()) { + args.rval().setBoolean(false); + return true; } - RootedValue traceValue(cx); - int32_t traceKind = -1; - void* traceThing = nullptr; - if (args.length() > 1) { - JSString* str = ToString(cx, args[1]); - if (!str) - return false; - JSFlatString* flatStr = JS_FlattenString(cx, str); - if (!flatStr) - return false; - if (JS_FlatStringEqualsAscii(flatStr, "specific")) { - if (args.length() < 3) { - JS_ReportError(cx, "tracing of specific value requested " - "but no value provided"); - return false; - } - traceValue = args[2]; - if (!traceValue.isMarkable()){ - JS_ReportError(cx, "cannot trace this kind of value"); - return false; - } - traceThing = traceValue.toGCThing(); - } else { - for (size_t i = 0; ;) { - if (JS_FlatStringEqualsAscii(flatStr, traceKindNames[i].name)) { - traceKind = traceKindNames[i].kind; - break; - } - if (++i == ArrayLength(traceKindNames)) { - JSAutoByteString bytes(cx, str); - if (!!bytes) - JS_ReportError(cx, "trace kind name '%s' is unknown", bytes.ptr()); - return false; - } - } - } - } - - CountHeapTracer countTracer(JS_GetRuntime(cx), CountHeapNotify); - if (!countTracer.visited.init()) { - JS_ReportOutOfMemory(cx); - return false; - } - countTracer.ok = true; - countTracer.traceList = nullptr; - countTracer.recycleList = nullptr; - - if (startValue.isUndefined()) { - js::TraceRuntime(&countTracer); - } else { - JS_CallUnbarrieredValueTracer(&countTracer, startValue.address(), "root"); - } - - JSCountHeapNode* node; - size_t counter = 0; - while ((node = countTracer.traceList) != nullptr) { - if (traceThing == nullptr) { - // We are looking for all nodes with a specific kind - if (traceKind == -1 || int32_t(node->kind) == traceKind) - counter++; - } else { - // We are looking for some specific thing - if (node->thing == traceThing) - counter++; - } - countTracer.traceList = node->next; - node->next = countTracer.recycleList; - countTracer.recycleList = node; - JS_TraceChildren(&countTracer, node->thing, node->kind); - } - while ((node = countTracer.recycleList) != nullptr) { - countTracer.recycleList = node->next; - js_free(node); - } - if (!countTracer.ok) { - JS_ReportOutOfMemory(cx); - return false; - } - - args.rval().setNumber(double(counter)); + HasChildTracer trc(cx->runtime(), child); + TraceChildren(&trc, parent.toGCThing(), parent.traceKind()); + args.rval().setBoolean(trc.found()); return true; } @@ -1170,7 +1042,7 @@ FinalizeCount(JSContext* cx, unsigned argc, jsval* vp) } static bool -DumpHeapComplete(JSContext* cx, unsigned argc, jsval* vp) +DumpHeap(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); @@ -1212,11 +1084,11 @@ DumpHeapComplete(JSContext* cx, unsigned argc, jsval* vp) } if (i != args.length()) { - JS_ReportError(cx, "bad arguments passed to dumpHeapComplete"); + JS_ReportError(cx, "bad arguments passed to dumpHeap"); return false; } - js::DumpHeapComplete(JS_GetRuntime(cx), dumpFile ? dumpFile : stdout, nurseryBehaviour); + js::DumpHeap(JS_GetRuntime(cx), dumpFile ? dumpFile : stdout, nurseryBehaviour); if (dumpFile) fclose(dumpFile); @@ -2785,14 +2657,10 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = { " Return an object describing some of the configuration options SpiderMonkey\n" " was built with."), - JS_FN_HELP("countHeap", CountHeap, 0, 0, -"countHeap([start[, kind[, thing]]])", -" Count the number of live GC things in the heap or things reachable from\n" -" start when it is given and is not null. kind is either 'all' (default) to\n" -" count all things or one of 'object', 'double', 'string', 'function'\n" -" to count only things of that kind. If kind is the string 'specific',\n" -" then you can provide an extra argument with some specific traceable\n" -" thing to count.\n"), + JS_FN_HELP("hasChild", HasChild, 0, 0, +"hasChild(parent, child)", +" Return true if |child| is a child of |parent|, as determined by a call to\n" +" TraceChildren"), JS_FN_HELP("setSavedStacksRNGState", SetSavedStacksRNGState, 1, 0, "setSavedStacksRNGState(seed)", @@ -2927,8 +2795,8 @@ gc::ZealModeHelpText), "isProxy(obj)", " If true, obj is a proxy of some sort"), - JS_FN_HELP("dumpHeapComplete", DumpHeapComplete, 1, 0, -"dumpHeapComplete(['collectNurseryBeforeDump'], [filename])", + JS_FN_HELP("dumpHeap", DumpHeap, 1, 0, +"dumpHeap(['collectNurseryBeforeDump'], [filename])", " Dump reachable and unreachable objects to the named file, or to stdout. If\n" " 'collectNurseryBeforeDump' is specified, a minor GC is performed first,\n" " otherwise objects in the nursery are ignored."), diff --git a/js/src/devtools/rootAnalysis/annotations.js b/js/src/devtools/rootAnalysis/annotations.js index 9b796c2183..1816ff7aa8 100644 --- a/js/src/devtools/rootAnalysis/annotations.js +++ b/js/src/devtools/rootAnalysis/annotations.js @@ -57,7 +57,6 @@ function indirectCallCannotGC(fullCaller, fullVariable) // Ignore calls through functions pointers with these types var ignoreClasses = { - "JS::CallbackTracer" : true, "JSStringFinalizer" : true, "SprintfState" : true, "SprintfStateStr" : true, @@ -330,6 +329,54 @@ function isOverridableField(initialCSU, csu, field) if (field == 'GetWindowProxy' || field == 'GetWindowProxyPreserveColor') return false; } - + if (initialCSU == 'nsICycleCollectorListener' && field == 'NoteWeakMapEntry') + return false; + if (initialCSU == 'nsICycleCollectorListener' && field == 'NoteEdge') + return false; return true; } + +function listGCTypes() { + return [ + 'JSObject', + 'JSString', + 'JSFatInlineString', + 'JSExternalString', + 'js::Shape', + 'js::AccessorShape', + 'js::BaseShape', + 'JSScript', + 'js::ObjectGroup', + 'js::LazyScript', + 'js::jit::JitCode', + 'JS::Symbol', + ]; +} + +function listGCPointers() { + return [ + 'JS::Value', + 'jsid', + + // AutoCheckCannotGC should also not be held live across a GC function. + 'JS::AutoCheckCannotGC', + ]; +} + +function listNonGCTypes() { + return [ + ]; +} + +function listNonGCPointers() { + return [ + ]; +} + +// Flexible mechanism for deciding an arbitrary type is a GCPointer. Its one +// use turned out to be unnecessary due to another change, but the mechanism +// seems useful for something like /Vector.*Something/. +function isGCPointer(typeName) +{ + return false; +} diff --git a/js/src/devtools/rootAnalysis/computeGCTypes.js b/js/src/devtools/rootAnalysis/computeGCTypes.js index 4f91ae6b4d..e7081bfc91 100644 --- a/js/src/devtools/rootAnalysis/computeGCTypes.js +++ b/js/src/devtools/rootAnalysis/computeGCTypes.js @@ -5,6 +5,8 @@ loadRelativeToScript('utility.js'); loadRelativeToScript('annotations.js'); +var annotatedGCPointers = []; + function processCSU(csu, body) { if (!("DataField" in body)) @@ -25,6 +27,8 @@ function processCSU(csu, body) addNestedStructure(csu, type.Name, fieldName); } } + if (isGCPointer(csu)) + annotatedGCPointers.push(csu); } var structureParents = {}; // Map from field => list of @@ -63,6 +67,8 @@ for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) { var gcTypes = {}; // map from parent struct => Set of GC typed children var gcPointers = {}; // map from parent struct => Set of GC typed children +var nonGCTypes = {}; // set of types that would ordinarily be GC types but we are suppressing +var nonGCPointers = {}; // set of types that would ordinarily be GC pointers but we are suppressing var gcFields = {}; // "typeName is a (pointer to a)*'depth' GC type because it contains a field @@ -77,7 +83,6 @@ function markGCType(typeName, child, why, depth, ptrdness) // UniquePtr goes out of scope. So we say that a UniquePtr's memory is just // as unsafe as the stack for storing GC pointers. if (!ptrdness && isUnsafeStorage(typeName)) { - printErr("Unsafe! " + typeName); // The UniquePtr itself is on the stack but when you dereference the // contained pointer, you get to the unsafe memory that we are treating // as if it were the stack (aka depth 0). Note that @@ -96,10 +101,14 @@ function markGCType(typeName, child, why, depth, ptrdness) return; if (depth == 0) { + if (typeName in nonGCTypes) + return; if (!(typeName in gcTypes)) gcTypes[typeName] = new Set(); gcTypes[typeName].add(why); } else if (depth == 1) { + if (typeName in nonGCPointers) + return; if (!(typeName in gcPointers)) gcPointers[typeName] = new Set(); gcPointers[typeName].add(why); @@ -133,18 +142,17 @@ function addGCPointer(typeName) markGCType(typeName, 'annotation', '', 1, 0); } -addGCType('JSObject'); -addGCType('JSString'); -addGCType('js::Shape'); -addGCType('js::BaseShape'); -addGCType('JSScript'); -addGCType('js::LazyScript'); -addGCType('js::ion::IonCode'); -addGCPointer('JS::Value'); -addGCPointer('jsid'); +for (var type of listNonGCTypes()) + nonGCTypes[type] = true; +for (var type of listNonGCPointers()) + nonGCPointers[type] = true; +for (var type of listGCTypes()) + addGCType(type); +for (var type of listGCPointers()) + addGCPointer(type); -// AutoCheckCannotGC should also not be held live across a GC function. -addGCPointer('JS::AutoCheckCannotGC'); +for (var typeName of annotatedGCPointers) + addGCPointer(typeName); function explain(csu, indent, seen) { if (!seen) diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 6187da8ae0..7487eb1c57 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -139,12 +139,11 @@ CheckHashTablesAfterMovingGC(JSRuntime* rt); #endif struct MovingTracer : JS::CallbackTracer { - explicit MovingTracer(JSRuntime* rt) : CallbackTracer(rt, Visit, TraceWeakMapKeysValues) {} - - static void Visit(JS::CallbackTracer* jstrc, void** thingp, JS::TraceKind kind); - static bool IsMovingTracer(JSTracer* trc) { - return trc->isCallbackTracer() && trc->asCallbackTracer()->hasCallback(Visit); - } + explicit MovingTracer(JSRuntime* rt) : CallbackTracer(rt, TraceWeakMapKeysValues) {} + void trace(void** thingp, JS::TraceKind kind) override; +#ifdef DEBUG + TracerKind getTracerKind() const override { return TracerKind::Moving; } +#endif }; class AutoMaybeStartBackgroundAllocation diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index 17ec432fdf..d8dae164e0 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -25,6 +25,7 @@ namespace js { class AutoLockGC; +class VerifyPreTracer; namespace gc { @@ -1008,7 +1009,7 @@ class GCRuntime * Number of the committed arenas in all GC chunks including empty chunks. */ mozilla::Atomic numArenasFreeCommitted; - void* verifyPreData; + VerifyPreTracer* verifyPreData; bool chunkAllocationSinceLastGC; int64_t nextFullGCTime; int64_t lastGCTime; diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 5f40a92a69..87ffae37c9 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -137,6 +137,13 @@ IsThingPoisoned(T* thing) } return false; } + +static bool +IsMovingTracer(JSTracer *trc) +{ + return trc->isCallbackTracer() && + trc->asCallbackTracer()->getTracerKind() == JS::CallbackTracer::TracerKind::Moving; +} #endif template bool ThingIsPermanentAtomOrWellKnownSymbol(T* thing) { return false; } @@ -184,8 +191,7 @@ js::CheckTracedThing(JSTracer* trc, T thing) if (IsInsideNursery(thing)) return; - MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc) && !trc->isTenuringTracer(), - !IsForwarded(thing)); + MOZ_ASSERT_IF(!IsMovingTracer(trc) && !trc->isTenuringTracer(), !IsForwarded(thing)); /* * Permanent atoms and things in the self-hosting zone are not associated @@ -197,8 +203,8 @@ js::CheckTracedThing(JSTracer* trc, T thing) Zone* zone = thing->zoneFromAnyThread(); JSRuntime* rt = trc->runtime(); - MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc), CurrentThreadCanAccessZone(zone)); - MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc), CurrentThreadCanAccessRuntime(rt)); + MOZ_ASSERT_IF(!IsMovingTracer(trc), CurrentThreadCanAccessZone(zone)); + MOZ_ASSERT_IF(!IsMovingTracer(trc), CurrentThreadCanAccessRuntime(rt)); MOZ_ASSERT(zone->runtimeFromAnyThread() == trc->runtime()); @@ -212,7 +218,7 @@ js::CheckTracedThing(JSTracer* trc, T thing) */ bool isGcMarkingTracer = trc->isMarkingTracer(); - MOZ_ASSERT_IF(zone->requireGCTracer(), isGcMarkingTracer || IsBufferingGrayRoots(trc)); + MOZ_ASSERT_IF(zone->requireGCTracer(), isGcMarkingTracer || IsBufferGrayRootsTracer(trc)); if (isGcMarkingTracer) { GCMarker* gcMarker = static_cast(trc); @@ -2295,17 +2301,15 @@ TypeSet::MarkTypeUnbarriered(JSTracer* trc, TypeSet::Type* v, const char* name) /*** Cycle Collector Barrier Implementation *******************************************************/ #ifdef DEBUG -static void -AssertNonGrayGCThing(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) -{ - DebugOnly thing(static_cast(*thingp)); - MOZ_ASSERT_IF(thing->isTenured(), !thing->asTenured().isMarked(js::gc::GRAY)); -} +struct AssertNonGrayTracer : public JS::CallbackTracer { + explicit AssertNonGrayTracer(JSRuntime* rt) : JS::CallbackTracer(rt) {} + void trace(void** thingp, JS::TraceKind kind) override { + DebugOnly thing(static_cast(*thingp)); + MOZ_ASSERT_IF(thing->isTenured(), !thing->asTenured().isMarked(js::gc::GRAY)); + } +}; #endif -static void -UnmarkGrayChildren(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind); - struct UnmarkGrayTracer : public JS::CallbackTracer { /* @@ -2313,19 +2317,21 @@ struct UnmarkGrayTracer : public JS::CallbackTracer * up any color mismatches involving weakmaps when it runs. */ explicit UnmarkGrayTracer(JSRuntime* rt) - : JS::CallbackTracer(rt, UnmarkGrayChildren, DoNotTraceWeakMaps), + : JS::CallbackTracer(rt, DoNotTraceWeakMaps), tracingShape(false), previousShape(nullptr), unmarkedAny(false) {} UnmarkGrayTracer(JSTracer* trc, bool tracingShape) - : JS::CallbackTracer(trc->runtime(), UnmarkGrayChildren, DoNotTraceWeakMaps), + : JS::CallbackTracer(trc->runtime(), DoNotTraceWeakMaps), tracingShape(tracingShape), previousShape(nullptr), unmarkedAny(false) {} + void trace(void** thingp, JS::TraceKind kind) override; + /* True iff we are tracing the immediate children of a shape. */ bool tracingShape; @@ -2366,18 +2372,18 @@ struct UnmarkGrayTracer : public JS::CallbackTracer * of the containers, we must add unmark-graying read barriers to these * containers. */ -static void -UnmarkGrayChildren(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) +void +UnmarkGrayTracer::trace(void** thingp, JS::TraceKind kind) { int stackDummy; - if (!JS_CHECK_STACK_SIZE(trc->runtime()->mainThread.nativeStackLimit[StackForSystemCode], + if (!JS_CHECK_STACK_SIZE(runtime()->mainThread.nativeStackLimit[StackForSystemCode], &stackDummy)) { /* * If we run out of stack, we take a more drastic measure: require that * we GC again before the next CC. */ - trc->runtime()->gc.setGrayBitsInvalid(); + runtime()->gc.setGrayBitsInvalid(); return; } @@ -2387,7 +2393,7 @@ UnmarkGrayChildren(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) // to only black edges. if (!cell->isTenured()) { #ifdef DEBUG - JS::CallbackTracer nongray(trc->runtime(), AssertNonGrayGCThing); + AssertNonGrayTracer nongray(runtime()); TraceChildren(&nongray, cell, kind); #endif return; @@ -2398,28 +2404,27 @@ UnmarkGrayChildren(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) return; tenured.unmark(js::gc::GRAY); - UnmarkGrayTracer* tracer = static_cast(trc); - tracer->unmarkedAny = true; + unmarkedAny = true; // Trace children of |tenured|. If |tenured| and its parent are both // shapes, |tenured| will get saved to mPreviousShape without being traced. // The parent will later trace |tenured|. This is done to avoid increasing // the stack depth during shape tracing. It is safe to do because a shape // can only have one child that is a shape. - UnmarkGrayTracer childTracer(tracer, kind == JS::TraceKind::Shape); + UnmarkGrayTracer childTracer(this, kind == JS::TraceKind::Shape); if (kind != JS::TraceKind::Shape) { TraceChildren(&childTracer, &tenured, kind); MOZ_ASSERT(!childTracer.previousShape); - tracer->unmarkedAny |= childTracer.unmarkedAny; + unmarkedAny |= childTracer.unmarkedAny; return; } MOZ_ASSERT(kind == JS::TraceKind::Shape); Shape* shape = static_cast(&tenured); - if (tracer->tracingShape) { - MOZ_ASSERT(!tracer->previousShape); - tracer->previousShape = shape; + if (tracingShape) { + MOZ_ASSERT(!previousShape); + previousShape = shape; return; } @@ -2429,7 +2434,7 @@ UnmarkGrayChildren(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) shape = childTracer.previousShape; childTracer.previousShape = nullptr; } while (shape); - tracer->unmarkedAny |= childTracer.unmarkedAny; + unmarkedAny |= childTracer.unmarkedAny; } bool diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index f0d38962a9..6ac96f75c3 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -300,8 +300,12 @@ class GCMarker : public JSTracer mozilla::DebugOnly strictCompartmentChecking; }; +#ifdef DEBUG +// Return true if this trace is happening on behalf of gray buffering during +// the marking phase of incremental GC. bool -IsBufferingGrayRoots(JSTracer* trc); +IsBufferGrayRootsTracer(JSTracer* trc); +#endif namespace gc { diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index f9b1e4b98c..5ba9ea70c7 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -484,21 +484,31 @@ class BufferGrayRootsTracer : public JS::CallbackTracer // Set to false if we OOM while buffering gray roots. bool bufferingGrayRootsFailed; - void appendGrayRoot(gc::TenuredCell* thing, JS::TraceKind kind); + void trace(void** thingp, JS::TraceKind kind) override; public: explicit BufferGrayRootsTracer(JSRuntime* rt) - : JS::CallbackTracer(rt, grayTraceCallback), bufferingGrayRootsFailed(false) + : JS::CallbackTracer(rt), bufferingGrayRootsFailed(false) {} - static void grayTraceCallback(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) { - auto tracer = static_cast(trc); - tracer->appendGrayRoot(gc::TenuredCell::fromPointer(*thingp), kind); - } - bool failed() const { return bufferingGrayRootsFailed; } + +#ifdef DEBUG + TracerKind getTracerKind() const override { return TracerKind::GrayBuffering; } +#endif }; +#ifdef DEBUG +// Return true if this trace is happening on behalf of gray buffering during +// the marking phase of incremental GC. +bool +js::IsBufferGrayRootsTracer(JSTracer* trc) +{ + return trc->isCallbackTracer() && + trc->asCallbackTracer()->getTracerKind() == JS::CallbackTracer::TracerKind::GrayBuffering; +} +#endif + void js::gc::GCRuntime::bufferGrayRoots() { @@ -527,13 +537,15 @@ struct SetMaybeAliveFunctor { }; void -BufferGrayRootsTracer::appendGrayRoot(TenuredCell* thing, JS::TraceKind kind) +BufferGrayRootsTracer::trace(void** thingp, JS::TraceKind kind) { MOZ_ASSERT(runtime()->isHeapBusy()); if (bufferingGrayRootsFailed) return; + gc::TenuredCell* thing = gc::TenuredCell::fromPointer(*thingp); + Zone* zone = thing->zone(); if (zone->isCollecting()) { // See the comment on SetMaybeAliveFlag to see why we only do this for @@ -566,12 +578,3 @@ GCRuntime::resetBufferedGrayRoots() const zone->gcGrayRoots.clearAndFree(); } -// Return true if this trace is happening on behalf of gray buffering during -// the marking phase of incremental GC. -bool -js::IsBufferingGrayRoots(JSTracer* trc) -{ - return trc->isCallbackTracer() && - trc->asCallbackTracer()->hasCallback(BufferGrayRootsTracer::grayTraceCallback); -} - diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp index 1e352b3647..25bddcc27f 100644 --- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -49,7 +49,7 @@ DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name) CheckTracedThing(trc, *thingp); JS::TraceKind kind = MapTypeToTraceKind::Type>::kind; JS::AutoTracingName ctx(trc, name); - trc->invoke((void**)thingp, kind); + trc->trace(reinterpret_cast(thingp), kind); return *thingp; } #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(name, type, _) \ @@ -312,27 +312,25 @@ TraceObjectGroupCycleCollectorChildrenCallback(JS::CallbackTracer* trc, struct ObjectGroupCycleCollectorTracer : public JS::CallbackTracer { explicit ObjectGroupCycleCollectorTracer(JS::CallbackTracer* innerTracer) - : JS::CallbackTracer(innerTracer->runtime(), - TraceObjectGroupCycleCollectorChildrenCallback, - DoNotTraceWeakMaps), + : JS::CallbackTracer(innerTracer->runtime(), DoNotTraceWeakMaps), innerTracer(innerTracer) {} + void trace(void** thingp, JS::TraceKind kind) override; + JS::CallbackTracer* innerTracer; Vector seen, worklist; }; void -TraceObjectGroupCycleCollectorChildrenCallback(JS::CallbackTracer* trcArg, - void** thingp, JS::TraceKind kind) +ObjectGroupCycleCollectorTracer::trace(void** thingp, JS::TraceKind kind) { - ObjectGroupCycleCollectorTracer* trc = static_cast(trcArg); JS::GCCellPtr thing(*thingp, kind); if (thing.isObject() || thing.isScript()) { // Invoke the inner cycle collector callback on this child. It will not // recurse back into TraceChildren. - trc->innerTracer->invoke(thingp, kind); + innerTracer->trace(thingp, kind); return; } @@ -341,11 +339,11 @@ TraceObjectGroupCycleCollectorChildrenCallback(JS::CallbackTracer* trcArg, // via the provided worklist rather than continuing to recurse. ObjectGroup* group = static_cast(thing.asCell()); if (group->maybeUnboxedLayout()) { - for (size_t i = 0; i < trc->seen.length(); i++) { - if (trc->seen[i] == group) + for (size_t i = 0; i < seen.length(); i++) { + if (seen[i] == group) return; } - if (trc->seen.append(group) && trc->worklist.append(group)) { + if (seen.append(group) && worklist.append(group)) { return; } else { // If append fails, keep tracing normally. The worst that will @@ -354,7 +352,7 @@ TraceObjectGroupCycleCollectorChildrenCallback(JS::CallbackTracer* trcArg, } } - TraceChildren(trc, thing.asCell(), thing.kind()); + TraceChildren(this, thing.asCell(), thing.kind()); } void diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp index b2b1977822..57a3cb8a69 100644 --- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -64,9 +64,6 @@ struct VerifyNode typedef HashMap, SystemAllocPolicy> NodeMap; -static void -AccumulateEdge(JS::CallbackTracer* jstrc, void** thingp, JS::TraceKind kind); - /* * The verifier data structures are simple. The entire graph is stored in a * single block of memory. At the beginning is a VerifyNode for the root @@ -80,10 +77,13 @@ AccumulateEdge(JS::CallbackTracer* jstrc, void** thingp, JS::TraceKind kind); * The nodemap field is a hashtable that maps from the address of the GC thing * to the VerifyNode that represents it. */ -struct VerifyPreTracer : JS::CallbackTracer +class js::VerifyPreTracer : public JS::CallbackTracer { JS::AutoDisableGenerationalGC noggc; + void trace(void** thingp, JS::TraceKind kind) override; + + public: /* The gcNumber when the verification began. */ uint64_t number; @@ -98,8 +98,7 @@ struct VerifyPreTracer : JS::CallbackTracer NodeMap nodemap; explicit VerifyPreTracer(JSRuntime* rt) - : JS::CallbackTracer(rt, AccumulateEdge), noggc(rt), number(rt->gc.gcNumber()), count(0), - root(nullptr) + : JS::CallbackTracer(rt), noggc(rt), number(rt->gc.gcNumber()), count(0), root(nullptr) {} ~VerifyPreTracer() { @@ -111,25 +110,23 @@ struct VerifyPreTracer : JS::CallbackTracer * This function builds up the heap snapshot by adding edges to the current * node. */ -static void -AccumulateEdge(JS::CallbackTracer* jstrc, void** thingp, JS::TraceKind kind) +void +VerifyPreTracer::trace(void** thingp, JS::TraceKind kind) { - VerifyPreTracer* trc = static_cast(jstrc); - MOZ_ASSERT(!IsInsideNursery(*reinterpret_cast(thingp))); - trc->edgeptr += sizeof(EdgeValue); - if (trc->edgeptr >= trc->term) { - trc->edgeptr = trc->term; + edgeptr += sizeof(EdgeValue); + if (edgeptr >= term) { + edgeptr = term; return; } - VerifyNode* node = trc->curnode; + VerifyNode* node = curnode; uint32_t i = node->count; node->edges[i].thing = *thingp; node->edges[i].kind = kind; - node->edges[i].label = trc->contextName(); + node->edges[i].label = contextName(); node->count++; } @@ -251,12 +248,10 @@ IsMarkedOrAllocated(TenuredCell* cell) return cell->isMarked() || cell->arenaHeader()->allocatedDuringIncremental; } -static void -CheckEdge(JS::CallbackTracer* jstrc, void** thingp, JS::TraceKind kind); - struct CheckEdgeTracer : public JS::CallbackTracer { VerifyNode* node; - explicit CheckEdgeTracer(JSRuntime* rt) : JS::CallbackTracer(rt, CheckEdge), node(nullptr) {} + explicit CheckEdgeTracer(JSRuntime* rt) : JS::CallbackTracer(rt), node(nullptr) {} + void trace(void** thingp, JS::TraceKind kind) override; }; static const uint32_t MAX_VERIFIER_EDGES = 1000; @@ -268,12 +263,9 @@ static const uint32_t MAX_VERIFIER_EDGES = 1000; * non-nullptr edges (i.e., the ones from the original snapshot that must have * been modified) must point to marked objects. */ -static void -CheckEdge(JS::CallbackTracer* jstrc, void** thingp, JS::TraceKind kind) +void +CheckEdgeTracer::trace(void** thingp, JS::TraceKind kind) { - CheckEdgeTracer* trc = static_cast(jstrc); - VerifyNode* node = trc->node; - /* Avoid n^2 behavior. */ if (node->count > MAX_VERIFIER_EDGES) return; @@ -308,7 +300,7 @@ AssertMarkedOrAllocated(const EdgeValue& edge) bool gc::GCRuntime::endVerifyPreBarriers() { - VerifyPreTracer* trc = static_cast(verifyPreData); + VerifyPreTracer* trc = verifyPreData; if (!trc) return false; @@ -390,8 +382,8 @@ gc::GCRuntime::maybeVerifyPreBarriers(bool always) if (rt->mainThread.suppressGC) return; - if (VerifyPreTracer* trc = static_cast(verifyPreData)) { - if (++trc->count < zealFrequency && !always) + if (verifyPreData) { + if (++verifyPreData->count < zealFrequency && !always) return; endVerifyPreBarriers(); @@ -410,8 +402,8 @@ js::gc::MaybeVerifyBarriers(JSContext* cx, bool always) void js::gc::GCRuntime::finishVerifier() { - if (VerifyPreTracer* trc = static_cast(verifyPreData)) { - js_delete(trc); + if (verifyPreData) { + js_delete(verifyPreData); verifyPreData = nullptr; } } diff --git a/js/src/jit-test/tests/basic/bug734196.js b/js/src/jit-test/tests/basic/bug734196.js deleted file mode 100644 index 9a0f588f74..0000000000 --- a/js/src/jit-test/tests/basic/bug734196.js +++ /dev/null @@ -1,4 +0,0 @@ -var x = new ArrayBuffer(2); -gczeal(4); -actual = [].concat(x).toString(); -var count2 = countHeap(); diff --git a/js/src/jit-test/tests/basic/bug747926.js b/js/src/jit-test/tests/basic/bug747926.js deleted file mode 100644 index 78e09604ee..0000000000 --- a/js/src/jit-test/tests/basic/bug747926.js +++ /dev/null @@ -1,12 +0,0 @@ -a = 'a'; -b = [,]; -exhaustiveSliceTest("exhaustive slice test 1", a); -print('---'); -exhaustiveSliceTest("exhaustive slice test 2", b); -function exhaustiveSliceTest(testname, a){ - x = 0 - var y = 0; - countHeap(); - for (y=a.length; y + a.length; y--) { print(y); - var b = a.slice(x,y); } -} diff --git a/js/src/jit-test/tests/basic/bug821340.js b/js/src/jit-test/tests/basic/bug821340.js deleted file mode 100644 index c7e49f3fd7..0000000000 --- a/js/src/jit-test/tests/basic/bug821340.js +++ /dev/null @@ -1,10 +0,0 @@ -var g = newGlobal(); -var dbg = Debugger(g); -function test(code, expected) { - var actual = ''; - g.h = function () { actual += dbg.getNewestFrame().environment.type; } - g.eval(code); -} -test("h();", 'object'); -gczeal(4); -var count2 = countHeap(); diff --git a/js/src/jit-test/tests/gc/bug-913224.js b/js/src/jit-test/tests/gc/bug-913224.js index d410e1b9c3..815164d764 100644 --- a/js/src/jit-test/tests/gc/bug-913224.js +++ b/js/src/jit-test/tests/gc/bug-913224.js @@ -1 +1 @@ -dumpHeapComplete(); +dumpHeap(); diff --git a/js/src/jsapi-tests/testGCMarking.cpp b/js/src/jsapi-tests/testGCMarking.cpp index d45fec26af..980ee09391 100644 --- a/js/src/jsapi-tests/testGCMarking.cpp +++ b/js/src/jsapi-tests/testGCMarking.cpp @@ -8,11 +8,7 @@ #include "jsapi-tests/tests.h" class CCWTestTracer : public JS::CallbackTracer { - static void staticCallback(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) { - static_cast(trc)->callback(thingp, kind); - } - - void callback(void** thingp, JS::TraceKind kind) { + void trace(void** thingp, JS::TraceKind kind) { numberOfThingsTraced++; printf("*thingp = %p\n", *thingp); @@ -32,7 +28,7 @@ class CCWTestTracer : public JS::CallbackTracer { JS::TraceKind expectedKind; CCWTestTracer(JSContext* cx, void** expectedThingp, JS::TraceKind expectedKind) - : JS::CallbackTracer(JS_GetRuntime(cx), staticCallback), + : JS::CallbackTracer(JS_GetRuntime(cx)), okay(true), numberOfThingsTraced(0), expectedThingp(expectedThingp), diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index e7a5fe518e..4b7ebdccab 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -838,8 +838,8 @@ struct DumpHeapTracer : public JS::CallbackTracer, public WeakMapTracer const char* prefix; FILE* output; - DumpHeapTracer(FILE* fp, JSRuntime* rt, JSTraceCallback callback) - : JS::CallbackTracer(rt, callback, DoNotTraceWeakMaps), + DumpHeapTracer(FILE* fp, JSRuntime* rt) + : JS::CallbackTracer(rt, DoNotTraceWeakMaps), js::WeakMapTracer(rt), prefix(""), output(fp) {} @@ -852,6 +852,8 @@ struct DumpHeapTracer : public JS::CallbackTracer, public WeakMapTracer fprintf(output, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n", map, key.asCell(), kdelegate, value.asCell()); } + + void trace(void** thingp, JS::TraceKind kind) override; }; static char @@ -904,25 +906,24 @@ DumpHeapVisitCell(JSRuntime* rt, void* data, void* thing, JS_TraceChildren(dtrc, thing, traceKind); } -static void -DumpHeapVisitGCThing(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) +void +DumpHeapTracer::trace(void** thingp, JS::TraceKind kind) { if (gc::IsInsideNursery((js::gc::Cell*)*thingp)) return; - DumpHeapTracer* dtrc = static_cast(trc); char buffer[1024]; - dtrc->getTracingEdgeName(buffer, sizeof(buffer)); - fprintf(dtrc->output, "%s%p %c %s\n", dtrc->prefix, *thingp, MarkDescriptor(*thingp), buffer); + getTracingEdgeName(buffer, sizeof(buffer)); + fprintf(output, "%s%p %c %s\n", prefix, *thingp, MarkDescriptor(*thingp), buffer); } void -js::DumpHeapComplete(JSRuntime* rt, FILE* fp, js::DumpHeapNurseryBehaviour nurseryBehaviour) +js::DumpHeap(JSRuntime* rt, FILE* fp, js::DumpHeapNurseryBehaviour nurseryBehaviour) { if (nurseryBehaviour == js::CollectNurseryBeforeDump) rt->gc.evictNursery(JS::gcreason::API); - DumpHeapTracer dtrc(fp, rt, DumpHeapVisitGCThing); + DumpHeapTracer dtrc(fp, rt); fprintf(dtrc.output, "# Roots.\n"); TraceRuntime(&dtrc); diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 9eb6cefe0b..be3ff44595 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -436,7 +436,7 @@ typedef enum { * fp is the file for the dump output. */ extern JS_FRIEND_API(void) -DumpHeapComplete(JSRuntime* rt, FILE* fp, DumpHeapNurseryBehaviour nurseryBehaviour); +DumpHeap(JSRuntime* rt, FILE* fp, DumpHeapNurseryBehaviour nurseryBehaviour); #ifdef JS_OLD_GETTER_SETTER_METHODS JS_FRIEND_API(bool) obj_defineGetter(JSContext* cx, unsigned argc, JS::Value* vp); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index b07c04550f..9b284974b6 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -2206,9 +2206,8 @@ GCRuntime::relocateArenas(Zone *zone, JS::gcreason::Reason reason, SliceBudget & return true; } - void -MovingTracer::Visit(JS::CallbackTracer* jstrc, void** thingp, JS::TraceKind kind) +MovingTracer::trace(void** thingp, JS::TraceKind kind) { TenuredCell* thing = TenuredCell::fromPointer(*thingp); @@ -3672,10 +3671,10 @@ GCRuntime::shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime, #ifdef DEBUG class CompartmentCheckTracer : public JS::CallbackTracer { + void trace(void** thingp, JS::TraceKind kind) override; + public: - CompartmentCheckTracer(JSRuntime* rt, JSTraceCallback callback) - : JS::CallbackTracer(rt, callback) - {} + explicit CompartmentCheckTracer(JSRuntime* rt) : JS::CallbackTracer(rt) {} Cell* src; JS::TraceKind srcKind; @@ -3708,31 +3707,23 @@ InCrossCompartmentMap(JSObject* src, Cell* dst, JS::TraceKind dstKind) return false; } -static void -CheckCompartment(CompartmentCheckTracer* trc, JSCompartment* thingCompartment, - Cell* thing, JS::TraceKind kind) -{ - MOZ_ASSERT(thingCompartment == trc->compartment || - trc->runtime()->isAtomsCompartment(thingCompartment) || - (trc->srcKind == JS::TraceKind::Object && - InCrossCompartmentMap((JSObject*)trc->src, thing, kind))); -} - struct MaybeCompartmentFunctor { template JSCompartment* operator()(T* t) { return t->maybeCompartment(); } }; -static void -CheckCompartmentCallback(JS::CallbackTracer* trcArg, void** thingp, JS::TraceKind kind) +void +CompartmentCheckTracer::trace(void** thingp, JS::TraceKind kind) { - CompartmentCheckTracer* trc = static_cast(trcArg); TenuredCell* thing = TenuredCell::fromPointer(*thingp); JSCompartment* comp = CallTyped(MaybeCompartmentFunctor(), thing, kind); - if (comp && trc->compartment) - CheckCompartment(trc, comp, thing, kind); - else - MOZ_ASSERT(thing->zone() == trc->zone || thing->zone()->isAtomsZone()); + if (comp && compartment) { + MOZ_ASSERT(comp == compartment || runtime()->isAtomsCompartment(comp) || + (srcKind == JS::TraceKind::Object && + InCrossCompartmentMap(static_cast(src), thing, kind))); + } else { + MOZ_ASSERT(thing->zone() == zone || thing->zone()->isAtomsZone()); + } } void @@ -3741,7 +3732,7 @@ GCRuntime::checkForCompartmentMismatches() if (disableStrictProxyCheckingCount) return; - CompartmentCheckTracer trc(rt, CheckCompartmentCallback); + CompartmentCheckTracer trc(rt); for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) { trc.zone = zone; for (auto thingKind : AllAllocKinds()) { diff --git a/js/src/tests/ecma_7/TypedObject/referencetypetrace.js b/js/src/tests/ecma_7/TypedObject/referencetypetrace.js index e0fa221716..02246c1d2f 100644 --- a/js/src/tests/ecma_7/TypedObject/referencetypetrace.js +++ b/js/src/tests/ecma_7/TypedObject/referencetypetrace.js @@ -1,4 +1,4 @@ -// |reftest| skip-if(!this.hasOwnProperty("TypedObject")||!this.hasOwnProperty("countHeap")) +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")||!this.hasOwnProperty("hasChild")) var BUGNUMBER = 898359; var summary = 'TypedObjects reference type trace'; var actual = ''; @@ -15,35 +15,22 @@ var Any = TypedObject.Any; var Object = TypedObject.Object; var string = TypedObject.string; -function assertCanReach(from, target, times) { - times = times || 1; - var count1 = countHeap(from, "specific", target); - print("canReach:", count1, times, from.toSource(), target.toSource()); - assertEq(count1, times); -} - -function assertCannotReach(from, target) { - var count1 = countHeap(from, "specific", target); - print("cannotReach:", count1, from.toSource(), target.toSource()); - assertEq(count1, 0); -} - function TestStructFields(RefType) { var rabbit = {}; var S1 = new StructType({f: RefType}); var s1 = new S1({f: rabbit}); - assertCanReach(s1, rabbit); + assertEq(hasChild(s1, rabbit), true); s1.f = null; - assertCannotReach(s1, rabbit); + assertEq(hasChild(s1, rabbit), false); } function TestArrayElements(RefType) { var rabbit = {}; var S1 = new ArrayType(RefType, 1); var s1 = new S1([rabbit]); - assertCanReach(s1, rabbit); + assertEq(hasChild(s1, rabbit), true); s1[0] = null; - assertCannotReach(s1, rabbit); + assertEq(hasChild(s1, rabbit), false); } function TestStructInArray(RefType) { @@ -51,9 +38,9 @@ function TestStructInArray(RefType) { var S2 = new StructType({f: RefType, g: RefType}); var S1 = new ArrayType(S2, 1); var s1 = new S1([{f: rabbit, g: {}}]); - assertCanReach(s1, rabbit); + assertEq(hasChild(s1, rabbit), true); s1[0].f = null; - assertCannotReach(s1, rabbit); + assertEq(hasChild(s1, rabbit), false); } function TestStringInStruct() { @@ -66,9 +53,9 @@ function TestStringInStruct() { var rabbit = "Hello" + Math.random(); var S1 = new StructType({f: string}); var s1 = new S1({f: rabbit}); - assertCanReach(s1, rabbit); + assertEq(hasChild(s1, rabbit), true); s1.f = "World"; - assertCannotReach(s1, rabbit); + assertEq(hasChild(s1, rabbit), false); } function runTests() diff --git a/js/src/tests/js1_8/extensions/regress-394709.js b/js/src/tests/js1_8/extensions/regress-394709.js index b070f60587..d04d8832fb 100644 --- a/js/src/tests/js1_8/extensions/regress-394709.js +++ b/js/src/tests/js1_8/extensions/regress-394709.js @@ -1,3 +1,4 @@ +// |reftest| skip-if(!xulRuntime.shell) /* -*- indent-tabs-mode: nil; js-indent-level: 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 @@ -9,14 +10,6 @@ var summary = 'Do not leak with object.watch and closure'; var actual = 'No Leak'; var expect = 'No Leak'; -if (typeof countHeap == 'undefined') -{ - countHeap = function () { - print('This test requires countHeap which is not supported'); - return 0; - }; -} - //----------------------------------------------------------------------------- test(); //----------------------------------------------------------------------------- @@ -27,25 +20,23 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - // Ensure that we flush all values so that gc() collects all objects that - // the user cannot reach from JS. - eval(); + assertEq(finalizeCount(), 0, "invalid initial state"); runtest(); gc(); - var count1 = countHeap(); + assertEq(finalizeCount(), 1, "leaked"); + runtest(); gc(); - var count2 = countHeap(); + assertEq(finalizeCount(), 2, "leaked"); + runtest(); gc(); - var count3 = countHeap(); - /* Try to be tolerant of conservative GC noise: we want a steady leak. */ - if (count1 < count2 && count2 < count3) - throw "A leaky watch point is detected"; + assertEq(finalizeCount(), 3, "leaked"); + function runtest () { - var obj = { b: 0 }; + var obj = { b: makeFinalizeObserver() }; obj.watch('b', watcher); function watcher(id, old, value) { diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index fd80a9997e..46e6e96805 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -384,7 +384,8 @@ ReadStructuredClone(JSContext* cx, uint64_t* data, size_t nbytes, MutableHandleV // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to // delete their transferables. static void -Discard(uint64_t* buffer, size_t nbytes, const JSStructuredCloneCallbacks* cb, void* cbClosure) +DiscardTransferables(uint64_t* buffer, size_t nbytes, + const JSStructuredCloneCallbacks* cb, void* cbClosure) { MOZ_ASSERT(nbytes % sizeof(uint64_t) == 0); uint64_t* end = buffer + nbytes / sizeof(uint64_t); @@ -443,27 +444,15 @@ Discard(uint64_t* buffer, size_t nbytes, const JSStructuredCloneCallbacks* cb, v } } -static void -ClearStructuredClone(uint64_t* data, size_t nbytes, - const JSStructuredCloneCallbacks* cb, void* cbClosure) +static bool +StructuredCloneHasTransferObjects(const uint64_t* data, size_t nbytes) { - Discard(data, nbytes, cb, cbClosure); - js_free(data); -} + if (!data) + return false; -bool -StructuredCloneHasTransferObjects(const uint64_t* data, size_t nbytes, bool* hasTransferable) -{ - *hasTransferable = false; - - if (data) { - uint64_t u = LittleEndian::readUint64(data); - uint32_t tag = uint32_t(u >> 32); - if (tag == SCTAG_TRANSFER_MAP_HEADER) - *hasTransferable = true; - } - - return true; + uint64_t u = LittleEndian::readUint64(data); + uint32_t tag = uint32_t(u >> 32); + return (tag == SCTAG_TRANSFER_MAP_HEADER); } namespace js { @@ -746,7 +735,8 @@ JSStructuredCloneWriter::~JSStructuredCloneWriter() uint64_t* data; size_t size; MOZ_ALWAYS_TRUE(extractBuffer(&data, &size)); - ClearStructuredClone(data, size, callbacks, closure); + DiscardTransferables(data, size, callbacks, closure); + js_free(data); } bool @@ -1832,8 +1822,9 @@ JSStructuredCloneReader::readTransferMap() MOZ_ASSERT(!cx->isExceptionPending()); } - // On failure, the buffer will still own the data (since its ownership will not get set to SCTAG_TMO_UNOWNED), - // so the data will be freed by ClearStructuredClone + // On failure, the buffer will still own the data (since its ownership + // will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by + // DiscardTransferables. if (!obj) return false; @@ -1960,9 +1951,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) { - ClearStructuredClone(data, nbytes, optionalCallbacks, closure); + DiscardTransferables(data, nbytes, optionalCallbacks, closure); + if (freeData) { + js_free(data); + } return true; } @@ -1970,11 +1964,7 @@ JS_PUBLIC_API(bool) JS_StructuredCloneHasTransferables(const uint64_t* data, size_t nbytes, bool* hasTransferable) { - bool transferable; - if (!StructuredCloneHasTransferObjects(data, nbytes, &transferable)) - return false; - - *hasTransferable = transferable; + *hasTransferable = StructuredCloneHasTransferObjects(data, nbytes); return true; } @@ -2022,6 +2012,7 @@ JS_StructuredClone(JSContext* cx, HandleValue value, MutableHandleValue vp, JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer&& other) { + ownTransferables_ = other.ownTransferables_; other.steal(&data_, &nbytes_, &version_); } @@ -2030,6 +2021,7 @@ JSAutoStructuredCloneBuffer::operator=(JSAutoStructuredCloneBuffer&& other) { MOZ_ASSERT(&other != this); clear(); + ownTransferables_ = other.ownTransferables_; other.steal(&data_, &nbytes_, &version_); return *this; } @@ -2038,7 +2030,10 @@ void JSAutoStructuredCloneBuffer::clear() { if (data_) { - ClearStructuredClone(data_, nbytes_, callbacks_, closure_); + if (ownTransferables_ == OwnsTransferablesIfAny) + DiscardTransferables(data_, nbytes_, callbacks_, closure_); + ownTransferables_ = NoTransferables; + js_free(data_); data_ = nullptr; nbytes_ = 0; version_ = 0; @@ -2049,9 +2044,7 @@ bool JSAutoStructuredCloneBuffer::copy(const uint64_t* srcData, size_t nbytes, uint32_t version) { // transferable objects cannot be copied - bool hasTransferable; - if (!StructuredCloneHasTransferObjects(data_, nbytes_, &hasTransferable) || - hasTransferable) + if (StructuredCloneHasTransferObjects(data_, nbytes_)) return false; uint64_t* newData = static_cast(js_malloc(nbytes)); @@ -2064,6 +2057,7 @@ JSAutoStructuredCloneBuffer::copy(const uint64_t* srcData, size_t nbytes, uint32 data_ = newData; nbytes_ = nbytes; version_ = version; + ownTransferables_ = NoTransferables; return true; } @@ -2074,6 +2068,7 @@ JSAutoStructuredCloneBuffer::adopt(uint64_t* data, size_t nbytes, uint32_t versi data_ = data; nbytes_ = nbytes; version_ = version; + ownTransferables_ = OwnsTransferablesIfAny; } void @@ -2087,6 +2082,7 @@ JSAutoStructuredCloneBuffer::steal(uint64_t** datap, size_t* nbytesp, uint32_t* data_ = nullptr; nbytes_ = 0; version_ = 0; + ownTransferables_ = NoTransferables; } bool @@ -2116,13 +2112,17 @@ JSAutoStructuredCloneBuffer::write(JSContext* cx, HandleValue value, void* closure) { clear(); - bool ok = !!JS_WriteStructuredClone(cx, value, &data_, &nbytes_, - optionalCallbacks, closure, - transferable); - if (!ok) { + bool ok = JS_WriteStructuredClone(cx, value, &data_, &nbytes_, + optionalCallbacks, closure, + transferable); + + if (ok) { + ownTransferables_ = OwnsTransferablesIfAny; + } else { data_ = nullptr; nbytes_ = 0; version_ = JS_STRUCTURED_CLONE_VERSION; + ownTransferables_ = NoTransferables; } return ok; } diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp index fb30bd83ed..3d3b54f38b 100644 --- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -111,11 +111,7 @@ class SimpleEdgeVectorTracer : public JS::CallbackTracer { // True if we should populate the edge's names. bool wantNames; - static void staticCallback(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind) { - static_cast(trc)->callback(thingp, kind); - } - - void callback(void** thingp, JS::TraceKind kind) { + void trace(void** thingp, JS::TraceKind kind) { if (!okay) return; @@ -154,7 +150,7 @@ class SimpleEdgeVectorTracer : public JS::CallbackTracer { bool okay; SimpleEdgeVectorTracer(JSContext* cx, SimpleEdgeVector* vec, bool wantNames) - : JS::CallbackTracer(JS_GetRuntime(cx), staticCallback), + : JS::CallbackTracer(JS_GetRuntime(cx)), vec(vec), wantNames(wantNames), okay(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 6dfbdc124e..0000000000 --- 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 - diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp index ba8dd47cc9..8aac162d94 100644 --- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -109,19 +109,15 @@ public: } // namespace mozilla -static void -TraceWeakMappingChild(JS::CallbackTracer* aTrc, void** aThingp, - JS::TraceKind aKind); - struct NoteWeakMapChildrenTracer : public JS::CallbackTracer { NoteWeakMapChildrenTracer(JSRuntime* aRt, nsCycleCollectionNoteRootCallback& aCb) - : JS::CallbackTracer(aRt, TraceWeakMappingChild), mCb(aCb), - mTracedAny(false), mMap(nullptr), mKey(nullptr), - mKeyDelegate(nullptr) + : JS::CallbackTracer(aRt), mCb(aCb), mTracedAny(false), mMap(nullptr), + mKey(nullptr), mKeyDelegate(nullptr) { } + void trace(void** aThingp, JS::TraceKind aKind) override; nsCycleCollectionNoteRootCallback& mCb; bool mTracedAny; JSObject* mMap; @@ -129,29 +125,24 @@ struct NoteWeakMapChildrenTracer : public JS::CallbackTracer JSObject* mKeyDelegate; }; -static void -TraceWeakMappingChild(JS::CallbackTracer* aTrc, void** aThingp, - JS::TraceKind aKind) +void +NoteWeakMapChildrenTracer::trace(void** aThingp, JS::TraceKind aKind) { - MOZ_ASSERT(aTrc->hasCallback(TraceWeakMappingChild)); - NoteWeakMapChildrenTracer* tracer = - static_cast(aTrc); JS::GCCellPtr thing(*aThingp, aKind); if (thing.isString()) { return; } - if (!JS::GCThingIsMarkedGray(thing) && !tracer->mCb.WantAllTraces()) { + if (!JS::GCThingIsMarkedGray(thing) && !mCb.WantAllTraces()) { return; } if (AddToCCKind(thing.kind())) { - tracer->mCb.NoteWeakMapping(tracer->mMap, tracer->mKey, - tracer->mKeyDelegate, thing); - tracer->mTracedAny = true; + mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, thing); + mTracedAny = true; } else { - JS_TraceChildren(aTrc, thing.asCell(), thing.kind()); + JS_TraceChildren(this, thing.asCell(), thing.kind()); } } @@ -317,27 +308,21 @@ JSZoneParticipant::Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) return NS_OK; } -static void -NoteJSChildTracerShim(JS::CallbackTracer* aTrc, void** aThingp, - JS::TraceKind aTraceKind); - struct TraversalTracer : public JS::CallbackTracer { TraversalTracer(JSRuntime* aRt, nsCycleCollectionTraversalCallback& aCb) - : JS::CallbackTracer(aRt, NoteJSChildTracerShim, DoNotTraceWeakMaps), - mCb(aCb) + : JS::CallbackTracer(aRt, DoNotTraceWeakMaps), mCb(aCb) { } + void trace(void** aThingp, JS::TraceKind aTraceKind) override; nsCycleCollectionTraversalCallback& mCb; }; static void -NoteJSChild(JS::CallbackTracer* aTrc, JS::GCCellPtr aThing) +NoteJSChild(TraversalTracer* aTrc, JS::GCCellPtr aThing) { - TraversalTracer* tracer = static_cast(aTrc); - // Don't traverse non-gray objects, unless we want all traces. - if (!JS::GCThingIsMarkedGray(aThing) && !tracer->mCb.WantAllTraces()) { + if (!JS::GCThingIsMarkedGray(aThing) && !aTrc->mCb.WantAllTraces()) { return; } @@ -349,15 +334,15 @@ NoteJSChild(JS::CallbackTracer* aTrc, JS::GCCellPtr aThing) * use special APIs to handle such chains iteratively. */ if (AddToCCKind(aThing.kind())) { - if (MOZ_UNLIKELY(tracer->mCb.WantDebugInfo())) { + if (MOZ_UNLIKELY(aTrc->mCb.WantDebugInfo())) { char buffer[200]; - tracer->getTracingEdgeName(buffer, sizeof(buffer)); - tracer->mCb.NoteNextEdgeName(buffer); + aTrc->getTracingEdgeName(buffer, sizeof(buffer)); + aTrc->mCb.NoteNextEdgeName(buffer); } if (aThing.isObject()) { - tracer->mCb.NoteJSObject(aThing.toObject()); + aTrc->mCb.NoteJSObject(aThing.toObject()); } else { - tracer->mCb.NoteJSScript(aThing.toScript()); + aTrc->mCb.NoteJSScript(aThing.toScript()); } } else if (aThing.isShape()) { // The maximum depth of traversal when tracing a Shape is unbounded, due to @@ -373,12 +358,11 @@ NoteJSChild(JS::CallbackTracer* aTrc, JS::GCCellPtr aThing) } } -static void -NoteJSChildTracerShim(JS::CallbackTracer* aTrc, void** aThingp, - JS::TraceKind aTraceKind) +void +TraversalTracer::trace(void** aThingp, JS::TraceKind aTraceKind) { JS::GCCellPtr thing(*aThingp, aTraceKind); - NoteJSChild(aTrc, thing); + NoteJSChild(this, thing); } static void @@ -1025,7 +1009,7 @@ CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports) void CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile) { - js::DumpHeapComplete(Runtime(), aFile, js::CollectNurseryBeforeDump); + js::DumpHeap(Runtime(), aFile, js::CollectNurseryBeforeDump); }