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