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.cpp409
1 files changed, 409 insertions, 0 deletions
diff --git a/dom/messagechannel/MessagePortService.cpp b/dom/messagechannel/MessagePortService.cpp
new file mode 100644
index 000000000..a2d495d77
--- /dev/null
+++ b/dom/messagechannel/MessagePortService.cpp
@@ -0,0 +1,409 @@
+/* -*- 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 "SharedMessagePortMessage.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "nsTArray.h"
+
+using mozilla::ipc::AssertIsOnBackgroundThread;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+StaticRefPtr<MessagePortService> gInstance;
+
+void
+AssertIsInMainProcess()
+{
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+}
+
+} // namespace
+
+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;
+
+ ~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<NextParent> mNextParents;
+ FallibleTArray<RefPtr<SharedMessagePortMessage>> 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;
+ }
+
+ 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!");
+ 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;
+ FallibleTArray<MessagePortMessage> array;
+ if (!SharedMessagePortMessage::FromSharedToMessagesParent(aParent,
+ data->mMessages,
+ array)) {
+ CloseAll(aParent->ID());
+ return false;
+ }
+
+ data->mMessages.Clear();
+
+ // We can entangle the port.
+ if (!aParent->Entangled(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.
+ MessagePortServiceData::NextParent* 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<SharedMessagePortMessage>>& 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->mWaitingForNewParent = true;
+ data->mParent = nullptr;
+ return true;
+ }
+
+ data->mParent = nextParent;
+ data->mNextParents.RemoveElementAt(index);
+
+ FallibleTArray<MessagePortMessage> 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;
+}
+
+void
+MessagePortService::CloseAll(const nsID& aUUID, bool aForced)
+{
+ 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;
+
+ // 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;
+ }
+
+#ifdef DEBUG
+ for (auto iter = mPorts.Iter(); !iter.Done(); iter.Next()) {
+ MOZ_ASSERT(!aUUID.Equals(iter.Key()));
+ }
+#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<RefPtr<SharedMessagePortMessage>>& 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<MessagePortMessage> 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());
+}
+
+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()");
+ return true;
+ }
+
+ if (!data->mDestinationUUID.Equals(aDestinationUUID) ||
+ data->mSequenceID != aSequenceID) {
+ NS_WARNING("DestinationUUID and/or sequenceID do not match.");
+ return false;
+ }
+
+ CloseAll(aUUID, true);
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla