MessagePortService.cpp (12003B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "MessagePortService.h" 8 9 #include "MessagePortParent.h" 10 #include "mozilla/StaticPtr.h" 11 #include "mozilla/WeakPtr.h" 12 #include "mozilla/dom/RefMessageBodyService.h" 13 #include "mozilla/dom/SharedMessageBody.h" 14 #include "mozilla/dom/quota/CheckedUnsafePtr.h" 15 #include "mozilla/ipc/BackgroundParent.h" 16 #include "nsTArray.h" 17 18 using mozilla::ipc::AssertIsOnBackgroundThread; 19 20 namespace mozilla::dom { 21 22 namespace { 23 24 StaticRefPtr<MessagePortService> gInstance; 25 26 void AssertIsInMainProcess() { 27 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 28 } 29 30 } // namespace 31 32 struct MessagePortService::NextParent { 33 uint32_t mSequenceID; 34 // MessagePortParent keeps the service alive, and we don't want a cycle. 35 WeakPtr<MessagePortParent> mParent; 36 }; 37 38 } // namespace mozilla::dom 39 40 namespace mozilla::dom { 41 42 class MessagePortService::MessagePortServiceData final { 43 public: 44 explicit MessagePortServiceData(const nsID& aDestinationUUID) 45 : mDestinationUUID(aDestinationUUID), 46 mSequenceID(1), 47 mParent(nullptr) 48 // By default we don't know the next parent. 49 , 50 mWaitingForNewParent(true), 51 mNextStepCloseAll(false) { 52 MOZ_COUNT_CTOR(MessagePortServiceData); 53 } 54 55 MessagePortServiceData(const MessagePortServiceData& aOther) = delete; 56 MessagePortServiceData& operator=(const MessagePortServiceData&) = delete; 57 58 MOZ_COUNTED_DTOR(MessagePortServiceData) 59 60 nsID mDestinationUUID; 61 62 uint32_t mSequenceID; 63 CheckedUnsafePtr<MessagePortParent> mParent; 64 65 FallibleTArray<NextParent> mNextParents; 66 FallibleTArray<RefPtr<SharedMessageBody>> mMessages; 67 68 bool mWaitingForNewParent; 69 bool mNextStepCloseAll; 70 }; 71 72 /* static */ 73 MessagePortService* MessagePortService::Get() { 74 AssertIsInMainProcess(); 75 AssertIsOnBackgroundThread(); 76 77 return gInstance; 78 } 79 80 /* static */ 81 MessagePortService* MessagePortService::GetOrCreate() { 82 AssertIsInMainProcess(); 83 AssertIsOnBackgroundThread(); 84 85 if (!gInstance) { 86 gInstance = new MessagePortService(); 87 } 88 89 return gInstance; 90 } 91 92 bool MessagePortService::RequestEntangling(MessagePortParent* aParent, 93 const nsID& aDestinationUUID, 94 const uint32_t& aSequenceID) { 95 MOZ_ASSERT(aParent); 96 MessagePortServiceData* data; 97 98 // If we don't have a MessagePortServiceData, we must create 2 of them for 99 // both ports. 100 if (!mPorts.Get(aParent->ID(), &data)) { 101 // Create the MessagePortServiceData for the destination. 102 if (mPorts.Get(aDestinationUUID, nullptr)) { 103 MOZ_ASSERT(false, "The creation of the 2 ports should be in sync."); 104 return false; 105 } 106 107 mPorts.InsertOrUpdate(aDestinationUUID, 108 MakeUnique<MessagePortServiceData>(aParent->ID())); 109 110 data = mPorts 111 .InsertOrUpdate( 112 aParent->ID(), 113 MakeUnique<MessagePortServiceData>(aDestinationUUID)) 114 .get(); 115 } 116 117 // This is a security check. 118 if (!data->mDestinationUUID.Equals(aDestinationUUID)) { 119 MOZ_ASSERT(false, "DestinationUUIDs do not match!"); 120 CloseAll(aParent->ID()); 121 return false; 122 } 123 124 if (aSequenceID < data->mSequenceID) { 125 MOZ_ASSERT(false, "Invalid sequence ID!"); 126 CloseAll(aParent->ID()); 127 return false; 128 } 129 130 if (aSequenceID == data->mSequenceID) { 131 if (data->mParent) { 132 MOZ_ASSERT(false, "Two ports cannot have the same sequenceID."); 133 CloseAll(aParent->ID()); 134 return false; 135 } 136 137 // We activate this port, sending all the messages. 138 data->mParent = aParent; 139 data->mWaitingForNewParent = false; 140 141 // We want to ensure we clear data->mMessages even if we early return, while 142 // also ensuring that its contents remain alive until after array's contents 143 // are destroyed because of JSStructuredCloneData borrowing. So we use 144 // Move to initialize things swapped and do it before we declare `array` so 145 // that reverse destruction order works for us. 146 FallibleTArray<RefPtr<SharedMessageBody>> messages( 147 std::move(data->mMessages)); 148 nsTArray<MessageData> array; 149 if (!SharedMessageBody::FromSharedToMessagesParent(aParent->Manager(), 150 messages, array)) { 151 CloseAll(aParent->ID()); 152 return false; 153 } 154 155 // We can entangle the port. 156 if (!aParent->Entangled(std::move(array))) { 157 CloseAll(aParent->ID()); 158 return false; 159 } 160 161 // If we were waiting for this parent in order to close this channel, this 162 // is the time to do it. 163 if (data->mNextStepCloseAll) { 164 CloseAll(aParent->ID()); 165 } 166 167 return true; 168 } 169 170 // This new parent will be the next one when a Disentangle request is 171 // received from the current parent. 172 auto nextParent = data->mNextParents.AppendElement(mozilla::fallible); 173 if (!nextParent) { 174 CloseAll(aParent->ID()); 175 return false; 176 } 177 178 nextParent->mSequenceID = aSequenceID; 179 nextParent->mParent = aParent; 180 181 return true; 182 } 183 184 bool MessagePortService::DisentanglePort( 185 MessagePortParent* aParent, 186 FallibleTArray<RefPtr<SharedMessageBody>> aMessages) { 187 MessagePortServiceData* data; 188 if (!mPorts.Get(aParent->ID(), &data)) { 189 MOZ_ASSERT(false, "Unknown MessagePortParent should not happen."); 190 return false; 191 } 192 193 if (data->mParent != aParent) { 194 MOZ_ASSERT( 195 false, 196 "DisentanglePort() should be called just from the correct parent."); 197 return false; 198 } 199 200 // Let's put the messages in the correct order. |aMessages| contains the 201 // unsent messages so they have to go first. 202 if (!aMessages.AppendElements(std::move(data->mMessages), 203 mozilla::fallible)) { 204 return false; 205 } 206 207 ++data->mSequenceID; 208 209 // If we don't have a parent, we have to store the pending messages and wait. 210 uint32_t index = 0; 211 MessagePortParent* nextParent = nullptr; 212 for (; index < data->mNextParents.Length(); ++index) { 213 if (data->mNextParents[index].mSequenceID == data->mSequenceID) { 214 nextParent = data->mNextParents[index].mParent; 215 break; 216 } 217 } 218 219 // We didn't find the parent. 220 if (!nextParent) { 221 data->mMessages = std::move(aMessages); 222 data->mWaitingForNewParent = true; 223 data->mParent = nullptr; 224 return true; 225 } 226 227 data->mParent = nextParent; 228 data->mNextParents.RemoveElementAt(index); 229 230 nsTArray<MessageData> array; 231 if (!SharedMessageBody::FromSharedToMessagesParent(data->mParent->Manager(), 232 aMessages, array)) { 233 return false; 234 } 235 236 (void)data->mParent->Entangled(std::move(array)); 237 return true; 238 } 239 240 bool MessagePortService::ClosePort(MessagePortParent* aParent) { 241 MessagePortServiceData* data; 242 if (!mPorts.Get(aParent->ID(), &data)) { 243 MOZ_ASSERT(false, "Unknown MessagePortParent should not happend."); 244 return false; 245 } 246 247 if (data->mParent != aParent) { 248 MOZ_ASSERT(false, 249 "ClosePort() should be called just from the correct parent."); 250 return false; 251 } 252 253 if (!data->mNextParents.IsEmpty()) { 254 MOZ_ASSERT(false, 255 "ClosePort() should be called when there are not next parents."); 256 return false; 257 } 258 259 // We don't want to send a message to this parent. 260 data->mParent = nullptr; 261 262 CloseAll(aParent->ID()); 263 return true; 264 } 265 266 void MessagePortService::CloseAll(const nsID& aUUID, bool aForced) { 267 MessagePortServiceData* data; 268 if (!mPorts.Get(aUUID, &data)) { 269 MaybeShutdown(); 270 return; 271 } 272 273 if (data->mParent) { 274 data->mParent->Close(); 275 data->mParent = nullptr; 276 } 277 278 for (const auto& nextParent : data->mNextParents) { 279 MessagePortParent* const parent = nextParent.mParent; 280 if (parent) { 281 parent->CloseAndDelete(); 282 } 283 } 284 data->mNextParents.Clear(); 285 286 nsID destinationUUID = data->mDestinationUUID; 287 288 // If we have informations about the other port and that port has some 289 // pending messages to deliver but the parent has not processed them yet, 290 // because its entangling request didn't arrive yet), we cannot close this 291 // channel. 292 MessagePortServiceData* destinationData; 293 if (!aForced && mPorts.Get(destinationUUID, &destinationData) && 294 !destinationData->mMessages.IsEmpty() && 295 destinationData->mWaitingForNewParent) { 296 MOZ_ASSERT(!destinationData->mNextStepCloseAll); 297 destinationData->mNextStepCloseAll = true; 298 return; 299 } 300 301 mPorts.Remove(aUUID); 302 303 CloseAll(destinationUUID, aForced); 304 305 // CloseAll calls itself recursively and it can happen that it deletes 306 // itself. Before continuing we must check if we are still alive. 307 if (!gInstance) { 308 return; 309 } 310 311 MOZ_ASSERT(!mPorts.Contains(aUUID)); 312 313 MaybeShutdown(); 314 } 315 316 // This service can be dismissed when there are not active ports. 317 void MessagePortService::MaybeShutdown() { 318 if (mPorts.Count() == 0) { 319 gInstance = nullptr; 320 } 321 } 322 323 bool MessagePortService::PostMessages( 324 MessagePortParent* aParent, 325 FallibleTArray<RefPtr<SharedMessageBody>> aMessages) { 326 MessagePortServiceData* data; 327 if (!mPorts.Get(aParent->ID(), &data)) { 328 MOZ_ASSERT(false, "Unknown MessagePortParent should not happend."); 329 return false; 330 } 331 332 if (data->mParent != aParent) { 333 MOZ_ASSERT(false, 334 "PostMessages() should be called just from the correct parent."); 335 return false; 336 } 337 338 MOZ_ALWAYS_TRUE(mPorts.Get(data->mDestinationUUID, &data)); 339 340 if (!data->mMessages.AppendElements(std::move(aMessages), 341 mozilla::fallible)) { 342 return false; 343 } 344 345 // If the parent can send data to the child, let's proceed. 346 if (data->mParent && data->mParent->CanSendData()) { 347 { 348 nsTArray<MessageData> messages; 349 if (!SharedMessageBody::FromSharedToMessagesParent( 350 data->mParent->Manager(), data->mMessages, messages)) { 351 return false; 352 } 353 354 (void)data->mParent->SendReceiveData(messages); 355 } 356 // `messages` borrows the underlying JSStructuredCloneData so we need to 357 // avoid destroying the `mMessages` until after we've destroyed `messages`. 358 data->mMessages.Clear(); 359 } 360 361 return true; 362 } 363 364 void MessagePortService::ParentDestroy(MessagePortParent* aParent) { 365 // This port has already been destroyed. 366 MessagePortServiceData* data; 367 if (!mPorts.Get(aParent->ID(), &data)) { 368 return; 369 } 370 371 if (data->mParent != aParent) { 372 // We don't want to send a message to this parent. 373 for (uint32_t i = 0; i < data->mNextParents.Length(); ++i) { 374 if (aParent == data->mNextParents[i].mParent) { 375 data->mNextParents.RemoveElementAt(i); 376 break; 377 } 378 } 379 } 380 381 CloseAll(aParent->ID()); 382 } 383 384 bool MessagePortService::ForceClose(const nsID& aUUID, 385 const nsID& aDestinationUUID, 386 const uint32_t& aSequenceID) { 387 MessagePortServiceData* data; 388 if (!mPorts.Get(aUUID, &data)) { 389 NS_WARNING("Unknown MessagePort in ForceClose()"); 390 // There is nothing to close so we are ok. 391 return true; 392 } 393 394 NS_ENSURE_TRUE(data->mDestinationUUID.Equals(aDestinationUUID), false); 395 396 // If StructuredCloneData includes a MessagePort, StructuredCloneData 397 // serialization failure in postMessage can trigger MessagePort::ForceClose(). 398 // And since the serialized port transfered has started but not finished yet, 399 // the SequenceID will not be synchronized to the parent side, which will 400 // cause the SequenceID to mismatch here. See bug 1872770. 401 NS_WARNING_ASSERTION(data->mSequenceID == aSequenceID, 402 "sequence IDs do not match"); 403 404 CloseAll(aUUID, true); 405 return true; 406 } 407 408 } // namespace mozilla::dom