/* -*- 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 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 mNextParents; FallibleTArray> 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; // 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> messages(std::move(data->mMessages)); FallibleTArray array; if (!SharedMessagePortMessage::FromSharedToMessagesParent(aParent, messages, array)) { CloseAll(aParent->ID()); return false; } // 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>& 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 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>& 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; } 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()"); 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