Source code

Revision control

Other Tools

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