diff options
Diffstat (limited to 'dom/messagechannel/MessagePortService.cpp')
-rw-r--r-- | dom/messagechannel/MessagePortService.cpp | 409 |
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 |