зеркало из https://github.com/mozilla/gecko-dev.git
405 строки
11 KiB
C++
405 строки
11 KiB
C++
/* -*- 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;
|
|
}
|
|
|
|
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 mozilla::dom
|