summaryrefslogtreecommitdiffstats
path: root/dom/messagechannel/MessagePortService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/messagechannel/MessagePortService.cpp')
-rw-r--r--dom/messagechannel/MessagePortService.cpp408
1 files changed, 408 insertions, 0 deletions
diff --git a/dom/messagechannel/MessagePortService.cpp b/dom/messagechannel/MessagePortService.cpp
new file mode 100644
index 0000000000..ce7afd4aeb
--- /dev/null
+++ b/dom/messagechannel/MessagePortService.cpp
@@ -0,0 +1,408 @@
+/* -*- 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 "MessagePortService.h"
+#include "MessagePortParent.h"
+#include "mozilla/dom/RefMessageBodyService.h"
+#include "mozilla/dom/SharedMessageBody.h"
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WeakPtr.h"
+#include "nsTArray.h"
+
+using mozilla::ipc::AssertIsOnBackgroundThread;
+
+namespace mozilla::dom {
+
+namespace {
+
+StaticRefPtr<MessagePortService> gInstance;
+
+void AssertIsInMainProcess() {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+}
+
+} // namespace
+
+struct MessagePortService::NextParent {
+ uint32_t mSequenceID;
+ // MessagePortParent keeps the service alive, and we don't want a cycle.
+ WeakPtr<MessagePortParent> mParent;
+};
+
+} // namespace mozilla::dom
+
+namespace mozilla::dom {
+
+class MessagePortService::MessagePortServiceData final {
+ public:
+ explicit MessagePortServiceData(const nsID& aDestinationUUID)
+ : mDestinationUUID(aDestinationUUID),
+ mSequenceID(1),
+ mParent(nullptr)
+ // By default we don't know the next parent.
+ ,
+ mWaitingForNewParent(true),
+ mNextStepCloseAll(false) {
+ MOZ_COUNT_CTOR(MessagePortServiceData);
+ }
+
+ MessagePortServiceData(const MessagePortServiceData& aOther) = delete;
+ MessagePortServiceData& operator=(const MessagePortServiceData&) = delete;
+
+ MOZ_COUNTED_DTOR(MessagePortServiceData)
+
+ nsID mDestinationUUID;
+
+ uint32_t mSequenceID;
+ CheckedUnsafePtr<MessagePortParent> mParent;
+
+ FallibleTArray<NextParent> mNextParents;
+ FallibleTArray<RefPtr<SharedMessageBody>> mMessages;
+
+ bool mWaitingForNewParent;
+ bool mNextStepCloseAll;
+};
+
+/* static */
+MessagePortService* MessagePortService::Get() {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ return gInstance;
+}
+
+/* static */
+MessagePortService* MessagePortService::GetOrCreate() {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ 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;
+ }
+
+ mPorts.InsertOrUpdate(aDestinationUUID,
+ MakeUnique<MessagePortServiceData>(aParent->ID()));
+
+ data = mPorts
+ .InsertOrUpdate(
+ aParent->ID(),
+ MakeUnique<MessagePortServiceData>(aDestinationUUID))
+ .get();
+ }
+
+ // This is a security check.
+ if (!data->mDestinationUUID.Equals(aDestinationUUID)) {
+ MOZ_ASSERT(false, "DestinationUUIDs do not match!");
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ if (aSequenceID < data->mSequenceID) {
+ MOZ_ASSERT(false, "Invalid sequence ID!");
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ if (aSequenceID == data->mSequenceID) {
+ if (data->mParent) {
+ MOZ_ASSERT(false, "Two ports cannot have the same sequenceID.");
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ // We activate this port, sending all the messages.
+ data->mParent = aParent;
+ data->mWaitingForNewParent = false;
+
+ // We want to ensure we clear data->mMessages even if we early return, while
+ // also ensuring that its contents remain alive until after array's contents
+ // are destroyed because of JSStructuredCloneData borrowing. So we use
+ // Move to initialize things swapped and do it before we declare `array` so
+ // that reverse destruction order works for us.
+ FallibleTArray<RefPtr<SharedMessageBody>> messages(
+ std::move(data->mMessages));
+ nsTArray<MessageData> array;
+ if (!SharedMessageBody::FromSharedToMessagesParent(aParent->Manager(),
+ messages, array)) {
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ // We can entangle the port.
+ if (!aParent->Entangled(std::move(array))) {
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ // If we were waiting for this parent in order to close this channel, this
+ // is the time to do it.
+ if (data->mNextStepCloseAll) {
+ CloseAll(aParent->ID());
+ }
+
+ return true;
+ }
+
+ // This new parent will be the next one when a Disentangle request is
+ // received from the current parent.
+ auto nextParent = data->mNextParents.AppendElement(mozilla::fallible);
+ if (!nextParent) {
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ nextParent->mSequenceID = aSequenceID;
+ nextParent->mParent = aParent;
+
+ return true;
+}
+
+bool MessagePortService::DisentanglePort(
+ MessagePortParent* aParent,
+ FallibleTArray<RefPtr<SharedMessageBody>> 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(std::move(data->mMessages),
+ mozilla::fallible)) {
+ return false;
+ }
+
+ ++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 = std::move(aMessages);
+ data->mWaitingForNewParent = true;
+ data->mParent = nullptr;
+ return true;
+ }
+
+ data->mParent = nextParent;
+ data->mNextParents.RemoveElementAt(index);
+
+ nsTArray<MessageData> array;
+ if (!SharedMessageBody::FromSharedToMessagesParent(data->mParent->Manager(),
+ aMessages, array)) {
+ return false;
+ }
+
+ Unused << data->mParent->Entangled(std::move(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;
+}
+
+void MessagePortService::CloseAll(const nsID& aUUID, bool aForced) {
+ MessagePortServiceData* data;
+ if (!mPorts.Get(aUUID, &data)) {
+ MaybeShutdown();
+ return;
+ }
+
+ if (data->mParent) {
+ data->mParent->Close();
+ data->mParent = nullptr;
+ }
+
+ for (const auto& nextParent : data->mNextParents) {
+ MessagePortParent* const parent = nextParent.mParent;
+ if (parent) {
+ parent->CloseAndDelete();
+ }
+ }
+ data->mNextParents.Clear();
+
+ nsID destinationUUID = data->mDestinationUUID;
+
+ // If we have informations about the other port and that port has some
+ // pending messages to deliver but the parent has not processed them yet,
+ // because its entangling request didn't arrive yet), we cannot close this
+ // channel.
+ MessagePortServiceData* destinationData;
+ if (!aForced && mPorts.Get(destinationUUID, &destinationData) &&
+ !destinationData->mMessages.IsEmpty() &&
+ destinationData->mWaitingForNewParent) {
+ MOZ_ASSERT(!destinationData->mNextStepCloseAll);
+ destinationData->mNextStepCloseAll = true;
+ return;
+ }
+
+ mPorts.Remove(aUUID);
+
+ CloseAll(destinationUUID, aForced);
+
+ // CloseAll calls itself recursively and it can happen that it deletes
+ // itself. Before continuing we must check if we are still alive.
+ if (!gInstance) {
+ return;
+ }
+
+ MOZ_ASSERT(!mPorts.Contains(aUUID));
+
+ 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<RefPtr<SharedMessageBody>> 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(std::move(aMessages),
+ mozilla::fallible)) {
+ return false;
+ }
+
+ // If the parent can send data to the child, let's proceed.
+ if (data->mParent && data->mParent->CanSendData()) {
+ {
+ nsTArray<MessageData> messages;
+ if (!SharedMessageBody::FromSharedToMessagesParent(
+ data->mParent->Manager(), data->mMessages, messages)) {
+ return false;
+ }
+
+ Unused << data->mParent->SendReceiveData(messages);
+ }
+ // `messages` borrows the underlying JSStructuredCloneData so we need to
+ // avoid destroying the `mMessages` until after we've destroyed `messages`.
+ data->mMessages.Clear();
+ }
+
+ 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());
+}
+
+bool MessagePortService::ForceClose(const nsID& aUUID,
+ const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ MessagePortServiceData* data;
+ if (!mPorts.Get(aUUID, &data)) {
+ NS_WARNING("Unknown MessagePort in ForceClose()");
+ // There is nothing to close so we are ok.
+ return true;
+ }
+
+ NS_ENSURE_TRUE(data->mDestinationUUID.Equals(aDestinationUUID), false);
+
+ // If StructuredCloneData includes a MessagePort, StructuredCloneData
+ // serialization failure in postMessage can trigger MessagePort::ForceClose().
+ // And since the serialized port transfered has started but not finished yet,
+ // the SequenceID will not be synchronized to the parent side, which will
+ // cause the SequenceID to mismatch here. See bug 1872770.
+ NS_WARNING_ASSERTION(data->mSequenceID == aSequenceID,
+ "sequence IDs do not match");
+
+ CloseAll(aUUID, true);
+ return true;
+}
+
+} // namespace mozilla::dom